Custom ICENode Processing

An ICENode typically acts as a function by reading the input data, computing new values, filtering out the input data, and ultimately setting the output port being evaluated. ICENodes are processed in several phases: BeginEvaluate, Evaluate and EndEvaluate. The Evaluate phase is the function where the bulk of the ICENode algorithm is implemented and the input port data accessed.

Depending of the current graph configuration, Softimage will determine the size and the number of packets to send to the custom ICENode for evaluation. This is done in a multi-thread context where packets are processed in parallel by the Evaluate callback. For instance, suppose the portion of a sub-graph, on which a custom ICENode is connected, is evaluated in the context of an output mesh geometry of 3800 vertices on a 4 CPU machine. The packet size will be determined by Softimage, for example, to 500 vertices and will be used during the evaluation as follows:

• CPU 1: vertices 0 - 499

• CPU 2: vertices 500 - 999

• CPU 3: vertices 1000 - 1999

• CPU 4: vertices 2000 - 2499

As soon as the first CPU finishes, it will get the next available batch. A possible scenario could be:

• CPU 3: vertices 2500 - 2999

• CPU 2: vertices 3000 - 3499

• CPU 1: vertices 3500 - 3800

Each of these packets (or subsets) will go through the graph independently. A ICENode might be evaluating for many CPUs at the same time, but the same subset should not go through the same ICENode twice.

Port Data Access

To access the input port data from the Evaluate function, the SDK provides a set of classes that match the type of input and output ports defined during the ICENode registration.

The following classes are provided for accessing complex data types:

XSI::MATH::CVector2f -> siICENodePortDataVector2

XSI::MATH::CVector3f -> siICENodePortDataVector3

XSI::MATH::CVector4f -> siICENodePortDataVector4

XSI::MATH::CQuaternionf -> siICENodePortDataQuaternion

XSI::MATH::CMatrix3f -> siICENodePortDataMatrix33

XSI::MATH::CMatrix4f -> siICENodePortDataMatrix44

XSI::MATH::CColor4f -> siICENodePortDataColor4

XSI::MATH::CRotationf -> siICENodePortDataRotation

XSI::MATH::CShape -> siICENodePortDataShape

The input data buffers sent to custom ICENodes are either stored in a 1D- or 2D-array depending of the port structure type set during the ICENode registration. The CDataArray and CDataArray2D template classes can be combined with one of the above classes for handling theses arrays. For instance, if a port type is defined as siICENodePortDataVector3 + siICENodePortStructureSingle then CDataArray<CVector3f> must be used to access the data. If a port is defined as siICENodePortDataRotation + siICENodePortStructureArray, then CDataArray2D<CRotationf> must be used, etc. For simple port data types such as float and bool, simply use the type as the template parameter (for example, CDataArray< float > or CDataArray2D< bool >). The input data is considered read-only and cannot be changed, nor can items be removed with these template classes.

| Using the wrong template class to access a given port data will result in unexpected behavior.

For convenience, a number of array types have been defined for each port data type (CDataArrayLong, CDataArrayBool, CDataArrayShape, CDataArray2DVector2f etc.)

Port Data Indexing

The input data arrays are indexed with the CIndexSet and CIndexSet::Iterator classes. The CIndexSet class is always set up with the right indices according to the subset to evaluate. Unlike the input data, array indices exposed by CIndexSet are not immutable and can be removed from the subset. This operation is called element filtering. Filtering out indices from a subset hides these indices from other ICENodes in the graph. Element filtering is an important concept which enables condition-based optimizations that could occur in a ICE graph.

Removing indices from the subset doesn't alter the content of the input data which will always be set to the same size. The CIndexSet::Iterator class has been designed to automatically skip the filtered elements of a subset, that's why it is absolutely crucial to always iterate over the input data with the CIndexSet::Iterator class.

Iterating Over Port Data

Given the following ICENode ports:

    nodeOpDef.AddInputPort( ID_Port0, ID_Group0, siICENodePortDataLong, siICENodePortStructureSingle, siICENodeEvaluationContextAny, L"Port_0", L"Port_0" );
    nodeOpDef.AddInputPort( ID_Port1, ID_Group0, siICENodePortDataVector3, siICENodePortStructureSingle, siICENodeEvaluationContextAny, L"Port_1", L"Port_1" );
    nodeOpDef.AddInputPort( ID_Port2, ID_Group0, siICENodePortDataFloat, siICENodePortStructureArray, siICENodeEvaluationContextAny, L"Port_2", L"Port_2" );
    nodeOpDef.AddOutputPort( ID_OutPort0, ID_Group1, siICENodePortDataVector3, siICENodePortStructureSingle, siICENodeEvaluationContextAny, L"OutPort_0", L"OutPort_0" );
    nodeOpDef.AddOutputPort( ID_OutPort1, ID_Group1, siICENodePortDataFloat, siICENodePortStructureArray, siICENodeEvaluationContextAny, L"OutPort_1", L"OutPort_1" );
    nodeOpDef.AddOutputPort( ID_OutPort2, ID_Group1, siICENodePortDataLong, siICENodePortStructureSingle, siICENodeEvaluationContextAny, L"OutPort_2", L"OutPort_2" );

The next block demonstrates how to iterate over the port data defined above and how to filter out elements:

    XSIPLUGINCALLBACK CStatus SampleNode_Evaluate( ICENodeContext& in_ctxt )
    {
       // Switch over the output port being evaluated
       ULONG outportID = in_ctxt.GetEvaluatedOutputPortID( );

       // Create the index set
       CIndexSet indexSet( in_ctxt );
       switch ( outportID )
       {
           // This section demonstrates a regular array iteration
           case ID_OutPort0:
           {
              // Get the data from port 0 and port 1
              CDataArrayLong port0( in_ctxt, ID_Port0 );
              CDataArrayVector3f port1( in_ctxt, ID_Port1 );

              // Get the output array
              CDataArrayVector3 outPort0( in_ctxt );

              // Now fill the output array
              for( CIndexSet::Iterator it = indexSet.Begin(); it.HasNext(); it.Next())
              {
                  ULONG nIndex = it;
                  XSI::MATH::CVector3f v3f( port1[ nIndex ] );
                  outPort0[ nIndex ].Set( v3f.GetY(), port0[ nIndex ] * v3f.GetZ(), v3f.GetX() );
              }
           }
           break;

           // This section demonstrates how to iterate over a 2D array
           case ID_OutPort1:
           {
              // Get the 2D array from port 2
              CDataArray2DFloat port2( in_ctxt, ID_Port2 );

              // Get the output 2D array
              CDataArray2DFloat outPort1( in_ctxt );

              // The first loop iterates normally on outPort1
              for( CIndexSet::Iterator it = indexSet.Begin(); it.HasNext(); it.Next())
              {
                  // Now we use the Accessor class to allocate the output buffer and to iterate over the sub-array of outPort1
                  CDataArray2DFloat::Accessor floatAccessor = port2[it];

                  CDataArray2DFloat::Accessor outAccessor = outPort1.Resize( it, floatArray.GetCount( ) );

                  for (ULONG i=0; i<floatAccessor.GetCount( ); i++)
                  {
                     // Set the output 2D array
                     outAccessor[i] = floatAccessor[i];
                  }
              }
           }
           break;

           // This section demonstrates how to filter out element items
           case ID_OutPort2:
           {
              // Get the data from port 0
              CDataArrayLong port0( in_ctxt, ID_Port0 );

              // Get the output array
              CDataArrayLong outPort2( in_ctxt );

              for( CIndexSet::Iterator it = indexSet.Begin(); it.HasNext(); /* do not call it.Next() here as it could increment the iterator twice! */ )
              {
                  outPort2[ it ] = port0[ it ];

                  if ( port0[ it ] < 10 )
                  {
                     // Filter out elements of port 0 below 10
                     it.Remove( );

                     // Important: Remove() will increment the iterator position by itself
                  }
                  else
                  {
                     // Nothing to filter out, go to next item
                     it.Next( );
                  }
              }
           }
           break;
       };

       return CStatus::OK;
    }

Multiple Port Connections

Custom ICENodes can be defined with an unlimited number of input connections, which is useful for implementing nodes that need to process multiple input values of similar types. The maximum number of connections must be specified for each port group with ICENodeDef::AddPortGroup. By default, a port group is created with only one input connection.

The code snipet below demonstrates how to access the multiple connections of a port group from the Evaluate callback:

    /*
       Register an input group with up to 10 connections
    */
    CStatus RegisterMaxNode( PluginRegistrar& in_reg )
    {
       ICENodeDef nodeDef = Application().GetFactory().CreateICENodeDef(L"MaxNode");

       // Define a port group with a maximum of 10 connections.
       CStatus st = nodeDef.AddPortGroup(ID_G_100, 1, 10);
       st.AssertSucceeded();

       st = nodeDef.AddInputPort(
           ID_IN_PointPositions, ID_G_100,
           siICENodeDataVector3, siICENodeStructureSingle, siICENodeContextComponent0D,
           L"PointPositions", L"PointPositions"
       );
       st.AssertSucceeded();

       // Add output port.
       st = nodeDef.AddPortGroup(ID_G_200);
       st.AssertSucceeded();

       st = nodeDef.AddOutputPort(
           ID_OUT_Max, ID_G_200,
           siICENodeDataVector3, siICENodeStructureSingle, siICENodeContextSingleton,
           L"MaxPos", L"MaxPos"
       );
       st.AssertSucceeded();

       in_reg.RegisterICENode(nodeDef);

       return CStatus::OK;
    }

       /*
       Evaluate each instance's value to find the maximum value amongst all instances
    */
    XSIPLUGINCALLBACK CStatus Max_Evaluate( ICENodeContext& in_ctxt )
    {
       ULONG nPortID = in_ctxt.GetEvaluatedOutputPortID();
       switch (nPortID)
       {
           case ID_OUT_MaxPos:
           {
              Application xsi;

              CDataArrayVector3f outData(in_ctxt);
              CVector3f v3fMax(-FLT_MAX,-FLT_MAX,-FLT_MAX);

              // Get the number of port instances defined for group ID_G_100
              ULONG nInstCount;
              in_ctxt.GetGroupInstanceCount(ID_G_100, nInstCount);

              // Iterate over the number of instances to access the data buffer of each input connection
              for (ULONG nInstID=0; nInstID<nInstCount; nInstID++)
              {
                  CDataArrayVector3f posArray(in_ctxt, ID_IN_PointPositions, nInstID);

                  CIndexSet indexSet( in_ctxt );
                  for ( CIndexSet::Iterator it = indexSet.Begin(); it.HasNext(); it.Next() )
                  {
                     CVector3f& v3f = posArray[it];
                     if ( v3fMax.GetX() <= v3f.GetX() && v3fMax.GetY() <= v3f.GetY() && v3fMax.GetZ() <= v3f.GetZ() )
                     {
                         v3fMax = v3f;
                     }
                  }
              }

              // Output max value
              outData[0] = v3fMax;
           }
       };

       return CStatus::OK;
    }

Handling Port Polymorphism

Polymorphism is the ability of ports to expose multiple data types. A more advanced implementation technique is needed for handling port polymorphism during the evaluation of a custom ICENode. Since the type resolution will occur at connection time, the coding of the ICENode must be done in a generic way to support multiple data types. The recommended approach is to query the ICENodeContext object for the current output port types and to use a template class to delegate the evaluation.

This examples demonstrates this technique for both 1D- and 2D-arrays:

    template < class T >
    class CEvaluator
    {
       public:
       static void Do( ICENodeContext& in_ctxt, ULONG in_nInPortID, ULONG in_nInstanceIndex )
       {
           // Get the port arrays
           T outData( in_ctxt );
           T inData( in_ctxt, in_nInPortID, in_nInstanceIndex );

           // We need a CIndexSet to iterate over the data
           CIndexSet indexSet( in_ctxt );
           for(CIndexSet::Iterator it = indexSet.Begin(); it.HasNext(); it.Next())
           {
              // Set the output data buffer
              outData[it] = inData[it];
           }
       }
    };

    // Special case for the bool type
    template <>
    class CEvaluator< CDataArrayBool >
    {
       public:
       static void Do( ICENodeContext& in_ctxt, ULONG in_nInPortID, ULONG in_nInstanceIndex )
       {
           // Get the output port array ...
           CDataArrayBool outData( in_ctxt );
           CDataArrayBool inData( in_ctxt, in_nInPortID, in_nInstanceIndex );

           // We need a CIndexSet to iterate over the data
           CIndexSet indexSet( in_ctxt );
           for(CIndexSet::Iterator it = indexSet.Begin(); it.HasNext(); it.Next())
           {
              //Set the output data buffer
              outData.Set( it, inData[it] );
           }
       }
    };

    #define EVALUATE1D( OUT, INPORT )\
       switch( OUT )\
       {\
           case siICENodePortDataFloat: CEvaluator<CDataArrayFloat>::Do( in_ctxt, INPORT, 0 ); break;\
           case siICENodePortDataLong: CEvaluator<CDataArrayLong>::Do( in_ctxt, INPORT, 0 ); break;\
           case siICENodePortDataBool: CEvaluator<CDataArrayBool>::Do( in_ctxt, INPORT, 0 ); break;\
           case siICENodePortDataVector2: CEvaluator<CDataArrayVector2f>::Do( in_ctxt, INPORT, 0 ); break;\
           case siICENodePortDataVector3: CEvaluator<CDataArrayVector3f>::Do( in_ctxt, INPORT, 0 ); break;\
           case siICENodePortDataVector4: CEvaluator<CDataArrayVector4f>::Do( in_ctxt, INPORT, 0 ); break;\
           case siICENodePortDataQuaternion: CEvaluator<CDataArrayQuaternionf>::Do( in_ctxt, INPORT, 0 ); break;\
           case siICENodePortDataRotation: CEvaluator<CDataArrayRotationf>::Do( in_ctxt, INPORT, 0 ); break;\
           case siICENodePortDataMatrix33: CEvaluator<CDataArrayMatrix3f>::Do( in_ctxt, INPORT, 0 ); break;\
           case siICENodePortDataMatrix44: CEvaluator<CDataArrayMatrix4f>::Do( in_ctxt, INPORT, 0 ); break;\
           case siICENodePortDataColor4: CEvaluator<CDataArrayColor4f>::Do( in_ctxt, INPORT, 0 ); break;\
       }

    #define EVALUATE2D( OUT, INPORT )\
       switch( OUT )\
       {\
           case siICENodePortDataFloat: CEvaluator<CDataArray2DFloat>::Do( in_ctxt, INPORT, 0 ); break;\
           case siICENodePortDataLong: CEvaluator<CDataArray2DLong>::Do( in_ctxt, INPORT, 0 ); break;\
           case siICENodePortDataBool: CEvaluator<CDataArray2DBool>::Do( in_ctxt, INPORT, 0 ); break;\
           case siICENodePortDataVector2: CEvaluator<CDataArray2DVector2f>::Do( in_ctxt, INPORT, 0 ); break;\
           case siICENodePortDataVector3: CEvaluator<CDataArray2DVector3f>::Do( in_ctxt, INPORT, 0 ); break;\
           case siICENodePortDataVector4: CEvaluator<CDataArray2DVector4f>::Do( in_ctxt, INPORT, 0 ); break;\
           case siICENodePortDataQuaternion: CEvaluator<CDataArray2DQuaternionf>::Do( in_ctxt, INPORT, 0 ); break;\
           case siICENodePortDataRotation: CEvaluator<CDataArray2DRotationf>::Do( in_ctxt, INPORT, 0 ); break;\
           case siICENodePortDataMatrix33: CEvaluator<CDataArray2DMatrix3f>::Do( in_ctxt, INPORT, 0 ); break;\
           case siICENodePortDataMatrix44: CEvaluator<CDataArray2DMatrix4f>::Do( in_ctxt, INPORT, 0 ); break;\
           case siICENodePortDataColor4: CEvaluator<CDataArray2DColor4f>::Do( in_ctxt, INPORT, 0 ); break;\
       }

    XSIPLUGINCALLBACK CStatus MyCustomICENode_Evaluate( ICENodeContext& in_ctxt )
    {
       // The current output port being evaluated...
       ULONG out_portID = in_ctxt.GetEvaluatedOutputPortID( );

       switch( out_portID )
       {
           case ID_OutPort:
           {
              XSI::siICENodePortDataType outPortType;
              XSI::siICENodePortStructureType outPortStruct;
              XSI::siICENodeEvaluationContextType outPortContext;
              in_ctxt.GetPortInfo( ID_OutPort, outPortType, outPortStruct, outPortContext );

              XSI::siICENodePortDataType inPortType;
              XSI::siICENodePortStructureType inPortStruct;
              XSI::siICENodeEvaluationContextType inPortContext;
              in_ctxt.GetPortInfo( ID_InPort, inPortType, inPortStruct, inPortContext );

              if ( outPortStruct == XSI::siICENodePortStructureSingle )
              {
                  EVALUATE1D( outPortType, ID_InPort );
              }
              else if ( outPortStruct == XSI::siICENodePortStructureArray )
              {
                  EVALUATE2D( outPortType, ID_InPort );
              }
           }
           break;

           // Other output ports...

       };

       return CStatus::OK;
    }

User Data

Custom ICENodes support user data which can be added with Context::PutUserData and retrieved with Context::GetUserData. Any data type supported by CValue can be used as user data type. User data is typically set from the BeginEvaluate callback, and any memory allocated in BeginEvaluate must be released in the EndEvaluate callback. You can also set the user data from the Init callback when the custom node gets created, in which case the Term callback must be used for releasing any memory allocated in Init. The user data stored in BeginEvaluate or Init is always accessible from the Evaluate callback. However, unless the custom node threading mode is single-threading, the Context::PutUserData property cannot be used from the Evaluate callback.

One important issue when using multiple threads is to avoid conflicts when one or more threads need to modify the user data. Write access to the user data must be synchronized with thread locks. If you're not careful, overlapping modifications to user data from multiple threads may cause all sort of problems. Locking is a fundamental synchronization mechanism that allows multiple threads to access shared data: when a thread holds a lock to access the data, other threads that need to access the same data are halted until the lock is released.

While using locks is a safe mechanism for sharing data, it may slow down the execution of your custom ICENode since only one thread at a time can access the user data. Locks can be overcome by using a thread safe buffer. This technique consists of creating a buffer containing a copy of the user data for each evaluation thread created by Softimage, and since each thread works with their own data copy data access conflicts will not happen. Thread safe buffers must be created in the BeginEvaluate callback. The size of the buffer must be set with the number of evaluation threads returned by ICEContextNode::GetEvaluationThreadCount. In some cases the number of threads may increase for a given evaluation, therefore you should always make sure your safe thread buffer can be extended to the new number of threads. The Init callback cannot be used for creating thread-safe buffers and locks must be used in the Evaluate callback to avoid data access conflicts.

| Avoid using CValueArray to store C pointers, the CValue objects of type siPrtType are not supported by CValueArray.

The following example demonstrates how to set and get user data from BeginEvaluate using the technique of thread safe buffers:

    // For storing CSampleData user data objects
    #include <vector>

    // Simple user data struct
    struct CSampleData
    {
       CSampleData() : nLen(0), pBuffer(NULL) {}
       ~CSampleData()
       {
           if (pBuffer)
              delete [] pBuffer;
       }

       LONG nLen;
       XSI::MATH::CVector3f* pBuffer;
    };

       XSIPLUGINCALLBACK CStatus TestCustomICENode_BeginEvaluate( ICENodeContext& in_ctxt )
    {

       CValue userData = in_ctxt.GetUserData( );

       ULONG nThreadCount = in_ctxt.GetEvaluationThreadCount( );
       std::vector<CSampleData>* pPerThreadData = NULL;
       if ( userData.IsEmpty() )
       {
           pPerThreadData = new std::vector<CSampleData>;
           in_ctxt.PutUserData( (CValue::siPtrType)pPerThreadData );
       }
       else
       {
           // Reuse the user data buffer if already created.
           pPerThreadData = (std::vector<CSampleData>*)(CValue::siPtrType)in_ctxt.GetUserData( );
       }

       if ( pPerThreadData && pPerThreadData->size() < nThreadCount)
       {
           // Extend buffer if needed
           for(ULONG i = (ULONG)pPerThreadData->size(); i < nThreadCount; i++)
           {
              // Create a CSampleData object for each thread
              pPerThreadData->push_back( CSampleData() );
           }
       }
       return CStatus::OK;
    }

       XSIPLUGINCALLBACK CStatus TestCustomICENode_Evaluate( ICENodeContext& in_ctxt )
    {

       // Get the user data that we allocated in BeginEvaluate
       std::vector<CSampleData>* pPerThreadData = (std::vector<CSampleData>*)(CValue::siPtrType)in_ctxt.GetUserData( );

       // The user data array is indexed by the current thread ID
       ULONG nThreadID = in_ctxt.GetCurrentThreadIndex( );
       CSampleData& userData = (*pPerThreadData)[ nThreadID ];
       Application().LogMessage( L"User data thread("+CString(nThreadID)+L"): " + CString( userData.nLen ) );

       // Processing code goes here ...

       return CStatus::OK;
    }

       XSIPLUGINCALLBACK CStatus TestCustomICENode_EndEvaluate( ICENodeContext& in_ctxt )
    {

       // Release memory allocated in BeginEvaluate
       CValue userData = in_ctxt.GetUserData( );
       if ( userData.IsEmpty() )
       {
           return CStatus::OK;
       }

       std::vector<CSampleData>* pPerThreadData = (std::vector<CSampleData>*)(CValue::siPtrType)in_ctxt.GetUserData( );
       delete pPerThreadData;

       // Clear user data
       in_ctxt.PutUserData( CValue() );

       return CStatus::OK;
    }

CustomVector3ToScala r Example

This summarizes the ICENode processing topic with a custom version of the built-in Vector3ToScalar ICENode.

    #include <xsi_application.h>
    #include <xsi_context.h>
    #include <xsi_pluginregistrar.h>
    #include <xsi_status.h>

    #include <xsi_mdnodecontext.h>
    #include <xsi_mdnodedef.h>
    #include <xsi_factory.h>
    #include <xsi_math.h>
    #include <xsi_vector2f.h>
    #include <xsi_vector3f.h>
    #include <xsi_vector4f.h>
    #include <xsi_matrix3f.h>
    #include <xsi_matrix4f.h>
    #include <xsi_rotationf.h>
    #include <xsi_quaternionf.h>
    #include <xsi_color4f.h>
    #include <xsi_shape.h>
    #include <xsi_indexset.h>
    #include <xsi_dataarray.h>
    #include <xsi_dataarray2D.h>

    // Defines port, group and map identifiers used for registering the ICENode
    enum IDs
    {
       ID_IN_v3 = 0,
       ID_G_100 = 100,
       ID_OUT_x = 200,
       ID_OUT_y = 201,
       ID_OUT_z = 202,
       ID_G_300 = 300,
       ID_G_301 = 301,
       ID_G_302 = 302,
       ID_TMAP = 400,
       ID_SMAP,
       ID_CMAP,
       ID_UNDEF = ULONG_MAX
    };

    XSI::CStatus RegisterCustomVector3ToScalar( XSI::PluginRegistrar& in_reg );

    using namespace XSI;

    XSIPLUGINCALLBACK CStatus XSILoadPlugin( PluginRegistrar& in_reg )
    {
       in_reg.PutAuthor(L"Softimage");
       in_reg.PutName(L"CustomVector3ToScalar Plugin");
       in_reg.PutEmail(L"");
       in_reg.PutURL(L"");
       in_reg.PutVersion(1,0);

       RegisterCustomVector3ToScalar( in_reg );

       //RegistrationInsertionPoint - do not remove this line

       return CStatus::OK;
    }

    CStatus RegisterCustomVector3ToScalar( PluginRegistrar& in_reg )
    {
       ICENodeDef nodeOpDef;
       nodeOpDef = Application().GetFactory().CreateICENodeDef(L"CustomVector3ToScalar");

       CStatus st;

       // Add input ports and groups.
       st = nodeOpDef.AddPortGroup(ID_G_100);
       st.AssertSucceeded( ) ;

       st = nodeOpDef.AddInputPort(
                                           ID_IN_v3, // port index
                                           ID_G_100, // group index
                                           siICENodePortDataVector3, // data type
                                           siICENodePortStructureSingle, // structure type
                                           siICENodeEvaluationContextComponent0D, // context type
                                           L"v3", // port name
                                           L"v3", // port scripting name
                                           MATH::CVector3f(1.0,1.0,1.0), // default value
                                           ID_UNDEF, // data type constraint
                                           ID_SMAP, // structure type constraint
                                           ID_CMAP // context type contraint
       );
       st.AssertSucceeded( ) ;

       // Add output ports and groups.
       st = nodeOpDef.AddPortGroup(ID_G_300);
       st.AssertSucceeded( ) ;

       st = nodeOpDef.AddPortGroup(ID_G_301);
       st.AssertSucceeded( ) ;

       st = nodeOpDef.AddPortGroup(ID_G_302);
       st.AssertSucceeded( ) ;

       st = nodeOpDef.AddOutputPort(
                                           ID_OUT_x, // port index
                                           ID_G_300, // group index
                                           siICENodePortDataFloat, // data type
                                           siICENodePortStructureSingle, // structure type
                                           siICENodeEvaluationContextComponent0D, // context type
                                           L"x", // port name
                                           L"x", // port scripting name
                                           ID_UNDEF, // data type constraint
                                           ID_SMAP, // structure type constraint
                                           ID_CMAP // context type contraint
       );
       st.AssertSucceeded( ) ;

       st = nodeOpDef.AddOutputPort(
                                           ID_OUT_y, // port index
                                           ID_G_301, // group index
                                           siICENodePortDataFloat, // data type
                                           siICENodePortStructureSingle, // structure type
                                           siICENodeEvaluationContextComponent0D, // context type
                                           L"y", // port name
                                           L"y", // port scripting name
                                           ID_UNDEF, // data type constraint
                                           ID_SMAP, // structure type constraint
                                           ID_CMAP // context type contraint
       );
       st.AssertSucceeded( ) ;

       st = nodeOpDef.AddOutputPort(
                                           ID_OUT_z, // port index
                                           ID_G_302, // group index
                                           siICENodePortDataFloat, // data type
                                           siICENodePortStructureSingle, // structure type
                                           siICENodeEvaluationContextComponent0D, // context type
                                           L"z", // port name
                                           L"z", // port scripting name
                                           ID_UNDEF, // data type constraint
                                           ID_SMAP, // structure type constraint
                                           ID_CMAP // context type contraint
       );
       st.AssertSucceeded( ) ;

       PluginItem nodeItem = in_reg.RegisterICENode(nodeOpDef);
       nodeItem.PutCategories(L"Custom ICENode Sample");

       return CStatus::OK;
    }

    XSIPLUGINCALLBACK CStatus CustomVector3ToScalar_Evaluate( ICENodeContext& in_ctxt )
    {
       // Get the output data array
       CDataArrayFloat outData( in_ctxt );

       // .. and the input array
       CDataArrayVector3f inputData( in_ctxt, ID_IN_v3 );

       // And the index set
       CIndexSet indexSet( in_ctxt );

       // Set the output data
       ULONG outport_uniqid = in_ctxt.GetEvaluatedOutputPortID( );

       switch( outport_uniqid )
       {
           case ID_OUT_x:
           {
              for(CIndexSet::Iterator it = indexSet.Begin(); it.HasNext(); it.Next())
              {
                  outData[it] = inputData[it].GetX();
              }
           }
           break;

           case ID_OUT_y:
           {
              for(CIndexSet::Iterator it = indexSet.Begin(); it.HasNext(); it.Next())
              {
                  outData[it] = inputData[it].GetY();
              }
           }
           break;

           case ID_OUT_z:
           {
              for(CIndexSet::Iterator it = indexSet.Begin(); it.HasNext(); it.Next())
              {
                  outData[it] = inputData[it].GetZ();
              }
           }
           break;
       };

       return CStatus::OK;
    }

Autodesk Softimage v7.5