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.
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 )
{
|
|
|
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'. |
|