Using (Native) Arrays

Even though each scripting language uses its own unique implementation of an array, all share certain commonalities. For example, an array in any language is a list of values or objects and each language provides a set of tools to add and remove items.

Most script writers probably use build and manipulate arrays for their own purposes, but many Softimage functions either take arrays as input or return arrays as output, so it’s important to understand how to work with arrays and Softimage smoothly. The following sections show how to use arrays with different scripting languages.

Working with Return Value Arrays

Some commands, methods, and properties return an array of values. Depending on the scripting language you use, you may get either a Visual Basic safe array or an array of arrays. For example, if you run the command GetMarking() from VBScript or JScript, you get a Visual Basic safe array; if you run GetMarking() from PerlScript, you get nested arrays (array of arrays); and from Python, you get a tuple.

In VBScript, the native array is called a SafeArray, which is the standard array used inside Variants and for Automation scripting objects like the Softimage object model. For scripting languages other than VBScript, different strategies must used:

• JScript

• PerlScript

• Python

VBScript

Since SafeArrays are native to VBScript, you can work with a returned array using the standard VBScript methods (see http://msdn.microsoft.com/ for more information).

VBScript Example: Looping through a returned array

    Dim arrMarkedParams, strItem, i
    GetPrim "Null"
    SetMarking "kine.local.pos,kine.global.pos"
    AddToMarking "kine.local.ori"
    arrMarkedParams = GetMarking()
    for i=0 to UBound(arrMarkedParams)
       strItem = arrMarkedParams(i)
       LogMessage "Value of item at index " & i " = & strItem
    next

    ' Expected result:
    'INFO : Value of item at index 0 = kine.local.pos
    'INFO : Value of item at index 1 = kine.global.pos
    'INFO : Value of item at index 2 = kine.local.ori

JScript

There are some restrictions in working with returned arrays in JScript, because while JScript has its own native Array object, it is not a SafeArray and it does not support more than one dimension. However, many parts of the Softimage interface allow you to provide JScript arrays instead of SafeArrays. For example, you can pass in a native JScript array to Envelope.SetDeformerWeights (Weights parameter) but Envelope.GetDeformerWeights returns a SafeArray (see the example for Envelope.SetDeformerWeights.

In addition, JScript also provides a special kind of object called a VBArray. The VBArray object provides functions that allow you to read from a SafeArray (for example, access to multiple dimensions, upper- and lower-bounds) and also provides a handy method called toArray(), which converts the VBArray to a native JScript Array.

Most scripters prefer to work with a JScript array even if they only need to read the values, because converting a VBArray to a JScript array is very simple (var jsArray = vbArray.toArray();) and the JScript Array object has many more methods that are useful to work with (the VBArray is really only designed to provide basic access to the data).

| For information on the methods available with either of these array objects, see ://msdn.microsoft.com/library/default.asp?url=/library/en-us/script56/html/js56jslrfjscriptobjectstoc.asp.

JScript and One-Dimensional (Flat) SafeArrays

Reading and writing one-dimensional arrays is not a problem, since JScript Array objects are one-dimensional:

• For reading, simply convert the returned SafeArray into a JScript array with the VBArray.toArray() method.

• For writing, use a JScript array either converted from a VBArray or created from scratch with the new Array() constructor. Functions that take a one-dimensional SafeArray as input will accept JScript arrays and convert them automatically.

| See JScript Example: Looping through a returned Array (via VBArray) for an example that demonstrates how to convert and read a one-dimensional SafeArray in JScript. See the JScript example on the Envelope.SetDeformerWeights reference page for an example of how to convert a one-dimensional SafeArray to a JScript array, modify it, and then write it back via Envelope.GetDeformerWeights.

JScript and Multiple Dimensions

SafeArrays support the notion of multiple dimensions. Softimage generally uses 1-dimensional (flat) arrays, but will occasionally use a 2-dimensional arrays, which are often characterized as tables or spreadsheet grids. The first dimension represents the rows, the second dimension represents the columns, and each entry represents a cell.

For example, ClusterElementCollections can be used to map ClusterProperty UV data to specific Clusters, which are managed as a 2-dimensional array where each row represents the clusters’ component indices and each column represents each of the RGBA vertex color values for that cluster.

The JScript (native) array is always one-dimensional; that is, one long list of values or references instead of a table or grid of values. For reading the returned values, you can use the methods of the VBArray object (see JScript and One-Dimensional (Flat) SafeArrays) to access the data.

| If you decide to convert the SafeArray to a JScript array, you need to know that the extra dimensions of the SafeArray are folded into the (flat) JScript array by appending each successive dimension to the first one.

Because Softimage commands, methods, and properties always return a SafeArray in JScript, you need to use one of the following workarounds:

• Store it in a GridData object, which simulates a 2-dimensional SafeArray. For more information, see Using the GridData Object.

• Encapsulate the returned SafeArray in a VBArray object:

JScript Example: Looping through a returned Array (via VBArray)

This example demonstrates how to convert and read a one-dimensional SafeArray in JScript.

    GetPrim( "Null" );
    SetMarking( "kine.local.pos,kine.global.pos" );
    AddToMarking( "kine.local.ori" );

    // GetMarking() returns a VBArray, which you can
    // convert to a JS Array with the toArray() function
    var vbaMarkings = new VBArray( GetMarking() );
    var jsaMarkings = vbaMarkings.toArray();
    for ( var i=0; i<jsaMarkings.length; i++ ) {
       LogMessage( jsaMarkings[i] );
    }

    // Expected result:
    //INFO : kine.local.pos
    //INFO : kine.global.pos
    //INFO : kine.local.ori

| The example above demonstrates how to convert the returned SafeArray to a JScript array step by step:
    var vbaMarkings = new VBArray( GetMarking() );
    var jsaMarkings = vbaMarkings.toArray();
You can also use the following shortcut in place of the above two lines:
    var jsaMarkings = GetMarking().toArray();

JScript Example: Managing a returned 2-dimensional VBArray with a GridData object

These are two excerpts from a longer example (see ClusterElementCollection.Array) in which a grid is created and vertex colors are applied. The GridData is then used to display the values of the cluster properties by inspecting the GridData object.

This snippet begins with getting a pointer to the cluster properties on the color vertex property:

    // Returns a ClusterElementCollection
    var cls_prop_elems = vertexcolor.Elements;

    // Returns a VBArray
    var vbValues = cls_prop_elems.Array;
    Application.LogMessage( "# of dimensions: " + vbValues.dimensions() );
    for ( var d=1; d<=vbValues.dimensions(); d++ ) {
       Application.LogMessage( "range of dimension " + d + ": " + vbValues.lbound(d) + ".." + vbValues.ubound(d) );
    }

    // Display info
    var gridtable = MakeMeAPPG( vbValues );
    InspectObj( gridtable, "", "", siModal, false );

    // Change info
    grid_mod = gridtable.Parameters("VertexColorChart").Value;
    grid_mod.SetCell( 0, 0, 1.23 );
    DeleteObj( gridtable );
    gridtable = MakeMeAPPG( grid_mod.Data );

    // Re-display modified info
    InspectObj( gridtable, "", "", siModal, false );

This snippet demonstrates how to construct a custom property with a GridData parameter, where the GridData acts like a 2-dimensional array.

    // ***************************************
    //
    //     Functions to support PPG
    //
    function MakeMeAPPG( in_SafeArray )
    {
       var gprop = ActiveSceneRoot.AddProperty( "CustomProperty", false, "Visual2DArray" );
       var gparam = gprop.AddGridParameter( "VertexColorChart" );
       var ugrid = gparam.Value;

       // Set bounds of this grid
       ugrid.ColumnCount = in_SafeArray.ubound(1)+1;
       ugrid.RowCount = in_SafeArray.ubound(2)+1;
       ugrid.Data = in_SafeArray;

       // Set up the labels
       var tmp = new Array( "R", "G", "B", "A" );
       for ( var c=0; c<ugrid.ColumnCount; c++ ) {
           ugrid.SetColumnLabel( c, tmp[c]) ;
       }
       for ( var r=0; r<ugrid.RowCount; r++ ) {
           ugrid.SetRowLabel( r, "Cluster " + r.toString() );
       }

       // Return the Property Set (not the Grid itself)
       return gprop;
    }

PerlScript

What you get when using PerlScript is an element which is a reference to an array containing:

• Values (when the returned array is 1-dimensional)

• References to arrays of values (when the returned array is 2-dimensional)

• References to arrays of references to arrays of values (when the returned array is 3-dimensional)

This means that you need to extract each dimension by dereferencing the nested array inside the returned array. In most cases, SDK functions return a 1-dimensional array, so you can expect the nested array to contain a single element, which you simply dereference to work with. In the case of multi-dimensional arrays, the nested arrays contain multiple elements, each of which can be dereferenced as demonstrated in PerlScript Example: Looping through a (multi-dimensional) returned array.

PerlScript Example: Looping through a (1-dimensional) returned array

    $Application->GetPrim( "Null" );

    # Mark some parameters on the null
    $Application->ClearMarking();
    $Application->SetMarking( "kine.local.pos,kine.global.pos" );
    $Application->AddToMarking( "kine.local.ori" );

    # The GetMarking command is supposed to return a 1-dimensional array...
    my $array = GetMarking();
    for $item (@$array) {
       $Application->LogMessage( "Marked parameters: $item" );
    }

    # Expected result:
    #INFO : kine.local.pos
    #INFO : kine.global.pos
    #INFO : kine.local.ori

PerlScript Example: Looping through a (multi-dimensional) returned array

This snippet is an excerpt from a longer example (see ClusterElementCollection) in which a texture support is applied to a cube and the UVW values are extracted as a 2-dimensional array.

    my $uvw_prop = $object->Material->CurrentUV;
    my @rtn = $uvw_prop->Elements->Array;

    for my $array_ref (@nested_array) {
       for my $value_for_u_v_or_w (@{$array_ref}) {
           # ...
       }
    }

Python

Since Python (using the ActiveX Scripting Engine) converts all SafeArrays to tuples automatically. Tuples are one of two types of arrays available in Python:

• tuples—fixed sequences which are often used to return multiple values from functions, roughly equivalent to VBScript’s static arrays.

• lists—dynamic arrays of values, roughly equivalent to VBScript’s dynamic arrays.

Python provides a wide variety of very powerful functions for working with tuples and lists. For more information, refer to one of the Python sites mentioned in Where to Find More Information.

| If you need to do any serious array manipulation (especially working with multi-dimensional arrays and matrices), you should use the Numerical Python extension. For more information, see http://sourceforge.net/projects/numpy.

Python Example: Looping through a returned array

    Application.GetPrim( "Null" )
    Application.SetMarking( "kine.local.pos,kine.global.pos" )
    Application.AddToMarking( "kine.local.ori" )
    markings = Application.GetMarking()
    i = 0
    for mrk in markings
       Application.LogMessage( "Value of item at index " + str(i) + " = " + mrk )
       i = i + 1

    # Expected result:
    #INFO : Value of item at index 0 = kine.local.pos
    #INFO : Value of item at index 1 = kine.global.pos
    #INFO : Value of item at index 2 = kine.local.ori

Autodesk Softimage v7.5