Script Manual

When Shake saves a script, it creates a file that is basically the C programming language. This makes the product open and flexible, as users can add their own functions, or use programing structures to procedurally do what are is extremely tedious by hand. The user can also quickly make minor modifications that are cumbersome to do in the interface (for example changing a FileIn to read BG2.iff instead of BG1.rla). This section of the manual explains the basic principles of this scripting language, and how they can be manipulated to make your own macros.

There is a mini-tutorial of sorts under Modifying Parameter Behavior in that it goes step-by-step through some ui scripting examples.

If you are a programmer, this document no doubt insults you. Read the Tip box below on if you should precompile or use macros, maybe read the other Tip boxes too, just to be nice, and then contact Apple for the SDK. If you are accustomed to MEL, TCL or shell scripting, this document is also a fast read.

There is also a tutorial on how to make a macro both in a script and interactively under GUI Advanced - How to Make a Macro.

 

Should You Use a Macro or Precompile with the SDK? Shake has two levels at which scripting can be done. The first is to use pre-existing functions and recombine them to make your own functions. These are called macros, and can be done relatively easily, and even within the interface. Shake compiles these on the fly. This compilation may take calculation time, which brings us to the second level of scripting, which is of precompiled functions. This second level is not what is discussed here (as I would only embarrass myself), but information can be found in the Shake SDK, available by contacting support@nothingreal.com. This leaves the question: If you know how to, should you precompile a function or not? In many cases precompiling with the SDK gets you little if anything for a lot more work. Since macros are compiled by Shake, anything that is just aggregating existing Shake functions does not have much to gain (if anything) from building in the SDK. However, the cases where there are significant gains to be had from precompiling plugins are when there are a lot of redundant calculations being performed. Using for loops or functions like ColorX/DisplaceX/etc are typical cases.

Need an example for speed diiferences? Try this:

  • Create a 16-bit Checker and a RGrad image.

  • Attach them both into a Warp - DisplaceX, with Checker being the first image.

  • Attach both into a Warp - IDisplace:

 

  • Load the DisplaceX into the Viewer,
  • Load the DisplaceX parameters and enter the following expressions in xExpr and yExpr :

    x-r*50
    y-r*50

  • Load the RGrad parameters and move the RGrad around in the Viewer. It should be somewhat poky, as the DisplaceX does not precompile the expression.

  • View and edit the IDisplace node, entering a xScale of 50. This is a precompiled version of the DisplaceX you just made.

  • Load up the RGrad parameters and move it around in the Viewer again. In comparison to the DisplaceX, IDisplace is pretty snappy, as the displacement equation has already been precompiled.

 

 

Again, this document is not about the SDK – it discusses macro creation and solutions accessible to the common user.

 

Scripting Principles

Scripting Controls:

This composites the truck over the background, with the sign mask as a holdout mask, saving it as a script named start.shk. The composite itself is unimportant, but the tree structure is. It looks like this in the interface:

 

It pops the image of the truck comped over the street.

Here is what the script itself looks like, more or less:

 

SetTimeRange("1");
SetFieldRendering(0);
SetFps(24);
SetMotionBlur(1, 1, 0);
SetQuality(1);
SetUseProxy("Base");
SetProxyFilter("default");
SetPixelScale(1, 1);
SetUseProxyOnMissing(1);
SetDefaultWidth(720);
SetDefaultHeight(486);
SetDefaultBytes(1);
SetDefaultAspect(1);
SetDefaultViewerAspect(1);
SetTimecodeMode("24 FPS");
SetDisplayThumbnails(1);
SetThumbSize(15);
SetThumbSizeRelative(0);
SetThumbAlphaBlend(1); // Input nodes bg = SFileIn("bg.iff", "Auto", 0, 0); sign_mask = SFileIn("sign_mask.iff", "Auto", 0, 0); truck = SFileIn("truck.iff", "Auto", 0, 0); // Processing nodes Outside1 = Outside(truck, sign_mask, 1); Over1 = Over(Outside1, bg, 1, 0, 0);

 

The first section has controls for the script, all of which are optional or can be overridden on the command line. See below under Frequently Used Script Commands, Routines, and Variables for more information. These next sections discuss the body of the script, the parts listed under the Input and Processing nodes comments.

 

Why "SFileIn" and not "FileIn"? The SFileIn is an improvement on the older FileIn node. The interface button FileIn is linked to SFileIn, but the older name was retained so the users wouldn't freak out. The SFileIn node allows extra subfunctions for timing to be attached.


 

Variables and Data Types:

Shake assigns types of data to a variable name. A variable works as a sort of shopping cart (pick any metaphor here...) to carry around your information to be re-used somewhere else. The variables in the following code (excerpted from above), are bold. The comment lines (lines starting with //) are omitted:

 

bg = SFileIn("bg.iff", "Auto", 0, 0);
sign_mask = SFileIn("sign_mask.iff", "Auto", 0, 0);
truck = SFileIn("truck.iff", "Auto", 0, 0);
Outside1 = Outside(truck, sign_mask, 1);
Over1 = Over(Outside1, bg, 1, 0, 0);

 

The above code assigns three variables (bg, sign_mask, truck) to three SFileIn nodes. These are then fed into an Outside node and then an Over node, both of which are also assigned variable names, Outside1 and Over1.

To load a script into the interface, you can do one of two things:

  • Save the script and load it into the interface with the Terminal command shake start.shk or the Load button in the interface.
  • Copy the script as text from the HTML browser/text editor and paste it into the Node View with Ctrl+V. When you paste it into the interface, it points out that the file paths are local, and probably not correct in terms of where the images are and where the interface is expecting to find them. Therefore, you can browse to the directory where the images are and press OK.

 

Here is a good reason to use a script – you can quickly set the path for the images by using copy/paste functions in a text editor. In this case, the local file paths (the images can only be found if the script is in the truck directory) are reset to an absolute path (the images can be found regardless of the location of the saved script, and are therefore more suitable for network rendering). This path is of course different on your machine, depending on where you have copied the tutorial project, so don't necessarily enter /Documents/Shake/doc/. That would be silly.


bg = SFileIn("/Documents/Shake/doc/pix/truck/bg.iff", "Auto", 0, 0);
sign_mask = SFileIn("/Documents/Shake/doc/pix/truck/sign_mask.iff", "Auto", 0, 0);
truck = SFileIn("/Documents/Shake/doc/pix/truck/truck.iff", "Auto", 0, 0);
Outside1 = Outside(truck, sign_mask, 1);
Over1 = Over(Outside1, bg, 1, 0, 0);

The left side is the variable name. You can change the variable name, but you must change them wherever they appear.


Gilligan = SFileIn("/Documents/Shake/doc/pix/truck/bg.iff", "Auto", 0, 0);
Skipper = SFileIn("/Documents/Shake/doc/pix/truck/sign_mask.iff", "Auto", 0, 0);
Lovey = SFileIn("/Documents/Shake/doc/pix/truck/truck.iff", "Auto", 0, 0);
Thurston = Outside(Lovey, Skipper, 1);
Ginger = Over(Thurston, Gilligan, 1, 0, 0);

Therefore, the left side of these lines is the variable to which you are assigning a value. The right side is the value, usually coming from a function like SFileIn, Outside or Over. The function is called by its name, then it has its arguments between parentheses. The arguments, called parameters, are very specifically organized and always specify the same thing, that is., the third parameter of Outside is 1, which is a numeric code to decide if you are taking the foreground or background resolution. (How do I know this? I looked it up in the docs under Function By Name - O - Outside.) The line ends with a semicolon.

 

These next steps illustrates how variables can help you. You insert the Mult color correction node to change the truck color. The script format for Mult (found under Function By Name - M - Mult) looks like this:


image Mult( 
image In, float red, float green, float blue, float alpha, float depth );

To add the Mult node in, assign a variable name (here, MaryAnn) and then the parameters. Since Lovey is the truck's variable name, it is fed in as the image. The numbers turn the truck yellow. Since Thurston (the Outside node) was loading Lovey before, switch it to MaryAnn, the new Mult node. (Note: the premultiplied state of the truck is being ignored for this tutorial).


Gilligan = SFileIn("/Documents/Shake/doc/pix/truck/bg.iff", "Auto", 0, 0);
Skipper = SFileIn("/Documents/Shake/doc/pix/truck/sign_mask.iff", "Auto", 0, 0);
Lovey = SFileIn("/Documents/Shake/doc/pix/truck/truck.iff", "Auto", 0, 0);
MaryAnn = Mult(Lovey, 1, 1, .2);
Thurston = Outside(MaryAnn, Skipper, 1);
Ginger = Over(Thurston, Gilligan, 1, 0, 0);

It looks like this in the interface:

 

You only entered four parameters for the Mult node – the alpha and depth parameters are omitted. These therefore defaulted to a value of 1. You can also see that Mult is looking for two types of data: an image (labeled In) and floats (labeled red, green, blue, etc). A float is any number with a decimal place in it, for example, 1.2, 10.00, .00001, 100,000.00. There are five types of data:

Data Type Notes
image Uh, an image...
float A number with a decimal place - .001, 1.01, 10,000.00
int Integer. A number with no decimal place - 0, 1, 10,000
string "this is a string"
curve float or curve int A special case of float or int that designates that the number has the possibility of being changed during script execution or when tuning it in the interface. More on this in a bit.

Shake is usually smart enough to convert an integer into a float, so when you entered

MaryAnn = Mult(Lovey, 1, 1, .2);

it didn't get too freaked out that you entered two integers (1, 1) for the red and green multipliers. However, this is an infrequent case concerning the conversion of floats and integers, as you couldn't put in a string or an image for those arguments. For example,

MaryAnn = Mult(Lovey,"1", Gilligan, .2);

doesn't work because you are drastically mixing your data types (plugging an image into a float). The one exception to this is when you enter 0 as an image input, indicating you don't want to designate any image input.

 

To designate that an function has no image input, place a 0 in the image position:

This example has no image input for the background image, the second argument:

MaryAnn = Over(Thurston, 0);

 

You have so far only assigned variables to image types. In this next example, create a float variable so you can multiply the truck image by the same amount on the red, green, and blue channels. Call this variable mulVal.

Note: For these examples, you must save the script and use Load Script - the Copy/Paste does not work properly.


float mulVal = .6;
Gilligan = SFileIn("/Documents/Shake/doc/pix/truck/bg.iff", "Auto", 0, 0);
Skipper = SFileIn("/Documents/Shake/doc/pix/truck/sign_mask.iff", "Auto", 0, 0);
Lovey = SFileIn("/Documents/Shake/doc/pix/truck/truck.iff", "Auto", 0, 0);
MaryAnn = Mult(Lovey, mulVal, mulVal, mulVal);
Thurston = Outside(MaryAnn, Skipper, 1);
Ginger = Over(Thurston, Gilligan, 1, 0, 0);

Now Mult takes .6 for its red, green, and blue parameters. To change all three, modify mulVal on the first line of the script and re-execute the script. This is swell, and you are probably patting yourself on the back on how blazingly clever you are. However, here is a problem. The variable mulVal is only applied when you load the script – Shake doesn't keep it around for later use. If you load the script (with Load Script, not Copy/Paste) into the interface and look at the Mult parameters, they all say .6; not mulVal. There is no place in the interface that you can find the mulVal parameter and modify it and have the Mult take the argument. If you are immediately executing the script, this is not a problem. If you are loading the script and are going to interactively edit it, this is a problem. You therefore must declare mulVal to be a curve float, a special type of float that tells Shake to wait until frame calculation to resolve the variable:


curve float mulVal = .6;
Gilligan = SFileIn("/Documents/Shake/doc/pix/truck/bg.iff", "Auto", 0, 0);
Skipper = SFileIn("/Documents/Shake/doc/pix/truck/sign_mask.iff", "Auto", 0, 0);
Lovey = SFileIn("/Documents/Shake/doc/pix/truck/truck.iff", "Auto", 0, 0);
MaryAnn = Mult(Lovey, mulVal, mulVal, mulVal);
Thurston = Outside(MaryAnn, Skipper, 1);
Ginger = Over(Thurston, Gilligan, 1, 0, 0);

In short, if you are loading a script to be modified in the interface, you probably want to declare it as a curve type of data. If you are going to execute the script on the command line and it isn't going to change (that is, it isn't animated), you can omit the curve data type.

Don't worry, there are examples later on.

 

Functions

Shake is a collection of functions. Some functions modify images, like Blur. Some return a number, like distance() (this calculates the distance between two points) or cosd() (a cosine function in degrees, not radians). Other functions build the interface, like nuiToolBoxItem, which loads a button into the interface and attaches a function to it. When you call a function, you assign a variable to it. The following example reads the node named Rotate1's angle parameter, and returns the cosine of it in degrees, assigning it to the variable named myCos:

curve float myCos = cosd(Rotate1.angle);

You can also use functions inside of other functions. In this example, the cosd function is inside of a Rotate function:

Rotate1 = Rotate(FileIn1, cosd(45));

When you place a function inside of another, it is called a nested function. In both cases, you call the function, and then encase its parameters in parentheses. When you assign a variable to a function, terminate the line with a semicolon, as is evident in both examples. Everything in between the parentheses can be formatted however you want, so the following two examples are identical:

Mult1 = Mult(FileIn1, 1,1,1);

and

Mult1 = Mult(
     FileIn1,
     1,
     1,
     1
); 

Function Formats. So, where the bejeezus are all of these functions and how do you find their formats? Typically, they are organized by the type of data they manipulate.

Here is the coolest way of getting a function format. Create the node(s) in the interface, select and copy them with Ctrl+C, and paste it into a text editor.

Otherwise,

Comments

You can temporally comment out lines in a script with the following symbols:

# This line is commented out
// This line is also commented out
This is not commented out //But this is
/*
All of
these lines
are
commented out
*/

 

Conditional Statements

Use conditional statements as you can in C. These are particularly useful in macros, where a conditional parameter is input by the user. Keep in mind that the conditional statements require you to work in the script and cannot be built with interface tools.

 

Conditional expression. If you simply want to switch a parameter, use the in-parameter conditional expression expr1?expr2:expr3, which reads as," if expr1 is true, use expr2, otherwise use expr3". For example, time>10?0:1. This reads as, "if time is greater than 10, then set the value to 0; otherwise, set it to 1".

 

In the interface, you can also use the Select node, which switches between any number of input nodes. Jump to its documentation for more information. This strategy allows you to stay within the interface without resorting to the script. However, the difference is that Shake only builds the part of the script that it needs when doing conditional statements listed below. Select has the overhead of all of its input nodes.

Finally, ++i or --i is supported for increments. i++ is as well, but returns a warning message.

Note: html makes the {curly brackets} largely illegible in some fonts. Therefore, I have colored them blue and BIG to make them obvious.

If/Else

Used to express decisions. If the test expression is true, then the first statement is executed. If false (and if an "else" part exists), then the second statement is executed. If "else" is left off, Shake does nothing and moves on.

if (expression evaluates to non-zero) { 
       do_this 
} else if { 
       do_this 
} else if { 
       do_this 
} else { 
       do_this 
}

or just

if (expression evaluates to non-zero) { 
       do_this 
} else if { 
       do_this 
}

 

For

Normally, the three expressions of the "for" loop are an initialization (a=1), a relational test (a<10), and an increment (a++): for (a=1; a<10; a++). So "a" is set to one, checked if it is less than 10, then if that is true, a statement performed, and "a" is incremented by one. The test is checked again and this cycle continues until "a" is found to be not less than 10 (untrue).

for (initialize; test; increment) 
{ 
    do_this 
}

While

If the given expression is true or non-zero, the following statements are performed, and the expression is reevaluated. The cycle continues until the expression becomes false.

while (this_expression_is_true) 
{ 
    do_this 
}

If there is no initialization or re-initialization, "while" often makes more sense than "for".

Do/While

This variation of "while" is different in that it tests at the bottom of the loop, so the statement body is done at least once. In the "while" above, the test may be false the first time and so nothing is done. Note on all of the other statements, a semicolon was not needed. This expression does need it at the end.

do { 
  do_this 
} while (this_expression_is_true);

Macros


You can build your own functions using pre-existing ones. These are called macros. These files can be saved into the scrip that uses them, or can be saved in a file with a .h file extension. These files are saved in your startup directory, which is located either in <ShakeDir>/include/startup, <UserDir>/nreal/include/startup, or a startup directory under a directory specified by the environment variable $NR_INCLUDE_PATH. For more information, jump to Customization - Where to Put Preference Files.

 

Basic Macro Structure

Here is what a macro looks like:

dataType MacroName(
dataType parameterName=defaultParameterValue,
dataType parameterName=defaultParameterValue,
...
)
{
Macro Body
return variable;
}

For example, the following function, called angle, calculates the angle between two points, returning a float, using the atan2d function. (How do I know this? Look at Functions By Class - Expressions under Trig functions.) Notice that the default parameter value is optional, so if you use this particular function, all four values must be supplied:

float angle(
	float x1,
	float y1,
	float x2,
	float y2
)
{
	return atan2d(y2-y1,x2-x1);
}

Because the macro is so simple (one function), there is no Macro Body per se; the function is attached to the return statement, which indicates what is spit out of the macro. To use this function, use something like:

myAngle = angle(0,0,100,100);

which returns 45.0.

Here is an example of an image function. It adds that "vaseline-on-the-lens" effect that is usually reserved for actresses who didn't have time for makeup. The tree looks like this, with a splitscreen example of the effect:

 

The LumaKey is used to extract only the highlights. These are blurred, and then applied back onto the original image with the Screen node, which is nice for glows and reflections. The Mix is used to control how much of the original image to show through. The example image shows the original image on the left, and the macro results on the right. Photo courtesy of Photron.

Here are the nodes reformated as a macro. The macro parameters are bold.


image SoftGlow(
image In=0,
float blur=0,
float lowClip=.3,
float hiClip=.9,
float percent=100
)
{
LumaKey1 = LumaKey(In, lowClip, hiClip, 0, 0, 1);
Blur1 = Blur(LumaKey1, blur, xPixels, 0, "gauss", xFilter, "rgba");
Screen1 = Screen(In, Blur1, 1);
Mix1 = Mix(In, Screen1, 1, percent, "rgba");
return Mix1;
}

The macro name is SoftGlow, and it pumps out an image (line 1). The parameters are expecting one image input named In (line 2). This gives you one knot at the top of the node; if you want two image inputs, there would be two image parameters, and so on. The other values are all float, controlling the macro settings (lines 3-6). These are assembled in the macro body, with the return statement indicating Mix1 is being output. The low and hiClip parameters determine what levels the highlights are at.

If you save this into a startup .h file, for example, $HOME/nreal/include/startup/SoftGlow.h, it is immediately available on the command line. You can also find a copy of this named SoftGlow.h under docs/cook/macros.

Type

shake -help softglow

and it should return:

-softglow [blur] [lowClip] [hiClip] [percent]

 

File Name vs Macro Name

Names of files have nothing to do with names of macros. Only the function name is important when calling it in the script. You can also have multiple macros per file.

 

Loading Image Macros into the Interface

If you launch the interface, your heart sinks to miserable depths of despair when you discover that it isn't in the interface. You need a separate file and set of functions to load it into the interface. These ui functions are saved in a subdirectory of startup called ui. Here is the ui code to make a button under the Filter tab with no icon that says "SoftGlow" and calls up the SoftGlow function when pressed. Save it in <UserDir>/nreal/include/startup/ui or $NR_INCLUDE_PATH/startup/ui. You can also find a copy of this as docs/cook/macros/SoftGlowUI.h

nuiPushMenu("Tools");
    nuiPushToolBox("Filter");
        nuiToolBoxItem("@SoftGlow",SoftGlow());
    nuiPopToolBox();
nuiPopMenu();


The @ sign is the indication that no icon is associated with it. If you had an icon, omit the @ sign (Do not add the tab prefix or image type extension) and save an icon 75x40 pixels in size under your icons directory (<ShakeDir>/icons or <UserDir>/nreal/icons or $NR_ICON_PATH), naming it Filter.SoftGlow.nri. This information is listed step-by-step under Advanced - How to Make a Macro.

These icons have the following characteristics:

 

 

Typical Errors when Creating Macros

Here is a list of typical errors. When diagnosing a macro, first run it in the command line, that is, shake -help myFunction. If nothing comes up, you know there is a problem with your startup .h file. If that works fine, move on to the interface, and read any error messages in the console window. This is your number one diagnostic tool.

Error Behavior Probable Cause

In the command line, the function doesn't appear when typing

shake -help myFunctionName

  • The file is not saved in a startup directory. See above.
  • The file does not have a .h file extension, or it has an improper extension.
  • The name of the macro (the second word in the file) is not the same as what you have called in the help command.
Function does not appear in the interface
  • The ui file is not saved in a ui directory. See above.
  • The ui file does not have a .h file extension, or it has a .txt extension
  • The name of the macro (the second word in the file) is not the same as what you have called in the ui file, possibly because of capitalization errors.
In the interface, the button appears without an icon You haven't removed the @ sign in your ui.h file. Otherwise, follow the How to Make a Macro tutorial.
The icon appears as a dimple

The icon can't be found.

  • Make sure the icon is saved in an icons directory. See above.
  • The icon should be named TabName.FunctionName.nri.
  • The ui code should say:

    nuiToolBoxItem("FunctionName",Function());

    NOT

    nuiToolBoxItem("TabName.FunctionName.nri",Function());

  • Check capitalization.
The icon is fine, but nothing happens when you click on it.
  • Check capitalization errors.
  • Check that the correct function is being called in the ui file and that the function exists (i.e., go type shake -help functionname in the command line).
  • Check that default arguments have been specified.

 

 

Setting Default Values for Macros

There are two places to set default values for your macros. The first is in the startup.h file when you declare the parameters. From the example above,

image SoftGlow(
image In=0,
float blur=0,
float lowClip=.3,
float hiClip=.9,
float percent=100
)
...

Each of these has a default value assigned to it. Note that the image has 0, meaning "no input".

These values are applied to both the command line or the gui defaults. If you don't supply a default argument, you must enter a value when you call the function. It is therefore recommended that you enter defaults.

The second place is in the ui.h file when the function is called. You can override the startup defaults by entering your own in the ui.h file. Normally, you have something like this in your ui.h file:

nuiPushMenu("Tools");
    nuiPushToolBox("Filter");
        nuiToolBoxItem("@SoftGlow",SoftGlow());
    nuiPopToolBox();
nuiPopMenu();

This sets new default values just for the interface:

...
        nuiToolBoxItem("@SoftGlow",SoftGlow(0,100,.5,1, 95));
...

Changing Default Settings

Most of Shake's functions (third-party development being the exception) are stored in two files under <ShakeDir>/include in nreal.h and nrui.h. nreal.h is the complete list of all functions and settings. The nrui.h file builds the interface. You can modify these files to suit your needs, but it is strongly recommended to make a backup of these files before you begin. Otherwise, if you screw up the file, you screw up Shake. Way to go.

Each of these files is an excellent source for examples as well.

 

Attaching Parameter Widgets

If you peek at Customization - Parameters Tabs and experiment with different nodes in the interface, you see there are many behaviors you can attach to parameters. This section takes a raw macro and shows different examples of behavior to change the parameters sliders to more efficient widgets.

The example macro is called VidResize. It takes an image of any size and resizes it to video resolution. There are slider controls to decide if you want NTSC or PAL (vidformat), to keep the aspect ratio (keepAspect), and if you do keep the aspect ratio, what color is the background area that is exposed. The original tree looks like this:

 

The Sample Macro VidResize

First, load the macro.

Launch the interface to test the macro:

 

image VidResize(
  image In=0,
  int keepAspect=1,	//keeps aspect ratio or not
  int vidFormat=0, 	//This select NTSC (0) or PAL (1) res	
  float bgRed=0,	//if keeping aspect ratio, determines
  float bgGreen=0,	//the bg color
  float bgBlue=0
)
{
    curve int yRes = vidFormat==0?486:576;
    Fit1 = Fit(In, 720, yRes, "default", xFilter, 1);
    Resize1 = Resize(In, 720, yRes, "default", 0);
    SetBGColor1 = SetBGColor(Fit1, "rgbaz", 
      bgRed, bgGreen, bgBlue, 0, 0
    );
    Select1 = Select(keepAspect, Resize1, SetBGColor1, 0, 0);
    return Select1;
}

The ui file looks like this:

nuiPushMenu("Tools");
    nuiPushToolBox("Transform");
        nuiToolBoxItem("@VidResize",VidResize());
    nuiPopToolBox();
nuiPopMenu();

The only tricky bits in the macro so far are the Select function and the logic to choose the height. When the branch of Select equals 1, the first image input is passed through. When it equals 2, the second branch is fed through. The keepAspect parameter is fed into Select to choose which branch you want. The other tricky bit is the height logic, the first line of the macro body. It declares an internal variable (it can't be seen outside the macro) named yRes. It tests vidFormat to see if it equals 0. If so, it sets yRes to equal 486, the height of an NTSC image. Otherwise, it sets yRes to 576, the standard PAL image height.


 

Setting Slider Ranges

The first irritating thing about the macro when used in the interface is the keepAspect slider goes from 0 to 1. The values you want are 1 or 2. Since you don't want to have to manually enter the format, it would be better to set the slider range from 1 to 2. This is done in the ui file VidResizeUI.h. The format is found by looking in Customization - Parameters Tab. If you are reading this document electronically, you can copy and paste the command from the docs into the text file.

 nuiPushMenu("Tools");
    nuiPushToolBox("Transform");
        nuiToolBoxItem("@VidResize",VidResize());
    nuiPopToolBox();
nuiPopMenu();

nuiDefSlider("VidResize.keepAspect",     1,   2);

The word VidResize of VidResize.keepAspect indicates you only want to modify the keepAspect slider in the VidResize function. If you don't specify this, that is, you just had nuiDefSlider("keepAspect", 1, 2);, it tries to apply that slider range to all parameters named keepAspect, no matter what function they are in.


Inappropriate Behavior in All the Wrong Places

If you start seeing controls on parameters that you haven't created, or if you see other functions that have odd behaviors, make sure you have specified which function receives the control. If you set

nuiDefSlider("depth", 1, 2);

anytime Shake sees a parameter named depth (for example, if somebody makes a macro to set bit depth to something), it takes a range of 1 to 2. Therefore, make sure you preface the depth with a function name:

nuiDefSlider("MyFunction.depth", 1, 2);

 

Creating an On/Off button

Now that you are playing with the function, you probably are saying to yourselves that it's pretty petty and a poor reflection on your selfish character to have a value (1 or 2) indicate to the user what that slider does. It would be much better if there was simply an on/off button there. If the button is on, you want to keep the aspect ratio. If it is off, you don't want to keep the aspect ratio. Again, looking up the format in Customization - Parameter Tabs, you can find what you want. If you are reading this document electronically, you can copy and paste the command.

nuiPushMenu("Tools");
    nuiPushToolBox("Transform");
        nuiToolBoxItem("@VidResize",VidResize());
    nuiPopToolBox();
nuiPopMenu();

nuxDefExprToggle("VidResize.keepAspect");

The only tricky thing here is it always returns a value of 0 or 1, with 0 being off and 1 being on. You want values of 1 or 2, because that is what the macro is counting on. Therefore, you must edit the macro.

    ...
    SetBGColor1 = SetBGColor(Fit1, "rgbaz", 
      bgRed, bgGreen, bgBlue, 0, 0
    );
    Select1 = Select(keepAspect+1, Resize1, SetBGColor1, 0, 0);
    return Select1;
}

It should look something like this:

 

Attaching Color Pickers and Subtrees.

Picking color on a slider isn't nearly as impressive to your arch-foes as using the Color Picker, so attach bgRed, bgGreen, and bgBlue to a Color Picker to interactively pick your color. Look up the format in Customization - Parameters Tabs - Assigning the Color Picker, You aren't expected to memorize this code, just to know where to copy if from when you need it.

This is a UI change, so back to the ui VidResizeUI.h file:

nuiPushMenu("Tools");
    nuiPushToolBox("Transform");
        nuiToolBoxItem("@VidResize",VidResize());
    nuiPopToolBox();
nuiPopMenu();

nuxDefExprToggle("VidResize.keepAspect");

nuiPushControlGroup("VidResize.Background Color");
    nuiGroupControl("VidResize.bgRed");
    nuiGroupControl("VidResize.bgGreen");
    nuiGroupControl("VidResize.bgBlue");
nuiPopControlGroup();
nuiPushControlWidget("VidResize.Background Color",
  nuiConnectColorTriplet(kRGBToggle,kCurrentColor,1)
);

This one is interesting because the first five new lines are the code to make a sub-tree. Even if you didn't add the last lines to attach the Color Picker, the three color sliders would still be organized under a subtree named Background Color.

The last three lines attach the color picker to the subtree called Background Color, setting it to pick RGB (as opposed to HSV, HLS, or CMY) color picker that uses the Current (as opposed to the Average, Maximum or Minimum scrub) color.

It looks something like this in the interface:


 

Attaching Button Toggles

The next thing to do is attach a button toggle to vidFormat. Again, 0 and 1 aren't terribly informative as to what precise video format one is setting, so it would be better if there were buttons saying NTSC or PAL to indicate the choices. Here are two different examples.

First get the buttons you are going to use:

The first button toggles between a button that says PAL (vr_pal.off.nri) and NTSC (vr_ntsc.off.nri) when it is pressed. Two additional buttons (vr_pal.off.focus.nri, vr_ntsc.off.focus.nri) are also used to indicate when the cursor is over the button. These are called focus buttons. The code looks like this in Customization - Parameter Tabs,

nuiPushMenu("Tools");
    nuiPushToolBox("Transform");
        nuiToolBoxItem("@VidResize",VidResize());
    nuiPopToolBox();
nuiPopMenu();

nuxDefExprToggle("VidResize.keepAspect");

nuiPushControlGroup("VidResize.Background Color");
    nuiGroupControl("VidResize.bgRed");
    nuiGroupControl("VidResize.bgGreen");
    nuiGroupControl("VidResize.bgBlue");
nuiPopControlGroup();
nuiPushControlWidget("VidResize.Background Color",
  nuiConnectColorTriplet(kRGBToggle,kCurrentColor,1)
);

nuxDefExprToggle("VidResize.vidFormat", 
  "ux/vr_ntsc.off.nri|ux/vr_ntsc.off.focus.nri",
  "ux/vr_pal.off.nri|ux/vr_pal.off.focus.nri" 
);

The new lines list the normal button, followed by the focus button. The icons directory is automatically scanned, but notice you have specified the ux subdirectory. The value returned is always 0 for the first entry, 1 for the next entry, 2 for the third entry, and so on. You can place as many entries as you want. Each button click toggles you to the next choice.

  • Save the file and relaunch Shake. Create the VidFormat node again.

 

This is a single button that toggles through multiple choices. This is fine for binary (on/off) functions, but less elegant for multiple choice toggles. The next example is a version with radio buttons. These are similar to toggle buttons, except that you simultaneously see all available buttons. This code lists only one button name. Shake automatically assumes there is an on, on.focus, off, and off.focus version of each button in the directory you specify. If you copied the vr_ buttons earlier, you indeed have all of these buttons. How special. The code looks like this in Customization - Parameters Tab - Creating Radio Buttons,

nuiPushMenu("Tools");
    nuiPushToolBox("Transform");
        nuiToolBoxItem("@VidResize",VidResize());
    nuiPopToolBox();
nuiPopMenu();

nuxDefExprToggle("VidResize.keepAspect");

nuiPushControlGroup("VidResize.Background Color");
    nuiGroupControl("VidResize.bgRed");
    nuiGroupControl("VidResize.bgGreen");
    nuiGroupControl("VidResize.bgBlue");
nuiPopControlGroup();
nuiPushControlWidget("VidResize.Background Color",
  nuiConnectColorTriplet(kRGBToggle,kCurrentColor,1)
);

nuxDefRadioBtnCtrl( 
	"VidResize.vidFormat", 1, 1, 0, 
	"0|ux/vr_ntsc", 
	"1|ux/vr_pal"
); 

The numbers immediately to the left of the icon listing, i.e., the 0 in "0|ux/vr_ntsc", is the value returned when that button is pressed. The nice thing about ratio buttons is they can return strings, float, or int. For example, the channel parameter in KeyMix is selecting the channel to do the masking, with R,G,B, or A returned, all strings. Because your macro VidResize only understands 0 or 1 to be meaningful, you use 0 and 1 as your return values.

 

 

Making Radio or Toggle buttons:

There is an unofficial function to create these buttons. That's right, I use a macro to help make my macros.

In doc/cook/macros, copy radiobutton.h and relief.h, to your startup directory.

In the command line, type something like:

shake -radio "NTSC size" 79 vr_ntsc $HOME/nreal/icons/ux -t 1-4 -v

This creates four buttons named vr_ntsc.on.nri, vr_ntsc.on.focus.nri, vr_ntsc.off.nri, and vr_ntsc.off.focus.nri in $HOME/nreal/icons/ux each 77 pixels wide and each one says NTSC size. You have to do it in the command line because at the moment the FileOut doesn't work in a macro unless it's the command line, and frames 1-4 create the four different files.

 

 

Attaching Pop-up Menus

Even though the radio buttons are pretty cool, another frequent option is a pop-up menu. The pop-up menu is good for when you have far more choices than will fit in a standard Parameters tab. The only catch is that they return strings (words), not numbers, so you have to add some logic in your macro to interpret the strings. Here is the ui code in Customization - Creating a Pop-Up menu, and here is the ui code.


nuiPushMenu("Tools");
    nuiPushToolBox("Transform");
        nuiToolBoxItem("@VidResize",VidResize());
    nuiPopToolBox();
nuiPopMenu();

nuxDefExprToggle("VidResize.keepAspect");

nuiPushControlGroup("VidResize.Background Color");
    nuiGroupControl("VidResize.bgRed");
    nuiGroupControl("VidResize.bgGreen");
    nuiGroupControl("VidResize.bgBlue");
nuiPopControlGroup();
nuiPushControlWidget("VidResize.Background Color",
  nuiConnectColorPCtrl(kRGBToggle,kCurrentColor,1)
);

nuxDefMultiChoice("VidResize.vidFormat", "NTSC|PAL");

You can add as many entries as you want, and each entry is separated by the | symbol. Here is the problem with pop-up menus. As mentioned before, they only return the word you have entered. Therefore, you must change the startup macro file. In VidResize.h in the startup directory, change vidFormat to be a string instead of an integer, placing its default parameter as either NTSC or PAL, both in quotes. You also use an if/else statement below it to evaluate vidFormat and assign the proper yRes value based on vidFormat's value.


image VidResize(
image In=0,
int keepAspect=1,	//keeps aspect ratio or not
string vidFormat="NTSC", 	//This select NTSC  or PAL  res	
float bgRed=0,		//if keeping aspect ratio, determines
float bgGreen=0,	//the bg color
float bgBlue=0
)
{
    if (vidFormat=="NTSC"){
	yRes=486;
	} else {
	yRes=576;
	}
//  curve int yRes = vidFormat==0?486:576;
    Fit1 = Fit(In, 720, yRes, "default", xFilter, 1);
    Resize1 = Resize(In, 720, yRes, "default", 0);
    SetBGColor1 = SetBGColor(Fit1, "rgbaz", 
	bgRed, bgGreen, bgBlue, 0,0
    );
    Select1 = Select(keepAspect+1, Resize1, SetBGColor1, 0, 0);
    
    return Select1;
}

Save the file and relaunch Shake:

 

Note that you could alternatively avoid the use of the if/else statement and used something similar to what you had before:

curve int yRes = vidFormat=="NTSC"?486:576;

The reason the if/else is used is that you usually have multiple entries on your pop-up menu, and the conditional expression gets a bit unwieldy, it's better to create a long if/else statement. Unwieldy and long, like that run-on sentence.

Standard Script Commands and Variables

Standard Script Commands:

Script Controls Command line equivalent Notes
SetTimeRange("1-100"); -t 1-100 The time range, for example., frames 1-200. Note this is always in quotes.
SetFieldRendering(1): -fldr 1

Field Rendering.

0 = off
1 = odd - PAL
2 = even - NTSC

SetFps(24); -fps 24 Frames per second.
SetMotionBlur(1, 1,0); -motion 1 1 , -shutter 1 1 Your three global motion blur parameters
SetQuality(1); -fast 0 or 1 Low (0) or high (1) quality.
SetProxyFilter("default"); -proxyfilter The default filter, taken from the Filters page.
SetProxyScale(1,1); -proxyscale 1 1

Proxy scale and ratio for the script. See Overview - About Proxies

SetPixelScale(1,1): -pixelscale 1 1 Pixel scale and ratio for the script. See Overview - About Proxies

SetDefaultWidth(720);
SetDefaultHeight(480);
SetDefaultAspect(1);
SetDefaultBytes(1);
SetDefaultViewerAspect(1);

  If a node goes to black or if you create an Image node like RGrad, it this resolution is taken by default.
SetFormat("Custom")  

Sets your defaults automatically for resolution, aspect, from the pre-created list of formats. These formats are stored in your startup.h file in the following format:

DefFormatType("Name", width, height, aspectRatio, framesPerSecond, fieldRenderign);

Variables

Common Variables Notes
width Returns the width of the current node
height

Returns the height of the current node

bytes Returns the bit depth in bytes, either 1,2 or 4.
in, outPoint Returns the in and out frames of the clip in time.
time The current frame number
width/2

The center of the image in X

height/2 The center of the image in Y
dod[0], dod[1], dod[2], dod[3] The left, bottom, right and top edges of the DOD of the current image
parameterName The value of a parameter within the same node
NodeName.parameterName The value of a parameter in a separate node named NodeName.

 

 

Examples

Here are several examples of macros. Many of these can be found in the Cookbook, but they aren't installed by default.

 

Parameter testing: AutoFit

This macro resizes an image to fit a specified size along 1 dimension. For example, the documentation images are 300, 250 or 166 pixels wide. No matter what size the screen snapshot, I can quickly resize it to one of the standard documentation sizes and easily keep the aspect ratio.

shake uboat.iff -autofit 166 w

This calculates an image that is 166x125 pixels. It is not necessary to calculate the height on your own.

Here is the code:

image AutoFit(image img=0, int size=166, int sizeIs=1)
{
  curve w=0;
  curve h=1;
  return Resize(img,
sizeIs ? size*width/height :size ,
sizeIs ? size : size*height/width
); }

The first line creates the parameters. Note that calculate is expecting an integer. The next two lines assign a value to both w and h. This way, the user can type in 0 or 1 to calculate width or height, or enter w or h, which is easier to remember. Since it assigns values to w and h, Shake is able to determine which axis to calculate. The Resize function uses embedded if/then statements to test the sizeIs parameter. Also, you won't be changing the value of w or h during processing, so they are not declared as curve int type of data.

Text Manipulation I: RandomLetter

This function generates a random letter up to a certain frame, at which point a static letter appears. You can select the color, switch frame, position, and size of the letter, as well as font. This function uses the standard C stringf function to pick a random letter, assigning it to the variable rdLetter. This is pumped into the Text function, which uses a conditional expression to evaluate if the current time is before the staticFrame time (set to frame 10 by default)

image RandomLetter(
	int width=720, 
	int height=486,
	int bytes=1,
	const char * letter="A",
	int staticFrame=10,
	float seed=time,
	const char * font="Courier",
	float xFontScale=100,
	float yFontScale=100,
	float xPos=width/2,
	float yPos=height/2,
	float red=1,
	float green=1,
	float blue=1,
	float alpha=1
	)
{	

	curve string rdLetter = stringf("%c", 'A'+(int)floor(rnd1d(seed,time)*26));
	return Text(
	width, height, bytes, 
    time < staticFrame?"{rdLetter}":"{letter}", 
    font, 
	xFontScale, yFontScale, 1, xPos, yPos, 
	0, 2, 2, red, green, blue, alpha, 0, 0, 0, 45
	);
}

Text Manipulation II: RadioButton

This is an excerpt from the RadioButton function which is used to generate radio buttons for the interface. The radio button code needs four icons to support it: name.on.nri, name.on.focus.nri, name.off.nri, and name.off.focus.nri. Since it is tedious to write out four separate files, I made it automatically change the file extensions over time with this excerpt from the macro:

image RadioButton(
const char *text="linear",
...
string fileName=text,
string filePath="/Documents/Shake/icons/ux/radio/",
int branch=time,
..
)
{
	 curve string fileState =
	 	branch == 1 ? ".on.nri" :
		branch == 2 ? ".on.focus.nri" :
		branch == 3 ? ".off.nri" : ".off.focus.nri";
	 curve string fileOutName = filePath + "/"+ fileName + fileState;
...
	 return FileOut(Saturation1, fileOutName);

Since you typically calculate this with a command like this:

shake -radio "HelloWorld" -t 1-4

it generates HelloWorld.on.nri at frame 1, HelloWorld.on.focus.nri at frame 2, HelloWorld.off.nri at frame 3 and HelloWorld.off.focus.nri at frame 4.


Text Manipulation III: A Banner

This little trick takes a string of letters and prints it out, one letter at a time. It declares a variable within the string section of a Text node:

Text1 = Text(720, 486, 1, 
  {{  string logo = "My Logo Here"; 
      stringf("%c", logo[(int) clamp(time-1, 0,strlen(logo))]) 
  }}, 
    "Courier", 100, xFontScale, 1, width/2, height/2, 
    0, 2, 2, 1, 1, 1, 1, 0, 0, 0, 45);

This uses strlen to determine the length of the string and extract the letter corresponding to the current frame.

 

Text Manipulation IV: Text with a loop to make a clock face

This eager little thing lays out a clock face. It uses a for loop to count from 1 to 12, printing the number into a Text function using a stringf function. I know, you should just be able to print the value of count with {count}, but it doesn't work. Go figure. It also uses the cosd and sind functions to calculate the position of the number on the clock face. Keep in mind that zero degrees in Shake points East, as it does in all Cartesian math. The falloffRadius serves no purpose in the function except to complete the onscreen control set to give a center and a radius widget:

image Clock(
  image In=0,
  float xCenter=width/2,
  float yCenter=height/2,
  float radius=150,
  float falloffRadius=0
)
{
	
NumComp=Black(In.width,In.height,1); 

for (int count=1;count<=12;++count)
{
	NumComp = Over(
        Text(In.width, In.height, 1, {{ stringf("%d",count) }}, 
        "Courier", radius*.25, xFontScale, 1, 
	    cosd(60+(count-1)*-30)*radius+xCenter, 
	    sind(60+(count-1)*-30)*radius+yCenter, 
         0, 2, 2, 1, 1, 1, 1, 0, 0, 0, 45), 
        NumComp 
    );
}
    
    return Over(NumComp,In);
}

Text Manipulation V: Extracting part of a string

This function can be used to extract the file name from a FileOut or FileIn function so you can print it on a slate. You would use it in a Text function.

const char *getBaseFilename(const char *fileName)
{
    extern const char * strrchr(const char *, char);
    const char *baseName = strrchr(fileName, '/');
    return baseName ? baseName+1 : fileName;
}

To use it, place in the text parameter of a Text function a line like this:

{getBaseFilename(FileIn1.imageName)}


Tiling Example: TileExample

This allows you to take an image and tile it out into rows and columns, similar to the Tile function in the Other tab. However, this one also randomly moves each tile, as well as scale the tiles down. The random movement is generated with the turbulence function (see Functions By Class - Expressions) Because of this, it is less efficient than the Tile function, which can be seen by looking at the <ShakeDir>/include/nreal.h file.

image TileExample(
	image In=0, 
	int xTile=3, 
	int yTile=3,
	float angle=0,
	float xScale=1,
	float yScale=1,
	float random=30,
	float frequency=10
)
{
result = Black(In.width, In.height, In.bytes);
curve float xFactor=(In.width/xTile)*.5;
curve float yFactor=(In.height/yTile)*.5;


for (int rows=1; rows<=yTile; ++rows){
	for (int cols=1; cols<=xTile; ++cols){

	Move2D1 = Move2D(In,
	-width/2+xFactor+((cols-1)*xFactor*2)+
	(turbulence(time+(cols+1)*(rows+1),frequency)-.5)*random,
	-height/2+yFactor+((rows-1)*yFactor*2)+
	(turbulence(time+(cols+1)/(rows+1),frequency)-.5)*random,
	angle, 1,
	1.0/xTile*xScale, 1.0/yTile*yScale
	); 
	result=Over(Move2D1,result);
	}  
  }    
  return result;
}