Hi again! This will be the 2nd part to our tracker, the script to automate the creation of the tracker and the setup of the ICETree.
If you look at the way we built our compound last week, we need only 3 things for it to function. We need a tracker object to put it on, a mesh object’s name, and a vertex number. Seems simple enough!
Let’s start by planning what we need to do step by step.
#Set up variables
#Get indices from selection
#Get object's name
#Create tracker object
#Create ICETree on tracker object
#Use our collected data to set the tracker compound
Now that we have a clear and concise game plan, let’s approach it step by step and break it down further.
Step 1: Set up variables
We’ll obviously start with our usual xsi variable. From our general breakdown, we also know that we will need to store a selection’s name and some vertex numbers, a string can’t be changed once it’s set so we’ll use a return value from a function to get that, but for the points we’ll use a list and lists are dynamic so we can set that up right now. We’ll also make sure to use a single selection for our mesh, from my experience, multiple meshes at the same time tend to be a bit problematic when dealing with subcomponents.
xsi = Application
selection = tuple(xsi.Selection(0))
points = []
ID = 0
Step 2: Get data from selection
We’ve setup the variables, now we need some data to put in them. We’ll have to extract the name of the object and the IDs of the points that have been selected.
Lucky for us, all that data is contained within a very useful parameter, FullName. If you select a couple of vertices on your mesh and print out the FullName of your selection:
print Application.Selection(0).FullName
you’ll end up with something that looks like this:
# Model.sphere.pnt[20,48]
All the info we need is in there, we have our point IDs, and the name (including parent model) of our mesh. We just need to do some small string manipulation to get all that data in the format we need. If you’re now familiar with string manipulation in python, have a look at the official documentation. The name of our object is basically everything that comes before the “.pnt[]” part, so we can simply use rfind to get the character ID of the last dot and get everything before it.
def getData(item):
allData = item.FullName
dotID = allData.rfind('.')
name = allData[:dotID]
Next, we need to get all the different point IDs from the brackets, so let’s look for all the characters contained within brackets and work from there (we’ll keep this in the same function).
IDin = allData.find('[')
IDout = allData.find(']')
ptIDs = allData[IDin+1:IDout]
What we have now is a string with comma-separated ID numbers that we need to isolate and convert to integers.
idList = []
stringList = ptIDs.split(',')
for pt in stringList:
points.append(int(pt))
This all looks good for multiple vertices, however it would fail miserably in the case of a single vertex selection because the script wouldn’t be able to find the comma. To prevent this, we can filter out single from multiple selections by using a simple “if” statement.
if "," in ptIDs:
stringList = ptIDs.split(",")
for pt in stringList:
points.append(int(pt))
else:
points.append(int(ptIDs))
So in the end, our function would look something like this (don’t forget to return the mesh name if you want to be able to use it later on):
def getData(item):
allData = item.FullName
dotID = allData.rfind('.')
name = allData[:dotID]
IDin = allData.find('[')
IDout = allData.find(']')
ptIDs = allData[IDin+1:IDout]
if "," in ptIDs:
stringList = ptIDs.split(",")
for pt in stringList:
points.append(int(pt))
else:
points.append(int(ptIDs))
return name
Ok! We have access to our data, it’s formatted properly so we can use it in the compound, so the next step will be to create the tracking object. I like to use nulls for this but any type of object would do. You could even use the key state trick we learned in the FCurve Interpolation tool to have different types of objects created depending on the keys the user presses while running the script. In my case I’ll keep things simple and just use a null, feel free to experiment.
Step 3: Create tracker
We’ll need to create one null per selected vertex, to do that we’ll need to know how many were selected. Since we have a list of all the point numbers already, we might as well use it. We already have our points all nicely set up in a list so we can use len() to get that number. We’ll use the result in a for loop so don’t forget to add 1 to the value you get, ranges aren’t inclusive of the last value so you would end up missing one tracker.
def createTracker(points):
total = len(points)
for each in range(1, total+1):
tracker = xsi.GetPrim("Null", "TRK_"+mesh+"_" + str(point))
Step 4: Create ICETree
While we’re creating our trackers, we’ll also create our ICETrees, so we’ll call our tree building function from the createTracker function. This script will assume that you are using the compound that we built last week and that you have it in your user compound folder. If you have it somewhere else or named it differently, edit the path accordingly.
First, we build our tree
def buildICETree(point, tracker, mesh):
xsi.ApplyOp("ICETree", tracker, "siNode", "", "", 0)
tree = tracker.ActivePrimitive.ICETrees(0)
Then, we’ll store the execute port in a variable to enable us to connect the compound later on
execPort = tree.NestedObjects('Port1')
Now, import the compound (this is where you’d want to put your own path if you put it somewhere other than the user compounds folder). Note that we’re using c.siUserPath, which means we’ll have to import contants as c again, we’ll add that to the top of our variables when putting the script together.
compound = XSIUtils.BuildPath( Application.InstallationPath(c.siUserPath), "Data", "Compounds", "pmTracker.xsicompound" )
TrackerCompound = xsi.AddICECompoundNode(compound, tree)
We’ll also need a get Data node to set our mesh reference
meshNode = xsi.AddICENode("GetDataNode", tree)
meshNode.Parameters('reference').Value = mesh
And now, we just connect everything up and set the vertex ID
xsi.ConnectICENodes(execPort, str(TrackerCompound)+'.Execute')
xsi.ConnectICENodes(str(TrackerCompound)+'.Mesh_Name', str(meshNode)+'.outname')
xsi.SetValue(str(TrackerCompound)+'.Index', point)
This is pretty much all that’s needed to make the script work, however, we’ll add one last thing to the script. This is meant to be used only with a specific type of sub components. We’ll build a small function that checks to make sure that the selection is indeed vertices before it tries to build trackers and crashes, polluting the scene with useless nulls.
def selType(item):
if item != None:
if item.Type == "pntSubComponent":
return True
else:
return False
else: return False
Now if the selection is anything other than vertices, the function will return a False value, so we can stop the process at that point.
We have all the elements we need, so let’s put everything together
import win32com.client
from win32com.client import constants as c
xsi = Application
selection = xsi.Selection(0)
ks = xsi.GetKeyboardState()
points = []
ID = 0
## Check if selection contains points
def selType(item):
if item != None:
if item.Type == "pntSubComponent":
return True
else:
return False
else: return False
## Get and format data from selection
def getData(item):
allData = item.FullName
dotID = allData.rfind('.')
name = allData[:dotID]
IDin = allData.find('[')
IDout = allData.find(']')
ptIDs = allData[IDin+1:IDout]
if "," in ptIDs:
stringList = ptIDs.split(",")
for pt in stringList:
points.append(int(pt))
else:
points.append(int(ptIDs))
return name
## Create Nulls for each vertex
def createTracker(points, mesh):
for point in points:
tracker = xsi.GetPrim("Null", "TRK_"+mesh+"_" + str(point))
buildICETree(point, tracker, mesh)
## Create ICETree, import compound and set inputs
def buildICETree(point, tracker, mesh):
xsi.ApplyOp("ICETree", tracker, "siNode", "", "", 0)
tree = tracker.ActivePrimitive.ICETrees(0)
execPort = tree.NestedObjects('Port1')
compound = XSIUtils.BuildPath( Application.InstallationPath(c.siUserPath), "Data", "Compounds", "pmTracker.xsicompound" )
TrackerCompound = xsi.AddICECompoundNode(compound, tree)
meshNode = xsi.AddICENode("GetDataNode", tree)
meshNode.Parameters('reference').Value = mesh
xsi.ConnectICENodes(execPort, str(TrackerCompound)+'.Execute')
xsi.ConnectICENodes(str(TrackerCompound)+'.Mesh_Name', str(meshNode)+'.outname')
xsi.SetValue(str(TrackerCompound)+'.Index', point)
## Alt-click Menu
if ks(1) == 4:
XSIUIToolkit.MsgBox( '''=============================================
Author: Phil Melancon
Website: https://philmelancon.wordpress.com/
=============================================
Notes about this tool:
pmTracker assumes that the pmTracker compound can be found in your User Compounds folder.
If you don't have it, download it from the download section of my website.
If you put it somewhere else for whatever reason, modify line 46 of this script accordingly.
=============================================
What this tool does:
Running this script while having vertices selected will create one null per vertex and constrain one to each vertex.
=============================================
Instructions:
Select vertices
Run the script
=============================================
''', 0, "pmTracker Info" )
else:
if selType(selection):
mesh = getData(selection)
createTracker(points, mesh)
else:
print "Select vertices first"
You might notice some small modifications to what we wrote up until now. The main addition is something that was suggested by a colleague of mine. From now on, my scripts will all contain an alt-click Pop-Up menu that will serve as an information source about the tool. I will eventually modify the existing scripts in the download section accordingly.
Cheers!