ICE: Getting the edge index of the longest edge

For each polygon, I want to get the EdgeIndex of the longest edge (ultimately, I want to get the midpoint of the longest edge for each polygon).

Being literal-minded, I started by getting the length of the longest edge for each polygon:
Here’s a Show Values to show it works:

From there, I hacked my way to the edge index:
The Show Values:

For my second try at getting the index of the longest edge, I stopped trying to go from the length back to the index. I think this is a little better approach:


Context matters: Using weightmaps with point clouds

A weightmap is per-point, but it’s per-point on the emitting geometry, not the point cloud. So you can’t just do a plain “get weights” if you want to use the weightmap to control particle values like Velocity or Speed.

Instead, you use get a location, like the particle emit location, and then get the weightmap value at that location. Then you’ll have a particle per-point context to work with.
When you get the weight at a location, you get an interpolated weight value.

Emitting strands from a polygon cluster

Here’s a simple ICE tree that emits strands from a polygon cluster.
A general technique for these kinds of filtered emissions is to delete the points you don’t want. In this case, we check Cluster.IsElement. The tricky part is that Cluster.IsElement is a bool-per-polygon attribute, so we need to get into a per-point context. To do that, we get the emit location, which is per-point, and then at that location, the value of Cluster.IsElement. Now we’re in a per-point context, and we can use those per-point boolean values to delete points.

Note the use of Not instead of an If. We know IsElement is False for points that were not emitted from the polygon cluster, so we can logically negate it with Not to get True and feed that into Delete Point.

In pseudocode, we do this:

if not( IsElement ) delete point

instead of

if (IsElement == False) then delete point

Of course, for filtering we could just drill down into Emit Strands and set the filter on the Generate Sample Set nodes.

Saturday Snippet: Getting the object under the mouse in a context menu callback

For most context menus, the Target attribute contains both the selected objects (if any) and the object under the mouse.

def MyCustomMenu_Init( in_ctxt ):
    oMenu = in_ctxt.Source
    oMenu.Filter = "camera"
    oMenu.AddCallbackitem("Custom Camera Tool","MyCameraTool")
    return true

def MyCameraTool(in_ctxt):
    obj = in_ctxt.GetAttibute("Target")

I’m pretty sure I’m the one who dug up that info and put it in the docs (on the Menu Item Callback page). I did not, however, write this bit, which to my ear does not sound good at all:

If your menu is attached to a contextual menu, the currently selected objects are passed in to your callback. The target object under the cursor is also passed in as part of the selected objects. However if no objects are selected, then only the target is passed in. The objects can be retrieved through the Context.GetAttribute method with “Target” specified as the value for the AttributeName parameter. The selected/target objects are not passed in to the callback of a custom menu item attached to a regular menu.

I prefer something more like this:

For context menus, the callback gets the currently selected objects and the object under the mouse. You can get these target objects from the Target attribute with Context.GetAttribute.

Context matters: Add Point is always per-object

Unlike Clone Point, the output context of Add Point is always per-object, no matter what you plug into its input ports. So, for example, you cannot plug Add Point into an Execute on Emit port (which is a per-point port). Making Add Point per-object makes sense to me, because typically you want to add N points to a point cloud, not add N points for every point that is already in the target point cloud (that already sounds confusing).

One consequence of Add Point being per object is that you cannot use an If node to copy over some subset of points to the target point cloud. For example, if your If node is already plugged into something that makes it per-point then you’ll get a context error:

If your If node wasn’t plugged in yet, you’d get some red nodes like this:

The solution is to use a Filter on the other side [upstream] of the Add Point:

hat tips to Gray and Julian

Copying a polygon attribute to vertices

This question came up last weekend (on si-community and xsibase): how do I copy a polygon attribute to a point attribute?

As Chris_TC and gray pointed out, a point can be associated with multiple polygons. So you use VertexToPolygons to either build a per-point array of polygon attribute values, or you do something like average the polygon attribute values and store that as a point attribute value.

Context matters: from PolygonToVertices to VertexToPolygons

This came up last week on the XSI mailing list. The goal, as I understand it, was this: for each polygon, build an array that contains the indices of all polygon neighbors.

So, to start, you can get an array of vertex locations for each polygon:

But when try to use those locations to get VertexToPolygons, that’s when you hit a problem:

Usually if it’s a context mismatch, you won’t be able to plug in the connection. In this situation, you can plug the result of Point Index to Location into the Get VertexToPolygons, but as soon as you do, both nodes go red. The tooltip indicates that ICE cannot figure out the structure of the data, that is, is this a single value or an array of values? The error goes back up to the Index port on Point Index to Location (if you right-click Point Index to Location and click Show Messages, you’ll get “ERROR: The type of port ‘Index’ is not defined.”).

If you unplug Get PolygonToVertex, then things are ok:

So, it is possible to use a Repeat node to build the per-polygon array of neighbor polygons.