Use the Object Model instead of GetValue and SetValue

 
 
 

There are usually two ways to get something done when scripting in Softimage: Native Scripting Commands and the Object Model.

Scripting Commands are the set of Softimage-specific functions that generally correspond to most user actions in the user interface, such as adding a 3D object to the scene graph (CreatePrim), connecting shaders to connection points (SIConnectShaderToCnxPoint) or changing a parameter value in any property set (SetValue), etc.

The Object Model gives you access at a much deeper level by mimicking the 3D world, where you create instances of classes that correspond to objects such as Cameras, NurbsSurfaceMeshes, Materials, Operators, ControlPoints, Parameters, Passes etc. These classes provide functions (methods and properties) that allow you to access something about that object, such as the rotation of a sphere, its vertex color, a camera's interest, an operator's connection point, etc.

Note

For more information on the difference between scripting commands and the object model, see Understanding the Softimage SDK Architecture.

Performance: Commands vs. the Object Model

There are two reasons why a script written with the object model runs faster than a script written with commands:

  • When commands run, they log command echoes to the Script Editor by default. It takes time to log commands, so if the log window is open your script runs slower and the more it's open the slower your script is.

    You can turn logging off temporarily; however, commands still run slower because...

  • The object model accesses the core of Softimage at a lower level, meaning that there are more layers of code that commands have to negotiate to perform a task than the object model. The object model is implemented using a COM (ActiveX) layer which basically negotiates directly with the core engine of Softimage.

Translating from SetValue and GetValue to Parameters

The GetValue and SetValue commands basically operate on property sets, which contain a lot of the information for Softimage scene objects. If you want to switch from using scripting commands to the object model, you will definitely need to learn how to translate what you used to do with these commands to the object model.

The object-model equivalent of the GetValue and SetValue commands are the Parameter and ParameterCollection objects which allow you to perform almost all the same functions. For example, here is a JScript code example that demonstrates how to use SetValue:

	/*
		The following example uses SetValue to set various parameters 
		on some scene elements
	*/

	NewScene( null, false );

	// Create a sphere and change the wireframe color to red
	// This demonstrates we can use SetValue for object properties.
	CreatePrim( "Sphere", "MeshSurface" );
	DeselectAll();
	MakeLocal( "sphere.display", siNodePropagation );
	SetValue( "sphere.display.wirecol", 15 );

	CreatePrim( "Circle", "NurbsCurve" );
	CreatePrim( "Circle", "NurbsCurve" );
	CreatePrim( "Circle", "NurbsCurve" );
	CreatePrim( "Circle", "NurbsCurve" );

	// Set the radius of all circles to 1
	// This demonstrates we can use SetValue in a bulk-edit fashion.
	SetValue( "circle*.circle.radius", 1 );

	// Set the radius of "circle" to 2, and the radius of "circle1" to 3
	// This demonstrates we can use SetValue with an array of values.
	SetValue( "circle.circle.radius,circle1.circle.radius", new Array(2,3) );

	// Hide the grid in all views, for all cameras (except scene camera)
	// This demonstrates we can use SetValue for cameras, views, etc.
	SetValue( "Views.*.*.camvis.gridvis", false );

	// Change current frame to 30 
	// This demonstrates we can use SetValue for playback settings
	SetValue( "PlayControl.Current", 30.000 );

...and this is the equivalent script written almost entirely with the object model (the MakeLocal command is the exception).

	/*
		The following example uses SetValue to set various parameters
		on some scene elements
	*/

	NewScene( null, false );
	var root = ActiveSceneRoot;

	// Create a sphere and change the wireframe color to red
	// This demonstrates the OM equivalent of using
	// SetValue for object properties.
	var obj = root.AddGeometry( "Sphere", "MeshSurface" );
	var prop = 	MakeLocal( obj.Properties( "Display" ), siNodePropagation );

	// MakeLocal returns an XSICollection containing the new local Display
	// property, so we can use it to get at the Wireframe Color parameter
	prop(0).Parameters( "wirecol" ).Value = 15;

	// Make four circles and add them to an XSICollection
	var circles = XSIFactory.CreateActiveXObject( "XSI.Collection" );
	for (var i=0; i<4; i++)
	{
		circles.Add( root.AddGeometry( "Circle", "NurbsCurve" ) );
	}

	// Set the radius of all circles to 1
	// This demonstrates how to access the Radius parameter the long way
	var c = new Enumerator( circles );
	for (; !c.atEnd(); c.moveNext() )
	{
		c.item().Parameters( "radius" ).Value = 1;
    }

    // Set the radius of the 1st circle to 2, and the radius of the 2nd to 3
    // This demonstrates how to access the Radius parameter with a shortcut
	circles(0).radius.Value = 2;
	circles(1).radius.Value = 3;

	// Since viewports are not available in the OM, there is no way to hide
	// the grid in all views, for all cameras (except scene camera) as you
	// can with SetValue:
	//
	//      SetValue( "Views.*.*.camvis.gridvis", false );
	//
	// However, you can hide the grid for the scene camera via the OM.
	// NB: The following three lines can all be combined into a single
	//     line, but it has been split up here for readability:
	var scncam = root.FindChild( "", siCameraPrimType );
	var camvis = scncam.Properties( "Camera Visibility" )
	camvis.Parameters( "gridvis" ).Value = false;

	// Change current frame to 30
	// This demonstrates the OM equivalent of using SetValue for playback 
	// settings.
	// NB: Again, this could be a single line but is split for readability:
	var playctrl = ActiveProject.Properties( "Play Control" )
	playctrl.Parameters( "Current" ).Value = 30.000;

Case Study: Placing Nulls Along an Animation Path

Courtesy of Michael Isner (www.isner.com)

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 and simple example of such a process: 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 (with some cosmetic changes to make it easier to read):

	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 transform and slide the positions for x, y, and 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 but currentNull is returning an object so we have to use the set keyword.

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 x, y and z 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 means relative coordinates (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 cut and pasted, 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 (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 that 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 Softimage.

Creating an Object Model Version

So let's look at modifying 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 this:

	set currentTransform = mySphere.Kinematics.Global.Transform

Getting it over time would look like this:

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

But now we don't have have a easy way to deal with number like x = 34.333: we have a transform object. If you look in the Commands and Scripting Reference 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 objects, including SITransformation, offer 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 xyz position. A quick browse shows us there's a method called SITransformation.GetTranslation which returns a vector of the xyz position. So our script would look like this:

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

Now that we have the positions, let's 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 doesn't work. The reason 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 AddPrimitive (X3DObject) method is that you define the parent as you create the new object. So let's put the null as the child 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.