XSI Arrays and Python

November 16th, 2006 by Patrick Boucher - Viewed 5157 times - Popularity: 13% [?]




I was helping out a buddy the other day, helping him get a script running in Softimage XSI. I had the error fixed in a few minutes and then I started goofing off with his code and cutting it down. Then I hit a part of his script where XSI returns an array of data. Hmmm….

Now, often when you ask multi dimensional information from XSI it will return it to you in what might seem an odd manner. Let’s take some polygonal geometry for example. XSI will give you vertex data organized as three lists representing x, y and z positions with each element in the list representing a successive vertex. There is a demonstration of this situation at the Softimage Wiki. This happens well… regularly.

When you’re coding in C++ it is a rather nice and efficient way to do things. You know how many data points you have so you can use simple pointer arithmetic to walk your multiple data points. But we’re not in C++, we’re in Python and the equivalent walk would look something like this:

1
2
3
4
5
6
7
cube = Application.ActiveSceneRoot.AddGeometry("cube", "MeshSurface")
pa = cube.ActivePrimitive.Geometry.Points.PositionArray
for p in range(len(pa[0])):
    null = cube.AddPrimitive("Null")
    t = null.Kinematics.Global.Transform
    t.SetTranslationFromValues(pa[0][p], pa[1][p], pa[2][p])
    null.Kinematics.Global.Transform = t

In this example we create a cube and position eight nulls at the cube’s corners. It is a truly stupid example but it gets the point across. We first get the array of positions of the vertices, then loop over each vertex and move the appropriate null. In this first example we have to deal with XSI’s multidimensional array. Let’s get rid of that.

3D Programming in One Dimension

One way to get rid of the multidimensional array syntax is to flip it around. Instead of having data[x][v], data[y][v] and data[z][v], where x, y and z are positional components and v is a vertex we’ll convert the data to data[v][x], data[v][y] and data[v][z]. In this example flipping the data might seem trivial but in more complex scenarios I find it just fits my brain better. Maybe I just have a bizarre brain.

One way that is suggested in the Python Cookbook and that has made it’s way a few times on the XSI mailing list is a list comprehension.

1
2
3
4
5
6
7
cube = Application.ActiveSceneRoot.AddGeometry("cube", "MeshSurface")
pa = cube.ActivePrimitive.Geometry.Points.PositionArray
for p in [[r[column] for r in pa] for column in range(len(pa[0]))]:
    null = cube.AddPrimitive("Null")
    t = null.Kinematics.Global.Transform
    t.SetTranslationFromValues(p[0], p[1], p[2])
    null.Kinematics.Global.Transform = t

Alright! The multidimensional array syntax in SetTranslationFromValues is gone but we are left with a list comprehension that is utterly incomprehensible. An alternative is to zip things up.

1
2
3
4
5
6
7
cube = Application.ActiveSceneRoot.AddGeometry("cube", "MeshSurface")
pa = cube.ActivePrimitive.Geometry.Points.PositionArray
for p in zip(pa[0], pa[1], pa[2]):
	null = cube.AddPrimitive("Null")
	t = null.Kinematics.Global.Transform
	t.SetTranslationFromValues(p[0], p[1], p[2])
	null.Kinematics.Global.Transform = t

I’m feeling much better now… The zip function takes an arbitrary number of arguments, all of which must be lists. It will then create a resulting list containing all the first elements in the multiple lists, then all the second elements, then all the third elements and so on and so forth. Check out the python docs for an in depth explanation.

The Star of the Show

Check this last one out! I was really having fun here!

1
2
3
4
5
6
cube = Application.ActiveSceneRoot.AddGeometry("cube", "MeshSurface")
for p in zip(*cube.ActivePrimitive.Geometry.Points.PositionArray):
	null = cube.AddPrimitive("Null")
	t = null.Kinematics.Global.Transform
	t.SetTranslationFromValues(*p)
	null.Kinematics.Global.Transform = t

There is a special syntax in Python that you can use when calling a method. If you have a list and it contains the right number of arguments for a method call you wish to do, you can put *myList in the parenthesis of the method call instead of myList[0], myList[1], …, myList[n].

Again, the example I am using is a very trivial one but in many cases flipping the returned XSI data has been more than useful.

In case you were wondering, the performance hit is minimal. Running 500 iterations of the last flipped version of the code on this page took 63.157 seconds vs. 63.125 seconds for the first version of the code in it’s unflipped glory. And in case you were wondering it was 63.342 seconds for the list comprehension version. These are all wallclock times so minimal is hardly the word, I think at this point the difference is rather insignificant.

Its just all about typing a bit less and being a bit clearer.

Popularity: 13% [?]

2 Responses to “XSI Arrays and Python”

  1. Aloys says:

    Hi Patrick,

    I had to play around with the same kind of things recently, and I would recommend the use of the izip function, which does not create a full zipped list beforehand, but returns an iterator on the existing elements. Much more memory efficient, and a bit faster…

    from itertools import izip

    Cheers,

    Aloys

  2. Very helpful, thanks! If only I’d read that a couple of weeks ago…

Leave a Reply