Gotcha! ApplyICEOp and embedded compounds


When you export a compound, you can choose to embed internal compounds.

It turns out that if a compound has embedded compounds, then calling ApplyICEOp like this:

sFile = 'Test Compound'
Application.ApplyICEOp(sFile, "Sphere1", "", "siUnspecified")

will give you this error if the compound is not located in the Data\Compounds folder of your User location.

# ERROR : 21000-EDIT-PrivateGetCompoundConstructionMode - Unspecified failure - [line 720 in C:\Program Files\Autodesk\Softimage 2013\Application\DSScripts\operators.vbs]
Application.ApplyICEOp("Test compound", "Sphere1.sphere", "", "siUnspecified")
# ERROR : Traceback (most recent call last):
#   File "<Script Block >", line 58, in <module>
#     si.ApplyICEOp( sCompound, o.FullName )
#   File "<COMObject XSI.Application>", line 2, in ApplyICEOp
# COM Error: Unspecified error (0x-7fffbffb) - [line 58]

I noticed something was up when Softimage kept using the a version of the compound from my User location instead of the “bad” version I had in my Downloads folder. A quick check with Process Monitor showed that yep, Softimage was always looking for C:\Users\blairs\Autodesk\Softimage_2013\Data\Compounds\Test Compound.xsicompound, even though I had selected C:\Downloads\Test Compound.xsicompound as the compound to apply.

The workaround is to specify the full path:

sFile = 'C:\\Users\\blairs\\Documents\\Workgroup2\\Data\\Compounds\\Test compound.xsicompound'
Application.ApplyICEOp(sFile, "Sphere1", "", "siUnspecified")

I’ve updated my Applying an ICE compound to multiple objects script accordingly…

Finding the camera used by a texture projection


When you use camera projections, you end up with a data hierarchy like this.

So, given a 3d object, how do you find the cameras used by the projections? This post on xsibase shows how to do it with “nasty loops” (that’s not me saying that, it’s the person who posted the script). Here’s an alternative approach in Python. I’m still using loops, but I don’t think it looks as nasty 😉 To try and make it less nasty, I used filters on the different lists returned by the XSI SDK methods.

from siutils import si
si = si()					# win32com.client.Dispatch('XSI.Application')
from siutils import log		# LogMessage
from siutils import disp	# win32com.client.Dispatch
from siutils import C		# win32com.client.constants

#
# Filters
#
def fCameraTxt(x):
	return (not x.NestedObjects( 'CameraTxt' ) is None)

def fUvprojdef(x):
	return x.type == 'uvprojdef'

def fCamera(x):
	return x.type == 'camera'

#
# Get projection cameras for the selected object
#
cams = []
o = si.Selection(0)
if o.IsClassOf( C.siX3DObjectID ):
	for sample in o.ActivePrimitive.Geometry.Clusters.Filter( 'sample' ):
		for uvspace in filter( fCameraTxt, sample.LocalProperties.Filter( 'uvspace' ) ):
			for uvprojdef in filter(fUvprojdef, uvspace.NestedObjects):
				cams.append( filter(fCamera, uvprojdef.NestedObjects)[0] )


	if len(cams) > 0:
		print 'Projection cameras for {0}:'.format( si.Selection(0).Name )
		for c in cams:
			print '   {0}'.format( c.Name )

Checking whether Get Closest Location nodes use the cutoff distance


Here’s a little Python example that shows how to get all the Get Closest Location nodes in a scene and check whether Enable Cutoff Distance is enabled.

Location queries can be expensive, in terms of memory and performance, so you usually want to have some sort of cutoff distance.

si = Application
log = si.LogMessage
nodes = Application.FindObjects( '', "{2E206DD7-DC51-49F7-92BD-F6CD863125D3}" )
for o in nodes:
	log( '%s : Enable Cutoff Distance=%s' % (o, str(o.InputPorts('enablecutoffdistance').Value) ) )
	#Application.InspectObj( o )
	log( '%s : %s' % (o.Parent, si.ClassName(o.Parent) ) )
	
# pointcloud.pointcloud.flock_SIM.State_0.GetClosestLocationNode : Enable Cutoff Distance=True
# pointcloud.pointcloud.flock_SIM.CompoundNode.GetClosestLocationNode : Enable Cutoff Distance=False
# pointcloud.pointcloud.flock_SIM.State_3.Get_Closest_Location_on_Geometry.GetClosestLocationNode : Enable Cutoff Distance=True
# pointcloud.pointcloud.flock_SIM.State_10.Get_Closest_Location_on_Geometry.GetClosestLocationNode : Enable Cutoff Distance=False
# pointcloud.pointcloud.flock_SIM.State_10.GetClosestLocationNode : Enable Cutoff Distance=True
# pointcloud.pointcloud.flock_SIM.State_0.flock_CurveDirection.GetClosestLocationNode : Enable Cutoff Distance=True
# pointcloud.pointcloud.flock_SIM.State_10.Surface_Force.Get_Closest_Location_on_Geometry.GetClosestLocationNode : Enable Cutoff Distance=None
# pointcloud.pointcloud.flock_SIM.ExecuteInAllStates.Flow_Around_Surface.GetClosestLocationNode : Enable Cutoff Distance=None
# pointcloud.pointcloud.flock_SIM.State_2.Flow_Around_Surface.GetClosestLocationNode : Enable Cutoff Distance=None
# pointcloud.pointcloud.flock_SIM.State_1.Surface_Force.Get_Closest_Location_on_Geometry.GetClosestLocationNode : Enable Cutoff Distance=None
# pointcloud.pointcloud.flock_SIM.CompoundNode[1].GetClosestLocation.GetClosestLocationNode : Enable Cutoff Distance=True
# pointcloud.pointcloud.flock_SIM.State_9.GetClosestLocation.GetClosestLocationNode : Enable Cutoff Distance=False


#
#nodeAddress = "pointcloud.pointcloud.flock_SIM.State_0"
#Application.SelectObj(nodeAddress)
#Application.OpenView("ICE Tree")

The Enable Cutoff Distance=None results mean that the cutoff distance is an exposed port in a compound. If you wanted to get that setting, it would take a bit more scripting…exercise left to the reader.

Checking if a material is being used by somebody anybody


If you want to know whether a material is used by any objects in the scene, you can check the UsedBy property.

Here’s a Python snippet that finds unused materials in the current material library:

from siutils import si

if Application.Version().split('.')[0]>= "11":
	si = si()					# win32com.client.Dispatch('XSI.Application')

from siutils import log		# LogMessage
from siutils import disp	# win32com.client.Dispatch
from siutils import C		# win32com.client.constants


matlib = si.ActiveProject.ActiveScene.ActiveMaterialLibrary
for mat in matlib.Items:
	if mat.UsedBy.Count == 0:
		log( '%s <Not used>' % mat.Name )

Scripting – Selecting keys at the current frame


#Python
from win32com.client import constants

f = Application.GetValue( "PlayControl.Current" )
Application.SelectKeysInTimespan( ".kine.*", constants.siSetKeySelection, f, f, constants.siAnimatedParameters )
' VBScript
SelectKeysInTimespan ".kine.*", siSetKeySelection,GetValue( "PlayControl.Current" ),GetValue( "PlayControl.Current" ),siAnimatedParameters 

Hat tip: luceric

#include statement in JScript


JScript doesn’t include anything like the #include statement, or like modules in Python.

But what you can do is read the contents of a file into a string, and then eval() the string. For example:

var fso = ActiveXObject("Scripting.FileSystemObject");
var f = fso.OpenTextFile( "//server/scripts/toinclude.js", ForReading );
var s = f.ReadAll();
eval(s);
s.Close();

Note that if there’s any errors in the included code, you won’t get the right line numbers in the error messages.

In JScript, another approach would be to create a custom object that has all the helper functions as methods. For an example of that kind of thing, check out the WizardHelperObj object in

%XSI_HOME%\Addons\sdkui\Application\Plugins\SDKWizards.js

Then you just provide one custom command that returns an instance of the custom object to whatever code needs to use the helper functions.

More on getting the version of Softimage used to create a scene


Whenever you open a scene, you’ll see a message like this logged to the script history:

# INFO : 4034 - Loaded scene was created with build number: 10.5.98.0 - compatibility version: 1000
Application.OpenScene("C:\\Users\\blairs\\MyProject\\Scenes\\2012SAP_Scene.scn", "", "")

# INFO : 4034 - Loaded scene was created with build number: 10.1.62.0 - compatibility version: 1000
Application.OpenScene("C:\\Users\\blairs\\MyProject\\Scenes\\2012SP1_Scene.scn", "", "")

If you want to know the version of Softimage that was used to create the scene, you need to check the specific build number (and there’s a couple of ways to do that, we’ll get to that in a second…).

The compatibility version is more a property of Softimage itself than of the scene. You can get the value of the project.CompatibilityVersion parameter, but it’s always going to be the compatibilty version of the current Softimage instance, not of the loaded scene.

p = Application.Dictionary.GetObject( "project.CompatibilityVersion" )
print Application.ClassName(p)
print p.Value

# OR

print Application.GetValue( "project.CompatibilityVersion" )

To find out the version of Softimage used to “build” a scene, you can use the printver utility, or look in the scntoc file. In this context, “build” means the version of Softimage that was last used to save the scene. I note that just opening a scene and saving it isn’t enough to bump up the build version. You need to do something to the scene, or at least do something and then undo it.

From Jeremie Passerin on the Softimage mailing list, here’s a Python snippet that reads the version from the scntoc:

# Python Code
import xml.etree.ElementTree as etree

ext = 'scntoc'
scn = 'C:\\Users\\blairs\\Project\\Scenes\\Test.%s' % ext

tree = etree.parse( scn )
root = tree.getroot()
version = root.get("xsi_version")

LogMessage(version)

Here’s a JScript snippet that reads the version from the scntoc:

var dom = new ActiveXObject("msxml2.DOMDocument.6.0");
dom.async = false;
dom.resolveExternals = false;

ext = 'scntoc';
scntoc = 'C:\\Users\\blairs\\Project\\Scenes\\Test.' + ext;

dom.load( scntoc );
var oNode = dom.selectSingleNode("xsi_file");
LogMessage( oNode.getAttribute( "xsi_version" ) );

If you don’t want to rely on the existence of a scntoc, you could use the printver.exe utility that ships with Softimage. Given a scene file, printver prints a message that looks like “This Scene was built with version: 11.0.525.0”.

Here’s a JScript snippet that runs printver and gets the version number from STDOUT:

// JScript
var WshShell = new ActiveXObject("WScript.Shell");

scn = "\\\\server\\Project\\Scenes\\Whatever.scn"

sExec = "printver " + scn

var oExec    = WshShell.Exec( sExec );

while ( !oExec.StdOut.AtEndOfStream )
{
	s = oExec.StdOut.ReadLine();
	if ( s.indexOf("This Scene was built with version") != -1 )
	{
		var version = s.split(":")[1].replace(/^\s\s*/, '').replace(/\s\s*$/, '');
	}
}

LogMessage( version )

And here’s a Python snippet:

import subprocess

scn = 'C:\\Users\\blairs\\Documents\\Support\\Project\\Scenes\\MySphere.scn'
p = subprocess.Popen( 'printver -l %s' % scn, stdout=subprocess.PIPE )
stdout = p.stdout.readlines()
print stdout
print stdout[-1].split(':')[1].lstrip().rstrip()

See the thread on the Softimage mailing list, which includes a VBScript snippet for getting the build version.

Checking the version used to create a scene


If you can live with the flash of a command prompt window, you can use subprocess.Popen to get the scene file version from printver.

import subprocess

scn = 'C:\\Softimage\\XSI-EXP_3.0\\Data\\XSI_SAMPLES\\Scenes\\dog.scn'
p = subprocess.Popen( 'printver %s' % scn, stdout=subprocess.PIPE )
stdout = p.stdout.readlines()
print stdout[-1].split(':')[1].lstrip().rstrip()
# 3.0.2002.0715


#scn = 'C:\\Softimage\\XSI_7.01_x64\\Data\\XSI_SAMPLES\\Scenes\\ICE\\Particle_Basic_Fire.scn'
# 7.0.2008.0708

Python print statement in Softimage 2013


If you’re using an external install of Python, you’ll probably notice that the print statement doesn’t work in Softimage 2013. That’s because Softimage 2013 was compiled with Visual C++ 2010, and Python wasn’t.

We recompiled the built-in Python that ships with Softimage 2012, so if you use that Python, the print statement works.

Workaround courtesy of Jo benayoun:

import sys
class STDOutHook(object):
	def __init__(self):
		pass
	def write(self, data):
		# py engine adds a newline at the end of the data except if
		# the statement was ended by a comma.
		data = "" if data == "\n" else data

		if data != "":
			Application.LogMessage(data)
		return None

sys.stdout = STDOutHook()

print "Hello"
print "\n"       # wont work due to the workaround
print "World",

I found this same workaround by googling “python print redirect stdout”. For example, here.