Phil Barrenger's - 3D Stuff
 
 
Tutorials - A Beginners guide to scripting - JScript

This tutorial is for people new to scripting. It's a little more advanced than just cut and pasting script from the command log. I'll try to explain things as fully as possible, if I assume too much and people find it hard to follow then please let me know and I'll add to it.

Download tutorial Files : Start Scene

Learning outcomes :
1) Preparing your computer and yourself for scripting.
2) Copying code from the command log.
3) Adding to the code to perform multiple repetitive tasks.

1) Preparations

Firstly I'll tell you what exciting script you will be making!! We're going to make a script which extrudes a curve along multiple curves at one time. In this case it's going to make a quasi tree structure.

Step 0. Make sure xsi is set up to use Jscript by going into the scripting tab of the prefferences and changing the drop down list from VBScript to Jscript.

Step 1 is to download the Microsoft Windows Script Documentation and install it. This is the best refference around (so far as i know) for JScript and VBScript. It's basically the help files for scripting. If you continue with scripting then you well become well aquatinted with this documentation.

Step 2, Download the start scene and open it up inside XSI. What I have prepared is just a simple scene with some curves laid out for branches of a tree as well as a circle to be extruded along the branches and a ground plane.

Step 3, Open up the script editor as in fig.1 - or in xsi 4 there is a very handy Relational View already set up called RV Script Editor Dual Pane, this has two script windows embed ed in one view which is very handy for testing code snippets in one view while working on your main script in another view.

fig.1
2) Writing the pseudo code
Pseudo code is not a step that you have to perform but I find it really useful, especially for larger scripts. Basically it's just a way of previsualising / planning the script before you start the code. For our Script the pseudo code would be >>

Pseudo code:

Collect the selected curves

pick the curve to be extruded

extrude the picked curve along each of the selected curves

3) Collect the selected curves

Our script is going to use the curves that the user has already selected as the paths to be extruded along. So we need to write code that does this.

Analyzing the script >> you'll notice that the first line is preceded by // this is how you write a comment in JScript. A comment is a way of writing a note that describes what you are doing but is not executed as code. It's a good idea to get into the habit of commenting your code so that when you come back to it a month later you know what is going on, or if someone else has to read your code they can see what's happening. more info on comments can be found in the Microsoft Windows Script Documentation .

The next bit of code consists of some of the building blocks of coding.

var osel = new ActiveXObject("XSI.Collection")

Firstly var is how you create a variable. A variable is a place holder that you can assign a value to and the use the var to refference that value later in your script. The value that you can assign to variables can be anything, not just a number, in this case we are assigning it to a new collection.

The osel that follows the var is the name of the variable and is what we will use later as a refference to the collection. We could have made osel into any name we liked provided that the name isn't a reserved keyword in xsi or JScript. It is common to precede a variable name with the letter o when it is reffering to an object (such as a collection) just to keep things neat and tidy and to ensure that your variable name wont be the same as a reserved keyword.

For more information on what a collection is simple type 'collection' into the script editor, highlight the text and press f1. This will open up a xsi help page with the collection page already open. Get used to doing this, it's a great way of finding what it is that xsi is doing or what values it expects etc.

ok, the next bit of code : new ActiveXObject ("XSI.Collection") creates an empty collection. It is possible to assign the selection directly to a variable, the keyword selection is already a collection of the selected object in xsi, the reason for making a new collection to put the selectoin into will become apparent a little later.

So we have just created an empty collection that is assigned to the variable osel. Next we want to fill up our empty collection with the current selection.

To do this we use the addItems command - for more info on this command just type it into the script editor and press F1.

osel.additems (selection) ;

So we take our empty collection osel and add the items in the selection collection (do a F1 on Selection for more info). Selection is a keyword in xsi that refers to the currently selected objects as a collection.

The ; that follows this line is just a way of saying "i have finished this line of code, move on to the next line." It is not essential to add this in most cases but is is considered good coding practice to include it, and the last thing you want to do with coding is get into bad habits.

Ok first section done, lets move onto picking our extrusion curve.

//getting the selection

var osel = new ActiveXObject("XSI.Collection")

osel.additems (selection) ;

4) Pick the curve to be extruded

XSI helps us out here with PickObject function. If you type pickobject into the script editor and press F1 you'll get something like this:

Scripting Syntax
PickObject (LeftMessage, MiddleMessage, [PickedElement], [ButtonPressed], [ModifierPressed])

This tells us how to use pickobject.

LeftMessage - is the text that will be displayed at the bottom of the xsi window for left clicking.
MiddleMessage - is the text that will be displayed at the bottom of the xsi window for middle clicking.
[PickedElement] - will return the name of the picked object.
[ButtonPressed] - returns which mouse button was pressed.
[ModifierPressed] - returns what modifier key was pressed, ie shift, cntrl, alt.

We are only really concerned with the left message and the picked element.

Firstly I create a new variable called opicked. This provides us with a method of referencing the information that the pickobject returns. I then set the LeftMessage to "select a curve to be extruded". This will be the message that appears for the left click instructions. Notice that I have encased the message within quotation marks " ". These tell xsi that the information in between is a string. A string refers to text, it has no value, it is just a message. You have to supply PickObject with a string here because that is what it expects to receive.
I could have made a variable and assigned it to the text and then called the variable inside the PickObject :

var omessage = "select a curve to be extruded"
var opicked = PickObject (omessage) ;

This would do the same thing - notice that the omessage in the pickobject doesn't require the quotation marks. If you put quotation marks around it the xsi would think that it was a string and would print it as the message instead of the value that we have assigned to it.

 

var opicked = PickObject ("select a curve to be extruded") ;

logmessage ("you have selected : " + opicked.item(2)) ;

5) code debugging

Code debugging is you friend, unfortunately xsi doesn't really have much in the way of help for you. It will print out error messages and tell you where it's going wrong but its not always easy to find. So as a way to manually debug you can use the logmessage command. I often use logmessage("here") and place it in the code somewhere where I think things might be going wrong. Then run the code and see if the message "here" is logged in the script editor. If it isn't then the code must have had an error before it reached the logmessage, so I cut it out and paste if further up the code until I narrow down where things are going wrong, then you can check the code below the message and find your error.

With :

logmessage ("you have selected : " + opicked.item(2)) ;

what I am doing is printing the name of the selected object - I'll go into what opicked.item(2) is referencing in the next section.

Notice that inside the logmessage i have both a string and a refference to an object that are added together with the addition sign +. This is called concatenation which refers to the process of adding strings together. It's a very handy thing to do in lots of different situations.

It takes the form :

"string" + "string" = stringstring

so

"h" + "e" + "ll" + "o" is the same as writing " hello"

var opicked = PickObject ("select a curve to be extruded") ;

logmessage ("you have selected : " + opicked.item(2)) ;

6) Running our Function before Making our function?

Ok this is fundamental to writing scripts so pay attention! :)

make_tree ( osel , opicked.item(2) )

This line is calling the function make_tree, but we haven't written make_tree yet! What it is really saying is "go find a function called make_tree and run the code that makes up the function. This means that the function could be anywhere in your code. Functions don't get run until you make a call to them which you do by typing their name followed by brackets (). For more info on functions in JScript do a search in the Microsoft Windows Script Documentation.

You'll also notice that within the brackets of make_tree I have included our variable osel and our picked curve. Doing this allows me to send these objects to the function so that i can use them within the function In this case I don't strictly have to do this as the function is in the same scope as the variables. Where this does become important is if you want to pass variables from one function to another. Variables declared within functions are local to the function they were created in and can't be used outside of their parent function.
This is true unless you define a variable as being 'global'. Global variables can be used anywhere in a script. To declare a variable as being global you simply don't use the 'var' keyword to define it. So :

var test = 0 ;

becomes

osel = 0 ;

 

make_tree ( osel , opicked.item(2) )

7) Accessing the info from the PickObject

One of the disadvantages of using Jscript is that it doesn't support output arguments. The xsi docs for pickobject explain what you have to do to get around this limitation very well so I'll just summarise.

Basically the pickobject returns a special kind of collection called a ISIVTCollection. We can access the members of this collection through a number index.

ISIVTCollection.item(n)

What this is saying in layman's terms is "get me the n'th element of the ISIVTCollection. In our example we want the third element of the collection so we write :

opicked.item(2)

opicked is the variable name that we assigned the pickobject to, it now holds the ISIVTCollection. We wanted the third item but I typed item(2), this is because collections and arrays in jscript are zero based, so item(0) is the first element, item(1) is the second, making item(2) the third.

I know that we want the third element because if you go back to the pickobject properties in step 4 you'll see that (PickedElement] is the third element in the list.

So opicked.item(2) will return the object that was picked in the pick session.

opicked.item(2)
8) Making our function - the guts of our script

Ok, it's time to define out make_tree function and get this script rolling.

We define a function using the function keyword (check you windows scripting docs for more info on functions), followed by the name of the function. The name can be anything we like, but like a variable name it can't be the same as an xsi or jscript reserved keyword. In this case we are calling it 'make_tree'.

A function is always followed by brackets (). These brackets can, but don't have to, have references placed inside them separated by commas.

I have two references inside the brackets - limbs , profile. You'll remember from step 6 that when we called the make_tree function we included our selection collection and the picked object :

make_tree ( osel , opicked.item(2) )

What this does is assign limbs to osel and profile to opicked.item(2). So now when we write limbs inside the function we are reffering to our selection collection, and it follows that when we write profile we will be getting the picked object.

Another thing you must include when writing a function is curly brackets {}. All the code of a function must be enclosed within these brackets. So the basic code for a function is this :

function()
{
do stuff here
}

function make_tree( limbs , profile )
{
9) For loops

For a full description of for loops check your windows script docs.

for loops provide a way of looping through all the items in a collection. Because we have to apply a command such as 'extrude' to one object at a time we need to isolate our selected objects. It'd be great if you could include a collection of objects as the input for an extrude, but you can't :(

So the first line:

for (var i = 0 ; i < limbs.count ; i++)

In pseudo code this would read as:

for the variable i equals zero and is less than the number of elements inside the collection 'limbs' go and do some stuff and once you have finished increment i by one before starting the loop again.

Make sense? It's a bit of a mind bender to begin with, you just need to remember that it's made up of three parts:

1) the initial starting point for i (or any variable)
2) a rule that tells the loop when to stop
3 a way of modifying i so that it will eventually reach our rule from 3

To break it down even further:

for - this tells xsi that what follows is a loop

() - the rule for the loop has to be enclosed within brackets

var i = 0 - a starting point for the loop

; - the three parts have to be separated by semicolons

i < limbs.count - the rule for when to end the loop, do the loop while i is less than limbs.count. limbs

.count
- a property of collections, it returns the number of elements inside the collection.

i++ - adding ++ to a variable increments the variables value by 1. So if i were equal to five and you write i++, i will now be equal to 6.
It's the same as writing i = i + 1.

 

for (var i = 0 ; i < limbs.count ; i++)
{
10) Understanding where we are at - re-cap

Try running this code with some of the limb curves selected(or anything for that matter) >>

In the script editor log you should see the names of all the selected objects printed out. Pretty cool huh? :)

You can see where I'm going with this I'm sure. We have just managed to isolate each selected object so that we can now apply our extrude op to them all. Or we could apply any op we wanted - this is where a script like this becomes really handy, it's very simple to, once you have gotten this far, change it to perform a different operation on the selection.

var osel = new ActiveXObject ("XSI.Collection")
osel.additems (selection) ;

make_tree ( osel )

function make_tree( limbs )
{
for (var i = 0 ; i < limbs.count ; i++)
{
logmessage (limbs(i))
}
}

11) Applying the extrude command finally!

Ok, so we now have a loop that is going to loop through the selected objects and we have a profile curve that we have assigned to the variable profile. We are ready for our extrusion command. Now there aren't too many people who can remember all of the names of all of the commands so soft has provided a nice easy way to find them. In our sample scene select our extrusion curve circle. Go to Model - Create - Surf.Mesh - Extrusion Along Curve and then pick one of the branch curves. This is the command we want to use in our script. When you run a command in xsi the code for that command is logged in the script editor log. You should see code like this >>

ApplyGenOp("Extrusion", "NurbsSurface", "extrusion_curve;tree_limbs_curves25", 3, siPersistentOperation, siKeepGenOpInputs, null);

The ApplyGenOp creates a generator operator F1 on ApplyGenOp for detailed descriptions.

Extrusion is the name of the preset object that xsi is going to call.

NurbsSurface tells xsi that the object we make will be a nurbs surface.

"extrusion_curve;tree_limbs_curves25" is the part we are really interested in. These are where you put the input objects, namely our profile and our branch. We'll have to modify how it looks a bit though.

I'll let you read the help for siPersistentOperation, siKeepGenOpInputs and null.

 

OK, so we have the code for the extrusion command but we need to format our input objects so that they are in the correct form. What we need to end up with is :

"profile_curve;branch_curve"

What we currently have to work with is:

profile which is a refference to our profile curve.

and

limbs which is a collection that holds all our limbs.

We need to use a little string concatenation again to add some strings together to get the correct format.

profile + ";" + limbs(i)

That'll do the trick nicely.
Profile will return - "extrusion_curve"
";"
will return - ";"
limbs(i)
will return - tree_limbs_curves1 -the first time and then as the script loops through it'll return a different element of the limbs collection as i is incremented.

So we end up with - "extrusion_curve;tree_limbs_curves1"
Which is exactly what we want.

 

{
ApplyGenOp("Extrusion", "NurbsSurface", profile + ";" + limbs(i), 3, siPersistentOperation, siKeepGenOpInputs, null);
}
}

12) Final Code

Download script file

That's it! You can now select all your limb curves, then run the script, when the pick session starts select the extrusion_curve and sit back.

Now Earlier I said you would see why I made a new Collection right at the start for adding the selection into. I did that because when you run the extrusion command once it is finished it selects the object that it created. This means that the selection changes from our limbs to our new surface, so in the next loop when we try to apply the extrusion we no longer have a collection of limb curves to work with, instead we have one surface which is no good for extruding.
Making a new collection removes the LIVE CONNECTION between our osel variable and the selection keyword, so our osel doesn't change even though the selection does.

If it all went according to plan then you will have something like this:

Cool huh?

Now I'm sure you can already see where you could use this script to perform heaps of different actions. You just have to run a command in xsi, copy the code for that command, work out what format you need to supply the inputs as and run it. Easy!

var osel = new ActiveXObject ("XSI.Collection")

osel.additems (selection) ;

var opicked = PickObject ("select a curve to be extruded") ;

logmessage ("you have selected : " + opicked.item(2)) ;


make_tree ( osel , opicked.item(2) )

function make_tree( limbs , profile )
{
for (var i = 0 ; i < limbs.count ; i++)
{
ApplyGenOp("Extrusion", "NurbsSurface", profile + ";" + limbs(i), 3, siPersistentOperation, siKeepGenOpInputs, null);
}
}

13) Further development ideas

1) Create a custom parameter set with sliders that you hook up to the extruded circles radius and the noise amount on a randomise deformer. And copy the profile curve for each extrusion so that you have individual branch control.

2) Use y pos value of each branch to set the size of the profile so that you can make the branches thinner as they get 'higher'.