Chapter 7: Animation


click to expand

Working with animation is not much different from working with geometry in Maya. The important thing to remember about working in Maya is that all data is stored in nodes; the only thing you have to do is find that node. In this chapter, we will begin to work with animation data, as well as one of the more useful features of MEL, the scriptJob .

Project 7.1: The Cycling Tool

Project Overview

One of the more exciting technologies that has become prevalent among all 3D applications is non-linear animation (NLA). NLA provides a way to take animations and link them together to create a new performance for a character. For game developers, this is nothing new, as this has been the fundamental way character animation in video games has worked since their inception. In Maya, this feature is supported through the Trax editor, although we will not be using it here.

When creating animation assets for use within an NLA element, the most important quality for the animation to possess is to cleanly loop. Ironically, although this is vital for an asset to work effectively, there are surprisingly few ways of working with animation data that guarantees that they are, in fact, looping.

We will rectify this situation with MEL.

Definition and Design

Our project goal is simple to state: alter the Maya environment to aid in the construction of cycling assets. Because we are constructing a tool for animators, it would be advisable to discuss the issue with animators, and find out what their needs are.

After doing so, the items that the animators felt were most important were:

  • The ability to scrub animation across a cycling boundary

  • Retaining the relationship between start and end keys, including value and tangency

  • Little to no setup

Looking at our list of requirements, we can split our tool into two logical sections, one to let the artist scrub across the cycle boundary, the other to work with the key data. The issue of ease of use will be more an issue of implementation than overall design. Our guiding principle in design is that the most input we will require from our user is that he only needs to turn the tool on or off.

The flowchart for the first aspect of our tool is seen in Flowchart 7.1.

click to expand
Flowchart 7.1: The plan for our first tool.

Our goal is to create a situation where the user is free to alter time and scrub his animation, but if that time is before or after our cycle time, we are returned to the corresponding time within the cycle. This prevents a user from setting a key outside of the cycle.

The second part of our tool requires a slightly more complex flowchart, seen in Flowchart 7.2.

click to expand
Flowchart 7.2: The second tool is somewhat more complex.

Now that our designs are in place, we can figure out exactly how we re going to build it.

Research and Development

Both flowcharts require us to alter the Maya environment to keep track of what the user is doing. To do this we use the scriptJob . Although not a difficult concept to understand, script jobs are better explained in context, and we will discuss them in detail later. We will be using script jobs extensively throughout this project.

The major departure we will make from our previous projects is to split the tool into multiple script files. As with procedures, the decision to split a script into multiple files can never really be codified into a hard-and-fast rule. It is often a good idea to make script files as short as possible, which can be aided by splitting the procedures off to their own files. In addition, when your tool is made of multiple global procedures, as this one will be, it is often good practice to allow each of those procedures to be individually sourced and executed.

Since there is no advanced math or physics involved in this project, we can now move on and begin to program our tool.

Implementation

We will first program the tool that allows a user to scrub across time. As always, we begin by creating an empty procedure and executing it to check its validity. Note that we have added a print statement to the procedure. We often use print statements during the writing and debugging of a script, especially when using script jobs, to track the execution of procedures. Anytime we create a suite of files for a tool, it is a good idea to have a common prefix for all the scripts. In our case, we will use " cyc ". See Example 7.1.

Example 7.1: Declaring our procedure.

 global proc cycTimeCheck ()     {     print "Executing timeControl.\n";     } 

Now, when we execute cycTimeCheck , in the Script Editor s history we see our test phrase printed out.

Now that our script works, we can begin to add functionality to it. First, we need to determine the range of our cycle. There are multiple ways we could do this, from asking the user for input, to looking at the time of the first and last keyframes that exist within the scene. However, for our tool, we will simply look to the Playback Time Range, assuming that the user will have that set to the time of his cycle. We can simply document this with the tool. See Example 7.2.

Example 7.2: Finding the length of the animation.

 global proc cycSetTimes ()     {  // Find the start and end times of   // the playback slider  float $cycStartTime = `playbackOptions                             -query                             -minimum`                             ;     float $cycEndTime = `playbackOptions                             -query                             -maximum`                             ;  // determine the length of the animation  float $range = ($cycEndTime - $cycStartTime) ;     } 

Now that we are able to determine the range of our animation, we need to do something with this data. Start a new script file and procedure, both called cycTimeCheck , and check their validity. Our goal with this procedure is to see if the current time is outside the range set in cycSetTimes , and if so, set the current time to be within that range. In order to allow our new procedure to gain access to the start and end times gathered in cycSetTimes , we will change the variables to global variables , to allow them to be accessed by cycTimeCheck . Global variables do not allow a value to be assigned to them upon declaration using a queried value. So first, we declare them, and then assign a value to them. See Example 7.3.

Example 7.3: Creating the global variables.

 global proc cycSetTimes ()     {  // Declare the global variables  global float $cycStartTime;     global float $cycEndTime ;  // Find the start and end times of   // the playback slider  $cycStartTime = `playbackOptions                             -query                             -minimum`                             ;     $cycEndTime = `playbackOptions                             -query                             -maximum`                             ;     . . . 

Now we can access this data in cycTimeCheck . Whenever you want to access a global variable in a script, we need to declare it, even if we are just accessing the data. We then find the range of the animation. While we could declare the range as a global variable, we should always endeavor to keep the use of global variables to a minimum, so we simply recalculate it in the cycTimeCheck procedure. See Example 7.4.

Example 7.4: Using the global variables in the time check procedure.

 global proc cycTimeCheck ()     {  // Declare the global variables  global float $cycStartTime;     global float $cycEndTime;  // Find the range  float $range = $cycEndTime - $cycStartTime; 

Our next task is to find out what the current time the user has set the time slider to. We do this with a simple query to the command currentTime . See Example 7.5.

Example 7.5: Finding what the current frame is.

 float $nowTime = `currentTime                         -query`                         ; 

Next, we use two while statements to see if the time assigned to $nowTime is outside our established cycle range. If the condition is true, we adjust the time by the range. For example, if the range is from frame 0 to frame 25, a range of 25 frames , and the user sets the time to frame 27, when cycTimeCheck is activated the current time should be reset to 27 “25, or frame 2. We use a while loop instead of an if loop so that if the current time is more than one range outside the actual range of the cycle, the process will repeat until the current time is in our range. See Example 7.6.

Example 7.6: Changing the current time so it falls within the cycle range.

  // If the current time is higher than the range  while ($nowTime > $cycEndTime)         {              currentTime -edit ($nowTime-$range);             $nowTime = `currentTime -query`;         }  // If the current time is lower than the range  while ($nowTime < $cycStartTime)         {              currentTime -edit ($nowTime+$range);             $nowTime = `currentTime -query`;         }     } 

As long as the procedure cycSetTimes is executed first, if we set the current time to anything outside the range and execute cycTimeCheck , our time is adjusted.

As our scripts currently exist, the user has to adjust the playback range manually. If instead we add a piece of code to the cycSetTimes to add padding to the playback range, we can automate the process, once again abstracting the needed input from the user to simply turning the tool on or off. We will simply triple the range, with the actual cycle range sitting in the middle. See Example 7.7.

Example 7.7: Giving the user three cycles in the timeline.

 global proc cycSetTimes ()     {  // Declare the global variables  global float $cycStartTime;     global float $cycEndTime ;  // Find the start and end times of   // the playback slider  $cycStartTime = `playbackOptions                             -query                             -minimum`                             ;     $cycEndTime = `playbackOptions                             -query                             -maximum`                             ;  // determine the length of the animation  float $range = ($cycEndTime - $cycStartTime) ;     playbackOptions -min ($cycStartTime - $range);     playbackOptions -max ($cycEndTime + $range);     } 

Currently, we have two separate procedures. We will now add a new procedure to act as, to borrow a phrase from TRON , the master control program. We will call this procedure and the corresponding file cyclingTool . Rather than pass an argument to the script, we will again use a global variable. When the cyclingTool command is called, we turn the tool either on or off depending on the value stored in this variable. We also update the value so the next time our tool is executed, the script knows how to react . We will also set a return value, just to inform the user of the status of the tool. See Example 7.8.

Example 7.8: Addition of a procedure to start the entire tool.

 global proc string cyclingTool ()     {  // Declare global variables  global int $cycToolStatus;     global float $cycStartTime;     global float $cycEndTime;  // if cyclingTool is inactive  if ($cycToolStatus == 0)         {             cycSetTimes;             $cycToolStatus = 1;             return "Cycling Tool is on." ;         }      else         {             playbackOptions                 -ast $cycStartTime                 -aet $cycEndTime                 ;             $cycToolStatus = 1;             return "Cycling Tool is off." ;         }     } 

We can now run our cycling tool as much as we want; each time we do, we can watch the time range change with each execution.

We now want to alter the Maya environment to automatically run cycTimeCheck whenever the user sets the time to something outside of the cycle range determined in cycSetTimes . We do this with a scriptJob .

A script job is a MEL command that is executed whenever a certain condition is satisfied or a specific action takes place. This condition or action is defined when the script job is created. Script jobs are incredibly useful for modifying or adding functionality to the Maya environment that appears to have been placed there by the original programmers.

In the Script Editor, enter the command from Example 7.9 to create a script job that will execute only once. Make sure the cycling tool is active, so that cycTimeCheck will have the information it needs to function.

Example 7.9: Creating a scriptJob.

 scriptJob         -runOnce true          -event timeChanged cycTimeCheck         ; 

Now, the next time we change the time, cycTimeCheck is executed. To make this a standing condition, we remove the runOnce flag. It is often a good move when testing the construction of script jobs to have the runOnce flag on as a precaution against getting Maya stuck in an endless loop, most particularly when using the idle event to trigger a script. Much like the while loop, a simple typo in a script job can send Maya into a cycle that cannot be stopped short of terminating the Maya process. For this reason, it is good practice to construct script jobs either in scenes that have been backed up or in proxy scenes. See Example 7.10.

Example 7.10: Creating the scriptJob to keep the user s time in the cycle range.

 scriptJob         -event timeChanged cycTimeCheck         ; 

When the script job is executed, we see an odd return value, a number; specifically , an integer number. See Example 7.11.

Example 7.11: The result of creating a scriptJob.

  // Result: 31 //  

This is the job number. We can use this integer to stop, or kill , the script job. Obviously, we will want to stop the script job when we turn the cycling tool off, as well as when we start a new scene. To have the job automatically killed when a new scene is created, we use the rather obvious -killWithScene flag. To do this, we again use a global variable, and assign the script job number to it. We will use a placeholder integer variable since we cannot directly capture a value to a global integer. See Example 7.12.

Example 7.12: Storing scriptJob identities in a global variable.

 global proc string cyclingTool ()     {  // Declare global variables  global int $cycToolStatus;     global int $cycTimeScriptJob ;     global float $cycStartTime;     global float $cycEndTime;  // if cyclingTool is inactive  if ($cycToolStatus == 0)         {         cycSetTimes;         int $i = `scriptJob                     -killWithScene                     -event timeChanged cycTimeCheck`;         $cycTimeScriptJob = $i;         $cycToolStatus = 1;         return "Cycling Tool is on." ;         }      else         {         scriptJob -kill $cycTimeScriptJob -force;         playbackOptions                 -ast $cycStartTime                 -aet $cycEndTime;         $cycToolStatus = 1;         return "Cycling Tool is off." ;         }     } 

With these changes in place, activate the cycling tool. Now, whenever the time is changed to a value outside our declared range, cycTimeCheck is executed, bringing us back into the actual range. Moreover, when we turn the tool off, the script job that executed cycTimeCheck is deleted from memory. We use the force flag to ensure that the job is deleted. Our script job was not created with the protected flag on, so while the force flag is not required to kill it, using it is non-detrimental and can be helpful to remember the option to force the killing of a script job.

It should be noted that while it might seem as if we are breaking one of the major rules of MEL, avoiding global variables, that rule is actually to avoid global variables unless absolutely necessary . And in the situations we are using them in, they are. Last chapter, we learned how procedures can pass data to each other, but here, our scripts are not being executed one after the other as steps in a single process. Rather, they are working in conjunction with each other, and in order to pass data to each other, we have to store that data somewhere in memory. We are not using extremely large arrays or an inordinate number of global variables, so we are not creating a situation that should cause concern.

We have now successfully completed the first part of our tool. Now, we move onto the more complex tool, which will allow us to edit our bookend keys and maintain their cyclical nature.

Referring to our flowchart, we see that our first goal of this second action is to determine when the user selects keyframes that are both on the bookend times, and are involved in an animation that is cycling. The first step in this is to determine what the user has selected, and to update that information every time the user changes the selection list.

In a new Maya scene, create a sphere, and add animation to any of the channels. If in the Script Editor, we query the selection list using the command ls -selection , we get Example 7.13.

Example 7.13: Finding the selection list.

  // Result: pSphere1 //  

However, if we then select any or all of the keyframes on the animation curve within the Graph Editor, we can query the selection list, we still get what is shown in Example 7.14.

Example 7.14: The results do not reflect the selection of keyframes.

  // Result: pSphere1 //  

While it might seem odd that our selection changed, yet our selection list did not, the answer lies in looking at the up and downstream connections in the Hypergraph, seen in Figure 7.1.

click to expand
Figure 7.1: The up and downstream connections.

As we can see, the actual selection remains the same. The animation curve is a separate node, feeding information into the sphere. The secret to finding which keys are selected is found by looking at the feedback in the Script Editor, after a keyframe is selected in the Graph Editor. See Example 7.15.

Example 7.15: Example results of selecting keyframes.

 selectKey -clear ;     selectKey -add -k -t 13 pSphere1_rotateY ; 

Keyframes are actually selected through the command selectKey , rather than the command select . We can, through some investigation of the command selectKey and some logical association, find the command keyframe , which is used to set keyframes. Most commands that create data can also be used to query information, and keyframe is no different.

Before we start querying animation data, it is important to understand how Maya handles this data "behind the scenes," as it were. Animation and keyframe data is somewhat analogous to geometry and component objects. Keyframe animation is physically stored in a type of node called an animCurve . There are currently eight subtypes of animCurves, depending on both the input and the output data type. For example, the animation for a keyframed scale channel is an animCurveTU , with time being the input and a double type output. However, the animation for a rotation channel that is driven by a driven key is stored in an animCurveUA , which takes a numeric value as an input and outputs angular data. This distinction is normally only important when using the nodeType command, which will return the subtype, not the parent type of animCurve , although it is a useful method for determining if a channel is driven by a driven key. In order to access the animation stored on any animation curve, we must first determine the name of the animation curve itself.

We could assume that the animCurve has the default name Maya assigns to an animation curve, object_channel , but assumptions like that are often dangerous and will likely lead to errors. We could also track the connections from the object using the MEL command listConnections . However, this is more useful when tracking animation that could be driven by anything ”an expression, an animation curve, or even a proprietary solution such as a plug-in that is driven by an external hardware device. In this situation, we only care when the user has selected keys, so we will simply use the command keyframe in query mode, looking for the name of the selected animation curve. See Example 7.16.

Example 7.16: Finding the selected animation curves.

 keyframe         -query         -selected         -name         ; 

Which returns the name of the animCurve on which any selected keys reside. If no keys are selected, the return is empty. We will now create the first piece of our second tool. Start and validate a new file and global procedure called cycSelectCheck . We will, for now, simply put our keyframe query into the procedure, and print that data to the Script Editor. See Example 7.17.

Example 7.17: A procedure to find the selected curves.

 global proc cycSelectCheck ()     {     string $selectedCurves[] = `keyframe                                     -query                                     -selected                                     -name`;     print $selectedCurves;     } 

Now, when cycSelectCheck is evaluated, if there are any keyframes selected, we will see the names of their associated curves printed to the Script Editor. If we now create a script job to execute the script whenever the selection changes, we can constantly monitor the user's selection, and determine if the user has selected any keyframes. See Example 7.18.

Example 7.18: Constantly monitoring the users keyframe selection with a scriptJob.

 scriptJob         -killWithScene          -event SelectionChanged cycSelectCheck; 

Now, we can add this script job to the cyclingTool procedure. We use all the same flags and structures we used when adding the cycTimeCheck script job, including the addition of another global variable. See Example 7.19.

Example 7.19: Updating the master tool procedure to reflect our modifications.

 global proc string cyclingTool ()     {     global int $cycToolStatus;     global int $cycTimeScriptJob ;     global int $cycSelectScriptJob ;     global int $cycSelectStatus;     global float $cycStartTime;     global float $cycEndTime;     if ($cycToolStatus == 0)         {             cycSetTimes;             int $i = `scriptJob                 -killWithScene                   -event timeChanged cycTimeCheck`                 ;             $cycTimeScriptJob = $i;             $i = `scriptJob                 -killWithScene                  -event SelectionChanged cycSelectCheck`                 ;             $cycSelectScriptJob = $i;             $cycToolStatus = 1;             return "Cycling Tool is on." ;         }      else if ($cycToolStatus == 1)         {             scriptJob -kill $cycSelectScriptJob -force;              scriptJob -kill $cycTimeScriptJob -force;             playbackOptions                 -ast $cycStartTime                 -aet                 $cycEndTime ;             $cycToolStatus = 0;             return "Cycling Tool is off." ;         }     } 

Now, the cycling tool will monitor the selection to see if a user selection includes any keys and finds the names of the associated animCurve nodes. Of course, simply finding the name of the animCurve is not much use, we have to find the actual keys that are selected. We can again use the keyframe command to query the selected keys, returned as a float array of the time values of the selected keys, by using the command seen in Example 7.20.

Example 7.20: Finding the actual keyframes selected in the graph editor.

 keyframe         -query         -selected         ; 

This is usable; however, if there are keys on multiple animCurve nodes selected, we have no way of knowing which time returned by the query is associated with which animCurve . To get the data for each animCurve explicitly, we will place the query of the keyframe values in a for loop, and use the for-in structure since we are iterating through an array. See Example 7.21.

Example 7.21: Clarifying our keyframe selection list.

 global proc cycSelectCheck ()     {     string $selectedCurves[] = `keyframe                                     -query                                     -selected                                     -name`;     print $selectedCurves;     for ($eachAnimCurve in $selectedCurves)         {             float $totalSelectedKeys[] = `keyframe                                             -query                                             -selected                                             $eachAnimCurve`;             print $totalSelectedKeys;         }     } 

Now, when cycSelectCheck is evaluated, no matter how many keys on any number of animCurve nodes is selected by the end user, we see a neatly ordered listing in the Script Editor.

Next, we need to parse this data and determine if the selected keys are on the bookend times, and if the curve those keys are on is cycling. To determine which curves are cycling, we will look at the pre and post infinity behaviors of the animation curves, which are set in the Graph Editor. This allows us to leave the determination of which curves will be accessible to the cycling tool up to the user, and avoid the hassle of building and introducing the user to a new interface.

Because the animCurve nodes have attributes, we will query the attributes for both the pre and post infinity value. The attributes are both enumerated, or enum, attributes which hold different text values in a list, with each value assigned an index value, similar to an array. We use a somewhat complicated if statement with a series of nested conditional statements to check if a particular key satisfies both conditions. We again use a for-in loop to iterate through the array. Also note the declaration of the global variables containing our start and end times. See Example 7.22.

Example 7.22: Finding whether a curve is set to cycle.

 global proc cycSelectCheck ()     {     global float $cycStartTime;     global float $cycEndTime;     string $selectedCyclingCurves[];     string $selectedCurves[] = `keyframe                                     -query                                     -selected                                     -name`;     for ($eachAnimCurve in $selectedCurves)         {         float $totalSelectedKeys[] = `keyframe                                             -query                                             -selected                                             $eachAnimCurve`;         for ($keyTime in $totalSelectedKeys)             {             int $preInfinity = `getAttr                     ($eachAnimCurve + ".preInfinity")`;             int $postInfinity = `getAttr                     ($eachAnimCurve + ".postInfinity")`;             if (($keyTime == $cycStartTime                  $keyTime == $cycEndTime)                 && ($preInfinity == 3                  $preInfinity == 4                   $postInfinity == 3                  $postInfinity == 4))             $selectedCyclingCurves[`size $selectedCyclingCurves`]                 = $eachAnimCurve ;             }         }     } 

Because we might have selected both the start and ending times of a curve, the array containing the names of the cycling curves might contain the same curve multiple times. We will use an accessory script that comes with Maya called stringArrayRemoveDuplicates , which, and this should be no surprise, removes duplicate entries from a string array. See Example 7.23.

Example 7.23: Cleaning up the variable arrays.

 . . .     $selectedCyclingCurves[`size $selectedCyclingCurves`]         = $eachAnimCurve ;         }     }     $selectedCyclingCurves =          `stringArrayRemoveDuplicates $selectedCyclingCurves`; 

If we wanted, we could now add a print statement to test the functionality so far. For now, we will move on and do something with our gathered selection info . We next have to check each of the curves to determine if a curve held in the variable $selectedCyclingCurves has keys on both the beginning and end of the cycle range determined when the cycling tool was activated. We do this to ensure that each of those curves is valid for all further operations we are going to carry out. See Example 7.24.

Example 7.24: Determining the validity of our animation curves.

 string $cycToolCurves[];     for ($eachItem in $selectedCyclingCurves)         {         float $keyframes[] = `keyframe -query $eachItem`;         int $onBoth = 0;         for ($eachKey in $keyframes)             if (($eachKey == $cycStartTime)                  ($eachKey == $cycEndTime))                 $onBoth++;             if ($onBoth == 2)                 $cycToolCurves[`size $cycToolCurves`] =                     $eachItem;          } 

Now that we are secure with the sanctity of our gathered data, we need to gather a vast amount of data from each of these curves.. With this data in memory, we will then set up script jobs to monitor for any changes to the curves, and then adjust the curves to maintain the cyclic nature of the curve.

For each curve we need to gather, for both of the bookend keys the tangency in angle, the in tangent type, the in tangent weight, the tangency out angle, the out tangent type, the out tangent weight, the tangency lock, the weight lock, and the actual value of the keyframe. The actual gathering of this data is rather easy, a matter of multiple calls of the keyframe command with the appropriate flags. The handling of this data is, however, a much more complex beast .

We have a variety of options available to us. We could use a number of arrays, using corresponding indices to co-ordinate values. However, keeping that many global variables in memory could be bad. Instead, we will use a workaround called a token matrix .

MEL suffers a major limitation in not allowing for dynamically sized matrices. Because we can never know how many cycling curves a user might select, we need to use a variable that can be dynamically resized, so we will use an array. At the same time, each index of that array needs to hold a variety of information, of varying types. To do this, we will take all the information and combine it into one string. Each value will be treated as a single word, and we can then use the tokenize command to split up the string when we once again need it.

Create a new script and procedure called cycGetCyclingCurveData . Since we will want to return two strings, one for the keyframe at the beginning of the cycle and one containing all the information for the keyframe at the end of the cycle, we set the return type to a string array. We will also want to pass it the name of the animCurve. See Example 7.25.

Example 7.25: Gathering the data of our animation curves.

 global proc string[] cycGetCyclingCurveData                             (string $animCurveName)     {  // string array to hold return variables  string $returnString[];  // declare global variables  global float $cycStartTime;     global float $cycEndTime;  // strings to build return info  string $startInfo, $endInfo;  // add the name of the animCurve   // (the " " is for tokenizing later)  $startInfo = ($animCurveName + " ");  // add the start time   // (the " " is for tokenizing later)  $startInfo = ($startInfo + $cycStartTime + " ");  // find the in tangent angle and add it to the string   // (the " " is for tokenizing later)  float $cycKIA[] = `keyTangent                             -time $cycStartTime                             -query                             -inAngle                             $animCurveName`;     $startInfo = ($startInfo + $cycKIA[0] + " ");  // find the in tangent type and add it to the string   // (the " " is for tokenizing later)  string $cycKITT[] = `keyTangent                             -time $cycStartTime                             -query                             -inTangentType                             $animCurveName`;     $startInfo = ($startInfo + $cycKITT[0] + " ");  // find the in tangent weight and add it to the string   // (the " " is for tokenizing later)  float $cycKIW[] = `keyTangent                             -time $cycStartTime                             -query                             -inWeight                             $animCurveName`;     $startInfo = ($startInfo + $cycKIW[0] + " ");  // find the out tangent angle and add it to the string   // (the " " is for tokenizing later)  float $cycKOA[] = `keyTangent                             -time $cycStartTime                             -query                             -outAngle                             $animCurveName`;     $startInfo = ($startInfo + $cycKOA[0] + " ");  // find the out tangent type and add it to the string   // (the " " is for tokenizing later)  string $cycKOTT[] = `keyTangent                             -time $cycStartTime                             -query                             -outTangentType                             $animCurveName`;     $startInfo = ($startInfo + $cycKOTT[0] + " ");  // find the out tangent weight and add it to the string   // (the " " is for tokenizing later)  float $cycKOW[] = `keyTangent                             -time $cycStartTime                             -query                             -outWeight                             $animCurveName`;     $startInfo = ($startInfo + $cycKOW[0] + " ");  // find if the tangents are locked   // and add it to the string   // (the " " is for tokenizing later)  int $cycKTL[] = `keyTangent                             -time $cycStartTime                             -query                             -lock                             $animCurveName`;     $startInfo = ($startInfo + $cycKTL[0] + " ");  // find if the tangent weights are locked   // and add it to the string   // (the " " is for tokenizing later)  int $cycKTWL[] = `keyTangent                             -time $cycStartTime                             -query                             -weightLock                             $animCurveName`;     $startInfo = ($startInfo + $cycKTWL[0] + " ");  // find the key value and add it to the string   // (the " " is for tokenizing later)  float $cycKV[] = `keyframe                             -time $cycStartTime                             -query                             -eval                             $animCurveName`;     $startInfo = ($startInfo + $cycKV[0] + " ");  // Assign the start info to the return array  $returnString[0] = $startInfo;  // add the name of the animCurve   // (the " " is for tokenizing later)  $endInfo = ($animCurveName + " ");  // add the end time   // (the " " is for tokenizing later)  $endInfo = ($endInfo + $cycEndTime + " ");  // find the in tangent angle and add it to the string   // (the " " is for tokenizing later)  float $cycKIA[] = `keyTangent                             -time $cycEndTime                             -query                             -inAngle                             $animCurveName`;     $endInfo = ($endInfo + $cycKIA[0] + " ");  // find the in tangent type and add it to the string   // (the " " is for tokenizing later)  string $cycKITT[] = `keyTangent                             -time $cycEndTime                             -query                             -inTangentType                             $animCurveName`;     $endInfo = ($endInfo + $cycKITT[0] + " ");  // find the in tangent weight and add it to the string   // (the " " is for tokenizing later)  float $cycKIW[] = `keyTangent                             -time $cycEndTime                             -query                             -inWeight                             $animCurveName`;     $endInfo = ($endInfo + $cycKIW[0] + " ");  // find the out tangent angle and add it to the string   // (the " " is for tokenizing later)  float $cycKOA[] = `keyTangent                             -time $cycEndTime                             -query                             -outAngle                             $animCurveName`;     $endInfo = ($endInfo + $cycKOA[0] + " ");  // find the out tangent type and add it to the string   // (the " " is for tokenizing later)  string $cycKOTT[] = `keyTangent                             -time $cycEndTime                             -query                             -outTangentType                             $animCurveName`;     $endInfo = ($endInfo + $cycKOTT[0] + " ");  // find the out tangent weight and add it to the string   // (the " " is for tokenizing later)  float $cycKOW[] = `keyTangent                             -time $cycEndTime                             -query                             -outWeight                             $animCurveName`;     $endInfo = ($endInfo + $cycKOW[0] + " ");  // find if the tangents are locked and add it to   // the string   // (the " " is for tokenizing later)  int $cycKTL[] = `keyTangent                             -time $cycEndTime                             -query                             -lock                             $animCurveName`;     $endInfo = ($endInfo + $cycKTL[0] + " ");  // find if the tangent weights are locked and add   // it to the string   // (the " " is for tokenizing later)  int $cycKTWL[] = `keyTangent                             -time $cycEndTime                             -query                             -weightLock                             $animCurveName`;     $endInfo = ($endInfo + $cycKTWL[0] + " ");  // find the key value and add it to the string   // (the " " is for tokenizing later)  float $cycKV[] = `keyframe                             -time $cycEndTime                             -query                             -eval                             $animCurveName`;     $endInfo = ($endInfo + $cycKV[0] + " ");  // Assign the end info to the return array  $returnString[1] = $endInfo;  // return the data  return $returnString;     } 

We now will modify cycSelectCheck to use the newly created script to gather the data on our qualifying curves. We then assign each of the returned strings to a global string array we have added called $cycleData . We use our standard tricks of iterating through an array with a for-in loop, and using the size command to dynamically build a new array Add the following after building the array of the curves that have keyframes on both the beginning and end of the cycle. Make sure the global variable $cycleData is declared at the beginning of the procedure. See Example 7.26.

Example 7.26: Alter the selectCheck procedure to regather data each time the selection changes.

 for ($eachItem in $cycToolCurves)         {             string $returnedInfo[] =                         `cycGetCyclingCurveData $eachItem`;             for ($eachInfo in $returnedInfo)                 $cycleData[`size $cycleData`] = $eachInfo;         } 

Now that we have gathered the baseline data for our animation curves, we must create a script job to monitor for changes in the animation curves. Because there is no condition available to us to track most of the data we just gathered, we will instead track the attributes controlled by the selected animation curves. The one aspect of the animation curve we can explicitly track is whether the user has an animation curve set to use weighted tangents.

We now will create a script job for each of the animation curves held in $cycToolCurves . To set these up, we will actually create two new scripts and procedures, cycBuildScriptJob and cycUpdateCurve . Again, we will pass the animation curve name to the procedures. We are creating two scripts at the same time, since the script jobs created in cycBuildScriptJob will need a command to execute when the condition is met.. For now, we will simply put a print statement in cycUpdateCurve , to give us feedback in the Script Editor, letting us know when our script job executes.

First, our proxy script for cycUpdateCurve . See Example 7.27.

Example 7.27: When creating interdependent scripts, it is often necessary to create a proxy script.

 global proc cycUpdateCurve  (string $animCurve)     {         print ("Executing cycUpdateCurve on "                 + $animCurve                 + "\n");     } 

Now that a valid command exists for cycUpdateCurve , we can move on to the script to build the script jobs. Our first task will be to find the attributes connected to our animation curve. We use the command listConnections . Using listConnections can sometimes provide an overflow of data, requiring large amounts of data parsing to bring that returned information into a reasonable form. Here, however, the only connections our curves have are to the attributes they are driving. Using the -plugs flag returns the names of the attributes, rather than the name of the objects. We also set up a script job to monitor if the curve uses weighted tangents. See Example 7.28.

Example 7.28: The procedure to build our scriptJobs begins to take shape.

 global proc cycBuildScriptJob (string $animCurve)     {  // get the attributes driven by the animations curve  string $connections[] = `listConnections                                     -plugs true                                     $animCurve`;     // setup the command each script job will execute     string $scriptJobCmd = ("cycUpdateCurve "                                 + $animCurve);  // setup script job for each attribute found   // with listConnections  for ($eachItem in $connections)         scriptJob -attributeChange $eachItem $scriptJobCmd;     scriptJob -attributeChange ($animCurve                                     + ".weightedTangents")                                     $scriptJobCmd;     } 

At the same time, we now need to make calls to cycBuildScriptJob from cycSelectCheck . In the same for loop we collected the data on each of the animation curves, we now make a call to cycBuildScriptJob , which means we now monitor each of the curves named in $cycToolCurves . See Example 7.29.

Example 7.29: Tool suites require constant updating of multiple procedures. Here we update the SelectCheck procedure again to account for the procedure that creates the scriptJobs.

 for ($eachItem in $cycToolCurves)         {             string $returnedInfo[] =                         `cycGetCyclingCurveData $eachItem`;             for ($eachInfo in $returnedInfo)                 $cycleData[`size $cycleData`] = $eachInfo;             cycBuildScriptJob $eachItem;         } 

If we now activate the cycling tool with these changes, and modify a valid cycling curve, we see readout in the Script Editor. However, each time we change the selection, and include a previously selected cycling animation curve, our return gets longer, and longer and longer. The reason for this is that with each new selection, we are creating new script jobs to monitor the animation curves, but never delete the script jobs previously created. In order to track the script jobs created in cycBuildScriptJob , we will capture the script job numbers on creation, and assign them to a global integer array. We will also add the killWithScene flag to ensure that the jobs are deleted with a new scene. See Example 7.30

Example 7.30: Killing pre-existing scriptJobs.

 global proc cycBuildScriptJob (string $animCurve)     {  // Declare global variables  global int $cyclingScriptJobs[];  // get the attributes driven by the animations curve  string $connections[] = `listConnections                                 -plugs true                                 $animCurve`;  // setup the command each script job will execute and   // the temporary array to hold the job numbers  string $scriptJobCmd = ("cycUpdateCurve "                                 + $animCurve);      int $scriptJobNumbers[];  // setup script job for each attribute found   // with listConnections  for ($eachItem in $connections)         $scriptJobNumbers[`size $scriptJobNumbers`] =                 `scriptJob                     -killWithScene                     -attributeChange $eachItem                     $scriptJobCmd`                     ;     $scriptJobNumbers[`size $scriptJobNumbers`] =                 `scriptJob                     -killWithScene                     -attributeChange ($animCurve                             + ".weightedTangents")                         $scriptJobCmd`                         ;  // iterate through the temporary array and assign   // the values to the global variable  for ($eachNum in $scriptJobNumbers)         $cyclingScriptJobs[`size $cyclingScriptJobs`] = $eachNum;     } 

Now that we can track any of the script jobs created with cycBuildScriptJob , we place the code to delete any existing code in the cycSelectCheck procedure. See Example 7.31.

Example 7.31: Another modification to the SelectCheck procedure.

 . . .     $selectedCyclingCurves =         `stringArrayRemoveDuplicates $selectedCyclingCurves`;  // This kills any existing Script Jobs that check   // for Attribute changes  for ($eachJob in $cyclingScriptJobs)         scriptJob -kill $eachJob -force;     clear $cyclingScriptJobs; 

We use the clear command to delete any previous job numbers that might be stored in $cyclingScriptJobs , and prevent warnings that would be issued if we tried to delete non-existent script jobs.

We will now add some actual functionality to cycUpdateCurve . This procedure needs to do two things. It will only execute when an aspect of our animation curve changes, so its first job will be to find out what data has been modified. The second thing the script will do is initiate the alteration of the corresponding bookend keyframe.

To find out if any data has been modified, we first get updated data on the animation curve. We then compare it to the information stored in the global variable $cycleData . Our first goal will be to do a quick error check to see if the user has moved a key off the bookend times, and issue a warning if he does. See Example 7.32.

Example 7.32: Begin tracing the modifications the user has made to the curve.

 global proc cycUpdateCurve (string $animCurve)     {  // declare global variables  global string $cycleData[];     float $keyframes[] = `keyframe -query $animCurve`;     int $onBoth = 0;     for ($eachKey in $keyframes)         if (($eachKey == $cycStartTime)                  ($eachKey == $cycEndTime))             $onBoth++;     if ($onBoth != 2)         warning ("You have altered a key on curve "                     + $animCurve                     + " off the bookend times.");         {  // find the curve information after the change  string $updatedCurveInfo[] =                     `cycGetCyclingCurveData $animCurve`;         }     } 

Next, we will actually compare the data held in the two arrays, $cycleData and $updatedCurveInfo . We use an integer, $dataChanged , to track not only if data is changed, but what the changed data is. We use the command tokenize to break up the stored curve data into individual elements, and then compare them to find any changes. Note that we have again used getAttr to get the pre and post infinity behavior, and assigned those values to variables. See Example 7.33.

Example 7.33: Tokenizing the stored data.

 if ($onBoth == 2)         {  // find the curve information after the change  string $updatedCurveInfo[] =                     `cycGetCyclingCurveData $animCurve`;         for ($data in $cycleData)             {  // initialize the integer to track if any   // data was changed  int $dataChanged = 0;  // initialize the token buffer and tokenize   // the data stored in the global variable  string $dataBuffer[];             tokenize $data $dataBuffer;  // only continue if the current data   // structure is for the current animCurve  if ($dataBuffer[0] == $animCurve)                 {  // iterate through both the returns   // stored in the updated info  for ($eachUpdated in $updatedCurveInfo)                     {  // initialize the token buffer and   // tokenize the data stored in the   // updated data variable  string $updateBuffer[];                     tokenize $eachUpdated $updateBuffer;  // only continue if the current global   // data structure is for the same frame   // as the data in the updated buffer  if ($dataBuffer[1] == $updateBuffer[1])                         {  // find out if the data has   // changed   // In Angle  if ($updateBuffer[2] !=                                     $dataBuffer[2])                             $dataChanged = 1;  // In Weight  if ($updateBuffer[4] !=                                     $dataBuffer[4])                             $dataChanged = 1;  // Out Angle  if ($updateBuffer[5] !=                                     $dataBuffer[5])                             $dataChanged = 1;  // Out Weight  if ($updateBuffer[7] !=                                      $dataBuffer[7])                             $dataChanged = 1;  // Tangency Lock  if ($updateBuffer[8] !=                                     $dataBuffer[8])                             $dataChanged = 1;  // Weight lock  if ($updateBuffer[9] !=                                     $dataBuffer[9])                             $dataChanged = 1;  // Key Value  if ($updateBuffer[10] !=                                     $dataBuffer[10]                                     && ($preInfinity == 3                                      $postInfinity == 3))                             $dataChanged = (-1);                         }                     }                 }             }          } 

We are now aware of which of the selected keyframes has affected an alteration of our stored cycling data. We checked to see if the curve was cycling or cycling with offset in the check of the values because, obviously, if the curve is cycling with an offset, the values will likely not be the same. We now will alter the corresponding bookend keys of the altered curves. Our first step will be to get around a limitation of Maya, which does not allow us to edit keyframe data with MEL if there are any keys selected. Therefore, we simply deselect the keys. However, that will trigger the script job monitoring our selection list, removing all our stored data from memory, and making all that we have done pointless. We will put a flag into cycSelectCheck , in the form of a global integer to give us the ability to temporarily disable the evaluation of cycSelectCheck .

In both cycSelectCheck and cycUpdateCurve , add a global int variable called $cycActivity . In cycSelectCheck , enclose the entire procedure, except the declaration of the global variables, in an if statement. We will now only execute the main body if the value of our variable $cycActivity is zero. What we are doing is letting cycSelectCheck know when the cycling tool is editing data, and to not re-evaluate the selection list. At the same time, it would be a good idea to clear the array $cycleData whenever cycSelectCheck is evaluated with the activity set to off. See Example 7.34.

Example 7.34: Preventing the script from entering into a loop.

 global proc cycSelectCheck ()     {     global float $cycStartTime;     global float $cycEndTime;     global string $cycleData[];     global int $cycActivity;     global int $cyclingScriptJobs[];     if ($cycActivity == 0)         {          clear $cycleData;         string $selectedCyclingCurves[];         . . . 

Now, in the cycUpdateCurve procedure, after the if statements that check to see what aspects of our animCurve have changed, we can prepare to edit our data. See Example 7.35.

Example 7.35: Prepare the environment so we can modify the animation data.

  // prepare data to be edited  if ($dataChanged != 0)         {         $cycActivity = 1;  // so cycSelectCheck doesn't trigger  selectKey -clear ;  // Can't edit keyframes with   // keys selected.  } 

To edit the keyframe data, we will use one final script. Create a new script and global procedure call editCycleKey . We will want to pass it both a string array and an integer argument. We will be passing it both the tokenized data from the updated curve info and an integer that will control what aspect will be edited. Our first goal will be to determine whether we are editing the start key or the end key. If the first key was edited, we will modify the last key, and vice versa. See Example 7.36.

Example 7.36: Create a final procedure to execute the modification of the data.

 global proc editCycleKey (string $updateBuffer[],                                 int $index)     {     float $editKey;           if ($updateBuffer[1] == $cycStartTime)         $editKey = $cycEndTime;     else         $editKey = $cycStartTime;     } 

The order in which we edit the data is very important. If either of the tangency handle locks has been turned off or on, it is important to change those before we alter the other data. Next, in the cycUpdateCurve script, we check to see if the handle type has been changed, then the key value, and finally the angles of the tangent handles. We evaluate for each of these in turn, and evaluate editCycleKey with an integer argument, which we will then use in a switch statement in editCycleKey .

After we edit the key values, we reactivate cycSelectCheck , and reset the $dataChanged integer. See Example 7.37.

Example 7.37: Calls to editCycleKey are made based on what has changed.

  // prepare data to be edited  if ($dataChanged != 0)         {         $cycActivity = 1;  // so cycSelectCheck doesn't trigger  selectKey -clear ;  // Can't edit keyframes with keys selected.  }     if (($updateBuffer[8] != $dataBuffer[8])          ($updateBuffer[9] != $dataBuffer[9]))             editCycleKey $eachUpdated 4;     if (($updateBuffer[3] != $dataBuffer[3])          ($updateBuffer[6] != $dataBuffer[6]))             editCycleKey $eachUpdated 3;     if ($dataChanged == (-1))         editCycleKey $eachUpdated (-1);     if ($dataChanged != 0)         {         editCycleKey $eachUpdated 1;         editCycleKey $eachUpdated 2;         }     $cycActivity = 0;     $dataChanged = 0;  // resets $dataChanged status  

In addition, within the editCycleKey script, we will now add a switch case statement and dynamically build a string containing our command, which we will then evaluate. See Example 7.38.

Example 7.38: Dynamically building the command.

 global proc editCycleKey (string $updateBuffer[],                                 int $index)     {     global float $cycStartTime;     global float $cycEndTime;     float $editKey;     if ($updateBuffer[1] == $cycStartTime)         $editKey = $cycEndTime;     else         $editKey = $cycStartTime;     if ($index == (-1))         {         string $keyCmd = ("keyframe -edit -time "                             + $editKey                             + " -absolute -valueChange "                             + $updateBuffer[10]                             + " "                             + $updateBuffer[0]);         eval $keyCmd;         }     else         {         string $keyTanFlags;         switch ($index)             {                 case 1:                     $keyTanFlags = (" -inAngle "                                         + $updateBuffer[2]                                         + " -inWeight "                                         + $updateBuffer[4]                                         + " ");                     break;                 case 2:                     $keyTanFlags = (" -outAngle "                                         + $updateBuffer[5]                                         + " -outWeight "                                         + $updateBuffer[7]                                         + " ");                     break;                 case 3:                     $keyTanFlags = (" -inTangentType "                                         + $updateBuffer[3]                                         + " -outTangentType "                                         + $updateBuffer[6]                                         + " ");                     break;                 case 4:                     $keyTanFlags = (" -lock "                                         + $updateBuffer[8]                                         + " -weightLock "                                         + $updateBuffer[9]                                         + " ");                     break;             }         string $keyTanCmd = ("keyTangent                                     -edit                                     -absolute                                     -time "                                 + $editKey                                 + $keyTanFlags                                 + $updateBuffer[0]) ;         eval $keyTanCmd;         }     } 

After these additions, all of the changes are made to the curve, and our tool is complete. There are some minor user interface additions we could make, like setting up a logic system to reselect what the user started with. However, that would be a whole new tool in and of itself, because Maya cannot easily track what handle the user has selected in the Graph Editor.

Project Conclusion and Review

This project introduced us to a few important concepts useful in creating robust tools in MEL. Primarily was the use of global variables to pass data among different scripts when those scripts are not executed in a particular sequence. In addition, we learned how to modify the operating environment of Maya by using the scriptJob command. Finally, we learned how using multiple scripts can aid in the creation of a tool. While we did all the programming of this tool ourselves , it could easily have been split up among multiple programmers, each working on a separate piece.

Project Script Review

cyclingTool.mel

 global proc string cyclingTool ()     {     global int $cycToolStatus;     global int $cycTimeScriptJob ;     global int $cycSelectScriptJob ;     global int $cycSelectStatus;     global float $cycStartTime;     global float $cycEndTime;     if ($cycToolStatus == 0)         {             cycSetTimes;                          int $i = `scriptJob                         -killWithScene                           -event timeChanged cycTimeCheck`                         ;             $cycTimeScriptJob = $i;                     -killWithScene                      -event SelectionChanged cycSelectCheck`                     ;             $cycSelectScriptJob = $i;             $cycSelectStatus = 0;             $cycToolStatus = 1;             return "Cycling Tool is on." ;         }      else if ($cycToolStatus == 1)         {             scriptJob -kill $cycSelectScriptJob -force;              scriptJob -kill $cycTimeScriptJob -force;             playbackOptions -ast $cycStartTime -aet $cycEndTime ;             $cycToolStatus = 0;             return "Cycling Tool is off." ;         } } 

cycSetTimes.mel

 global proc cycSetTimes ()     {     global float $cycStartTime;     global float $cycEndTime;          $cycStartTime = `playbackOptions -q -min`;     $cycEndTime = `playbackOptions -q -max`;          float $range = ($cycEndTime - $cycStartTime);          playbackOptions -min ($cycStartTime - $range);     playbackOptions -max ($cycEndTime + $range);     cycTimeCheck;     } 

cycTimeCheck.mel

 global proc cycTimeCheck ()     {     global float $cycStartTime;     global float $cycEndTime;     float $range = $cycEndTime - $cycStartTime;     float $nowTime = `currentTime -q`;     while ($nowTime > $cycEndTime)         {              currentTime -e ($nowTime-$range);             $nowTime = `currentTime -q`;         }      while ($nowTime < $cycStartTime)         {              currentTime -e ($nowTime+$range);             $nowTime = `currentTime -q`;         }      } 

cycSelectCheck.mel

 global proc cycSelectCheck ()     {     global float $cycStartTime;     global float $cycEndTime;     global string $cycleData[];     global int $cycActivity;     global int $cyclingScriptJobs[];          if ($cycActivity == 0) {      clear $cycleData;          string $selectedCyclingCurves[];     string $selectedCurves[] = `keyframe -query -selected -name`;     for ($eachAnimCurve in $selectedCurves)         {         float $totalSelectedKeys[] = `keyframe                                         -query                                         -selected                                         $eachAnimCurve`;         for ($keyTime in $totalSelectedKeys)             {             int $preInfinity = `getAttr                     ($eachAnimCurve  + ".preInfinity")`;             int $postInfinity = `getAttr                     ($eachAnimCurve + ".postInfinity")`;                               $keyTime == $cycEndTime)                 && ($preInfinity == 3                  $preInfinity == 4                  $postInfinity == 3                  $postInfinity == 4))                     $selectedCyclingCurves                         [`size $selectedCyclingCurves`] =                         $eachAnimCurve ;                 }         }          $selectedCyclingCurves =         `stringArrayRemoveDuplicates $selectedCyclingCurves`;     // This kills any Existing Script Jobs that     // check for Attribute changes          for ($eachJob in $cyclingScriptJobs)         scriptJob -kill $eachJob -force;          clear $cyclingScriptJobs;     string $cycToolCurves[];          for ($eachItem in $selectedCyclingCurves)         {         float $keyframes[] = `keyframe -query $eachItem`;                      int $onBoth = 0;                      for ($eachKey in $keyframes)             if (($eachKey == $cycStartTime)                  ($eachKey == $cycEndTime))                     $onBoth++;                      if ($onBoth == 2)             $cycToolCurves[`size $cycToolCurves`] =                 $eachItem;          }              for ($eachItem in $cycToolCurves)         {         string $returnedInfo[] = `cycGetCyclingCurveData                 $eachItem`;         for ($eachInfo in $returnedInfo)             $cycleData[`size $cycleData`] = $eachInfo;                      cycBuildScriptJob $eachItem;         }     }     } 

cycBuildScriptJob.mel

 global proc cycBuildScriptJob (string $animCurve)     {     global int $cyclingScriptJobs[];          string $connections[] = `listConnections                                     -plugs true                                     $animCurve`;          int $scriptJobNumbers[];     string $scriptJobCmd = ("cycUpdateCurve "                                     + $animCurve);           for ($eachItem in $connections)         $scriptJobNumbers[`size $scriptJobNumbers`] =                     `scriptJob                             -attributeChange $eachItem                             $scriptJobCmd`;          $scriptJobNumbers[`size $scriptJobNumbers`] =                     `scriptJob                             -attributeChange                             ($animCurve                             + ".weightedTangents")                             $scriptJobCmd`;     for ($eachNum in $scriptJobNumbers)         $cyclingScriptJobs[`size $cyclingScriptJobs`] =             $eachNum;     } 

cycGetCyclingCurveData.mel

 global proc string[] cycGetCyclingCurveData             (string $animCurveName)     {  // string array to hold return variables  string $returnString[];  // declare global variables  global float $cycStartTime;     global float $cycEndTime;  // strings to build return info  string $startInfo, $endInfo;  // add the name of the animCurve   // (the " " is for tokenizing later)  $startInfo = ($animCurveName + " ");  // add the start time   // (the " " is for tokenizing later)  $startInfo = ($startInfo + $cycStartTime + " ");  // find the in tangent angle and add it to the string   // (the " " is for tokenizing later)  float $cycKIA[] = `keyTangent                             -time $cycStartTime                             -query                             -inAngle                             $animCurveName`;     $startInfo = ($startInfo + $cycKIA[0] + " ");  // find the in tangent type and add it to the string   // (the " " is for tokenizing later)  string $cycKITT[] = `keyTangent                             -time $cycStartTime                             -query                             -inTangentType                             $animCurveName`;     $startInfo = ($startInfo + $cycKITT[0] + " ");  // find the in tangent weight and add it to the string   // (the " " is for tokenizing later)  float $cycKIW[] = `keyTangent                             -time $cycStartTime                             -query                             -inWeight                             $animCurveName`;     $startInfo = ($startInfo + $cycKIW[0] + " ");  // find the out tangent angle and add it to the string   // (the " " is for tokenizing later)  float $cycKOA[] = `keyTangent                             -time $cycStartTime                             -query                             -outAngle                             $animCurveName`;     $startInfo = ($startInfo + $cycKOA[0] + " ");  // find the out tangent type and add it to the string   // (the " " is for tokenizing later)  string $cycKOTT[] = `keyTangent                             -time $cycStartTime                             -query                             -outTangentType                             $animCurveName`;     $startInfo = ($startInfo + $cycKOTT[0] + " ");  // find the out tangent weight and add it to the string   // (the " " is for tokenizing later)  float $cycKOW[] = `keyTangent                             -time $cycStartTime                             -query                             -outWeight                             $animCurveName`;     $startInfo = ($startInfo + $cycKOW[0] + " ");  // find if the tangents are locked and add it to   // the string   // (the " " is for tokenizing later)  int $cycKTL[] = `keyTangent                             -time $cycStartTime                             -query                             -lock                             $animCurveName`;     $startInfo = ($startInfo + $cycKTL[0] + " ");  // find if the tangent weights are locked and add it   // to the string   // (the " " is for tokenizing later)  int $cycKTWL[] = `keyTangent                             -time $cycStartTime                             -query                             -weightLock                             $animCurveName`;     $startInfo = ($startInfo + $cycKTWL[0] + " ");  // find the key value and add it to the string   // (the " " is for tokenizing later)  float $cycKV[] = `keyframe                             -time $cycStartTime                             -query                             -eval                             $animCurveName`;     $startInfo = ($startInfo + $cycKV[0] + " ");  // Assign the start info to the return array  $returnString[0] = $startInfo;  // add the name of the animCurve   // (the " " is for tokenizing later)  $endInfo = ($animCurveName + " ");  // add the end time   // (the " " is for tokenizing later)  $endInfo = ($endInfo + $cycEndTime + " ");  // find the in tangent angle and add it to the string   // (the " " is for tokenizing later)  float $cycKIA[] = `keyTangent                             -time $cycEndTime                             -query                             -inAngle                             $animCurveName`;     $endInfo = ($endInfo + $cycKIA[0] + " ");  // find the in tangent type and add it to the string   // (the " " is for tokenizing later)  string $cycKITT[] = `keyTangent                             -time $cycEndTime                             -query                             -inTangentType                             $animCurveName`;     $endInfo = ($endInfo + $cycKITT[0] + " ");  // find the in tangent weight and add it to the string   // (the " " is for tokenizing later)  float $cycKIW[] = `keyTangent                             -time $cycEndTime                             -query                             -inWeight                             $animCurveName`;     $endInfo = ($endInfo + $cycKIW[0] + " ");  // find the out tangent angle and add it to the string   // (the " " is for tokenizing later)  float $cycKOA[] = `keyTangent                             -time $cycEndTime                             -query                             -outAngle                             $animCurveName`;     $endInfo = ($endInfo + $cycKOA[0] + " ");  // find the out tangent type and add it to the string   // (the " " is for tokenizing later)  string $cycKOTT[] = `keyTangent                             -time $cycEndTime                             -query                             -outTangentType                             $animCurveName`;     $endInfo = ($endInfo + $cycKOTT[0] + " ");  // find the out tangent weight and add it to the string   // (the " " is for tokenizing later)  float $cycKOW[] = `keyTangent                             -time $cycEndTime                             -query                             -outWeight                             $animCurveName`;     $endInfo = ($endInfo + $cycKOW[0] + " ");  // find if the tangents are locked and add it   // to the string   // (the " " is for tokenizing later)  int $cycKTL[] = `keyTangent                             -time $cycEndTime                             -query                             -lock                             $animCurveName`;     $endInfo = ($endInfo + $cycKTL[0] + " ");  // find if the tangent weights are locked and add it   // to the string   // (the " " is for tokenizing later)  int $cycKTWL[] = `keyTangent                             -time $cycEndTime                             -query                             -weightLock                             $animCurveName`;     $endInfo = ($endInfo + $cycKTWL[0] + " ");  // find the key value and add it to the string   // (the " " is for tokenizing later)  float $cycKV[] = `keyframe                             -time $cycEndTime                             -query                             -eval                             $animCurveName`;     $endInfo = ($endInfo + $cycKV[0] + " ");  // Assign the end info to the return array  $returnString[1] = $endInfo;  // return the data  return $returnString;     } 

cycUpdateCurve.mel

 global proc cycUpdateCurve (string $animCurve)     {     global string $cycleData[];     global float $cycStartTime;     global float $cycEndTime;     /*     Anim Curve Name = $index[0];     Time = $index[1];     In Angle = $index[2];     In Tangent Type = $index[3];     In Weight = $index[4];     Out Angle = $index[5];     Out Tangent Type = $index[6];     Out Weight = $index[7];     Lock = $index[8];     Weight Lock = $index[9];     */          string $newSelectedKeys[];     global int $cycActivity;          int $preInfinity = `getAttr             ($animCurve + ".preInfinity")`;     int $postInfinity = `getAttr             ($animCurve + ".postInfinity")`;          float $keyframes[] = `keyframe -query $animCurve`;                  int $onBoth = 0;          for ($eachKey in $keyframes)         if (($eachKey == $cycStartTime)              ($eachKey == $cycEndTime))                 $onBoth++;          if ($onBoth != 2)         warning ("You have altered a key on curve "                     + $animCurve                     + " off the bookend times.");     if ($onBoth == 2)         {         string $updatedCurveInfo[] =                     `cycGetCyclingCurveData $animCurve`;         int $dataChanged = 0;         for ($data in $cycleData)             {             string $dataBuffer[];                      tokenize $data $dataBuffer;             if ($dataBuffer[0] == $animCurve)                 {                 for ($eachUpdated in $updatedCurveInfo)                     {                     string $updateBuffer[];                     tokenize $eachUpdated $updateBuffer;                     if ($dataBuffer[1]==$updateBuffer[1])                         {  // find out if the data   // has changed;   // In Angle  if                         ($updateBuffer[2]!=$dataBuffer[2])                             $dataChanged = 1;  // In Weight  if                         ($updateBuffer[4]!=$dataBuffer[4])                             $dataChanged = 1;  // Out Angle  if                         ($updateBuffer[5]!=$dataBuffer[5])                             $dataChanged = 2;  // Out Weight  if                         ($updateBuffer[7]!=$dataBuffer[7])                             $dataChanged = 2;  // Tangency Lock  if                         ($updateBuffer[8]!=$dataBuffer[8])                             $dataChanged = 1;  // Weight lock  if                         ($updateBuffer[9]!=$dataBuffer[9])                             $dataChanged = 1;  // Key Value  if                         ($updateBuffer[10]!=$dataBuffer[10]                         &&                         ($preInfinity==3$postInfinity==3))                             $dataChanged = (-1);  // prepare data to be edited  if ($dataChanged != 0)                             {                             $cycActivity = 1;  // so cycSelectCheck doesn't   // trigger  selectKey -clear ;  // Can't edit keyframes with   // keys selected.  }                                                  if                         ($updateBuffer[8]!=$dataBuffer[8])                             editCycleKey $updateBuffer 4;                                                  if                         ($updateBuffer[9]!=$dataBuffer[9])                             editCycleKey $updateBuffer 5;                                                  if                         (($updateBuffer[3]!=$dataBuffer[3])                                                  ($updateBuffer[6]!=$dataBuffer[6]))                             editCycleKey $updateBuffer 3;                         if ($dataChanged == (-1))                             editCycleKey                                 $updateBuffer (-1);                         if ($dataChanged == 1)                             editCycleKey $updateBuffer 1;                         if ($dataChanged == 2)                             editCycleKey $updateBuffer 2;                             }                     $cycActivity = 0;                     $dataChanged = 0;  // resets $dataChanged status  }                     }                 }             }         } 

editCycleKey.mel

 global proc editCycleKey (string $updateBuffer[],                                 int $index)     {     global string $cycleData[];     /*     Anim Curve Name = $index[0];     Time = $index[1]     In Angle = $index[2];     In Tangent Type = $index[3];     In Weight = $index[4];     Out Angle = $index[5];     Out Tangent Type = $index[6];     Out Weight = $index[7];     Lock = $index[8];     Weight Lock = $index[9];     */              global float $cycStartTime;     global float $cycEndTime;     global string $selectedKeys[];              float $editKey;              if ($updateBuffer[1] == $cycStartTime)          $editKey = $cycEndTime;     else          $editKey = $cycStartTime;              if ($index == (-1))         {         string $keyCmd = ("keyframe                                 -edit                                 -time "                             + $editKey                             + " -absolute -valueChange "                             + $updateBuffer[10]                             + " "                             + $updateBuffer[0]);         eval $keyCmd;         }                                    else             {             string $keyTanFlags;                      switch ($index)                 {                 case 1:                     $keyTanFlags = (" -inAngle "                                         + $updateBuffer[2]                                         + " -inWeight "                                         + $updateBuffer[4]                                         + " ");                     break;                 case 2:                     $keyTanFlags = (" -outAngle "                                         + $updateBuffer[5]                                         + " -outWeight "                                         + $updateBuffer[7]                                         + " ");                     break;                 case 3:                     $keyTanFlags = (" -inTangentType "                                         + $updateBuffer[3]                                         + " -outTangentType "                                         + $updateBuffer[6]                                         + " ");                     break;                 case 4:                     $keyTanFlags = (" -lock "                                         + $updateBuffer[8]                                         + " ");                     break;                 case 5:                     $keyTanFlags = (" -weightLock "                                         + $updateBuffer[9]                                         + " ");                     break;             }                          string $keyTanCmd = ("keyTangent                                         -edit                                         -absolute                                         -time "                                     + $editKey                                     + $keyTanFlags                                     + $updateBuffer[0]) ;                  eval $keyTanCmd;                      } 



The MEL Companion
The MEL Companion: Maya Scripting for 3D Artists (Charles River Media Graphics)
ISBN: 1584502754
EAN: 2147483647
Year: 2003
Pages: 101

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net