Workarounds for caching ICE attributes [and sidestepping ICE optimizations]


Because of how ICE is optimized, it won’t bother setting data if you never use that data (see Beware of ICE Optimizations here). For example, suppose I have a point cloud where I set a per-point scalar value, but I never use that scalar data (because I just want to cache that value and use it later).

ICE_Opt_1

That scalar attribute is never used, so it is optimized away (it makes sense: you’re not using it, so why keep it?). You won’t see that attribute in the Cache Manager, and the Cache on File node won’t write it out (even if you add that attribute to the list).

One way to force ICE to evaluate the attribute so you can cache it is to create an Attribute Display property for the attribute, but disable Show Values.
ICE_Opt_AttributeDisplay

Another way is to insert a Log Values node, and disable logging.
ICE_Opt_Log

Yet another way is to use Show Values. Enable Show Values for Tagged Components Only to hide the values (if you’re going to tag components, you could set the Display % to 1).
ICE_Opt_ShowValues

And last but not least, if you have emTools from Mootzoid, you can use the Force Value Evaluation node, which uses a Show Values on a value that is never set.
emToolsForceValueEvaluation

hat tips to Stefano, Mathieu, and Vincent on the SItoA list

Checking what attributes are in a cache


On the Read tab of the Cache Manager there’s a Dump Header button:
CacheManager_DumpHeader

Dump Header dumps the cache header and attribute list to XML and pops up NetView:
DumpHeader_XML

If you want to dump the XML and parse it yourself, you can do it with this scripting command:

Application.DumpCacheHeader("[project path]\\Simulation\\scene_root\\pointcloud\\pointcloud_AnimTake1_[1..100].icecache", False)

Setting the DataArray2D attribute in scripting


Last time I tried this, I gave up on JScript (it seemed impossible) and got something to work in Python.

In JScript, I kept getting errors like “# WARNING : 3392 – Invalid offset specified while extracting data from this attributeÈ.

si = Application
from win32com.client import constants as C            # win32com.client.constants

oObj = si.Selection(0)
oICEAttrMats = oObj.ActivePrimitive.AddICEAttribute("MyString", C.siICENodeDataString, C.siICENodeStructureArray, C.siICENodeContextSingleton)
oICEAttrMats.DataArray2D = [["a", "b", "c", "d"]]

x = oICEAttrMats.DataArray2D
print x
print len(x)
print len(x[0])
print len(x[0][0])

for d in x[0][0]:
    print d


# (((u'a', u'b', u'c', u'd'),),)
# 1
# 1
# 4
# a
# b
# c
# d

See also this Getting DataArray2D attribute values post.

Finding degenerate polygons by area


Degenerate polygons are usually zero-area polygons.

Here’s a script that uses the ICE attribute PolygonArea to find polygons with area less than a specified epsilon:

si = Application
epsilon = 0.00001

# Get PolygonArea DataArray (which is a tuple)
attr = si.Selection(0).ActivePrimitive.GetICEAttributeFromName( "PolygonArea" )
areaData = attr.DataArray

#
# Find the indices of the bad polys
#
bad = [ x for x,y in enumerate( areaData ) if y < epsilon]

# Select the degenerates with a string like 'cube.poly[112,114,155]'
si.SelectGeometryComponents( 'cube.poly[%s]' % ','.join(str(i) for i in bad) )


### OR ###

#
# Get the actual Polygon objects
#
polys = si.Selection(0).ActivePrimitive.Geometry.Polygons
bad = []
for i in range( len(areaData) ):
	if areaData[i] < epsilon:
		bad.append( polys(i) )

si.SelectObj( polys )

Getting the DataArray2D for the Materials ICE attribute


Here’s the Python way:

Application.SelectObj("Pedestrian_Mesh.Actor_Copies", None, None);
o = Application.Selection(0)

a = o.ActivePrimitive.Geometry.GetICEAttributeFromName("Materials")
print len(a.DataArray2D)
print len(a.DataArray2D[0] )
print a.DataArray2D[0][0]
for s in a.DataArray2D[0][0]:
    print s

# 1
# 1
# (u'', u'Sources.Materials.PedestrianLib.Shoes', u'Sources.Materials.PedestrianLib.Hair', u'Sources.Materials.PedestrianLib.Legs', u'Sources.Materials.PedestrianLib.Skin', u'Sources.Materials.PedestrianLib.Shirt')
# Sources.Materials.PedestrianLib.Shoes
# Sources.Materials.PedestrianLib.Hair
# Sources.Materials.PedestrianLib.Legs
# Sources.Materials.PedestrianLib.Skin
# Sources.Materials.PedestrianLib.Shirt

And here’s how to do it in JScript:

o = Selection(0);

a = o.ActivePrimitive.Geometry.GetICEAttributeFromName("Materials");

x = new VBArray( a.DataArray2D ).toArray();
y = new VBArray( x[0] ).toArray();
for ( var i = 0; i < y.length; i++ )
{
    LogMessage( y[i] )
}

Saturday Snippet: Getting data from a DataArray2D ICE attribute


I wanted to do this JScript, but I had to do it in Python first, to establish that it was actually possible (with JScript, you’ve got to mess around with VBarrays and such).

# Using one of the CrowdFX sample scenes:
Application.SelectObj("Pedestrian_Mesh.Actor_Copies", None, None);
o = Application.Selection(0)

a = o.ActivePrimitive.Geometry.GetICEAttributeFromName("Materials")
print len(a.DataArray2D)
print len(a.DataArray2D[0] )
print a.DataArray2D[0][0]
for s in a.DataArray2D[0][0]:
    print s

# 1
# 1
# (u'', u'Sources.Materials.PedestrianLib.Shoes', u'Sources.Materials.PedestrianLib.Hair', u'Sources.Materials.PedestrianLib.Legs', u'Sources.Materials.PedestrianLib.Skin', u'Sources.Materials.PedestrianLib.Shirt')
# Sources.Materials.PedestrianLib.Shoes
# Sources.Materials.PedestrianLib.Hair
# Sources.Materials.PedestrianLib.Legs
# Sources.Materials.PedestrianLib.Skin
# Sources.Materials.PedestrianLib.Shirt

After I had it working in Python, I was able to figure it out in JScript:

// Using one of the CrowdFX sample scenes:
SelectObj("Pedestrian_Mesh.Actor_Copies", null, null);
o = Selection(0);

a = o.ActivePrimitive.Geometry.GetICEAttributeFromName("Materials");

x = new VBArray( a.DataArray2D ).toArray();
y = new VBArray( x[0] ).toArray();
for ( var i = 0; i < y.length; i++ )
{
    LogMessage( y[i] );
}

Finding empty polygon meshes


Now that there are intrinsic ICE attributes like NbPoints and NbPolygons, there a couple of ways you can check for an empty mesh:

# Assume a polymesh is selected...
o = Application.Selection(0)

# Check the intrinsic ICE attribute
nb =  o.ActivePrimitive.ICEAttributes("NbPoints")
print nb.IsDefined and nb.DataArray[0] <= 0

# Check using the object model
print o.ActivePrimitive.Geometry.Points.Count <= 0

The typical way to package this up for users it to define a filter. Then a user just has to select the filter and press CTRL+A. Here’s the Match callback for a filter that finds empty polygon meshes. Note that I switched to checking the number of polygons. That way, if somehow there was something weird like a mesh with just one point, you’d still find it.

The intrinsic attribute NbPolygons should always exist, but just to be sure I check IsDefined, and if that is False, I fall back to checking Geometry.Polygons.Count.

# Match callback for the EmptyPolygonMesh custom filter.
def EmptyPolygonMesh_Match( in_ctxt ):
	Application.LogMessage("EmptyPolygonMesh_Match called",constants.siVerbose)

	o = in_ctxt.GetAttribute( 'Input' )
	if o.type == 'polymsh':
		nb =  o.ActivePrimitive.ICEAttributes("NbPolygons")
		return (nb.IsDefined and nb.DataArray[0] <= 0) or o.ActivePrimitive.Geometry.Polygons.Count <= 0
	else:
		return False

I packaged the filter as an addon. Get it here.