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 )

Spawning particles into a different point cloud


Spawning into different point clouds is [supposed to be] easy to set up and allows you to use different shaders on different clouds, which gives you more control over the look of your particles.

Spawn on Collision, however, doesn’t seem to work with a different point cloud. As soon as you change Self to a different point cloud, everything goes red and there’s all kinds of errors and warnings. Look at the Show Messages for Spawn on Collision:

It’s like that all the way back to 7.01. If there’s a way to do it right, I don’t know it yet.

To spawn particles into a new point cloud, try using Spawn on Trigger instead.

The case of the scene that wouldn’t Undo


In this case, a customer sent me a scene where Undo didn’t work. As soon as you opened the scene, nothing you did could be undone, and Undo wouldn’t start working again until you loaded some other scene.

I thought it might be something in the scene, so I deleted practically everything under the Scene_Root, but Undo still didn’t work. Then I noticed that the .scn file was still pretty big (18MB) so I poked around a bit more in the scene and found thousands and thousands of materials (14K of materials, to be exact).

There were over 13 thousand AutoCAD_Color_Index materials. Just opening the Material Manager took minutes, and deleting 13 thousand materials wasn’t as easy as you might think.
I first tried with the DeleteAllUnusedMaterials command, but that took so long that I figured that Softimage was hung and I killed it.

In the end, I deleted some manually (a hundred at a time) and then the rest with the Material Manager > Delete Unused Materials. But I could also have done it like this:

import time

start = time.clock()

import win32com.client
oObj = win32com.client.Dispatch( "XSI.Collection" )
oObj.Items = 'Sources.Materials.DefaultLib.AutoCAD_Color_Index_*'
print oObj.count
# 13782

Application.SetValue("preferences.General.undo", 0, "")

for mat in oObj:
	Application.DeleteObj(mat)

Application.SetValue("preferences.General.undo", 50, "")

end = time.clock()

Application.LogMessage( round( end - start, 3) )
# INFO : 264.117