3 Programmer API

3 Programmer API

An application programmers interface exists to allow the construction of user written collaborative modules. These modules can then operate within the COVISA framework in the same way as any other collaborative module. All of the collaborative data sharing modules supplied with this release use this library, and their source code can be found in %EXPLORERHOME%\src, where %EXPLORERHOME% is the root directory of the installed IRIS Explorer tree. Some annotated source code is available, see Section 3.2.

To compile a collaborative module, you must select Collaborative under Module->Options... within Module Builder. You must write your collaborative code in C++; this may of course be combined with other code written in C or Fortran.

3.1 COVISA Collaboration Class for IRIS Explorer

3.1.1 Name

cxCollab – collaboration class library.

3.1.2 Synopsis

#include <cx/cxCollab.h>

3.1.3 Constructor and Destructor from Class cxCollab

 cxCollab(void);
~cxCollab(void);

3.1.4 Methods from Class cxCollab

Register ports to be managed:
int portRegister(int numPorts, char **names, int *portTypes, int *dataTypes);
int portRegister(char *name, int portType, int dataType);
Inquiry functions:
void checkWidgets(void);
int isConnected(void);
int newData(char *name);
Set/Send/Delete data from receiver objects:
int setData(char *name, void *ptr);
int sendData(int numPorts, char **names);
int sendData(char *name);
void clearData(char *name);
Get Data:
cxLattice * getLattice(char *name);
cxParameter * getParameter(char *name);
cxPyramid * getPyramid(char *name);
cxPick * getPick(char *name);
cxGeo * getGeometry(char *name);
Get Identity:
const char * getOwnUserName(void);
const char * getUName(char *name);

3.1.5 Description

This class is used to make IRIS Explorer modules collaboratively aware. It allows them to share any of the internal datatypes of IRIS Explorer between separate instances of the visualization system.

3.1.5.1 Constructors

cxCollab(void);

Creates an instance of cxCollab. It should be created as a static variable so that state parameters within the class are not lost between firings of an IRIS Explorer module.

3.1.5.2 Methods

int portRegister(char *name, int portType, int dataType);

Registers a name to be associated with a data object that is to be shared. It requires a name, a port type and a datatype. Port types are either INPUT or INTERNAL. An INPUT type associates the given name with a module input port. Sending data of type INPUT takes the current data object on that named port and passes it into the collaborative session. An INTERNAL type takes a reference to an IRIS Explorer data object created within the module during its execution and passes it into the collaborative session. Data types are either PARAMETER, LATTICE, PYRAMID or GEOMETRY and correspond directly to the IRIS Explorer datatype of that name.

int portRegister(int numPorts, char **names, int *portTypes, int *dataTypes);

Registers a number of ports at one time to be associated with a data object that is to be shared. See above for a description of the parameters.

void checkWidgets(void);

A number of reserved widgets are required to make this system work. The reserved names are:

Initiate, Connection_State, Join, ID, Name, Application.

This function checks the state of these widgets and takes appropriate action. It should be called once on each firing.
int isConnected(void);

Checks whether the module is currently connected to the collaborative session. Returns 1 (TRUE) if it is connected, else 0 (FALSE).

int newData(char *name);

Checks whether there is any new data associated with a registered name. Returns 1 (TRUE) if it is a valid registered name and new data has arrived else 0 (FALSE).

int setData(char *name, void *ptr);

For port types of type INTERNAL, the pointer to the data object to be sent must be set before calling sendData. After sendData has been called, the internal pointer is reset to NULL. Returns 1 (TRUE) if successful, else 0 (FALSE).

int sendData(char *name);

Send the data associated with a given registered port name. If the port type is INPUT, then the module input port of that name will be opened and the current data sent. If the port type is INTERNAL then the data associated with that name by a previous call to setData will be sent. Returns 1 (TRUE) if successful, else 0 (FALSE). Failure will occur if the name given does not match one of the registered port names.

int sendData(int numPorts, char **names);

As above but sends multiple data objects in a single message. This minimises the number of firings of the attached modules in the session. Returns 1 (TRUE) if successful, else 0 (FALSE). Failure will occur if the name given does not match one of the registered port names.

void clearData(char *name);

To be more memory efficient, the incoming data object held by the named port may be deleted once it has been used. If not deleted, it remains until a new data object is sent from the session, this has the advantage that it may be re-referenced.

cxLattice * getLattice(char *name);

Get the incoming data object, which should be a lattice, from the named port. Returns NULL on error.

cxParameter * getParameter(char *name);

Get the incoming data object, which should be a parameter, from the named port. Returns NULL on error.

cxPyramid * getPyramid(char *name);

Get the incoming data object, which should be a pyramid, from the named port. Returns NULL on error.

cxPick * getPick(char *name);

Get the incoming data object, which should be a pick, from the named port. Returns NULL on error.

cxGeo * getGeometry(char *name);

Get the incoming data object, which should be a geometry, from the named port. The geometry is returned as a piece of scene graph under the returned geometry node. It should be instanced using a call to cxGeoInventorDefine. Returns NULL on error.

const char * getOwnUserName(void);

Get the string representing the username of the user within the collaborative session.

const char * getUName(char *name);

All objects passed into the collaborative session are tagged with the username of the person who created them. This function returns the string containing the username of the user who created the object associated with the named object.

3.1.6 Further Notes

A number of port names are reserved for use by the collaborative class and should not be used by users' code. Instances of these ports must be included in any user written collaborative module. The port names are as follows:

  • Initiate

  • Join

  • ID

  • Name

  • Connection_State

  • Application

Additionally, to facilitate the automatic launching and connection of collaborative modules, none of its ports should be set to Required.

3.2 Annotated Examples

Some example source code is shown here, the collaborative code is highlighted in blue and the comments are in green. The modules shown here are MShareLat, a completely new module, and MShareGraph3D which is based on the Graph3D module but allows for collaborative work. The lines of code of MShareGraph3D shown in black are the original module code of Graph3D. This demonstrates how little additional code was required to convert it to be a collaborative module.

All of the source code shown here, plus code for all of the other collaborative modules, is available in %EXPLORERHOME%\src\

Example 3-1 User Function for the MShareLat Module

// Collab Includes
#include <cx/cxCollab.h>

// Explorer Includes
#include <cx/Typedefs.h>
#include <cx/Info.h>
#include <cx/cxLattice.api.h>
#include <cx/DataAccess.h>
#include <cx/PortAccess.h>

#ifdef __cplusplus
    extern "C" {
#endif
/*
 * User Function
 */
void ShareLat(long pass,cxLattice **OutLat)
{
  static cxCollab collab_info;// Create a static instance of the
                              // collaboration class so that
                              // connection state and other info
                              // is not lost between each
                              // invocation of this function.
  static int firsttime = 1;

  if (firsttime == 1) {
    firsttime = 0;
    //Register a single collaborative port: name, source type, datatype
    collab_info.portRegister("In_Lat",INPUT,LATTICE);
    return;
  }

  // Check reserved widgets looking for connect/disconnect instructions
  collab_info.checkWidgets();

  // If connected then can do things
  if (collab_info.isConnected()) {
    // Check for data to read
    if (collab_info.newData("In_Lat")) {
      *OutLat = collab_info.getLattice("In_Lat");
      cxDataRefInc((void *)*OutLat);
    }
  }

  // Do stuff in here if need to, sing, dance etc . . .

  // If connected then can do things
  if (collab_info.isConnected()) {
    // Check for data to send
    int port = cxInputPortOpen("In_Lat");
    if (cxInputDataChanged(port))
      collab_info.sendData("In_Lat");
  }

  if ((pass) && (cxInputDataChanged(cxInputPortOpen("In_Lat")))) {
    *OutLat = (cxLattice *)cxInputDataGet(cxInputPortOpen("In_Lat"));
  }

} // end User function

#ifdef __cplusplus
}
#endif

Example 3-2 User Function for the MShareGraph3D Module

// Collab Includes
#include <cx/cxCollab.h>

/* Inventor Includes */
#include <Inventor/nodes/SoGroup.h>
#include <Inventor/nodes/SoMaterial.h>
#include <Inventor/nodes/SoTransform.h>
#include <Inventor/nodes/SoCube.h>
#include <Inventor/fields/SoSFVec3f.h>
#include <Inventor/nodes/SoComplexity.h>
#include <Inventor/nodes/SoShapeHints.h>

/* Explorer Includes */
#ifdef WIN32
#include <float.h>
#else
#include <values.h>
#endif
#include <cx/DataAccess.h>
#include <cx/DataTypes.h>
#include <cx/Geometry.h>
#include <cx/Lookup.h>
#include <cx/PortAccess.h>
#include <cx/UI.h>

// A convenient way to list all of the ports
// that we are going to register for collaboration,
// along with their port types and data types
char *port_names[] = {"Graph Type","Color By What","Height Scale",
                      "Surface Scale","Treat Negatives As ?"};
int port_dataTypes[] = {PARAMETER, PARAMETER, PARAMETER,
                        PARAMETER, PARAMETER};
int port_types[] = {INPUT,INPUT,INPUT,INPUT,INPUT};
int num_ports = 5;

#ifdef __STDC__
extern "C" void graph3d(cxGeometry **geom)
#else
extern "C" void graph3d(geom)
     cxGeometry **geom;
#endif
{
  static cxCollab collab_info; // class instance
               // for collaborative elements
  static int firsttime = 1;

  cxCoord *coord;
  cxCoordType coordtype;
  cxData *data_struct;
  cxErrorCode err;
  cxLookup *clut;
  cxPrimType primType;
  double smin, smax;
  float color_array[4], color_value, *coordvals, cyl_point[6], *data,
  delx, dely, dx, dy, max_val, min_val, point[3], radius, scalefactor,
  size, s_min = 0.01, s_max = 1.0, xmin, ymin;
  int port;
  long *dims, flag, i, j, nDataVar, nDim;
  int cmap_flag;
  int ngtve;
  double val;

  // Ports handled internally
  cxLattice *lat, *cmap;
  long color_by_what, graph_type, n_vals;
  double h_scale, s_scale;
  cxParameter *Scale_Param;

  lat = (cxLattice *)cxInputDataGet(cxInputPortOpen("Data In"));
  cmap = (cxLattice *)cxInputDataGet(cxInputPortOpen("ColorMap"));

  // Check to see if we are connected. If we are then check to see if any
  // new data has arrived on any of the ports that we registered as being
  // collaborative. If we find new data (or the "sync values" button has
  // been pressed) compile a list of port names for which data should be
  // sent. Then send the data.
  if (collab_info.isConnected()) {

    char **portNames = new char * [num_ports];
    int port,numPorts=0;
    int sendAll = FALSE;
    port = cxInputPortOpen("Sync Values");
    if (cxInputDataChanged(port))
      sendAll = TRUE;

    // Compile list of port names for which data is new
    for (int i = 0; i < num_ports; i++ ) {
      port = cxInputPortOpen(port_names[i]);
      if ((cxInputDataChanged(port)) || (sendAll)) {
        int len = strlen(port_names[i])+1;
        portNames[numPorts] = new char [len];
        bzero(portNames[numPorts],len);
        strcpy(portNames[numPorts],port_names[i]);
        numPorts++;
      }
    }

    // If some new data, then send to collaborators
    if (numPorts > 0)
      collab_info.sendData(numPorts,portNames);
    //collab_info.sendData(portNames[0]);

    // Tidy Up
    for (i = 0; i < numPorts; i++ )
      free(portNames[i]);
    free(portNames);

    // If we were just sync(ing) values, then stop having sent the
    // data values.
    if (sendAll)
      return;
  }

  long dummy = cxParamLongGet((cxParameter*)cxInputDataGet(
                              cxInputPortOpen("Sync Values")));
  graph_type = cxParamLongGet((cxParameter*)cxInputDataGet(
                              cxInputPortOpen("Graph Type")));
  color_by_what = cxParamLongGet((cxParameter*)cxInputDataGet(
                              cxInputPortOpen("Color By What")));
  n_vals = cxParamLongGet((cxParameter*)cxInputDataGet(
                              cxInputPortOpen("Treat Negatives As ?")));
  h_scale = cxParamDblGet((cxParameter*)cxInputDataGet(
                              cxInputPortOpen("Height Scale")));
  s_scale = cxParamDblGet((cxParameter*)cxInputDataGet(
                              cxInputPortOpen("Surface Scale")));
  if (firsttime == 1) {
    firsttime = 0;
    // Register (once only) the names of ports that we want to be
    // collaborative
    collab_info.portRegister(num_ports,port_names, port_types,port_dataTypes);
    // consume this value
    long dummy = cxParamLongGet((cxParameter*)cxInputDataGet(
                                 cxInputPortOpen("Sync Values")));
    return;
  }

  // Check reserved widgets looking for connect/disconnect/re-connect
  // instruction
  collab_info.checkWidgets();

  // If connected then can do things
  if (collab_info.isConnected()) {

    // Turn on Sync Values button
    cxInWdgtShow("Sync Values");

    // Check for data to be read from the collaborative session
    // associated with port names that we have registered.
    // Update the control panel if new data is found.
    if (collab_info.newData("Graph Type")) {
      graph_type = cxParamLongGet(collab_info.getParameter("Graph Type"));
      cxInWdgtLongSet("Graph Type",graph_type);
    }
    if (collab_info.newData("Color By What")) {
      color_by_what = cxParamLongGet(collab_info.getParameter("Color By What"));
      cxInWdgtLongSet("Color By What",color_by_what);
    }
    if (collab_info.newData("Treat Negatives As ?")) {
      n_vals = cxParamLongGet(collab_info.getParameter("Treat Negatives As ?"));
      cxInWdgtLongSet("Treat Negatives As ?",n_vals);
    }
    if (collab_info.newData("Height Scale")) {
      h_scale = cxParamDblGet(collab_info.getParameter("Height Scale"));
      cxInWdgtDblSet("Height Scale",h_scale);
    }
    if (collab_info.newData("Surface Scale")) {
      s_scale = cxParamDblGet(collab_info.getParameter("Surface Scale"));
      cxInWdgtDblSet("Surface Scale",s_scale);
    }
  }

  // If no data then finish
  if (lat == NULL)
    return;

  /* Get the input lattice and determine its data type */
  err = cxLatDescGet(lat, &nDim, &dims, NULL, &nDataVar, &primType,
                     NULL, NULL, &coordtype);
  if (err == cx_err_error) {
    cxModAlert("No memory to get lattice description, return control");
    return;
  }

  /* Get the data structure and data */
  cxLatPtrGet(lat, &data_struct, (void **) (&data), &coord,
              (void **) (&coordvals));
  if (err == cx_err_error) {
    cxModAlert("Invalid lattice pointer used, return control");
    return;
  }

  /* Get the maximum size of the ground area for the blocks
   * This code assumes that a uniform lattice is connected
   */
  if (coordtype != cx_coord_uniform) {
    cxModAlert
      ("Unexpected lattice type connected, return control");
    return;
  }

  delx = (coordvals[1] - coordvals[0])/((float) dims[0] - 1.0);
  dely = (coordvals[3] - coordvals[2])/((float) dims[1] - 1.0);
  dx = 0.5 * delx;
  dy = 0.5 * dely;
  xmin = coordvals[0];
  ymin = coordvals[2];

  /* If a colour map is attached, generate its lookup table */
  if  (cmap != NULL) {
    clut = cxLookupCreate(cmap, cx_lookup_nearest);
    cmap_flag = 1;
  }
  else
    cmap_flag = 0;

  /* Find minimum and maximum values in input lattice */
#ifdef WIN32
  min_val = FLT_MAX;
  max_val = FLT_MIN;
#else
  min_val = MAXFLOAT;
  max_val = MINFLOAT;
#endif
  for (j = 0; j < dims[1]; j++) {
    for (i = 0; i < dims[0]; i++) {
      val = extract_value(data, primType, (j*dims[0]+i));
      if (min_val > val)
        min_val = val;
      if (max_val < val)
        max_val = val;
    }
  }

  /* Scale the height according to the minimum and maximum values
   * allowing for a user-specified multiplication factor h_scale
   */
  if (max_val == min_val)
    scalefactor = 0.08 * (float) h_scale;
  else
    scalefactor = 8.0 * (float) h_scale / (max_val - min_val);

  /* Set external Scale Factor */
  Scale_Param = cxParamDoubleNew((double)scalefactor);
  cxOutputDataSet(cxOutputPortOpen("Scale"),(void *)Scale_Param);

  /* Initialise the geometry structure */
  *geom = cxGeoNew();
  cxGeoBufferSelect(*geom);

  /* New code to use shared memory geometry transcription */
  port = cxOutputPortOpen("Geometry");
  cxGeoBufferPortSet(port);

  /* Create the geometry */
  cxGeoRoot();
  cxGeoDelete();

  /* If a colour map is attached get a colour value
   * else use a fixed value 0f 0.8
   */
  if (!cmap_flag) {
    color_array[0] = 0.8;
    color_array[1] = 0.8;
    color_array[2] = 0.8;
  }

  /* The distance between blocks is determined by the
   * user-specified scale factor s_scale
   */
  if (s_scale < s_min)
    {
      /* If the size would be too small then it is clamped
       * and the widget must be examined for correct values
       * before setting it to the clamped value
       */
      size = s_min;
      cxInWdgtMinMaxGet("Surface Scale", &smin, &smax);
      flag = 0;
      if (smin > size)
      {
        smin = (double) size;
        flag = 1;
      }
      if (smax < smin)
      {
        smax = smin;
        flag = 1;
      }
      if (flag == 1)
        cxInWdgtDblMinMaxSet("Surface Scale", smin, smax);
      cxInWdgtDblSet("Surface Scale", (double) size);
    }
  else if (s_scale > s_max)
    {
      /* If the size would exceed the available space then it is
       * clamped and the widget must be examined for correct
       * values before setting it to the clamped value
       */
       size = s_max;
       cxInWdgtMinMaxGet("Surface Scale", &smin, &smax);
       flag = 0;
       if (smin > size)
       {
         smin = (double) size;
         flag = 1;
       }
       if (smax < smin)
       {
         smax = smin;
         flag = 1;
       }
       if (flag == 1)
         cxInWdgtDblMinMaxSet("Surface Scale", smin, smax);
       cxInWdgtDblSet("Surface Scale", (double) size);
    }
  else
    size = (float) s_scale;

  /* Build up the geometry by inspecting each node */
  for (j = 0; j < dims[1]; j++)
    {
      for (i = 0; i < dims[0]; i++)
      {
        val = extract_value(data, primType,(j*dims[0]+i));

        /* The colour used for the node */
        if (cmap_flag) {
          switch (color_by_what)
          {
            case 0:
              color_value = (float) i / (float) (dims[0]-1);
              break;
            case 1:
              color_value = (float) j / (float) (dims[1]-1);
              break;
            case 2:
              color_value = (float) val;
              cxLookupInterp(clut, &color_value, (void *)color_array);
              break;
          }
          cxLookupInterp(clut, &color_value, (void *)color_array);
        }

        /* See if negative */
        if (val < 0.0)
          ngtve = 1;  /*True*/
        else
          ngtve = 0;  /* False */

        /* Make Positive */
        if ((n_vals == 1) && (ngtve)) {
          val =  -1 * val;
          ngtve = 0;  /* False */
        }

        /* Select the type of graph */
        switch (graph_type)
        {
          case 0:
            /* Blocks */
            if ((n_vals != 2) || (!ngtve)) {
              /* Create a group */
              SoGroup *Group = new SoGroup ;
              Group->ref();

              /* Add Color */
              SoMaterial *mtl = new SoMaterial;
              Group->addChild(mtl);
              mtl->diffuseColor.setValue(color_array[0],color_array[1],color_array[2]);

              /* Create Transform */
              SoTransform *Trans = new SoTransform;
              Group->addChild(Trans);
              point[0] = (float) xmin + i * delx ;
              point[1] = (float) ymin + j * dely ;
              point[2] = (float) (scalefactor * val)/2 ;
              Trans->translation.setValue(point);
              SoComplexity *complex = new SoComplexity;
              Group->addChild(complex);
              complex->value.setValue(0.2);

              /* Create cube */
              point[0] = (float) (xmin + i * delx + dx * size) -
                                 (xmin + i * delx - dx * size);
              point[1] = (float) (ymin + j * dely + dy * size) -
                                 (ymin + j * dely - dy * size);
              if (ngtve)
                point[2] = (float) scalefactor * val * -1;
              else
                point[2] = (float) scalefactor * val ;
              SoCube *cube = new SoCube;
              Group->addChild((SoNode *)cube);
              cube->width.setValue(point[0]);
              cube->height.setValue(point[1]);
              cube->depth.setValue(point[2]);

              /* Add to Explorer Geom */
              cxGeoInventorDefine(Group);
              Group->unref();
            }
            break;

          case 1:
            /* Cylinders */
            /* A cylinder is defined by 2 centres and a radius
             * A cone is defined by a centre, a top and a base radius
             */
            if ((n_vals != 2) || (!ngtve)) {
              cyl_point[0] = cyl_point[3] = xmin + i * delx;
              cyl_point[1] = cyl_point[4] = ymin + j * dely;
              cyl_point[2] = 0.0;
              cyl_point[5] = scalefactor * val;
              if (dx < dy)
                radius = dx * size;
              else
                radius = dy * size;

              cxGeoCylindersDefine(1, &cyl_point[0], &cyl_point[3],
                   &radius);
              cxGeoComplexityAdd(0.2);
              cxGeoColorAdd(1, color_array, CX_GEO_PER_OBJECT);
            }
            break;

          case 2:
            /* Cones */
            /* A cylinder is defined by 2 centres and a radius
             * A cone is defined by a centre, a top and a base radius
             */
            if ((n_vals != 2) || (!ngtve)) {
              cyl_point[0] = cyl_point[3] = xmin + i * delx;
              cyl_point[1] = cyl_point[4] = ymin + j * dely;
              cyl_point[2] = 0.0;
              cyl_point[5] = scalefactor * val;
              if (dx < dy)
                radius = dx * size;
              else
                radius = dy * size;

              cxGeoConesDefine(1, &cyl_point[0], &cyl_point[3],
                               &radius);
              cxGeoComplexityAdd(0.2);
              cxGeoColorAdd(1, color_array, CX_GEO_PER_OBJECT);
            }
            break;

          default:
            /* Unexpected type */
            cxModAlert
              ("unexpected graph type requested, return control");
            return;
          }
        }
      }
      if (cmap_flag) {
        cxLookupDestroy(clut);
    }
  cxGeoBufferClose(*geom);
}