3.1.1. An Example of an Export Program

The following is an annotated listing of the C source code for a reasonably simple example of a customized Export program. The full source code is available for use as a template and is located in CFX/examples/ExportTemplate.c, where CFX is the directory in which CFX is installed.

The example program is a reasonably simple example of an export program, which opens a CFX results file, writes a geometry file (ignoring pyramid elements) and several files containing results. After the program listing, a sample of the output produced is shown.

3.1.1.1. File Header

The file header uses several #include entries. The first set includes standard header files.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <io.h>

The second set includes cfx5export header files.

#include "cfxExport.h"
#include "getargs.h"

Obtaining CFX-Mesh and Results Export API header files is described in more detail. For details, see Linking Code with the Mesh and Results Export API.

3.1.1.2. Allowed Arguments

The definition of allowed arguments appears as:

static char options[] = "u:d:t:cif";

The following piece of code simply defines the message that is printed if you enter incorrect options to the program.

static char *usgmsg[] = {
   "usage: ExportTemplate [options] res_file [basename]",
   " options are:",
   " -u<level>     = user level of interest",
   " -d<domain>    = domain of interest (default is 0 - all the domains",
   "                 are combined into a single domain)",
   " -t<timestep>  = timestep of interest (if set to -1, all timesteps",
   "                 are exported)"
   " -c            = use corrected boundary node data",
   " -i            = include boundary node only data",
   " -f            = get info on the res_file (No output is created)",
   " <basename> is the base filename for Template file output.",
   "If not specified, it defaults to ‘res_file’. The Template",
   "geometry file will be written to <basename>.geom, the",
   "results file to <basename>.res, and the variables to",
   "<basename>.s## or <basename>.v## where ## is the variable",
   "number and s indicates a scalar and v a vector.",
    NULL
};

3.1.1.3. Main Program Initialization

As is standard, the variables argc and argv are the number of arguments and a pointer to the argument list. The variable cfxCNT_SIZE and the types cfxNode and cfxElement are defined in the header file cfxExport.h as are all variables and functions starting with the letters cfx. For details, see Mesh and Results Export API. The variables level, zone, alias, bndfix and bnddat are used for setting the default values for the various parameters that can be set on the command line of the program.

void main (int argc, char *argv[])
{
   char *pptr;
   char baseFileName[256], fileName[256], errmsg[256];
   int i, n, counts[cfxCNT_SIZE], dim, length, namelen;
   int nnodes, nelems, nscalars, nvectors, nvalues;
   int level = 1, zone = 0, alias = 1, bndfix = 0, bnddat = 0;
   int timestep = -1, infoOnly = 0;
   int ts, t, t1, t2;
   int nTimeDig = 1;     /* number of digits in transient file suffix */
   char zoneExt[256];    /* zone extension added to the base filename */
   int isTimestep = 0;
   float timeVal = 0.0;  /* time value in the single timestep mode */
   char *wildcard = { "******" }; /* used in transient file specification */
   FILE *fp;
   cfxNode *nodes;
   cfxElement *elems;
   float *var;

The variable cfxCNT_SIZE and the types cfxNode and cfxElement are defined in the header file cfxExport.h as are all variables and functions starting with the letters cfx. For details, see Mesh and Results Export API. The variables level, zone, alias, bndfix and bnddat are used for setting the default values for the various parameters that can be set on the command line of the program.

The following line prints an error message if there are not enough arguments to proceed.

if (argc < 2)
   cfxUsage (usgmsg, NULL);

The following piece of code reads the specified options and assigns values to certain variables accordingly. If an invalid or incomplete option is specified, then getargs prints an error message and the export program stops.

while ((n = getargs (argc, argv, options)) > 0) {
   switch (n) {
      case ‘u’:
         level = atoi (argarg);
         break;
      case ‘d’:
         zone = atoi (argarg);
         break;
      case ‘t’:
         timestep = atoi (argarg);
         isTimestep = 1;
         break;
      case ‘c’:
         bndfix = 1;
         break;
      case ‘i’:
         bnddat = 1;
         break;
      case ‘f’:
         infoOnly = 1;
         break;
   }
}

After this, the level variable contains the user level specified. All results are output if they are of this user level or below it. The zone variable contains the domain number that you specified. The variable alias determines whether the variables are referred to by their long names or short names. The default here is for short names to be used because some post-processors need variable names to contain no spaces, but you are encouraged to use long variable names wherever possible. The variable bndfix determines whether the variables are exported with corrected boundary node values - if bndfix is set to 1, then corrected values are used. Finally, bnddat determines whether variables that contain meaningful values only on the boundary (such as Yplus) are exported or not; if bnddat is set to 1, then these variables are exported.

3.1.1.4. Checking File Names

The following code checks to make sure that a CFX results file has been specified, and that it can be read by the export program. If this is not the case, the export program exits.

/* CFX-5 results file */
   if (argind >= argc)
       cfxUsage (usgmsg, "CFX-5 results file not specified");
   if (access (argv[argind], 0)) {
      fprintf (stderr, "result file <%s> does not exist\n", argv[argind]);
      exit (1);
   }

The following code writes the basename specified to the character array baseFileName. If one was not specified, then it defaults to the name of the results file specified. A basename name may be specified in another directory (for example, "../template/output"). However, later in the code this basename without the preceding directory information is required (in this example "output"); and so the pointer pptr is assigned to point to the first character of this name.

/* base file name */
   if (argind + 1 < argc)
      strcpy (baseFileName, argv[argind+1]);
   else
      strcpy (baseFileName, argv[argind]);
if (NULL != (pptr = strrchr (baseFileName, ‘/’)))
      pptr++;
   else if (NULL != (pptr = strrchr (baseFileName, ‘\\’)))
      pptr++;
   else
      pptr = baseFileName;

The following code checks that the results file that will be produced by the export program will not overwrite an existing results file.

/* don’t overwrite results file */
sprintf (fileName, "%s.res", baseFileName);
if (0 == strcmp (argv[argind], fileName)) {
fprintf (stderr, "Template res file would overwrite CFX results file\n");
fprintf (stderr, "Need to select new Template output base file name\n");
exit (1);
   }

3.1.1.5. Opening the CFX Results File

The following code prints a message to the screen telling you that the program is reading the results file. It then calls cfxExportInit, which must always be called before any of the other export routines. The variable n is set to equal the number of zones in the results file. If the -f option has been selected, information about the results file will be displayed. The number of domains to be exported is also determined so that the format of the exported file includes the appropriate suffix. Finally, a check is made to make sure that the zone (if any) that you specified in the program options is a valid zone for this results file.

/* open CFX-5 results file */
printf ("\nreading CFX results from <%s>\n", argv[argind]);
   n = cfxExportInit (argv[argind], NULL);
   if (infoOnly) {
      int nt;
      printf("\n%d domains:\n", n);
      for(i = 1; i <= n; i++)
         printf("  %d   %s\n", i, cfxExportZoneName(i));
      nt = cfxExportTimestepCount();
      printf("%d timesteps:\n", nt);
      if(nt) {
         for(i = 1; i <= nt; i++)
            printf("  %d\n", cfxExportTimestepNumGet(i));
      }
      cfxExportDone();
      exit (0);
   }
   /* determine the zone suffix for the export files */
   strcpy(zoneExt, "");
   if(zone == 0) {
      printf ("processing all domains\n");
      cfxExportSetVarParams(bndfix, level);
   }
   else {
      printf ("processing domain %d\n", zone);
if(n != 1) {
         float f;
         int nZoneDig = 0;
/* count number of digits needed to fit any zone number */
         f = (float) n;
         while((f /= 10) >= 1) nZoneDig++;
         sprintf(zoneExt, "_d%*.*d", nZoneDig, nZoneDig, zone);
      }
   }
if (cfxExportZoneSet (zone, counts) < 0)
         cfxExportFatal ("invalid zone number");

The following code is ignoring any pyramid elements (elements with 5 nodes) and decreases nelems by the number of pyramid elements. It then checks to make sure that neither the number of nodes nor the number of elements is zero; if so, the program exits with return code -1.

The first two lines focus on the number of nodes in the zone and the number of elements in the zone.

nnodes = cfxExportNodeCount();
nelems = cfxExportElementCount();
if (counts[cfxCNT_PYR]) {
   printf ("%d pyramid elements found - they are being ignored\n",
      counts[cfxCNT_PYR]);
   nelems -= counts[cfxCNT_PYR];
}
if (!nnodes || !nelems)
   cfxExportFatal ("no nodes and/or elements");

3.1.1.6. Timestep Setup

The following code determines whether all of the timesteps, a specific timestep or the final timestep (steady-state) have been selected for export.

if(isTimestep && timestep == -1 && !cfxExportTimestepCount()) {
      isTimestep = 0;
   }
   if(isTimestep) {
      int i;
      float f;
      if(timestep == -1) {
         printf("processing all timesteps\n");
         t1 = 1;
         t2 = cfxExportTimestepCount() + 1;
      }
      else {
         int isFound = 0;
         printf("processing timestep %d\n", timestep);
         for(i = 1; i <= cfxExportTimestepCount() + 1; i++)
            if(cfxExportTimestepNumGet(i) == timestep) {
               timeVal = cfxExportTimestepTimeGet(i);
               t1 = t2 = i;
               isFound = 1;
               break;
            }
         if(!isFound) {
            sprintf(errmsg, "\nTimestep %d not found. "
               "Use -f to see the list of valid timesteps.\n", timestep);
            cfxExportFatal (errmsg);
         }
      }
      /* count number of digits needed to fit any timestep number */
      f = (float) cfxExportTimestepCount();
      while((f /= 10) >= 1) nTimeDig++;
   }
   else {
      timeVal = cfxExportTimestepTimeGet(cfxExportTimestepCount() + 1);
      timestep = cfxExportTimestepNumGet(cfxExportTimestepCount() + 1);
      t1 = t2 = cfxExportTimestepCount() + 1;
   }

3.1.1.7. Geometry File Output

The following code opens the geometry file basename.geom, printing an error if it cannot be opened for any reason. A message is then displayed informing you that the application is writing the geometry file.

/* Template geometry output */
   sprintf (fileName, "%s.geom", baseFileName);
   if (NULL == (fp = fopen (fileName, "w+"))) {
      sprintf (errmsg, "can’t open <%s> for output", fileName);
      cfxExportFatal (errmsg);
   }
   printf ("writing Template Geometry file to <%s>\n", fileName);

The header of this file is shown after the program listing.

/* write header */
   fprintf( fp, "Template Geometry file exported from CFX\n");
   fprintf( fp, " \n");
   fprintf( fp, "node id given\n");
   fprintf( fp, "element id off\n");

The following code writes first the word "coordinates" and the number of nodes that will be written. The pointer nodes is initialized to point at the data for the first node and the node data is written into the geometry file. For each node, a node number is written, followed by the three coordinates of that node. Note that n ranges between 0 and nnodes-1. This program adds 1 to each node number so that the nodes in the geometry file are numbered between 1 and nnodes. When it has finished, the cfxExportNodeFree routine frees the memory that was used to store the node data, and finally the word "done" is printed on the screen to alert you that it has finished writing the node data.

/* write nodes */
   fprintf( fp, "coordinates\n");
   fprintf( fp, "%8d\n", nnodes );
   nodes = cfxExportNodeList();
   printf ("  writing %d nodes ...", nnodes);
   fflush (stdout);
   for (n = 0; n < nnodes; n++, nodes++) {
fprintf( fp, "%8d %12.5e %12.5e %12.5e\n", n + 1, nodes->x,
   nodes->y, nodes->z );
}
   cfxExportNodeFree();
printf (" done\n");

Next, the data for each element must be written.

Firstly, some general information is written. Then the data for each element type is written in turn.

/* write elements */
   fprintf( fp, "part 1\n" );
   fprintf( fp, "volume elements\n");
   printf ("  writing %d elements...", nelems);
   fflush (stdout);

For tetrahedral elements, the word "tetra4" is written to the file, followed by the number of tetrahedral elements written.

/* tets */
   fprintf( fp, "tetra4\n");
   fprintf( fp, "%8d\n", counts[cfxCNT_TET] );

The following code is executed only if the number of tetrahedral elements is non-zero. Assuming this, elems is set to point to the list of elements stored in the results file. The index n loops over all the elements. For each element, the following step is carried out: "If the element is a tetrahedron, then loop over its four vertices and write their node numbers to the geometry file, then start a new line (ready for the next set of data)." The output produced can be seen in the examples of the exported files in the next section.

if (counts[cfxCNT_TET]) {
   elems = cfxExportElementList();
   for (n = 0; n < nelems; n++, elems++) {
      if (cfxELEM_TET == elems->type) {
         for (i = 0; i < elems->type; i++)
            fprintf (fp, "%8d", elems->nodeid[i]);
         putc (‘\n’, fp);
      }
   }
}

For wedges (triangular prisms) and hexahedral elements, the same procedure is followed. However, there is a slight difference in the way that the fprintf line is written for hexahedral elements. This is because the order that the element nodes are written to the geometry file is different to the order in which they were read from the results file. This may need to be done if a post-processor has a different convention for node order than the one that the cfx5export node routines have. The order the nodes are written in will affect which node is connected to which. The node ordering for exported elements is illustrated in cfxExportElementList.

/* wedges */
   fprintf( fp, "penta6\n");
   fprintf( fp, "%8d\n", counts[cfxCNT_WDG] );
   if (counts[cfxCNT_WDG]) {
      elems = cfxExportElementList();
      for (n = 0; n < nelems; n++, elems++) {
         if (cfxELEM_WDG == elems->type) {
            for (i = 0; i < elems->type; i++)
               fprintf (fp, "%8d", elems->nodeid[i]);
         }
         putc (‘\n’, fp);
      }
   }
/* hexes */
   fprintf( fp, "hexa8\n");
   fprintf( fp, "%8d\n", counts[cfxCNT_HEX] );
   if (counts[cfxCNT_HEX]) {
      elems = cfxExportElementList();
      for (n = 0; n < nelems; n++, elems++) {
         if (cfxELEM_HEX == elems->type)
            fprintf (fp, "%8d%8d%8d%8d%8d%8d%8d%8d\n",
               elems->nodeid[0], elems->nodeid[1],
               elems->nodeid[3], elems->nodeid[2],
               elems->nodeid[4], elems->nodeid[5],
               elems->nodeid[7], elems->nodeid[6]);
      }
   }

Then the geometry file is closed and the memory occupied by the element data is freed.

printf (" done\n");
   fclose (fp);
   cfxExportElementFree();

3.1.1.8. Template Results File

Despite its name, the Template results file does not contain any actual values of results. It simply contains information about how many variables there are and in which file each is stored.

The first job is to make sure that there are some results for export. First, the code checks that there is a nonzero number of variables that have the specified user level. Then it counts the number of scalar and vector variables that will be exported. To be exported, a variable must:

  1. Have a dimension of 1 (scalar variable) or 3 (vector variable) and,

  2. Either be a variable with useful values everywhere in the zone or be a variable that has values only on the boundaries (in which case it will be exported only if you asked to "include boundary node only data" by specifying the option -i when starting the export program, which translated to setting bnddat = 1 when the arguments were processed).

Review the cfxExportVariableSize routine if this logic is unclear. For details, see cfxExportVariableSize.

Once results are identified, the code calculates the variable namelen, which is the length of the longest variable name to be exported (the alias variable was set when processing the arguments passed to the export program, and depends upon whether you wanted to use long names or short names). If there are no vector or scalar variables to be exported, the export program exits.

/* output results file */
   nscalars = nvectors = namelen = 0;
   if ((nvalues = cfxExportVariableCount(level)) > 0) {
      for (n = 1; n <= nvalues; n++) {
         cfxExportVariableSize (n, &dim, &length, &i);
         if ((1 != dim && 3 != dim) ||
            (length != nnodes && length != bnddat))
            continue;
         if (1 == dim)
            nscalars++;
         else
            nvectors++;
         i = strlen (cfxExportVariableName (n, alias));
         if (namelen < i)
            namelen = i;
      }
   }
   if (0 == (nscalars + nvectors)) {
      cfxExportDone ();
      exit (0);
   }

The following code checks that the results file can be opened for writing to, and exits if not. The number of scalar and vector variables are written to the file, followed by some numbers (which EnSight, for example, requires) that are always the same for any export of this kind.

sprintf (fileName, "%s.res", baseFileName);
   if (NULL == (fp = fopen (fileName, "w+"))) {
      sprintf (errmsg, "can’t open <%s> for writing", fileName);
      cfxExportFatal (errmsg);
   }
   printf ("writing Template results file to <%s>\n", fileName);
   fflush (stdout);
   fprintf( fp, "%d %d 0\n", nscalars, nvectors );
   fprintf( fp, "%d\n", t2 - t1 + 1 );
   for(i = t1; i <= t2; i++) {
      fprintf( fp, "%13.4e", cfxExportTimestepTimeGet(i));
      if(!(i % 6)) fprintf( fp, "\n");
   }
   fprintf( fp, "\n");
   if(isTimestep && t1 != t2)
      fprintf( fp, "0 1\n");

Next, for each scalar variable, a line is written that contains the filename where the scalar will be written, and then the name of the variable. Note that the filename is not the basename, but the basename with all the directory structure (if any) stripped off the front. For details, see Checking File Names. This is done because these file will be written in the same directory as this Template results file, so there is no need for directory information.

if ( nscalars ) {
   for (n = 1; n <= nvalues; n++) {
      cfxExportVariableSize (n, &dim, &length, &i);
      if (1 == dim && (length == nnodes || length == bnddat))
         if(!isTimestep)
            fprintf (fp, "%s%s.s%2.2d %s\n", pptr, zoneExt,
               n, cfxExportVariableName(n, alias));
         else if(t1 == t2)
            fprintf (fp, "%s%s_t%d.s%2.2d %s\n", pptr, zoneExt,
               cfxExportTimestepNumGet(t1), n,
               cfxExportVariableName(n, alias));
         else
            fprintf (fp, "%s%s_t%*.*s.s%2.2d %s\n", pptr, zoneExt,
               nTimeDig, nTimeDig, wildcard, n,
               cfxExportVariableName(n, alias));
      }
   }

The same information is then written for each vector variable and the Template results file is closed.

if ( nvectors ) {
   for (n = 1; n <= nvalues; n++) {
      cfxExportVariableSize (n, &dim, &length, &i);
      if (3 == dim && (length == nnodes || length == bnddat))
         if(!isTimestep)
            fprintf (fp, "%s%s.v%2.2d %s\n", pptr, zoneExt,
               n, cfxExportVariableName(n, alias));
         else if(t1 == t2)
            fprintf (fp, "%s%s_t%d.v%2.2d %s\n", pptr, zoneExt,
               cfxExportTimestepNumGet(t1), n,
               cfxExportVariableName(n, alias));
         else
            fprintf (fp, "%s%s_t%*.*s.v%2.2d %s\n", pptr, zoneExt,
               nTimeDig, nTimeDig, wildcard, n,
               cfxExportVariableName(n, alias));
      }
   }
fclose( fp );

3.1.1.9. Creating Files with Results for Each Variable

The results for each variable are written to separate files, called <basename>.s01, <basename>.s02, <basename>.v03, for example. Each file with an extension containing a letter "s" contains a scalar variable, and each with a "v" contains a vector variable. Which variable is written to each file is tabulated in the Template results file that has just been written.

The following code reads the information for each variable, after you decide that it should be exported - the logic is very similar to that used when counting the relevant variables when creating the Template results file. The marked if loop executes if the variable needs to be exported. It checks to make sure that the variable information can be read, and (assuming it can) then builds the filename and checks to see if it can be opened. Continuing, it writes to the screen where it is putting the variable, and then loops through all the values, writing them to the file, inserting a new line every six values. After each variable, the memory used to store that variable is restored.

After all the variable files have been written, the program calls the cfxExportDone routine, which close the CFX results file, and frees up any remaining memory. This routine must be the last call to any of the API routines. The program then exits.


Note:  This program makes no use of any of the region routines, which enable access to boundary condition data, nor the volume routines that enable access to the subdomains that are defined for a problem.


  • Region Routines

  • Volume Routines

    /* output each timestep to a different file */
       for(t = t1; t <= t2; t++) {
          ts = cfxExportTimestepNumGet(t);
          if(cfxExportTimestepSet(ts) < 0) {
             continue;
          }
       /* build file name and open file */
       if(!isTimestep)
          sprintf( fileName, "%s%s.%c%2.2d", baseFileName, zoneExt,
             1 == dim ? ‘s’ : ‘v’, n);
          else if(t1 == t2)
             sprintf( fileName, "%s%s_t%d.%c%2.2d", baseFileName, zoneExt,
                ts, 1 == dim ? ‘s’ : ‘v’, n);
          else
             sprintf( fileName, "%s%s_t%*.*d.%c%2.2d", baseFileName, zoneExt,
                nTimeDig, nTimeDig, t-1, 1 == dim ? ‘s’ : ‘v’, n);
          if (NULL == (fp = fopen (fileName, "w+"))) {
             sprintf (errmsg, "can’t open <%s> for writing\n", fileName);
             cfxExportFatal (errmsg);
          }
          printf ("  %-*s -> %s ...", namelen,
             cfxExportVariableName(n, alias), fileName);
          fflush (stdout);
          fprintf( fp, "%s\n", cfxExportVariableName(n, alias));
          length = nnodes * dim;
          for ( i = 0; i < length; i++, var++ ) {
             fprintf( fp, "%12.5e ", *var );
             if ( i && 5 == (i % 6) )
                putc (‘\n’, fp);
          }
          if ( 0 != ( nvalues % 6 ) )
             putc( ‘\n’, fp );
          fclose( fp );
          cfxExportVariableFree (n);
          printf (" done\n");
          }
       }
       } /* loop for each timestep */
          cfxExportDone();
          exit (0);
    }