Accessing Geometry Components

The ability to access object position, rotation and scaling information is relatively straight- forward in scripting.
Simple getvalue commands can be used to read in any of the above transformations.
e.g.ex1_simple.vbs
dim x,y,z
CreatePrim "Cube", "MeshSurface"
x = getvalue("cube.kine.local.posx")
y = getvalue("cube.kine.local.posy")
z = getvalue("cube.kine.local.posz")
logmessage "The Cube is located(locally) at " & x & "," & y & "," & z
The above approach uses the command model API - we are using XSI commands to access the specific data hierarchy positions for the required information. We can use the command model to get and set information, e.g.:ex2_simple.vbs
dim x,y,z
CreatePrim "Cube", "MeshSurface"
x = InputBox ("Please enter
the local X value for 'cube':")
y = InputBox ("Please enter the local Y value for 'cube':")
z = InputBox ("Please enter the local Z value for 'cube':")
setvalue "cube.kine.local.posx",x
setvalue "cube.kine.local.posy",y
setvalue "cube.kine.local.posz",z
The above example uses the setvalue command to replace the data associated with the local position values of the object "cube".
The command model is fairly straightforward in its usage and thus the easier API to use for simple scripting. There are also various levels of command - some are based upon GUI like instructions, whilst others are like building blocks that you use to fabricate a GUI like algorithm.
On the down side, we're limited to the scope of the commands available and also the fact that the command model is the slowest API we can use. When scripts get larger, speed may become more of an issue and thus a dominating factor that dictates the API and language of the plugin/script.
The alternate VBScript API that we will look at as well is the object model.
We've already looked at what objects are in previous lessons - in the case of XSI, think of it as a tree-like structure where data and functions are classified.
It is very hard to do anything to components in XSI without using the object model. In fact, in most ways it's a lot harder as well in the long run.
An extensive help file is available in the scripting help so the first place to understand how objects and XSI work - it includes the workflow for using objects.
Start a new scene and create a polygon cube.
Now lets consider this tiny script:obj_ex1.vbs
set oObj = selection(0)
set oGeometry = oObj.activeprimitive.geometry
set oPoints = oGeometry.Points
Run the script - nothing happens. Why is that? Lets analyze the script:
Line 1 first of all stores the currently selected geometry - "cube" - in an object called oObj.
Line 2 then defines the geometry of that object as an additional VBScript object.
Line 3 finally defines the point component information as a VBScript object.
Well, as the manual states, there are usually three steps to accessing object model data.
Step1> Define
the workspace
Step2> Define the object to work upon
Step3> Getting or putting data to that object
We don't actually address the 3rd stage - we are not using the data yet. We are only addressing stages 1 and 2.
In the script example, we are accessing a collection of objects - the selection list - and associating the currently selected object into an XSI object variable. The properties and methods of that object are then stored inside the VBScript variable.
Lines 2 and 3 specify 2 new VBScript object variables called oGeometry and oPoints. Why store the geometry object AND the point object? Surely, we could condense those 2 lines down into 1 line, e.g.:
set oObj = selection(0)
set oPoints = oObj.activeprimitive.geometry.Points
Well that is true. We are working on the idea that we want to play with the components - in particular the point components. I guess we need to look at what we have - we start off by specifying the object to work upon - thus in actuality stages 1 and 2 of the manual above - we're specifying the workspace and the object in 1 go - this is because these details are stored in the selection list for us.
So what does the line
set oGeometry = oObj.activeprimitive.geometry
mean? Well this line refers to the primitive geometry of the object in question. In other words, it's the actual primitive geometry type of the object.
We can examine this further by making a tiny script that visualizes this process. You can find this file here - called obj_ex2.vbs
set oObj = selection(0)
LogMessage oObj.ActivePrimitive.type
Consider the above script: it logmessages out the type of activeprimitive of the currently selected object. What's that mean? Well now we can see. Select the "cube" in the scene and run the script. You should get the following line:
'INFO : "polymsh"
You can see that this script is telling us what type of primitive geometry we have selected. Get a NURBS sphere, select it and run the script again. You should now get:
'INFO : "surfmsh"
Try the script with a null
'INFO : "null"
or a light
'INFO : "light"
You get the point. Hopefully this clarifies what the "activeprimitive" part of the line does. So what about the geometry part?
Of course, the help files really er help, so put the cursor over geometry and hit F1. Select the first geometry in the list. You can see from the description that we no have " access to the X3Dobject's geometry". Basically, this part of the object allows us to play with values that that make up the geometry of the object - vertices, edges, polygons, normals, etc.
The help page also lets us talk about properties and methods associated with objects. We've already seen how not only can objects store variables but that they can also include subroutines and functions as well. XSI's foundations lie in the same manner - objects in XSI have properties - e.g. a polygon mesh object has point component properties, edge component properties, etc. and they also have methods too - in the case of the above polygon mesh object you have the ability to assign clusters to the object and save shape keys. Properties on objects vary from object to object - cluster properties and shapekey properties are obviously specific to actual geometry - and properties are specific to the type of object too. Some objects can have similar properties and methods.
Going back a few steps, and the reason as to why we choose to use 3 lines instead of 2, is that by using 3 lines we generate easy access points for the geometry of the object (via oGeometry) and specifically the points of the object - the reason for this object model traversal - via oPoints.
Line 3 itself actually takes the current geometry object - oGeometry - and specifies the points property as an object itself, allowing use to access information about the points of the object via the oPoints object variable.
As we saw before, the interpretation of the script has no effect - we aren't actually doing anything yet, just setting up some object variables to access the required info.
The next step is to take our current situation further and play with some data.
The oPoint object allows access to the properties of the points in our "cube". Lets take the position of the points to start with. The easiest way to do this is to add a fourth line to our script (obj_ex3.vbs).
Pnt = oPoints.PositionArray
This line simple copies the positionarray of the points on the "cube" into a variable called pnt. Pnt is now defined implicitly as an array data type due to the VBScripts variant. What this means is that the spreadsheet like array inside XSI that governs every points local X, Y and Z coordinates has been transferred to our new variable.
The array takes the usual XSI array structure of :
Array_name ( axis , component number)
In the case of Pnt, we have the following examples:
Pnt(0,0)
returns the x value of point one
Pnt(1,0) returns the y value of point one
Pnt(2,0) returns the z value of point one
Pnt(0,1) returns
the x value of point two
Pnt(1,1) returns the y value of point two
Pnt(2,1) returns the z value of point two
Pnt(0,2) etc
The important aspect to remember is that array dimension indexes start at zero - i.e. point 1 will start at index 0, point 2 at index 1, etc.
This is important in that if we consider a loop running from the first point to the last point on an object, the index for such a point is 0 to (number of points - 1).
Lets add a loop to the script, but first we need to know how big the array is that holds the points - an error would be generated if we tried to access an index higher than actually exists.
There are a number of ways to actually perform this option but 2 are prominent.
1>Using the object model approach to give a ".count" reading of the total point amount, e.g.
For index = 0 to oPoints.Count - 1
a useful little method that returns the amount of objects in a collection
2>Using the Ubound function in VBScript to return the upper boundary of an array dimension.
For index = 0 to UBound(Pnt,2)
another useful function. The syntax for this function is:
Ubound( array_name , number of the dimension to examine).
In this case, the second dimension holds the point numbers, and therefore the upper boundary represents the last point index on the object point array.
Note that with these 2 options we have differing results. The object model approach tells us the total point count and as such we need to subtract a value of 1 from it to interpret the results for the array loop, whilst the Ubound function returns the upper boundary of the array and as such we can use the number directly.
Choose one of the above lines and add them to the script.
Using the loop index to cycle through the points, we can read out the point positions. Try the following lines:
logmessage "Point " & (index+1) & " lies at "& pnt(0,index) & "," & pnt(1,index) & "," & _ pnt(2,index)
next
You can find the entire example here (obj_ex4.vbs).
Select the cube again, run the script and you should get the following:
'INFO
: "Point 1 lies at -4,-4,-4"
'INFO : "Point 2 lies at 4,-4,-4"
'INFO : "Point 3 lies at -4,4,-4"
'INFO : "Point 4 lies at 4,4,-4"
'INFO : "Point 5 lies at -4,-4,4"
'INFO : "Point 6 lies at 4,-4,4"
'INFO : "Point 7 lies at -4,4,4"
'INFO : "Point 8 lies at 4,4,4"
We used the Pnt array to read out the X,Y and Z values for each point - the index in our loop drove the point number required. Notice that I also added a value of 1 to the first reference to the point number so that it lay more in line with the actual numbering of the point ( 1,2,3,4 ) rather than the storage numbering ( 0,1,2,3 )
Move the cube in a little in Xsi and run the script again. Notice any changes? Try rotating the cube and running the script. What about now?
Well that's because the positions of the points are saved locally in reference to the centre of the cube. Accessing the points globally is more involved but locally will suffice for today.
Lets try and play with some of these points - make our own deformation plugin if you like! Lets try scaling the points a little - say scale the whole object by a factor of 2, i.e. every points position is halved.
Remove the logmessage line and add the following lines:
Pnt(0,Index)
= Pnt(0,Index) / 2
Pnt(1,Index) = Pnt(1,Index) / 2
Pnt(2,Index) = Pnt(2,Index) / 2
Notice how we take the current position for each point individually in terms of X,Y and Z and divide them by 2 - scaling it down to half of its size. Effectively we could have done this in another loop - looping through the x,y, and z indexs. E.g.
For Index2 = 0 to 2
Pnt(Index2,Index) = Pnt(Index2,Index) / 2
Next
Both methods work fine - the final script should look something like this:obj_exp5.vbs
set
oObj = selection(0)
set oGeometry = oObj.activeprimitive.geometry
set oPoints = oGeometry.Points
Pnt = oPoints.PositionArray
For index = 0 to UBound(Pnt,2)
Pnt(0,Index) = Pnt(0,Index) / 2
Pnt(1,Index) = Pnt(1,Index) / 2
Pnt(2,Index) = Pnt(2,Index) / 2
next
Run the script - what happens? Why does nothing happen?
Well if you study the script, you can see that we're applying changes to the array storing the positions. Effectively the array is a copy of the properties inside the oPoints object. So how do we get the array back onto the object?
The answer is to simply reverse the process used earlier, by using the following line:(obj_exp6.vbs)
oPoints.PositionArray = Pnt
One last tip before running the script - freeze the "cube" first. Operations like this must be run on frozen objects - it says so in the manual! This in part limits the scope of this script - but in a scripted operator the problem does not occur. It's a bit more involved to use scripted operators, but like the object model itself, once you've got your head around them its fairly easy. I guess by speculation, the changes made to objects need to happen at an object level, and as such require an operator somewhere in the object stack. Scripts provide the ability to change objects without operators - which is somewhat of a conundrum and hence limits certain scripted operations. Scripted operators on the other hand are individual operators in the stack - as their name implies and as such avoid the problems of scripts.
If you freeze the cube and run the script you will see the cube shrink in size. Successive running will make the cube shrink smaller and smaller - each time shrinking by a factor of 2. Notice that the scale doesn't change - its not the object scale we're adjusting, it's the values of the points themselves.
That's the basic procedure for accessing object component information. This approach with minor alterations could allow the reading of surface normals, polygon data, edge(segment) data, and so on.
The following script is an example of how this can be used as an effective tool.
It was written when I was generating a realistic(ish) skeleton of a dinosaur. Dinosaur vertebrae subtly change as they travel along the body - I was looking for a way to build the start and end vertebrae and then slowly "evolve" in steps from one to the other.
Load polymorp.vbs:
To test polymorph, get a polygon cube and freeze it. Duplicate it, and then move some of the components so that it's a different shape. Freeze it again. Now run the script: pick the first cube, then the second cube and then enter an in between value of 5. The script will then make 5 new cubes - which morph in shape from the first object to the second.
Here is the code for it:
option explicit
dim oGeomS, oGeomE, oStart, oEnd, Steps, DupList
dim StepQuan, Obj, Point, StartPos, EndPos, ObjPos, oGeom, p,d,c
call PickObject ("Select Start
Object", ,oStart)
call PickObject ("Select End Object", ,oEnd)
StepQuan = int(inputbox("Please enter a step amount"))
set oGeomS = oStart.activeprimitive.geometry
set oGeomE = oEnd.activeprimitive.geometry
StartPos = oGeomS.Points.PositionArray
EndPos = oGeomE.Points.PositionArray
set DupList = Duplicate (oStart, StepQuan)
c=1
for each Obj in DupList
set oGeom = Obj.activeprimitive.geometry
ObjPos = OGeom.Points.PositionArray
for P =0 to UBound(ObjPos,2)
for d = 0 to 2
ObjPos(d,P) = StartPos(d,p) + ( c * ((EndPos(d,p) - StartPos(d,P) )/(StepQuan+1)))
next
next
OGeom.Points.PositionArray = ObjPos
c=c+1
next
The code starts by defining the variables to use, and setting option explicit
- this mode speeds up the script as it forces all variables to be allocated
before they can be used. The following commands
call PickObject ("Select
Start Object", ,oStart)
call PickObject ("Select End Object", ,oEnd)
are useful commands that prompt the user to pick an object. In this case, the user is picking two objects and the object picked is being stored in object variables - oStart first and then oEnd second.
We then use an inputbox to ask the user to enter a step quantity:
StepQuan = int(inputbox("Please enter a step amount"))
The variable "StepQuan" then stores this number for us. We next need to set the objects that we are going to look at:
set oGeomS = oStart.activeprimitive.geometry
set oGeomE = oEnd.activeprimitive.geometry
We've done this by defining 2 geometry objects - one for the start object and one for the end object. This enables us to access the geometry of the starting shape and the ending shape. We generate 2 more objects that actually address the position arrays of the objects in the next following lines:
StartPos = oGeomS.Points.PositionArray
EndPos = oGeomE.Points.PositionArray
Now we have 2 arrays: an array called StartPos and another called EndPos which hold the positions of the points for the start and end object respectively. So far we've been using the fast object model - now we actually revert back to the command model and use the duplicate command to duplicate the start object. We duplicate the object the amount of times defined by the StepQuan variable - i.e. we are duplicating the start object and making subtle changes from the basic state to morph it into the end shape. A useful thing to remember with duplicate is that is returns a collection of the objects made via the command - in this case we store that collection in variable called Duplist. Its kind of like a selectionlist in that we can use it to access all the objects we have just made via our duplication command.
set DupList = Duplicate (oStart, StepQuan)
For simplicities sake, I use a variable called "c" to count through the objects that we have made. This is defined in the line:
c=1
The next line starts the loop through the objects we have made with our duplicate command, storing the current object under inspection in the index variable called Obj.
for each Obj in DupList
The next 2 lines set up the object model API to inspect the position array of the points in the current object made from the duplication process.
set oGeom = Obj.activeprimitive.geometry
ObjPos = OGeom.Points.PositionArray
In this case ObjPos
stores the positions of all the points in the currently selected object.
The next line starts the loop through each point on the object:
for P =0 to UBound(ObjPos,2)
We use the upper boundary command here, but we could call a ".count" property of the points to see how many we have.
for d = 0 to 2
We then use another loop, this time to loop through the index notation for the x,y and z axis in the position array - i.e. 0,1 and 2. This means that we can process the translation of the points in x,y and z in one line looped three times - of course calling the index variable "d" as a substitute for axis number in the array. We can do this because the maths driving the script is the same for all three axis - the only changing factors in the equations are the axis themselves.
Here's crunch time:
ObjPos(d,P) = StartPos(d,p) + ( c * ((EndPos(d,p) - StartPos(d,P) )/(StepQuan+1))
What this equation does is simply take the position of the point on the starting model and then add change in position to it - this change in position is split amoungst the in-betweeen stages so that for example over 3 inbetween steps, the change in position is added as a sequence of thirds. Basically
The change in position can be calculated by subtracting the start position from the end position:
((EndPos(d,p) - StartPos(d,P))
Once we divide it by the amount of In-Between steps(stored in the variable StepQuan) we have the increment of how much each point should move per in-between stage. I'll explain why we add 1 to StepQuan in a moment.
((EndPos(d,p) - StartPos(d,P) )/(StepQuan+1))
We then multiply this increment by the current number of the in-between model being examined - the count of the loop is being stored in the "c" variable. Thus:
( c * ((EndPos(d,p) - StartPos(d,P) )/(StepQuan+1))
To finalise this equation we add the above change to the start position:
StartPos(d,p) + ( c * ((EndPos(d,p) - StartPos(d,P) )/(StepQuan+1))
Now going back to why we added +1 to StepQuan. The scripts need to make in-between steps : we want to avoid making steps that are duplications of the Start and End object. This situation arises when the count (variable c) of the in-between step is at 0 or equal to the stepquan variable. To prevent his from happening, we start the count at 1. This stops the first problem of c = 0. By raising the value of StepQuan by 1, we can never achieve condition where c = StepQuan and thus never obtain a duplicate of the End object.
Of course we need to copy these new point values back onto the object itself after we've finished adjusting all the points:
Next
next
OGeom.Points.PositionArray = ObjPos
And we also need to increment the count of the current in-between object selected prior to moving onto the next object - thus:
c=c+1
next
And that's it! This
script does takes for granted that the objects have the same amount of points
and that the objects are both frozen.