XSI Scripting: Commands VS Object Model
by misner Oct / 2001

I've created this document to help people understand how to best take advantage of the scripting tools inside XSI.  The main point I wanted to get across:

1) It's quicker to make a script from commands.  Basically you just do something in 3D, copy and paste the line and perhaps change the parameters a bit for your program.
2) A program made from the Object Model will run faster, but takes longer to make.  The Object Model is the SDK of XSI so it also has access to much more than scripting, like UV's, point positions, ect...  The Object Model is much more powerful than scripting and is really the only way to tackle anything complex like spatial transformations.

The way I work is usually to rough out a script with commands and then remake pieces of it with the Object Model to make it faster.  Here's a quick example of such a process.


Example Project: Placing nulls along an animation path.

So here's a simple example, animating a sphere and placing a null at every frame.  So lets start with a script to animate a sphere.  This is just a cut and paste of the log window:

NewScene
CreatePrim "Sphere", "NurbsSurface"
SaveKey "sphere.kine.local.posx,sphere.kine.local.posy,sphere.kine.local.posz", 1
SetValue "PlayControl.Current", 50
Translate , -16.6915152140782, 1.0970178921512E-15, -17.9162534756914, siRelative, siView, siObj, siXYZ
SaveKey "sphere.kine.local.posx,sphere.kine.local.posy,sphere.kine.local.posz", 50
SetValue "PlayControl.Current", 100
Translate , -17.3869950146648, -9.15956686650513E-16, 14.9592019311598, siRelative, siView, siObj, siXYZ
SaveKey "sphere.kine.local.posx,sphere.kine.local.posy,sphere.kine.local.posz", 100


Starting with Commands:

So to place a null at every frame we need to know how to cycle through time.  Scrubbing the timeline gives us the feedback:
SetValue "PlayControl.Current", 27

So it's pretty obvious if we want to cycle through time we could do something like:
for i = 0 to 100
    SetValue "PlayControl.Current", i
next

So next we need to get a null each frame, which is easy, just cut and paste getting a null.
 GetPrim "Null"

Of course by, just doing  GetPrim "Null" it's going to be hard to keep track of all 100 nulls.  To get the latest null we made, we should return the newly created null (or object) into a variable name like currentNull.  Do this by:
 Set currentNull = GetPrim ("Null")

Ok, so the next thing we need to do is get the sphere's position at each frame and put the current null there.  To "get" a value just find it in some slider form and move it, copy and paste the log, and turn the part where it says "SetValue" into "GetValue".  So to get the sphere's global position, open up the global transfor and slide the pos x,y,z.  It will return:

SetValue "sphere.kine.global.posx", -9.535
SetValue "sphere.kine.global.posy", 0.233
SetValue "sphere.kine.global.posz", -1.744

so one of these lines would become:

x = GetValue("sphere.kine.global.posx")

you can see we also had to add brackets.  You need to do this with a command anytime you set the result equal to a variable.  Notice we didn't use the word "set" at the beginning like we did when getting the null.  That's because x is just a number.  currentNull is returning an object so we have to use the word "Set".

So now let's bring this all together and log the position of the sphere at each frame.  It will look like:

for i = 0 to 100
    SetValue "PlayControl.Current", i
    x = GetValue("sphere.kine.global.posx")
    y = GetValue("sphere.kine.global.posy")
    z = GetValue("sphere.kine.global.posz")
    logmessage "("& x &","& y &","& z & ")"
next

So easy enough, now we need to bring in that null and move it to my xyz values.  If I move something it returns:
Translate , 0, 0, 0, siRelative, siView, siObj, siZ

Right off the bat there should be clue this isn't exactly right because it says "siRelative" which mean relative coordinates (ie not global like we are getting in the return values from the sphere.  If I'm not sure what's happening with a  command I just just and paste I look quickly in the help.  In this case it would tell us that we should use "siAbsolute" instead as the mode for the siDeltaMode. So bringing in the line that gets us the null as well gives us the script:

dim i, x, y, z
for i = 0 to 100
    SetValue "PlayControl.Current", i
    x = GetValue("sphere.kine.global.posx")
    y = GetValue("sphere.kine.global.posy")
    z = GetValue("sphere.kine.global.posz")
    Set currentNull = GetPrim ("Null")
    Translate currentNull, x, y, z, siAbsolute, siView, siObj, siXYZ
next

Ok it works.  You may have noticed I added the line: "dim i, x, y, z"  at the beginning.  I did this because by declaring variables you will speed up your scripts.  In fact it's a good habit to put the line "option explicit" on all your scripts because it will error every time you hit an undeclared variable making your scripts faster and easier to debug (ie pointing out variable typos and stuff like that)


Timing your scripts.

How fast your scripts are may not be an issue.  If that's the case, which it probably is doing something simple without extensive looping, then just use commands and call it a day.

However if  you're doing something that you need faster, but may not have time to redo it in C++, then using the Object Model may be a very good option.

Here's a simple variation on the script that returns how many seconds it took:

time_in = timer
dim i, x, y, z
for i = 0 to 100
    SetValue "PlayControl.Current", i
    x = GetValue("sphere.kine.global.posx")
    y = GetValue("sphere.kine.global.posy")
    z = GetValue("sphere.kine.global.posz")
Set currentNull = GetPrim ("Null")
Translate currentNull, x, y, z, siAbsolute, siView, siObj, siXYZ
next
time_out = timer
logmessage "elapsed time: " & round(time_out - time_in,3)

On my machine it took 17.3611 seconds.  Note this was with the log window open.  If you experiment a bit timing scripts you'll realize:

1) It takes time to log commands.  If the log window is open your script will be slower.  The more it's open the slower your script will be.
2) By dragging and dropping your script to a button it will be faster because it doesn't log.  In fact, just by dragging and dropping that script to a button, I dropped the time by  to 5.787 seconds.
3) One of the reasons the Object Model is faster, is that it also doesn't log anything.  Another is that it has lower level access to the core of XSI.


Creating an Object Model version.

So let's look at modify this script with the object model.  First let's look at getting the position.  One way to do this with the OM would be:

x = mySphere.Kinematics.Global.parameters("posx").value

You can see that's not really calling on any command like GetValue, but instead digging into the properties of that sphere object.  This would return a number and we could substitute lines like this in our original script instead of the GetValues.

However real time loss in this script is the fact that we are scrubbing through time.  In fact if this sphere was located in a much bigger scene we would lose a great deal of time updating every object in the scene each frame as we modified the PlayControl.  One big timesaver we could do here is to pick up the entire transform (description of of the Scale, Rotation, Position) of the sphere.  By picking up the whole transform we can define where in time we read the value.  Reading the transform at the current time would look like

set currentTransform = mySphere.Kinematics.Global.Transform

getting it over time would like:

set currentTransform = mySphere.Kinematics.Global.Transform(currentFrame)

But now we don't have have a easy to deal with number like x = 34.333, we have a transform object.  If you go look in the scripting help under: Contents > Reference > Reference Entries > Objects you can find many useful objects for manipulating space like: SIMatrix3, SIMatrix4, SIQuaternion, SIRotation, SITransformation and SIVector3.  If you scroll down a bit farther to XSIMath you will also find a pile of tools for creating these objects and converting between different Spaces.

Most of these Object, including SITransformation include a pile of tools, including methods to convert them to other objects.  In our case, we have a Transformation and we want to turn it into an x,y,z position.  A quick browse shows us there's a method in SITransformation called "GetTranslation" which returns a vector of the xyz position.  So our script would look like:

set posVector = xsimath.CreateVector3
set currentTransform = mySphere.Kinematics.Global.Transform(i)
currentTransform.GetTranslation posVector

Now that we have the positions, lets integrate it into our original script.  A quick look in the help for SIVector3 tells us that the easiest way to get the x from it is to write posVector.x. So our script becomes:

for i = 0 to 100
    set posVector = xsimath.CreateVector3
    set currentTransform = mySphere.Kinematics.Global.Transform(i)
    currentTransform.GetTranslation posVector
    Set currentNull = GetPrim ("Null")
    Translate currentNull, posVector.x, posVector.y, posVector.z, siAbsolute, siView, siObj, siXYZ
next

But if you run this it doens't work.  The reason it doesn't is that we haven't yet defined what "mySphere" is.  With commands, it's often good enough just to have the name of something.  With the Object Model you always need an Object. Probably the easiest and most frequent way to do this is from the selection.  You could add the line at the beginning:

SelectObj "sphere"
set mySphere = Selection(0)

Some people with programming experience may find working this way a bit hacky, and would like something that doesn't touch the selection.  Another approach to the same thing could be to get the scene root and then to search from there downwards for the name "sphere".  Here's our working script with these lines:

set oRoot = Application.ActiveProject.ActiveScene.Root
set mySphere = oRoot.FindChild("sphere")
for i = 0 to 100
    set posVector = xsimath.CreateVector3
    set currentTransform = mySphere.Kinematics.Global.Transform(i)
    currentTransform.GetTranslation posVector
    Set currentNull = GetPrim ("Null")
    Translate currentNull, posVector.x, posVector.y, posVector.z, siAbsolute, siView, siObj, siXYZ
next

So at this point we could call it a day and move on.  It is not necessary to do everything in the OM.  I rarely have time to do it and usually only use the OM where I think it makes the biggest performance difference, and where it's the most useful for the task at hand.  But in the interest of teaching people the OM, I'm going to continue to pick apart this script.

So a quick look at our script I can see there are still two things we are doing with commands: getting the null and translating it.  As far as the translation goes we can save a great deal of time, by not converting currentTransform to xyz coordinates and Translating, but by just swapping over the transform to currentNull.  That would look like:

set oRoot = Application.ActiveProject.ActiveScene.Root
set mySphere = oRoot.FindChild("sphere")
for i = 0 to 100
    set currentTransform = mySphere.Kinematics.Global.Transform(i)
    Set currentNull = GetPrim ("Null")
    currentNull.Kinematics.Global.Transform = currentTransform
next

So just to finish the job and make it 100% OM, let's get the null from the OM.  The nice thing about the Method AddPrimative is that you define the parent as you create the new object.  So let's put the nulls as children of the sphere and our final script would be:

dim oRoot, mySphere, currentTransform, currentNull
set oRoot = Application.ActiveProject.ActiveScene.Root
set mySphere = oRoot.FindChild("sphere")
for i = 0 to 100
    set currentTransform = mySphere.Kinematics.Global.Transform(i)
    set currentNull= mySphere.AddPrimitive("Null")
    currentNull.Kinematics.Global.Transform = currentTransform
next

So quickly timing it, this takes: 2.3148 seconds on my machine.  That's 2.5 times faster than the command version from a button, and 7.5 times faster than commands run from the UI.