Skelescript Essentials

Understanding the basic syntax of the skelescript language is crucial before moving any further. Skelescript is primarily a command-driven language (this is because it is intended to be compatible with Layout's command-driven language). A skelescript is really just a list of commands. Naturally, the commands are executed in sequential order. However, there are commands which can alter the natural sequence of code (through procedure calls, loops, and selections). Such commands are for intermediate to advanced users who want to structure their code. Beginners can feel free to skip them. They can get by with knowing just the commands, functions, and properties needed to operate Layout. Defining new variables and procedures, writing loops, performing comparisons, etc. are all very handy in structuring code, but they are not a necessity for getting full access of Layout's functionality.

Before you proceed, you should develop an understanding of how to use LS commander and write layout commands in it. See the Lightwave Manual for a guide on how to do this.


Command Syntax

The first thing we should look at is the basic syntax of a command. The syntax is simple: just type the command name followed by a space followed by a list of arguments separated by spaces.
command_name arg1 arg2 arg3 etc

You may also place commas optionally to help make the code more readable.
command_name arg1, arg2, arg3, etc

An example of a command is the
Position command. Looking at the reference guide, here is the description for the command:

Position number x, number y, number z
Moves the currently selected item to the specified position in meters.
Position 0, 0, 0 ; Moves item to position (0, 0, 0).

What this tells us is that the Position command has 3 parameters: x, y, and z. These parameters also happen to all be numbers .

There are various data types in Skelescript:

int : integers. Examples would be: 1, 2, -23, 14, 0.

bool : booleans (true or false values). Any non-zero value is treated as being true. Examples would be: true, false, 0, -2, 3, 123.

number : real numbers. Examples would be: 1.23, 2.0, 17.5, 3.333333, 0, 20.

string : strings. Strings are a collection of characters used to form text. In skelescript, literal strings must normally be surrounded with quotes to avoid confusing them with existing functions, variables, etc. Examples would be: "hello", "John", "Jane", "left_arm_goal".

identifier : identifiers. An identifier is basically a string type that can receive identifiers directly without quotes. You will find out more about identifiers and this data type later.

item_id : item IDs. Item IDs are layout's way of referring to objects unambiguously. If you look at the list of native layout commands, there are two ways to select an item: SelectByName and SelectItem. The problem with SelectByName is that it's possible for two items to exist under the same name. IDs are guaranteed to be unique, and, as a result, they are the encouraged way to reference items in skelescript. You should never have to specify item IDs directly in skelescript. Instead, you can retrieve existing IDs through functions and properties like me , parent , child , and selected .

collection : collections. Collections provide a way of storing multiple elements grouped together. You cannot specify collections directly. Instead, you can create them using the create_collection function or retrieve new collections using functions like get_selected_items.

any : any type. The any type accepts values of any data type. This means that you can pass integers, numbers, collections, booleans, strings, etc. They are all allowed.

Now that you've been introduced to the basic data types, let's get back to the Position command. The Position command has 3 parameters of the number data type, meaning that the command takes 3 numbers as arguments. The number data type allows for real numbers like 1.2, 7.0, 5, etc. The command affects the currently selected item by moving it to the specified position (in meters). If we want to move an item so that the X position is 0m, the Y position is 1.5m, and the Z position is -2m, we can write:
Position 0 1.5 -2 ; Move selected item to (0, 1.5, -2)
We can also write it like this with commas (encouraged):
Position 0, 1.5, -2 ; Move selected item to (0, 1.5, -2)
Try selecting any object in layout, fire up skelescript preview, and run the the line of code above by typing it and pressing execute (F5). If you typed it correctly, you should see the selected object move to the newly specified position.

You should now understand the basic syntax of how to write commands in skelescript. Remember that command arguments are separated by spaces with optional commas between them to help enhance readability.


Comments

Comments are simply text that is ignored by the interpreter. They allow you to create internal documentation directly inside of your code. Writing comments in skelescripts is simple. Just put a semicolon (;) followed by some text. Everything after the semicolon up to the end of the line will be treated as a comment.

; This is how you write a comment in skelescripts.

; The following is an example of how you can write comments
; immediately after a command on the same line.
Position 0, 0, 0 ; Move an item to the origin

Comments can be a very important part of your code. You can help separate and identify groups of code, explain what's happening, etc. Beginners can benefit a lot by writing English translations of what each line of code is doing as in the above example with the Position command.


Display Output

An important command to learn early on is the
msg command. The command allows you to display messages. Let's see the reference description for this one:

msg any message
Displays the specified message to the screen in a message box.
; displays 'hello world!' to the screen.
msg "hello world!"

; displays the coordinates of the currently selected item to the screen.
msg ("Position: (" & world_x & ", " & world_y & ", " & world_z & ")" )


Try running the sample code with Skelescript Preview and see how it works. Try changing some things and take note of the differences. The code makes use of strings which will be discussed in detail shortly. For now, take note of the basic syntax of the msg command. Being able to display messages is a useful tool for testing your code.


Strings

Remember from the data type descriptions that strings are a collection of characters. An example of a string is "bar", which is a collection of three characters: 'b', 'a', and 'r'. Whenever you want to write custom text directly in skelescripts, you must specify a string. The normal way to specify a string is to surround it in quotes, "like this." The reason for doing this is to avoid confusing parts of strings with important syntactical characters in skelescript. For instance, spaces are used to separate arguments. Imagine writing something like this:

msg hello world!

The interpreter would be very confused, because it would see this line as containing two arguments, 'hello' and 'world!', even though the msg command takes only one argument. Because strings can contain spaces as well as other characters which can potentially lead to ambiguities in the script, strings are marked with quotes to distinguish them, like this:

msg "hello world!"

On the other hand, LS Commander does not use quotes to distinguish strings, and because Skelescripts are designed with compatibility in mind, it provides the option to write strings without using quotes. To be able to do this, you must set the ls_compatible property to true (you will find out how to do this later).


Expressions

Expressions are simply a group of symbols making up a mathematical statement which evaluates to a single value.

An example of an expression is
1 + 2 which evaluates to a single value: 3. The + in that expression is said to be an operator, while the 1 and 2 are said to be the operands. Operators specify what mathematical operation is taking place while the operands specify the arguments to be used with the mathematical operation. In particular, the + operator is said to be a binary one since it operates on two operands.

An expression normally consists of operators and operands. However, it can also be a single value. An example would be
3 . This is an expression which simply evaluates to its own value: 3. Technically, there are no operators or operands involved in this expression. Single values are the only exception to this rule. Any time you have two or more values, they must be combined into a single result through one or more operators.

In skelescript, operators are either unary or binary. Unary operators act on a single operand. An example of a unary operator is the negative operator, -, which turns a positive value into a negative one.

Here is a list of unary operators available in skelescript:

-   turns a positive value into a negative value. This only applies for numeric types.

!   turns a non-zero (true) value into a zero (false) value and vice versa. !true would evaluate to 0 (false). !0 would evaluate to 1 (true).

#   string evaluation operator. This forces a string to be evaluated as though it were an expression. #"1 + 1" would evaluate to 2. See the skelescript reference guide under the eval function for more details.

Here is a list of binary operators available in skelescript:

&   concatenates both operands into a single string. 1 & 2 evaluates to the string, "12". "Hello" & 123 evaluates to the string, "Hello123".

==   returns true if the operands are equal.
!=   returns true if the operands are not equal.
<=   returns true if the left-hand operand is less than or equal to the right.
>=   returns true if the left-hand operand is greater than or equal to the right.
<   returns true if the left-hand operand is less than the right.
>   returns true if the left-hand operand is greater than the right.

+   returns the sum of the two operands (both operands must be numeric).
-   returns the difference of the two operands (both operands must be numeric).
*   returns the product of the two operands (both operands must be numeric).
/   returns the quotient after dividing the left hand operand by the right (both operands must be numeric).
%   returns the remainder after performing division with the two operands (both operands must be numeric).

When writing expressions for command arguments, they must be enclosed in parentheses (with the exception of single values). The reason is because skelescript command arguments are separated by spaces (commas are only optional enhancements for readability that are ignored by the interpreter), and without parentheses, the spaces between operands and operators in an expression could be confusing. For instance, if you write the following:
Position 1 + 1 2 3
It's difficult for the interpreter to figure out whether this line has 5 arguments or 3, especially since the + itself might be a perfectly valid argument when ls_compatible mode is on (more on this later). As a result, to group an expression together as a single argument, you must enclose it in parentheses like this:
Position (1 + 1) 2 3
When writing expressions for arguments of functions and properties (see below) where commas are required, enclosing the expression in parentheses is not required.


Functions

Functions are like commands. They perform an action, they can receive arguments, but they have one key difference: functions return a value.

The basic syntax of a function is similar to a command. However, unlike a command, the argument list of a function must be contained within parentheses following the function's name. Further, the arguments specified for a function call
must be separated with commas, not spaces.

function_name(arg1, arg2, arg3, etc)

Additionally, functions can be written only as part of command arguments. They cannot be written independently on a single line without being part of a command call.

An example of a function is the
name function. Let's take a look at its reference description:

string name (item_id item = selected)
Returns the name of the specified item (if no item is specified, the first selected item will be used by default).

The name command takes one argument: an item ID specifying which item's name you want to retrieve. Before we move on, let's take a look at one other function, the
selected function:

item_id selected (int index = 0)
Returns the item ID of one of the selected item based on the index. The index starts at 0, meaning that the first element has an index 0, the second has an index of 1, and so forth. If no index is supplied, the ID of the first selected item will be returned. The function returns null_item if there is no selected item for the specified index.

; Displays the name of the first selected item.
msg name(selected)

; This also displays the name of the first selected item.
msg name(selected(0) )

; This displays the name of the third selected item.
msg name(selected(2) )

This function takes one argument: an index specifying which selected item you want to retrieve. The index starts from 0 for the first selected item and advances to 1 for the second selected item, 2 for the third selected item, and so forth. Combining what we've learned about the selected command and the name command, let's write a command that displays the name of the second item in the selection:

; We'll use the msg command we learned earlier to display a message
; with the second selected item's name.
msg name(selected(1) )

The first thing that's evaluated in this line of code is the call to selected. The integer, 1, is passed to the selected function as an argument specifying which item we want to retrieve. The index of 1 indicates that we want the second selected item. The expression, selected(1), ends up being evaluated to the item ID of the second selected item, which is then used as the first argument to the name function. The name function in turn returns the name of that item (let's say the name was "foo" for example). The string "foo", which is returned from the name function, is then used as an argument to the msg function. The final effect is that the string, "foo" will be displayed in a message box, showing us the name of the second selected item.


Default Parameters

It's important to note in the reference descriptions for the functions above that the function parameters have an equal sign to the right of them followed by a value. These are default values which are used by the parameters if no arguments are specified. With the selected function, the index parameter receives a default value of 0 if no argument is specified for it. This means that if I write:

msg name(selected() )

It will display the name of the first selected item because an index of 0 will be used by default. When you aren't specifying any arguments with a function call, you can omit the parentheses completely like this:

msg name(selected)

This is the same as writing this:

msg name(selected(0) )

Likewise, the name function's item argument has a default value of selected , meaning that the selected function's return value will be used as the default value if no argument is specified. This means that if I write:

msg name

It will display the name of the first selected item, and is the same as writing (because of the item's default value of selected for the name function):

msg name(selected)

Which is the same as writing (because of the index's default value of 0 for the selected function):

msg name(selected(0) )

To beginners, this might be a bit confusing at first. The best way to learn is to look at various code samples and practice to see the difference for yourself. Just remember that any parameter specified in the command reference followed by an equal sign and a value is an optional parameter which does not have to receive an argument.


Remaining Arguments Collections

Let's take a look at the
add function.

number add (number lhs, ~number remaining)

The add function returns the sum of two or more numbers. The tilde right before the data type (number) of the second parameter,
remaining , is very important. It indicates that the remaining parameter is a remaining arguments collection . Whenever you see these in the reference guide, it means that the parameter can receive 0 or more arguments (virtually up to an infinite number of them). So, with regards to the add function's syntax, this means that the function can take 1 to an infinite number of arguments, because the first one, lhs , is required, while the second one, remaining , is a remaining arguments collection which can have 0 or more arguments passed to it.

msg add(1, 2) ; displays 3
msg add(1, 2, 3) ; displays 6
msg add(1, 2, 3, 4) ; displays 10
msg add(1, 2, 3, 4, 5) ; displays 15
msg add(1, 2, 3, 4, 5, 6) ; displays 21

; You can keep going like this forever (or at least until your computer runs 
; out of memory).

While it's syntactically correct to have only 1 argument for a call to a function like add , it is not always logically correct . Not all functions allow an empty remaining arguments collection, and the add function is no exception. It would be pointless to find the sum of only one number, since there's nothing else to add up. Therefore, while it's syntactically correct to write code like this:

msg add(1)

You will still receive an error when running the code because the add function will check the remaining arguments collection to make sure that there is at least 1 argument passed to it.


Syntactical Recap

For practice, let's take a look at several commands and functions and briefly analyze their syntax without getting into what the commands and functions do.

item_id create_null_at (item_id item, string name, number add_x = 0, number add_y = 0, number add_z = 0)

This is a function that takes 2 to 5 arguments and returns an item ID. The first two arguments are required since the parameters are not optional (they do not have default values). The first argument is an item ID, meaning it refers to a specific item. The second argument is a string, so we can pass arguments like "foo" or "hello". The remaining 3 parameters are optional number parameters which have a default value of 0, meaning that if we choose to only pass 2 arguments like this:

msg create_null_at(selected(0), "foo")

It would be the same as writing:

msg create_null_at(selected(0), "foo", 0, 0, 0)

Which would also be the same as writing (remember that the selected function's index optionally defaults to 0):

msg create_null_at(selected, "foo", 0, 0, 0)

Which would also be the same as writing:

msg create_null_at(selected, "foo", 0)

And so forth. All of the above lines of code will create a null at the first selected item with the name of "foo". Since the position we specified (or left by default) is (0, 0, 0), this means that the item is not displaced at all from the original position (which is the position of the selected item). Further, we wrote the function as an argument to the msg command, meaning that once the null is created, its ID will be returned by the create_null_at function to be displayed to the screen in a message box.

Let's look at another one, this time a command:

create_goal bool full_time = true, string null_name = concat(name, "_goal"), number add_x = 0, number add_y = 0, number add_z = 0

All parameters of this command have default values, meaning that this command can take 0 to 5 arguments. Here are some valid examples:

create_goal
create_goal true
create_goal false
create_goal false, "foo"
create_goal false, "bar", 1.5
create_goal false, "baz", 1.5, 2.0
create_goal false, "qux", 1.5, 2.0, 1.2345

create_goal 1, "quux", 1.5, 2.0, 1.2345
create_goal 123, "quuux", 1.5, 2.0, 1.2345 ; same as previous line
create_goal true, "quuuux", 1.5, 2.0, 1.2345 ; same as previous two lines

I'll now make up a new command which we can analyze.

foo bool arg1, number arg2, string arg3 = "hello", ~bool remaining_arguments

This command takes 2 to an infinite number of arguments. The first argument is required and must evaluate to a boolean value (true or false). The second argument is also required and must evaluate to a real number. The third argument is optional and must evaluate to a string. The remaining arguments are also optional and must be a boolean value. Here are some examples of syntactically correct command calls:

foo true, 123
foo false, 456, "qux"
foo false, 789, "quux", true
foo false, 789, "quuux", true, false
foo 1, 789, "quuuux", true, false, false
foo 0, 789, "quuuuux", true, false, false, 1
foo 0, 789, "quuuuuux", true, false, false, 1, 0, 0, true, false, true

Remember that a non-zero value is regarded as true while a non-zero value is regarded as false .

As long as you understand how to correct syntax, you should be well on your way to understanding how to write skelescripts.


Readable Properties

Readable properties are basically the same as functions. The only difference is that they can be adapted to have a writable portion as well. If you are a user who has no interest in defining your own functions and properties, the difference will be of no consequence to you. You can feel free to just think of readable properties as being no different from functions, because the code involving their usage is exactly the same. Like functions, they can take arguments, they require parentheses when specifying argument lists, they can only be written as part of command arguments, and they always return a value.

Let's look at the global state properties for layout:

bool auto_confirm
bool auto_key
bool parent_in_place
These properties should be fairly obvious by their name. They allow you to change boolean (true/false) layout states like whether autokey is on or off.

Unlike any of the procedures (refers to commands, functions, and properties) we've seen so far, these properties take no arguments at all.

If you want to find out whether auto_key is on or off, for instance, just use the auto_key property like a function. For instance, if you want to display whether auto_key is on or off, you can write this code:

; displays true (a non-zero value) if auto key is on, false (0)
; if auto key is off.
msg auto_key()

Like with any function or property that doesn't take or require arguments, you can omit the parentheses altogether and simply write:

msg auto_key

Working with readable properties is no different from functions, so you should have no problem working with them at this point.


Writable Properties

Writable properties are a new class of procedures. To write to these properties, you must use the
set command. Let's look at the writable versions of the properties above:

bool auto_confirm
bool auto_key
bool parent_in_place
These properties should be fairly obvious by their name. They allow you to change boolean (true/false) layout states like whether autokey is on or off.

set auto_key, true ; turn on autokey.
set parent_in_place, false ; turn off parent in place.

The example code provided in the reference should already give you a good idea of how to work with writable properties. You simply write set followed by the property name and any arguments you want to pass in parentheses (using the same syntax as a function call) followed by the value you want to assign to the property.

set property_name(arg1, arg2, etc), new_value

The value you assign to a property must be of the appropriate data type. Looking at the writable properties above, all of these properties are of the bool (boolean) type. This means that you can assign either true (non-zero value) or false (0).

Let's take a look another set of writable properties:

int pitch_controller (item_id item = selected)
int heading_controller (item_id item = selected)
int bank_controller (item_id item = selected)
Sets the specified item's controller for the appropriate channel (heading, pitch, or bank). The valid values are: controller_keyframes, controller_align_to_path, controller_targeting, and controller_ik.

; The following code selects the current skelescript bone and sets
; its pitch controller to inverse kinematics.
select me
set pitch_controller, controller_ik

These writable properties have one optional argument which can specify an existing item ID. They must be assigned values of the int (integer) type. Here are some valid examples of their usage:

set pitch_controller(selected(0) ), 2

; same as previous line because of default value for selected
set pitch_controller(selected), 2

; same as previous line because of default value for pitch_controller
set pitch_controller, 2

While we can specify integers like 2 and so forth, the integers have a special meaning in this case. Different integers refer to different controller types. To prevent you from having to memorize the number for each controller, a set of named constants have been pre-defined for you:

any controller_keyframes = 0
any controller_targeting = 1
any controller_align_to_path = 2
any controller_ik = 3
The above constants represent each of the different types of controllers that can control the orientation of an item.

These have the purpose of associating a name to a value. The
true /false values we've been using are also examples of named constants. The named constant, true , simply evaluates to 1 (remember that any non-zero is regarded by the interpreter as true). The named constant, false , evaluates to 0. While you can just write 1 (or any non-zero value) or 0 in their place, it is more intuitive to read and write 'true' and 'false' instead.

; instead of writing this to set the pitch controller to IK:
set pitch_controller, 3

; we can write this instead:
set pitch_controller, controller_ik

Named constants like these help make your code more readable and manageable by using words in place of numbers.


Hopefully, you now have an understanding of the basic syntax used with skelescripts. If you understand the syntax, then mastering skelescripts is just a matter of learning the individual procedures (commands, functions, and properties). If you are having any difficulties at this point, please go ahead and post any questions you have at the Skelescript message board.

The advanced lessons will cover techniques for writing more structured code. Only advanced users need to be concerned with them. If you understand the syntax up to this point, then you are ready to take full advantage of the layout functionality provided with Skelescripts. Procedures to define variables (local, const, global, global_const, int, number, string, etc), commands (define_command), functions (define_function), properties (define_get, define_set), loops (for_each, while), and selections (if, else_if, else) can be ignored.

Remember, Skelescripts is intended to be an easy language to learn. However, it is still a scripting language and takes a little bit of time. It may take a while before you can start to comfortably write scripts of your own, but remember, the effort can be well worth the price.

Return to index.
Return to Kung Fu Tools page..
Return to main page.