Surface and Mesh3d objects differ in that a Surface is really the two-dimensional boundary of a three-dimensional object (if it is closed) or the topological equivalent of a plane (if not closed). In other words, it is a two dimensional object which has been twisted, bent, or deformed through a third dimension. In contrast, a Mesh3d consists of a partition of a three-dimensional object into smaller three-dimensional objects called cells. With a Surface, only what is happening on the two dimensions of the Surface itself is of interest to us, whereas with a Mesh3d, what is happening inside the three dimensional object is of interest.^{1} This leads to a problem in visualization, because how can you see the inside of an object? The answer, usually, is that cells have to be stripped away to view what is going on underneath them. Alternatively, we can take sections through the Mesh3d, the most common of these being plane slices and isosurface slices. (Isosurfaces are slices upon which some specified function is equal to some constant.)

At any rate, a Mesh3d is a generalization of a Surface, and in fact a Mesh3d is a derived class of a Surface.

There are two kinds of Mesh3d objects:

- A structured Mesh3d consists of rectangular hexahedra with sides parallel to the axes, and is specified by three one dimensional vectors of coordinates, x, y, and z. Associated with each Mesh3d point is a component of a three-dimensional array of data called c.
- A nonstructured Mesh3d in principle could consist of cells of arbitrary shape, but we limit ourselves to the four standard shapes: hexahedra, tetrahedra, pyramids (with square bases) and prisms (with triangular bases). A nonstructured Mesh3d is specified by one-dimensional arrays of x, y, and z coordinates, the
*i*^{th}component of x, y, and z being the coordinates of the*i*^{th}node in the mesh. There is an associated one-dimensional array c of data, one value for each node point. Naturally the points alone are not sufficient to specify the connectivity of the mesh. Hence we need configuration information which, for each cell in the mesh, tells which nodes belong to the cell. The Mesh3d class accepts two formats, the Narcisse format peculiar to itself which we will not go into here (See "The Narcisse Format and Keywords" on page 65), and the AVS format. In the case of the AVS format, for each shape of cell in the Mesh3d, the user must supply a count of the cells, and a one-dimensional array of integer node numbers for each of the cells, in a standard order, as follows: - tetrahedra--apex, then base nodes, in inward normal order.
- pyramids--apex, then base nodes, in inward normal order.
- prisms--one triangular face, in outwards normal order, then the corresponding nodes of the opposite face, in inward normal order.
- hexahedra--one face, in outwards normal order, then the corresponding nodes of the opposite face, in inward normal order.
All Mesh3d objects are instantiated as described below; the keyword parameters are how PyGist distingushes what kind of Mesh3d it is.

- from mesh3d import *
- m3 = Mesh3d (
*<keylist>*)

- The list of keywords recognized by all types of Mesh3d objects are as follows:

- color_card, opt_3d, mesh_type, mask, z_c_switch, z_contours_scale, c_contours_scale, z_contours_array, c_contours_array, number_of_z_contours, number_of_c_contours
Since a Mesh3d is a Surface, it also accepts all the keywords that define a Surface object, ignoring any that might not be sensible (see Section "Keyword Arguments" on page 45).

In addition, the Mesh3d class has two methods set (inherited from Surface) and new (not inherited, but having exactly the same functionality).

The following keyword arguments can be specified for a Mesh3d object. Note that not all keywords are available in both PyGist and PyNarcisse. Generally, using an inapproriate keyword will not cause an error; it will be ignored or else the graphics engine will make a clever guess.

color_card = <value> specifies which color card (another name for palette) you wish to use, e. g., "rainbowhls" (the default), "random", etc. Although a characteristic of a Graph2d, it can be a Surface characteristic since 'link'ed surfaces can have different color cards (valid for Narcisse only). For a full description of available color cards, see "color_card = <value>" on page 46. The graphics interface is intelligent enough to make a good guess if you specify a Gist color card to Narcisse or vice versa; and if there is no near equivalent, it will simply assign the default color card.

opt_3d = <value> where <value> is a string or a sequence of strings giving the 3d or 4d surface characteristics. A surface is colored by height in z if a 3d option is specified, and by the value of the function c if a 4d option is specified. With a wire grid option, the grid is colored; with a flat option, the cells set off by grid lines are colored; with a smooth option, the surface itself is colored by height; and with an iso option, the contour lines are colored. Flat and iso options may be used together in any combination. Wire grid options are independent of the other options. Legal arguments for opt_3d are:

- 'wm'--monochrome wire grid (the default); 'w3' and 'w4'--3d and 4d coloring of wire grid. The latter two are not currently available in Gist.
- 'f3' and 'f4'--flat 3d and 4d coloring options.
- 'i3' and 'i4'--3d and 4d isoline (contour line) options. Colored isolines are currently not available in Gist.
- 's3' and 's4'--3d and 4d smooth coloring options (filled contours).
mesh_type = <string> in one of the wire modes, tells what form the wire grid takes: "x": x lines only; "y": y lines only; "xy": both x lines and y lines (the default). Gist currently supports only the default.

mask = <string>: specifies whether hidden lines will be eliminated, and if so, how complex the algorithm that will be used to determine what is hidden. Allowed values are "none" : see-through wire mesh (the default); "min": simple masking; "max" : better masking; "sort": slowest but most sophisticated. Gist currently supports only "none" and "sort"; spefifications of "min" and "max" are equivalent to "sort".

z_c_switch = 0 or 1: set to 1 means switch z and c in the plot.

z_contours_scale, c_contours_scale = "lin" or "log".

z_contours_array, c_contours_array = actual array of numbers to use for contours, if you don't want them computed automatically.

number_of_z_contours, number_of_c_contours = <integer> specifies how many contours to use; they will be computed automatically based on the data.

- from mesh3d import *
- m3 = Mesh3d (
*<keylist>*)Where

*<keylist>*contains keywords peculiar to regular meshes.

A structured Mesh3d consists of rectangular hexahedra with sides parallel to the axes, and is specified by three arrays of coordinates, x, y, and z. Associated with each Mesh3d point is a component of a three-dimensional array of data called c. Thus the keywords uniquely associated with structured (or regular) Mesh3ds are:

**x, y, z, c**

x = <values> , y = <values> , z = <values> To establish notation, assume that the mesh is k by l by m (i. e., there are k nodes in the x direction, l nodes in the y direction, and m nodes in the z direction.) Then there are three options for these keywords: (1) x, y, and z equally spaced: x is a vector consisting of the three integers k - 1, l - 1, m - 1 (the cell dimensions), y is a vector of three Floats giving dx, dy, dz (the increments in each direction), and z is an array of three Floats giving x0, y0, z0 (the starting values of x, y, and z); (2) x, y, and z not equally spaced: x, y, and z are one dimensional arrays of type Float specifying a k by l by m mesh (k = len (x), l = len (y), m = len (z); or (3) x, y, and z are each k by l by m, specifying a completely general hexahedral mesh.

c = <values> , a three-dimensional array dimensioned k by l by m, whose [i, j, k] element gives the associated data value at the (i, j, k)^{th}point of the mesh. c may also be one less in each direction, giving a cell-centered quantity. c may also be a list of such arrays, when isosurfaces of more than one function are to be plotted.

A pdb file named berts.py contains temperature data for filamentary flow in a plasma on a regular mesh. In this example we illustrate how to plot an isosurface slice trhrough the mesh, illustrating the filaments at a constant temperature. In order to get the isosurface slice, we use the function sslice, which is described later in the chapter (See 4.4 "Slice objects" on page 70). In this plot, we use the rainbow palette to shade the surface as if a light source were shining from behind and slightly to the right of the viewer. Polygons facing or nearly facing the viewer will be at the blue-violet end of the spectrum, and closer to the red end the closer they get to facing perpendicular to the line of sight. The code to produce this plot is as follows:

- f = PR ('./berts_plot')
- x = -80.0 + arange (64, typecode = Float) * 2.5
- y = -80.0 + arange (64, typecode = Float) * 2.5
- z = arange (50, typecode = Float) * 10.
- c = f.c
- m3 = Mesh3d (x = x, y = y, z = z, c = transpose (c))
- s3 = sslice (m3, 6.5, opt_3d = ["none"])
- g3 = Graph3d (s3, color_card = "rainbow.gp", gnomon = 1,
- xyequal = 1, diffuse = .2, specular = 1)
- g3.plot ()
Note the use of opt_3d = "none". Isosurfaces are shaded, so we use none of the usual 3d options. The plot looks a bit chaotic; it is possible that the PyGist sorting algorithm was a bit puzzled by the complexity of this plot. At any rate, if the user is interested in getting a closer look at this plot (or any PyGraph plot), place the cursor within the window and click the left mouse button a couple of times. Doing so causes the graph to zoom in, and you get something like the following:

You can zoom back out by clicking the third mouse button. To shift the plot around the window, click and drag with the middle button.

- from mesh3d import *
- m3 = Mesh3d (
*<keylist>*) - Where
*<keylist>*contains keywords peculiar to irregular meshes.

A nonstructured Mesh3d in principle could consist of cells of arbitrary shape, but we limit ourselves to the four standard shapes: hexahedra, tetrahedra, pyramids (with square bases) and prisms (with triangular bases). A nonstructured Mesh3d is specified by one-dimensional arrays of x, y, and z coordinates of the same length, the i^{th} component of x, y, and z being the coordinates of the i^{th} node in the mesh. There is an associated one-dimensional array c of data, one value for each node point. Naturally the points alone are not sufficient to specify the connectivity of the mesh. Hence we need configuration information which, for each cell in the mesh, tells which nodes belong to the cell. The Mesh3d class accepts two formats, the Narcisse format peculiar to itself which we will not go into here (See "The Narcisse Format and Keywords" on page 65), and the AVS format. In the case of the AVS format, for each shape of cell in the Mesh3d, the user must supply a count of the cells, and a one-dimensional array of integer node numbers for each of the cells, in a standard order, as follows:

- tetrahedra--apex, then base nodes, in inward normal order
- pyramids--apex, then base nodes, in inward normal order
- prisms--one triangular face, in outwards normal order, then the corresponding nodes of the opposite face, in inward normal order
- hexahedra--one face, in outwards normal order, then the corresponding nodes of the opposite face, in inward normal order

- x, y, z, c
- avs = 1, hex, tet, prism, pyr
The following keywords apply if the mesh is given in Narcisse internal format:

- avs = 0, no_cells, cell_descr

The following explains the keyword arguments in detail:

x = <values> , y = <values> , z = <values>: three vectors of equal lengths giving the coordinates of the nodes of a nonstructured Mesh3d.

c = <values> : a vector of the same size as x, y, and z giving a data value at each of the node points. c could also be an array of such vectors, when isosurfaces of more than one function are to be plotted. c is also allowed to be one smaller than x, y, and z in each dimension, for cell-centered values.

avs = 0 or 1: if 1, the input data represents a nonstructured Mesh3d in a sort of AVS format, which will be explained in more detail below. The data will be translated into the Narcisse format prior to being sent to Narcisse.

cell_descr = <integer array>: if present, this keyword signifies a nonstructured Mesh3d submitted in the Narcisse format, also explained in more detail below. avs must be zero (or absent) if this keyword is present.

If avs = 1, then one or more of the following keywords must also be present; these are used to specify the types of cells present, and their node coordinates, in a standard order. These keywords are:

hex = [ <list of hexahedral cell data> ] The two entries in the list (in order) must be: (1) an integer number n_zones, which is the number of hex cells in the Mesh3d; and (2) a matrix nz whose dimensions are n_zones by 8; nz [i][0], nz [i][1], ... nz [i][7] give the indices of the 8 vertices of the i^{th}zone in canonical order (one side in the outward normal direction, then the corresponding vertices of the opposite side in the inward normal direction).

tet = [ <list of tetrahedral cell data> ] The list is the same format as for hex data. The matrix nz will now be n_zones by 4, and each row gives the indices of the apex and then the base in inward normal order.

prism = [ <list of prismatic cell data> ] The list is the same format as for hex data. The matrix nz will now be n_zones by 6, and each row gives the indices of one of the triangular sides in the outward normal direction, then the corresponding vertices of the opposite side in the inward normal direction.

pyr = [ <list of pyramidal cell data> ] The list is the same format as for hex data. The matrix nz will now be n_zones by 5, and each row gives the indices of the apex and then the base in inward normal order.

Warning--your numbering of cells must be consistent: all cells of a particular type must be listed together; the actual ordering of the four cell types, however, is irrelevant.

The special Narcisse keywords, which apply when avs = 0, and their descriptions are:

no_cells = <integer value>, the total number of two-dimensional cells in the Mesh3d. (Here ``cells'' really refers to faces of 3d cells).

cell_descr = <integer array> , a vector of integers giving the description of the cells of the Mesh3d, as follows:

cell_descr [0], call_descr [1], ... , cell_descr [no_cells - 1] tell how many vertices cell [0], cell [1] , ... , cell [no_cells - 1] have.

cell_descr [no_cells] through cell_descr [no_cells + cell_descr [0] - 1] are the subscripts of the vertex coordinates of cell [0]; cell_descr [no_cells + cell_descr [0]] through cell_descr [no_cells + cell_descr [0] + cell_descr [1] - 1] are the subscripts of the vertex coordinates of cell [1], etc. Cell vertices must be given in the outward normal order.

In general, PyGist does not support graphing an entire mesh. Instead, PyGist includes support for graphing plane cross-sections and isosurface slices of meshes, which we will discuss later in the chapter.

The following example first reads data from a pdb file called bills_plot^{2}. The object partitioned by the mesh is an imploding sphere, and the intent is to graph the z component of the velocity of implosion. Although the mesh is unstructured, it has only hexahedral cells, and the data is already in an order accepted by PyNarcisse, so need not be rearranged. When PyNarcisse is given an entire mesh to plot, it will plot every face of every cell from front to back; if the mask is other than "none", then the front faces will cover the back ones. If one plots the entire sphere, all that will remain visible at the end is the portion of the exterior of the sphere facing the observer. Therefore in this example we strip away the ``front'' half of the sphere so that we can observe a cross section.

- f = PR ('./bills_plot')
- n_nodes = f.NumNodes
- n_z = f.NodesOnZones
- x = f.XNodeCoords
- y = f.YNodeCoords
- z = f.ZNodeCoords
- c = f.ZNodeVelocity
- n_zones = f.NumZones
- # Now we're going to plot it with all cells missing which
- # have an x coordinate greater than 0.005.
- n_zones_used = 0
- zones_not_used = [] # subscripts of zones to ignore
- zones_used = []
- for i in range (n_zones) :
- nz = n_z [i]
- used = 1
- for j in range (8) :
- if x [nz [j]] > 0.005 :
- zones_not_used.append (i)
- used = 0
- break
- if used == 1 :
- zones_used.append (i)
- n_zones_used = n_zones_used + 1
- new_n_z = zeros ( (n_zones_used, 8), Int32)
- for i in range (n_zones_used) :
- new_n_z [i] = n_z [zones_used [i]]
- m1 = Mesh3d (x = x, y = y, z = z, c = c, avs = 1,
- hex = [n_zones_used, new_n_z],
- mask = "max", opt_3d = "s4")
- # Uncomment below when we take the front face away
- g2 = Graph3d (m1,
- titles = ["Vertical component of velocity",
- "Imploding Sphere"])
- g2.plot ( )

In the next example, we read in imploding sphere data from file "ball.s0001", which is represented by an unstructured mesh containing all four kinds of cells. The data is not supplied in AVS order, so it is necessary to convert it into AVS format. We then do a number of plots of the data.

- f = PR ("ball.s0001")
- ZLss = f.ZLstruct_shapesize
- ZLsc = f.ZLstruct_shapecnt
- ZLsn = f.ZLstruct_nodelist
- x = f.sap_mesh_coord0
- y = f.sap_mesh_coord1
- z = f.sap_mesh_coord2
- c = f.W_vel_data
- # Now we need to convert this information to avs-style data
- istart = 0 # beginning index into ZLstruct_nodelist
- NodeError = "NodeError"
- ntet = 0
- nhex = 0
- npyr = 0
- nprism = 0
- nz_tet = []
- nz_hex = []
- nz_pyr = []
- nz_prism = []
- for i in range (4) :
- if ZLss [i] == 4 : # TETRAHEDRON
- # put node coords into 4 by no_tet_nodes array
- nz_tet = reshape (ZLsn [istart: istart + ZLss [i] *
- ZLsc [i]], (ZLsc [i], ZLss [i]))
- ntet = ZLsc [i]
- istart = istart + ZLss [i] * ZLsc [i]
- elif ZLss[i] == 5 : # PYRAMID
- # put node coords into 5 by no_pyr_nodes array
- nz_pyr = reshape (ZLsn [istart: istart + ZLss [i] *
- ZLsc [i]], (ZLsc [i], ZLss [i]))
- npyr = ZLsc [i]
- # Now reorder the points (data has the apex last
- # instead of first)
- for ip in range (npyr) :
- tmp = nz_pyr [ip, 4]
- for jp in range (4) :
- nz_pyr [ip, 4 - jp] = nz_pyr [ip, 3 - jp]
- nz_pyr [ip, 0] = tmp
- istart = istart + ZLss [i] * ZLsc [i]
- elif ZLss[i] == 6 : # PRISM
- # put node coords into 6 by no_prism_nodes array
- nz_prism = reshape (ZLsn [istart: istart + ZLss [i] *
- ZLsc [i]], (ZLsc [i], ZLss [i]))
- nprism = ZLsc [i]
- # now reorder the points (data has a square face first)
- for ip in range (nprism) :
- tmp = nz_prism [ip, 1]
- tmpp = nz_prism [ip, 2]
- nz_prism [ip, 1] = nz_prism [ip, 4]
- nz_prism [ip, 2] = nz_prism [ip, 3]
- nz_prism [ip, 3] = tmp
- nz_prism [ip, 4] = nz_prism [ip, 5]
- nz_prism [ip, 5] = tmpp
- istart = istart + ZLss [i] * ZLsc [i]
- elif ZLss[i] == 8 : # HEXAHEDRON
- # put node coords into 8 by no_hex_nodes array
- nz_hex = reshape (ZLsn [istart: istart + ZLss [i] *
- ZLsc [i]], (ZLsc [i], ZLss [i]))
- # hex points are in proper avs order
- nhex = ZLsc [i]
- istart = istart + ZLss [i] * ZLsc [i]
- else :
- raise NodeError, \QZLss[i]\Q + "is an incorrect number of nodes."
- # Create entire mesh, then create one mesh for each cell type
- m1 = Mesh3d (x = x, y = y, z = z, c = c, avs = 1,
- hex = [nhex, nz_hex] ,
- pyr = [npyr, nz_pyr] ,
- tet = [ntet, nz_tet] ,
- prism = [nprism, nz_prism] , mask = "max",
- opt_3d = ["s4","wm"])
- m2 = Mesh3d (x = x, y = y, z = z, c = c, avs = 1,
- hex = [nhex, nz_hex] , mask = "max",
- opt_3d = ["s4","wm"])
- m3 = Mesh3d (x = x, y = y, z = z, c = c, avs = 1,
- pyr = [npyr, nz_pyr] , mask = "max",
- opt_3d = ["s4","wm"])
- m4 = Mesh3d (x = x, y = y, z = z, c = c, avs = 1,
- tet = [ntet, nz_tet] , mask = "max",
- opt_3d = ["s4","wm"])
- m5 = Mesh3d (x = x, y = y, z = z, c = c, avs = 1,
- prism = [nprism, nz_prism] , mask = "max",
- opt_3d = ["s4","wm"])
- # Now we graph the cells of each type, and then draw the
- # whole sphere. N. B. "paws" is a function which halts
- # until user enters a carriage return.
- g1 = Graph3d (m5)
- g1.plot () # draw prisms
- paws ()
- g1 = Graph3d (m4)
- g1.plot () # draw tetrahedra
- paws ()
- g1 = Graph3d (m3)
- g1.plot () # draw pyramids
- paws ()
- g1 = Graph3d (m2)
- g1.plot () # draw hexahedra
- paws ()
- g1 = Graph3d (m1)
- g1.plot () # draw the entire mesh

[Top] [Prev] [Next] [Bottom]

Normally there is a function defined on the mesh--e. g., a physical quantity such as pressure, density, or velocity--that we want to visualize. These function values really add a fourth dimension to the plot.

bills_plot and other files mentioned in this chapter are available on kristen in /home/cs/motteler/wrk/EB.KEEP.

support@icf.llnl.gov