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.
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.
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 };
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.
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); }
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");
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; }
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();
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:
Have a dimension of 1 (scalar variable) or 3 (vector variable) and,
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 settingbnddat = 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 );
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.
/* 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); }