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.
Need an example for speed diiferences? Try this:
|
Again, this document is not about the SDK it discusses macro creation and solutions accessible to the common user.
Scripting Principles |
Scripting Controls:
To generate a test script, go to the doc/pix/truck directory.
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:
shake -script start.shk
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.
|
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.
|
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.
To find the absolute path of the images, go into that directory and type
the Current Working Directory command:
cwd
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.
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);
Load the script into the interface with Load Script.
Open the localParameters tab under the Globals tab to reveal the mulVal slider.
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 );
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.
|
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);
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]
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. |
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 |
|
Function does not appear in the interface |
|
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.
|
The icon is fine, but nothing happens when you click on it. |
|
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)); ...
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.
Copy the file VidResize.h from doc/cook/macros into your $HOME/nreal/include/startup directory.
Copy the file VidResizeUI.h from doc/cook/macros into your $HOME/nreal/include/startup/ui directory.
Launch the interface to test the macro:
Create a Grad node.
Set its width and height to 400x200.
Attach the Transform - VidResize node and test each slider. keepAspect needs to jump to 2 to have an effect.
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.
Load the ui/VidResizeUI.h file into the text editor.
Add the nuiDefSlider function to set a slider range:
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.
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.
Edit the startup file VidResize.h.
Add 1 to the test of keepAspect in the Select function:
... 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:
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:
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.
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.
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.
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. |
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 |
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); |
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
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; }