Various Scripting Notes and Files

Since I often find myself wondering how I could have missed such interesting commands, I suppose it would be a good idea to repeat them once more for the internet's web crawlers to take note of. I don't know what sorts of notes I'll store here but hopefully they might help somebody out there. For a little while this page may seem sparse as I'll only add to it when I'm doing a good amount of scripting. I'll focus more on insight than documentation.

MEL Scripting

eval( ) - Evaluates a string when it is called. I used this command to piece together predictable strings of code with slight variations to produce infinite layers in my layer editor. Where a button might be able to call a specific preset command at any time after your script has been run, the eval() function is allows you build an entirely new command on the spot and call it, all after the script has finished running. You can even use the eval() function along with a basic text box in order to create your own miniature scripting window with a "run script" button that attempts to run whatever text is in the text box at the time. The eval() command is extremely helpful when you don't know the variables for your script ahead of time and need to insert them as they are retrieved.

evalDeferred( ) - Much like the eval() command above but this command doesn't run its contents until after the computer finishes with its current processes. Maya crashes when you delete an interface within the same function that queried or edited it so using evalDeferred to delete the interface is sometimes necessary.

substring - This command catches me up because it doesn't use parenthesis to contain its parameters. It's purpose is to read a portion of a string as an array (something that Python makes very easy). rand is another command that doesn't use parenthesis.

stringToStringArray( ) - This is a useful command for compressing lists into and out of strings. Strings tend to be easier to transfer between portions of your code as they require less variable space. I personally transfer my data through interfaces so long as they are kept open so converting an array to a string allows me to store complex lists in a single text object. The stringToStringArray command identifies a dividing character and parses the chunks between them into an array. Commas are usually safe since they are rarely used in files or node names. Use a for loop to add the divider between elements when you're compressing the list into a string.

setFocus( ) - This command will immediately select and focus on an interface item or window when called. If you want the user to be able to type an answer to a prompt without having to stop and select the text field, this command will do the job nicely.

Assign Arrays with { } brackets - I always forget to use the special brackets and end up twiddling my thumbs for a few minutes trying to figure out what obvious thing I've missed is.

int $variable = rand 0 10 - The rand function outputs in float values by default. Integers, as it happens, round down. Given a rand function ranging from 0 to 10, the decimal values will hit everything from 0.001 through 9.999. The integers, however, will hit 0 through 9. Keep this in mind when building your code. MEL uses floor rounding for integers.

progressBar - A nifty interface item that I didn't know existed. It's exactly what it sounds like, a compact bar that fills up based on the value input into it. It's nice for previewing exact values visually as you test your scripts. The output is always on a scale of 0% to 100%, so unfortunately the displayed value will only ever be in that range. A nice way to make your script easier to read is to automatically update the progressBar from the drop command of a corresponding slider or input. You can do this by hard coding something like this into the input element (a slider's -dc in this case):

"progressBar -edit -progress ( `intSliderGrp -query -value mySlider` ) myProgressBar"

scrollField - This is basically a text box that generates a scroll bar when it grows beyond it's bounds. If you enable word wrapping (-ww 1), you will get a vertical scroll bar only. The text is editable by a user by default, though you can make it non-editable. The text itself can be selected and copied even in that state.

textScrollList - Another interface type I overlooked. In addition to other normal things, this field is capable of word wrapping, meaning it's ideal for boxes of text. This is for creating endless lists of selections within the same window. I haven't experimented with it too much but it could be useful for storing and accessing larger data collected from the current project. Maybe you could make a quick hypergraph window with easy icons to display lists of cameras, objects, lights, or whatever.

grid and currentUnit - These are the commands that I used for pretty much the entire Grid Breaker Panel script. They're easy to figure out and seem to cover everything you'd expect. Both are queryable in addition to being able to make changes to their data.

Time Functions

I've spent a lot of time goofing around with time based commands in MEL since they're kind of makeshift for my purposes. I wanted something that could put off any further code until it's condition had been met. After freezing my computer with numerous while loop workarounds, I eventually found "pause" which works just well enough. Here I list some of the ways I attempted to solve the problem.

timer - This is a command designed specifically to tell you how long it took for your code to run. It doesn't interrupt any of your code, so it can't be used to hold off on anything. I actually made a while loop which repeatedly checks the timer's current count (-lap) and compares it against the time you requested. Sadly, this is a great way to choke your computer to death with infinitely repeating 0.0000001 second increments. Instead, I placed a series of print commands into the while loop such that each cycle triggers 100 [  print ".";  ] commands. This took up precious tenths of a second until the timer actually reached X seconds and quit the loop. As a warning, definitely include a iteration cap for the loop:

float $timeSoFar = 0;
int $counter = 0;

timer -s; //Start the timer before the while loop.
while ($counter < 1000 && $timeSoFar < 1)
{
$counter += 1;
for ($timeWaste = 0; $timeWaste < 200; ++$timeWaste)
{ print "."; } //Creating lag to keep your computer from hyperventilating on the while loop.
$timeSoFar = `timer -lap`;
print ("\n" + $timeSoFar + " seconds have passed.\n");
}

timer -e; //End the timer.
print ("\n" + $timeSoFar + " seconds have passed.\n");

currentTime, playbackOptions, and scriptJob - So with this approach, I tried to wrestle the animation slider to act as my instigator. The playbackOptions command allows you to set its range and speed while the currentTime command sets the frame number or second number. The most important part was the scriptJob, which can be interpreted here as a delayed function that goes off when its conditions have been met. ScriptJob can be set to run a command when the mouse is clicked, objects are selected, a file is saved or opened, and in response to dozens of other conditions or events. Though it does have its own -timeChanged flag, I actually went with the event trigger instead (events and conditions are listed in the linked documentation. You can also choose whether or not the script runs once or repeatedly, though if it runs repeatedly you'll need to use a -kill flag on it when you're done with it.

The key observation that makes the animation slider useful is that playback disables GUI commands or something like that. It prevents code from running until it's done. What this means is that the scriptJob I set up to tell me when time changed on the slider only runs its code when the playback stops. Since I control the length of the playback and the play command (playButtonForward;) I can automatically play a command a precise amount of time in the future.

The one thing that makes this method kind of difficult to work with is that the scriptJob, like some helium balloon released to the wind, doesn't communicate with your own code. In order to run a continuous loop that pauses and returns to your code, you'll need to somehow juggle the control back and forth between the two. Below is an example of how, with zero global variables in play and without releasing untethered scriptJobs all over your file, you can still manage to cycle through actual seconds of time. A second limit could easily insert the max value at the bottom.

setDelay(0);

proc setDelay(int $iterationsCompleted)
{
playbackOptions -ast 1 -min 1 -aet 24 -max 24; //Range slider and animation length.
playbackOptions -ps 1 -mps 1; //Play speed and max play speed.
playbackOptions -loop "once"; //once, continuous, or oscillate.
currentTime -e 1; //Move the animation slider back to zero. This does not trigger the job.

string $newCommand = ("readTime(" + $iterationsCompleted + ")");
scriptJob -runOnce 1 -e "timeChanged" $newCommand;
playButtonForward; //This sets the repetition into motion.
}

proc readTime(int $iterationsCompleted)
{
$iterationsCompleted += 1;
int $framesPassed = ((`currentTime -q`) * $iterationsCompleted);
print ("\n" + $framesPassed + " frames, or " + ($framesPassed/24.0) + " seconds.");

if ($iterationsCompleted < 10)
{
setDelay($iterationsCompleted);
}
}

pause - This was ultimately the function I was looking for. Rather than setting off a timer and letting the rest of the code play, pause allows me to actually stop everything, then resume it. The only qualm I have is that it somehow runs itself before the rest of the procedure. What this means is that you have to place the pause command into its own personal procedure in order to keep things running smoothly.

I should note using this command freezes every command, not just those in your current project. If you want to poke around an interface while everything is paused, tough luck. It also means that a cycle of pauses may prevent you from hitting a cancel button, so definitely include an iteration limit here as well unless you're sure you don't want to use Maya again.

pauser();

proc pauser()
{
print "This command waits to run too.\n";
pause -sec 3;
print "Now run the program.\n";
}

 

Python Scripting

 

 

Zscripting

Timers - Zbrush is not particularly robust when it comes to persistent interfaces. It's built for singular commands that run and finish immediately. I attempted to create an interface that would help me sit down and sculpt through very fast studies and would force me to move on to the next without having to stop myself (outsourcing my self control!). It also was meant to draw from a library of images and words to give random inspirations for each sculpt ("giraffe, ballpoint pen, wagon") and had built in saving features. I got most of the way through this project before coming to the conclusion that Zbrush's timer features weren't going to allow it. Zbrush uses a sleep timer command that rests between triggers (mouse movement was my trigger). I never figured out a way around whatever issue I had with the timer but needless to say there appeared to be no way around it. Someday I'd like to take another go at it.