Parallel Computing Sciences, Dept. 9226
Sandia National Laboratories
1. CUBIT Geometry Module (CGM) *
Timothy J. Tautges *
Parallel Computing Sciences, Dept. 9226 Sandia National Laboratories *
A Introduction *
1 Geometry Model *
1.1 Topology *
1.2 Non-manifold modeling *
1.3 Groups *
1.4 Attributes *
2 User's Guide *
2.1 Introduction *
2.2 Module components *
2.2.1 Tools (API Interface) *
2.2.2 Topology (Direct Interface) *
2.2.3 Geometry (Direct Interface) *
2.3 Tools Component *
2.3.1 CGM Initialization and Termination *
2.3.2 GeometryTool *
2.3.2.1 Geometry import/export *
2.3.2.2 Primary point of access to geometry *
2.3.2.3 Geometry Primitives *
2.3.2.4 Geometry Creation *
2.3.2.5 Transformations *
2.3.2.6 Booleans *
2.3.2.7 Geometry Decomposition Functions *
2.3.3 MergeTool *
2.3.4 FeatureTool *
2.3.5 VirtualGeometryEngine *
2.4 Geometry Component *
2.4.1 Geometric representation: TopologyBridge *
2.4.2 Alternative geometric representations *
2.5 Topology Component *
2.5.1 Non-manifold modeling: BridgeManager *
2.6 Building Applications *
2.6.1 Overall directory structure *
2.6.2 Compiling applications *
2.7 Implementation Notes *
2.7.1 Singleton tools *
2.7.2 Creation/modification through GeometryTool *
2.7.3 Geometric and Topological Queries *
2.7.4 Geometry Attributes *
2.8 C++ Driver Application *
2.8.1 Forward declarations and main *
2.8.2 Reading geometry files *
2.8.3 Evaluating overlaps using pairwise intersections *
2.8.4 Imprinting bodies *
2.8.5 Merging and printing results *
2.8.6 Program output *
2.8.7 Summary *
3 Application Developer's Guide *
3.1 Introduction *
3.2 RefEntityFactory: Topological Entity Construction *
3.3 Application-Specific Attributes *
3.4 ToolData: Application-Specific Data *
3.5 CubitObserver: Application-Specific Observation of Entities *
3.6 Example: CUBIT Implementation Using CGM *
3.6.1 Derived topological entities *
3.6.2 MRefEntity class *
3.6.3 Topology traversal functions *
3.6.4 MRefEntityFactory *
3.6.5 AttribFactory for CUBIT-specific attributes *
3.6.6 DrawingTool *
3.6.7 The Model class *
3.6.8 Adding mesh data to MRefGroup *
4 CUBIT Developer's Guide *
4.1 MRefEntity Class Structure *
4.2 Library Initialization *
4.3 Access To Geometric Model *
4.3.1 Global lists *
4.3.2 Topological queries *
4.4 MRefEntity Construction/Destruction *
4.4.1 Construction *
4.4.2 Destruction *
4.4.3 Construction/Destruction and Multiple Inheritance *
4.5 Observer Notification *
4.5.1 Static observers *
4.5.2 Construction *
4.5.3 Destruction *
4.6 Attributes *
4.6.1 Entity Name *
4.6.1.1 Entity names are different because they are written back on solid model entities after being read (for persistence after booleans) *
4.6.1.2 Can't read and actuate entity name attributes twice in a row; second time through, read will remove attribute from solid model entity, actuate will fail because names are identical, and name attribute won't be written back onto solid model entity *
4.6.1.3 Can actuate twice, though; second actuation will fail, but attributes will still persist on solid model entity *
5 CGM, CUBIT Class Diagrams *
6 Calling CGM From C *
6.1 Introduction *
6.2 CGM C++ Object Handling Conventions *
6.3 Simple C Driver Code *
2.
The CUBIT Geometry
Module (CGM) is a code library which provides geometry functionality used for
mesh generation and other applications. This functionality includes that
commonly found in solid modeling engines, like geometry creation, query and
modification; CGM also includes capabilities not commonly found in solid modeling
engines, like geometry decomposition tools and support for shared material
interfaces. CGM is built upon the ACIS solid modeling engine, but also includes
geometry capability developed beside and on top of ACIS. CGM can be used as-is
to provide geometry functionality for codes needing this capability. However,
CGM can also be extended using derived classes in C++, allowing the geometric
model to serve as the basis for other applications, for example mesh
generation. CGM is supported on Sun Solaris, SGI and HP (Unix) platforms. Plans
also include porting CGM to the ASCI parallel platforms in support of parallel
finite element analysis codes.
This document is
organized as follows. Section 2 describes the geometric model used in CGM,
commonly referred to as a BREP model. Section 3 is a User's Guide, describing
how CGM can be used as-is to support geometry query functions. Section 4, the
CGM Developer's Guide, describes the design of CGM in more detail, and how CGM
capabilities can be extended using derived classes in C++. Section 5 gives
details of CGM usage that are specific to the CUBIT Mesh Generation Toolkit.
Appendix A is a reference to the most important API functions found in CGM.
This document is
intended to serve as both an introduction to CGM as well as a reference. For
those wanting a quick introduction to using CGM in its various modes, see
Sections 3.8, 3.9 and 4.7; these sections describe driver applications
(including CUBIT) and do not require reading of this manual to understand the
basic concepts.
The basic geometry
representation used in CGM is that of a Boundary Representation, or BREP. While
this is a commonly used representation, the nomenclature used to describe it
can vary greatly. Therefore, this section first introduces the basic elements
of topology, and more importantly the terminology used to describe those
elements in the rest of this document. In addition to topology, there are other
important elements of the geometric model used in CGM that are important to
understand; these elements are introduced in sections 2.2-2.4.
The basic
topological entities used in CGM, along with their dimensions, are shown in
Table 1. Note that in the case of Edges and Faces, the most common nomenclature
can be confusing in a finite element context, since there are also
"edge" and "face" element types. In cases where the type of
edge or face is unclear from the context, the terms "geometric" and
"mesh" are used to qualify the term as pertaining to geometry or
mesh, respectively.
Table 1
"Basic" topological entities in the CGM geometric model.
|
Basic Entity |
Dimension |
|
Vertex |
0 |
|
Edge |
1 |
|
Face |
2 |
|
Volume |
3 |
In addition to the
basic topological entities, there are a number of other geometric entities
which can be used to describe the geometric model. These entities are typically
not necessary for the complete geometric description of a BREP model, but they
are useful constructs nonetheless. The "full" set of geometric
entities used in CGM (including the basic entities listed earlier) is described
in Table 2.
Table 2 Other
geometric entities used in CGM geometric model.
|
|
Entity |
Description |
|
Grouping Entities |
Body |
One or more volumes, share a transform and participate together in geometry transforms and booleans. |
|
Shell |
Group of CoFaces describing a closed or open shell; if closed, describes a boundary of a volume. Can be inner or outer boundary. |
|
|
Loop |
Group of CoEdges describing a boundary of a face. Can be inner or outer boundary. |
|
|
Sense Entities |
CoFace |
Entity describing the orientation of a face as used in a shell, with respect to the face's normal. |
|
|
CoEdge |
Entity describing the orientation of an edge as used in a loop, with respect to the edge's tangent direction. |
In a typical
application, a geometric model consists of one or more volumes, each volume
consisting of two or more faces, with each face having one or more edges and
vertices. The entities in a BREP model are related to each other in a geometric
hierarchy, which can be represented using a graph. In general, an entity of
dimension d is bounded by one or more entities of dimension d-1, 0 < d <
3.
There are several
exceptions to the typical geometric model described above that are allowed in
CGM models. First, CGM (and ACIS) allows curves to be bounded by a single
vertex; these are called periodic curves, in reference to the periodic nature
of the curve's parameter space. Likewise, periodic surfaces are also allowed;
these are surfaces whose parameter space contains jump discontinuities, for
example a cylindrical surface. Periodic entities can complicate applications
like mesh generation; therefore, CGM can split these entities into non-periodic
entities.
In rare cases,
geometry exists where an entity of dimension d is not strictly bounded by an
entity of dimension d-1. The best example of this is a cone, where the apex is
represented topologically by a vertex. Although there is no apparent curve
corresponding to this vertex, CGM models this using a zero-length curve.
Applications not desiring this behavior can request that the periodic entities
in a body be split into non-periodic ones.
Geometry can be
envisioned whereby a single basic topological entity bounds a higher order
entity twice; and example would be a torus with a split face on one end. While
this geometry can be represented using judicious application of CoFaces, it is
not typically found in real-world geometries. Cases involving surfaces included
twice by a single shell are not supported in CGM; however, edges can occur
twice in a given loop.
Non-manifold
geometry is a general term describing allowable combinations and arrangements
of the basic topological entities into a solid of arbitrary dimension. In the
context of CGM, the term "non-manifold geometry" is used to indicate
the presence of entities of dimension d-1 shared by more than one entity of
dimension d. In practice, "non-manifold" is used to describe groups
of volumes which share surfaces in between them.
CGM represents shared
edges and surfaces by actually representing a single edge or surface, along
with sense entities (CoEdge and CoFace, respectively) to describe the
orientation of the entity with respect to the higher-dimension entity. For
example, an edge can be part of two adjacent surfaces. The edge has a
"natural" orientation, such that vertex V1 is the "start"
of the edge and V2 is the "end". Then, the two surfaces sharing that
edge use the edge with opposite "sense"; surface S1 uses the edge
with "forward" sense, that is with a sense corresponding to the
edge's natural direction, while surface S2 uses the edge with
"reverse" sense. Likewise, in the case where two volumes share a
common surface, one volume uses the surface with a forward sense while the other
uses the surface with a reverse sense. Note that multiple surfaces can share a
given edge in a non-manifold model. However, in typical geometries, at most two
volumes will share a given surface, each with an opposite sense. If the senses
are the same, overlapping geometry is usually indicated.
The grouping
entities listed in Table 2 are specific to the kinds of geometry they contain;
for example, bodies consist only of one or more volumes. In contrast, geometry
groups are used in CGM to store one or more geometry entities of arbitrary
type. The entities stored in a given group typically have no implied relation
to each other, and are grouped only for convenience, either for the user or for
a specific application. For example, users may want to create a group of all
entities falling on one side of a coordinate plane. Groups are used extensively
in the CUBIT application of CGM, but have relatively little use inside CGM
itself.
Attributes are
defined as information which can be associated with a particular geometric
entity, but which are not intrinsic to the representation of that entity. For
example, the name of a geometric entity, while not required to represent an
entity, is associated directly to that entity. CGM provides functions for
storing application-specific attributes directly on geometry entities, and
functions for managing that data when geometry is stored to disk.
CGM encapsulates
most of the geometry functionality required by the CUBIT Mesh Generation
Toolkit; this includes interfaces to the ACIS solid modeling engine, but also
incorporates non-manifold geometry, virtual geometry and other functions not
found in ACIS or other solid modeling engines. CGM provides all the functions
necessary to restore a geometry to the state used to generate a CUBIT mesh; if
geometry attributes are used to store the geometry after meshing in CUBIT, the
geometric model can be restored in its final state with a few simple API calls.
CGM can also be used to construct geometry from primitives or from fields of
points, and evaluate this geometry for application-specific needs.
This User's Guide
describes the use of CGM as-is to restore or create, and evaluate geometry. For
information on extending the functionality of CGM, for example to store mesh
directly on geometry, see the Developer's Guide in the following section. After
describing the primary components in CGM and some implementation details, this
section concludes with two example driver applications. Users looking for a
quick-start guide to CGM should refer to these applications.
There are multiple
components in CGM, distinguished by the functionality they implement and the
interface used to access that functionality. The three components are tools,
topology and geometry; these components are described in more detail in this
section.
The primary
interface to CGM is through its tools, and in particular through the GeometryTool
class. GeometryTool is implemented as a singleton class (see section 3.7),
which implies that the GeometryTool functions can be viewed as API functions
rather than functions associated with a geometry entity. GeometryTool
implements functions for reading and writing geometry files, creating geometry
from primitives, and most other general geometric functionality. GeometryTool
is also the point of access for global lists of geometric entities. Other tools
in CGM, most of them also implemented as singleton classes, perform functions
in specific areas, for example MergeTool, which changes manifold to
non-manifold geometry, and FeatureTool, for performing automatic decomposition.
2. Topology (Direct Interface)
There are various
topology traversal functions that are accessible from any of the CGM
topological entities; these functions give all the topological entities of a
given type connected to an entity, e.g. all the vertices in a volume, or all
the bodies containing an edge. Most topology traversal functions are called as
member functions of an entity object.
3. Geometry (Direct Interface)
Functions which are
used to evaluate the geometric representation of an entity are called as member
functions of that entity. There are member functions for an edge which move a
given coordinate position to the closest point on that edge, or which return
the length of the edge.
The primary access
point to geometric capability in CGM is through GeometryTool. In addition, CGM
provides MergeTool, for changing manifold to non-manifold geometry, and
VirtualGeometryEngine, which performs virtual geometry operations. These tools
are described in more detail in this section.
1. CGM Initialization and Termination
CGM is initialized
by calling the function CubitApp::instance(). This function initializes a few
static datastructure necessary for the operation of CGM. CGM is terminated by
calling CubitApp::delete_instance().
GeometryTool is
implemented as singleton class, therefore most functions are called using a
singleton instance, for example:
CubitStatus result =
GeometryTool::instance()->import_file(file_name);
GeometryTool acts
as a common interface point for functions implemented elsewhere (this reduces
the number of classes providing external interfaces to CGM). These functions
can be grouped by functionality, as follows.
The most common
method for defining geometry with CGM is to import it from an ACIS .sat file. These
files can be used to save geometry directly from CUBIT, and can also be written
directly from several CAD tools including SolidWorks. There is also a Pro/E to
ACIS translator available within Sandia National Labs. The functions used to
import and export geometry are listed in Table 3. Only the required arguments
are listed; each of the functions defines several optional arguments, which can
be used for example to define a log file to which any informational messages
are written.
Table 3 GeometryTool
functions for importing and exporting geometry from/to a disk file.
|
Function |
Purpose |
|
GeometryTool::import_solid_model |
Import a solid model file of a specified type (ACIS_SAT, ACIS_SAB, etc.) |
|
GeometryTool::export_solid_model |
Export the entities in ref_entity_list to a file of a specified type. |
2.
Primary point of access to geometry
After geometry is
imported into CGM, it is maintained in global lists accessible through
GeometryTool. Basic topological entities are identified primarily using integer
ids, but can also be identified using names (which are either defined by the
application or can be assigned to default values). Id numbers are unique within
a given entity type, while names are unique across all entities (this behavior
can be modified, but this is discouraged).
In addition to
retrieving individual entities, there are functions in GeometryTool which give
access to the global entity lists. There are functions which directly access
and step through lists, and functions which pass back copies of the global
lists for use in the application code. The functions used to access geometry
are summarized in Table 4.
Table 4: GeometryTool
functions for accessing global entity lists.
|
Function |
Description |
|
GeometryTool::cubit_entity_list |
Append entities of the specified type to a DLCubitEntityList |
|
GeometryTool::ref_entity_list |
Append entities of the specified type to a DLRefEntityList |
|
GeometryTool::bodies GeometryTool::ref_xxx (xxx = volumes, faces, edges, vertices, groups, parts, assemblies, coordsys) |
Append all bodies in the model to a
DLBodyList Similar for ref_xxx |
|
GeometryTool::get_ref_entity |
Return an entity of a given type and id |
|
GeometryTool::get_body GeometryTool::get_ref_xxx (xxx = volumes, faces, edges, vertices, groups, parts, assemblies, coordsys) |
Return a body of a given id Similar for get_ref_xxx |
|
GeometryTool::num_bodies GeometryTool::num_xxx (xxx = volumes, faces, edges, vertices, groups, parts, assemblies, coordsys) |
Return the number of bodies in the global
list Similar for num_ref_xxx |
|
GeometryTool::get_first_body GeometryTool::get_first_ref_xxx (xxx = volumes,
faces, edges, vertices, groups, parts, assemblies, coordsys) GeometryTool::get_next_body GeometryTool::get_next_ref_xxx GeometryTool::get_last_body GeometryTool::get_last_ref_xxx |
Return the first body in the global list, positioning the list at the beginning; similar for get_first_ref_xxx. For get_next_xxx, step the list and return that item. For get_last_xxx, get the last item in the list and position the list at the end. |
Geometry is often
created using a well-known primitive, e.g. bricks, cylinders and spheres. The
geometry primitive functions provided in GeometryTool are listed in Table 5
(see the Appendix for complete descriptions).
Table 5 Geometry
primitives and functions in GeometryTool.
|
Function |
Description |
|
GeometryTool::brick |
Brick with dimensions specified in x, y and z. |
|
GeometryTool::cylinder |
Cylinder of specified height and radius; can create elliptic cylinder or conic using minor radius and top radius, respectively. |
|
GeometryTool::sphere |
Sphere with specified radius. |
|
GeometryTool::prism |
N-sided prism with specified height (N >= 3). |
|
GeometryTool::torus |
Torus with major and minor radii specified. |
|
GeometryTool::pyramid |
Same as prism. |
In addition to
primitives, geometry can be created by creating vertices, then edges, then
faces, etc. These functions are summarized in Table 6.
Table 6 GeometryTool
functions for geometry creation.
|
Function |
Description |
|
GeometryTool::make_RefVertex |
Make a vertex from a point. |
|
GeometryTool::make_RefEdge |
Make an edge from two vertices, optionally on a face, along a list of segments, as an ellipse/parabola/logarithmic spiral |
|
GeometryTool::make_RefFace |
Make a face from a list of bounding edges, as a plane/sphere/cone/spline/best fit surface. |
|
GeometryTool::make_RefVolume |
Make a volume from a list of bounding faces. |
|
GeometryTool::make_Body |
Make a body from a list of volumes. |
In addition, bodies
can be created by sweeping 2-dimensional faces into the third dimension. Since
this is a common solid modeling operation, CGM provides several functions of
this type; these functions are summarized in Table 7. Complete syntax and
descriptions of these functions are located in the Appendix.
Table 7 GeometryTool functions which sweep a face into a
3-dimensional solid.
|
Function |
Description |
|
GeometryTool::sweep_translational |
Sweep a face into a volume by translation. |
|
GeometryTool::sweep_rotational |
Sweep a face into a volume by rotation. |
|
GeometryTool::sweep_along_curve |
Sweep a face into a volume by following a specified curve. |
Transformation
functions typically take a body and transform its geometry, for example by
scaling, rotation, or reflection. Since they involve local changes to the body
geometry, transformation functions are called primarily using the Body
pointers. The exception to this rule is the reflect function, which is called
using GeometryTool and which yields a new Body. Transformation functions
available on Body and GeometryTool are listed in Table 8.
Table 8 Transformation
functions in the Body and GeometryTool classes.
|
Function |
Description |
|
GeometryTool::reflect |
Reflect a body across a given plane. |
|
Body::move |
Move a body by a given dx, dy and dz. |
|
Body::scale |
Scale a body by a given factor. |
|
Body::rotate |
Rotate a body about a given axis, vector or curve. |
|
Body::restore |
Restore a body to the state before the previous transformation. |
Booleans are set
operations on two or more bodies, which yield two, one or no bodies as a
result, depending on the type of and success of the operation. Boolean
functions, as implemented in GeometryTool, also return a status value, which
should be checked after the functions are called. Boolean functions provided by
GeometryTool are summarized in Table 9. This list is not exhaustive, but
contains the most commonly used boolean functions; a complete list can be found
in the Appendix.
Unite, subtract and
intersect are similar to the corresponding set operations, and require no
further explanation. Imprint operations, as the name implies, imprints topology
from one body onto adjacent bodies; by definition, the imprinted topology is
not geometrically significant, i.e. it does not change the volume of a body.
Imprinting is done to ensure that adjacent surfaces share identical topology,
which enables them to be merged together. Regularize is the opposite of
imprint; this function removes topology from a body which is not geometrically
significant.
Table 9 Boolean
functions in GeometryTool.
|
Function |
Description |
|
GeometryTool::unite |
Unite the given body or bodies into a single body. |
|
GeometryTool::subtract |
Subtract one or more bodies from a given body. |
|
GeometryTool::intersect |
Intersect one or more bodies from a given body, or intersect pairwise. |
|
GeometryTool::imprint |
Imprint two or more bodies together. |
|
GeometryTool::regularize_body |
Regularize one or more bodies (opposite of imprint). |
7.
Geometry Decomposition Functions
Geometry
decomposition is an important part of a hexahedral meshing application, and
must be supported well by a geometry module. CGM provides many functions for
decomposing geometry. Typically, a cutting surface is specified using an
existing surface, a surface extending from an existing surface, or a
semi-infinite surface like a coordinate plane or cylindrical surface; or,
sometimes an entire body is used as a cutting tool. The cutting tool or surface
along with the body or bodies to be cut are passed to a specific webcut
function, which returns the results of the cutting operation. GeometryTool
functions supporting geometry decomposition are summarized in Table 10.
Table 10: Functions
supporting geometry decomposition.
|
Function |
Description |
|
GeometryTool::webcut_with_xxx (xxx = plane, sheet, vertices, cylinder, surface, extended_surface, body) |
Decompose one or more bodies with a given geometry entity or implicitly-defined geometric entity. Keeps all bodies resulting from decomposition. |
|
GeometryTool::section |
Decompose one or more bodies with a planar surface or coordinate axis. Keeps bodies on one side of the surface only. |
|
GeometryTool::split_body |
Changes multi-volume body into several single-volume bodies. |
|
GeometryTool::split_periodic |
Splits periodic surfaces on one or more bodies into multiple surfaces. |
Non-manifold
geometry, in the context of CGM, simply means geometry containing vertices,
edges and faces which can be shared by more than one volume or by multiple free
faces or edges. Solid models are created and imported as manifold models; a
merge operation must be performed on a model to convert it to a non-manifold
representation. The merge operation simply searches for geometry entities of
like topology and geometry (within a specified tolerance), and merges any it
finds. Merging is an important part of applications built on CGM, and care
should be taken to merge models requiring contiguous regions before interacting
with those models.
Typically, geometry
coming straight from a CAD system will require some cleaning up before it will
merge correctly (i.e. before the only surfaces remaining in a multi-material
model are surfaces on the "outside" of the part). Geometry can be
cleaned up by importing it into CUBIT or another application and performing the
necessary geometry manipulations. At a minimum, all bodies should be imprinted
on one another to guarantee that entities which are coincident in space also
have like topology. While this is most often done in another application before
saving the final geometry, it might also be necessary to imprint the bodies in
the CGM application.
Functions which
control merging are summarized in Table 11.
Table 11: MergeTool
functions which control merging of manifold into non-manifold geometry.
|
Function |
Description |
|
MergeTool::merge_all_bodies MergeTool::merge_all_refxxx (xxx = vertices, edges, faces, volumes) |
Performs merge check on all entities of the specified type. |
|
MergeTool::merge_bodies MergeTool::merge_entities MergeTool::merge_refxxx (xxx = vertices, edges, faces, volumes) |
Perform merge check on entities passed in as arguments. |
|
MergeTool::contains_merged_entities |
Returns CUBIT_TRUE if one or more bodies passed in contains merged child entities. |
|
MergeTool::entity_merged |
Returns CUBIT_TRUE if the entity passed in is merged. |
|
MergeTool::merge_has_occurred |
Returns CUBIT_TRUE if a merge has occurred anywhere in the model. |
Section 3.3.1
described functions in GeometryTool which perform geometry decomposition; these
functions all require a specification of the cutting surface or tool, along
with the specific bodies to be cut. FeatureTool provides functions for
automatically decomposing a body into multiple bodies, with the general goal of
removing concavities in the model. FeatureTool is a research tool, and should
be used with caution. Functions provided by FeatureTool are summarized in .
When a solid model
is imported or created based on the ACIS geometry engine, each entity in the
ACIS model has a corresponding entity in the CUBIT model. For example, each
FACE in the ACIS model will have a corresponding RefFace in CUBIT. Sometimes,
it is desirable to change the CUBIT model without modifying the underlying ACIS
model; VirtualGeometryEngine provides this capability.
VirtualGeometryEngine
provides functions for combining or splitting edges, faces and volumes. After
these operations, the CUBIT model reflects the new geometry, while the ACIS
model remains unchanged. This capability can be used, for example, to combine
many very small surfaces into a larger topological surface.
Table 12 summarizes
the functions for creating, removing, and performing other interactions with
virtual geometry.
Table 12:
VirtualGeometryEngine functions for creating, removing and otherwise
interacting with virtual geometry.
|
Function |
Description |
|
PartitionTool::partition |
Partition the given entity using explicit or implicit geometry (implicit geometry is geometry whose geometric data are specfied by the user; explicit geometry is a geometry entity already extant in the model) |
|
PartitionTool::unpartition |
Remove the partition on one or more entities; removes only one partition. |
|
PartitionTool::unpartitionAll |
Remove all partitions on one or more entities, passing back the list of restored entities. |
|
CompositeTool::composite |
Composite the given list of entities into a single entity. |
|
CompositeTool::uncomposite |
Remove composite, passing back list of restored entities. |
|
CompositeTool::isComposite |
Returns CUBIT_TRUE if the entity passed in is a composite entity. |
|
CompositeTool::okay_to_composite |
Returns CUBIT_TRUE if the entities passed in can be composited together. |
1. Geometric representation: TopologyBridge
Before any merging
operations, the geometric topology in CGM corresponds exactly to that of the
solid model. In the CGM datastructure, each solid model entity has a
corresponding TopologyBridge object, which is unique to that solid model
entity. Each RefEntity points to a single TopologyBridge (through a
BridgeManager object, the purpose of which is explained later). Thus, before
merging, there is a one-to-one correspondence between each RefEntity and its
corresponding solid model entity.
The TopologyBridge
object for a RefEntity is really the geometric representation of that entity.
All geometric queries to a RefEntity (e.g. the length of an edge, or the
distance between a point and the closest point on a face) are passed directly
to the TopologyBridge object, which forwards the request to the solid model
entity. In this way, the solid modeling engine is used for geometric queries,
rather than duplicating any data and code necessary to implement those queries
in CGM. The public interface to geometric queries is implemented in the
RefEntity objects instead of through TopologyBridge in order to reduce the
number of objects applications have to keep track of.
2. Alternative geometric representations
CGM has been
designed to allow alternative representations of geometry; indeed, this
capability is being developed actively in the virtual geometry component.
Alternative geometric representations can be used simply by providing code
underneath TopologyBridge that provides the evaluation functions required by
the corresponding RefEntity. See the VirtualCurve class for an example of such
an alternate representation. This design also simplifies the process of
substituting another solid modeling engine for ACIS; a PROE-based
implementation of CGM is being developed which utilizes these design features,
and other implementations are being considered.
Geometric models
consist of topological entities like vertices, edges and faces, related to one
another through a topology graph. Each entity of dimension d is bounded by one
or more entities of dimension d-1, and, most of the time, bounds one or more
entities of dimension d+1. One of the most common operations while querying
geometry is to traverse this topology graph, finding entities of dimension m
related to one or more entities of dimension n. CGM provides many functions for
topology traversal.
Most topology
traversal functions are called as member functions from the entity from which
the traversal starts; the functions are implemented in TopologyEntity. One
exception to this is the get_related_entity functions, which take as input a
list of entities and return a list of related entities of a specified type. In
all cases, if a function is called to return all entities of the same type as
the entity from which the function was called, that entity is returned in the
list.
The topology
traversal functions are summarized in Table 13.
Table 13: Topology
traversal functions implemented in TopologyEntity.
|
Function |
Description |
|
TopologyEntity::bodies TopologyEntity::ref_xxx (xxx=volumes,
faces, edges, vertices) TopologyEntity::shells TopologyEntity::loops TopologyEntity::co_faces TopologyEntity::co_edges |
Return list of bodies related to the entity
from which the function was called. Similar for ref_xxx functions. Similar for shells, loops, co_faces, co_edges functions. |
|
TopologyEntity::get_related_entities |
Given a list of entities as input and a
target EntityType, return a list of related entities of that type. Implemented as a static function. |
For example, the
following call is used to find all the vertices connected to volume pointer
vol1 and store them in list vertex_list:
CubitStatus return_status =
vol1->ref_vertices(vertex_list);
Sometimes, it is
desirable to gather the topological entities of the next higher or lower
dimension, without determining what the specific type of those entities is. For
example, for most of the meshing algorithms in CUBIT, before meshing an entity
of dimension d, all the bounding entities of dimension d-1 must already be
meshed. Topology traversal functions which return parent and child entities
without requiring the parent or child entity type as input are used for this
purpose and are defined in RefEntity.
The notion of a
parent or child entity of dimension d+1 or d-1 only makes sense for our basic
topology types vertex, edge, face, volume and body. Since we do not require a
target entity type as input, these traversal functions either give immediate
parent or child entities, or they give a list of all parent or child entities.
The topology
traversal functions defined in RefEntity are summarized in Table 14. Like the
traversal functions in TopologyEntity, these functions are called as member
functions from a RefEntity object.
Table 14: Parent and
child topology traversal functions defined in RefEntity.
|
Function |
Description |
|
RefEntity::get_child_ref_entities |
Return a list of all immediate children of the RefEntity. |
|
RefEntity::get_parent_ref_entities |
Return a list of all immediate parents of the RefEntity. |
|
RefEntity::get_all_child_ref_entities RefEntity::get_all_parent_ref_entities |
Return a list of all children, traversing down to dimension d=0 (i.e. vertices); similar for parents, but traversing up to dimension d=4 (i.e. bodies). |
|
RefEntity::get_child_ref_entity_type RefEntity::get_parent_ref_entity_type |
Return the EntityType of the the immediate child RefEntity's. Similar for get_parent_entity_type. |
|
RefEntity::is_child RefEntity::is_parent |
Return CUBIT_TRUE if the input entity is a child/parent (immediate or not) of the RefEntity |
There are
additional topology traversal functions defined in BasicTopologyEntity,
SenseEntity and GroupingEntity; these functions generally provide traversals to
the next higher or lower type of TopologyEntity, and are not as commonly used
as the traversal functions in TopologyEntity and RefEntity. Descriptions of
these functions are located in the automatic documentation for those classes.
1. Non-manifold modeling: BridgeManager
CGM accomplishes
non-manifold modeling not by merging actual solid model entities, but by having
a single RefEntity correspond to multiple solid model entities. Merging takes
entities with like topology and geometry (within a geometric tolerance), and
combines them into a single entity. The changes to the datastructure resulting
from this operation can be described with a simple example, merging two edges
together. First, the two RefEdges are compared, and the one with the lowest id
is designated the "keeper"; the other RefEdge is designated the
"dead" entity. The TopologyBridge object for the dead entity is added
to the list of TopologyBridge objects on the keeper's BridgeManager. Then, the
topology graph is changed such that all parents of the dead entity point to the
keeper entity instead. The sense entity immediately above is changed to
incorporate the correct sense (this process is explained in more detail below).
Finally, the dead entity, along with its TopologyBridge object, are deleted. We
are left with the keeper RefEdge and its BridgeManager object, which maintains
a list of two TopologyBridge objects. The process for merging more than two
entities together is similar, but now there are more than two TopologyBridge
objects in list in BridgeManager.
There are a few
subtle things to note about the datastructure for non-manifold topology. First,
we assume that lower order topology has already been merged (this is checked in
MergeTool and accomplished during the merging process); after fixing the
topology graph for the parents of the dead entity, the topology graph can be
traversed as before. After merging takes place, from the CGM model point of
view, there is a single shared entity. Any data assigned the shared entity,
either by CGM (e.g. names, ids) or in an application (e.g. mesh), applies to
both the underlying solid model objects.
The sense entities
immediately above the objects being merged may or may not be changed, depending
on the type of entity and it's actual geometry. Consider first merging two
edges together (see ). Edge 1 is part of loop 1, which uses the edge with a "forward"
sense, and similarly for edge 2 in loop 2. When edges 1 and 2 are merged
together, edge 2 goes away and loop 2 now uses edge 1. Note though that loop 2
now uses edge 1 with a "reverse" sense; this is stored in the
datastructure by modifying coedge 2, which maintains the connection between the
loop and the edge. Consider next merging three edges together (see ). Loops 2
and 3 now use edge 1; however, only coedge 2 must have its sense changed; the
sense of coedge 3 is unchanged.
Merging faces is
very similar. However, it is rare that two faces are merged together and the
corresponding cofaces both maintain the same sense; this would correspond to
two volumes occupying the same physical space, which is usally not done.
Therefore, MergeTool prints a warning when merging two faces like this.
The code which
checks and changes coedge and coface senses if necessary is located in
MergeTool.
The previous
sections in this Developer's Guide describe in more detail the design and use
of the geometry datastructure in CGM. In this section, the steps necessary to
build applications which use CGM are described.
1. Overall directory structure
CGM and its
components are arranged in a directory structure designed to minimize
unnecessary dependencies between components. This directory structure is
depicted in . In order to compile applications using CGM, all three
directories, as well as any subdirectories, must be available at compile time.
The purpose of the code in each subdirectory is described in Table 15.
Table 15: CGM
directory structure and purpose of code in each subdirectory.
|
Directory |
Purpose |
Depends on: |
|
$(SOURCE_DIR)/util |
Utility functions for other code in CGM |
(none) |
|
$(SOURCE_DIR)/util/OtherFiles |
Configuration files for compiling CGM on various platforms |
(none) |
|
$(SOURCE_DIR)/list |
Classes for lists, queues, and other common datastructures |
util |
|
$(SOURCE_DIR)/geom |
Core CGM datastructure and tool classes. |
util, list |
|
$(SOURCE_DIR)/geom/ACIS |
CGM interface to ACIS. |
geom, util, list, ACIS package |
|
$(SOURCE_DIR)/geom/PROE |
CGM interface to PROE |
geom, util, list |
|
$(SOURCE_DIR)/geom/virtual |
Virtual geometry component (datastructure and tools). |
geom, util, list |
A sample
application is given in the following section; the makefile associated with
this driver application should be used as a reference for developing new
applications based on CGM. Parts of this makefile can be inserted into the
makefile of existing applications that want to begin using CGM. This section
gives some additional notes on compiling applications based on CGM.
As noted in Table
15, some of the code in CGM depends on the ACIS solid modeling engine. This is
the only external code required to link applications based on CGM.
Unfortunately, the design of the ACIS toolkit does not allow applications to
link in small portions of ACIS; if you use part of ACIS, you get it all,
whether you want it or not. For this reason, applications linked with ACIS tend
to be quite large, for example over 50MB with ACIS version 5 on a Sun Solaris
workstation.
Included in this
section are notes about the general implementation of CGM that should be known
to applications developers.
Most tools accessed
through the public interface of CGM are implemented as singleton classes. This
well-known design pattern is used for implementing functionality that is of a
global nature, without requiring the functions themselves to be global.
Singleton classes typically have constructors that are private, which prevent
the creation of new singleton objects by classes other than the singleton class
itself. Singleton class objects are accessed using an instance function:
MyTool *tool = MyTool::instance();
This function is
static, so it does not require an object of type MyTool before it is called.
Inside the instance function, a singleton object of type MyTool is created if
it does not already exist, and a pointer to this tool is maintained in the
class. This pointer is then returned to the calling application.
Singleton classes
are also used to accomplish extensibility of CGM. CGM classes and tools call
singleton instances to accomplish things like create geometry and geometry
attributes. Applications can substitute their own implementations of these
singleton tools, which in effect allows the application-specific code to be
called from CGM without CGM being dependent on the applications. This requires
some careful initialization of CGM from applications which extend its
capabilities. This process is described in more detail in the following
chapter.
2. Creation/modification through GeometryTool
As stated earlier,
all geometry creation and modification is accomplished by calling functions in
GeometryTool. Although very few of these functions are actually implemented in
GeometryTool, this class provides a focal point for calling geometry functions,
reducing the size of the CGM interface applications developers need to
understand before using the package. In general, developers should look for
needed functionality first in GeometryTool.
3. Geometric and Topological Queries
Certain functions
are clearly object-oriented functions which are associated directly with geometry
objects. Examples include functions which return the length of an edge, the
vertices connected to a volume, or the closest point to a given surface.
Object-oriented functionality like this is usually implemented in the RefEntity
leaf classes (RefVertex, RefEdge, etc.), or in their parent classes
(BasicTopologyEntity or RefEntity). These implementations either pass the
request on to another class for actual implementation, or the functions are
defined and implemented in one of the parent classes. In general, functions
implemented on RefEntity objects are query-only; functions that modify geometry
are typically implemented in tools like GeometryTool or MergeTool.
Functions which
implement geometry attribute capabilities can be called directly from RefEntity
objects; these functions are actually implemented in CubitAttribUser, a parent
class of RefEntity. By default, only entity name attributes are saved and
restored automatically. To enable automatic storing and retrieval of geometry
attributes, set a parameter using the function call:
CubitAttrib::auto_flag(CUBIT_TRUE);
This function sets
options for all the attribute types such that they are written and read
automatically.
The geometry
attributes capability can be used to store application-specific information on
geometric entities. The mechanism used to implement application-specific
attributes is described in the next chapter.
In this section, a
simple CGM driver application is described. This application, called mergechk,
imports one or more geometry files, computes any overlaps between the bodies,
imprints the bodies, then merges geometry into non-manifold geometry. Although
very straightforward, this application demonstrates how to import geometry,
perform boolean operations on it, then further query the geometry. The source
code for this application is shown in Table 16 - Table 20. Some declarations
and comments in the driver code have been removed for brevity; the complete
mergechk application is distributed with the CGM libraries.
Each of the code
sections in mergechk is described below.
1. Forward declarations and main
The source code for
forward declarations and the main function are shown in Table 16. This file
begins with the inclusion of files containing declarations of functions and
datastructures. In most CGM applications, GeometryTool.hpp will be included,
since this is the primary point of access to CGM. Following the forward
declarations, two memory management files are included; these files contain declarations
of static class members in the memory management classes in CGM, and should be
included before the definition of the main function. In the main function, the
first few lines of code initialize CGM and GeometryTool, the primary point of
access to CGM. Even in cases where the GeometryTool object is not used
immediately, the instance() function should be called at the beginning of the
application, since the GeometryTool constructor initializes many static
datastructures used by CGM.
After CGM initialization,
the application calls several functions which import the geometry file(s),
evaluate any intersections, then perform imprint and merge operations on the
bodies. Results are then printed out for the user.
In this
application, since it exits immediately after these geometry calculations,
there is no need to explicitly shut down CGM.
Table 16: Pseudo code
for C++ driver code, forward declarations and main.
|
// include tool
and datastructure declarations #include
"GeometryTool.hpp" #include
"MergeTool.hpp" (…) // forward
declare some functions used and defined later (…) // initialize
some static objects (should be done first) #include
"AllocMemManagersList.cpp" #include
"AllocMemManagersGeom.cpp" /// main
program - initialize, then send to proper function int main (int
argc, char **argv) { // Initialize
the application CubitApp *app =
CubitApp::instance(); // Initialize
the GeometryTool GeometryTool
*gti = GeometryTool::instance(); // Read in the
geometry from files specified on the command line CubitStatus
status = read_geometry(argc, argv); // Check for
overlaps status =
evaluate_overlaps(); // Imprint
bodies together, reporting on results status =
imprint_bodies(); // Merge bodies status =
MergeTool::instance()->merge_all_bodies(); // Print number
and ids of non-shared surfaces status =
print_unmerged_surfaces(); } |
In the
read_geometry() function, shown in Table 17, each file is opened, then the file
pointer is passed to GeometryTool::import_solid_model function. This function
reads all the geometry entities defined in that file and stores them in the CGM
database. These entities are accessed through other functions in GeometryTool.
The return value
from import_solid_model() should be checked for indications of problems reading
the geometry file.
Table 17: Pseudo code
for C++ driver; read_geometry() function.
|
CubitStatus
read_geometry(int num_files, char **argv) { GeometryTool
*gti = GeometryTool::instance(); // For each
file, open and read the geometry for (i = 1; i
< num_files; i++) { file_ptr =
fopen(argv[i], "r"); if (file_ptr ==
NULL) PRINT_ERROR("Could not open file %s\n", argv[i]); else { status =
gti->import_solid_model(file_ptr, argv[i], "ACIS_SAT"); } } return
CUBIT_SUCCESS; } |
3. Evaluating overlaps using pairwise intersections
To find overlaps,
each body is intersected with all the other bodies in the model; this is
implemented in the evaluate_overlaps() function, shown in Table 18. A copy of
the body list is retrieved from CGM using the GeometryTool::bodies() function.
Intersections are checked by removing a body from the list and intersecting it
with all the bodies remaining on the list. An option is passed to the intersect
function instructing CGM to keep the old bodies being intersected, since these
bodies must be checked for merging later in the application. Because DLList is
implemented based on an array of pointers, it is most efficient to remove items
from the end of the list; therefore evaluate_overlaps() works from the end of
the body list backward.
If there are no
overlaps, there should be no geometry entities resulting from the overlap
calculation. If there are entities remaining, these are reported to the user.
These entities are reported by topology type, with the topology-type filtering
done using a list cast operation.
GeometryTool::delete_Body()
is called at the end of evaluate_overlaps() to delete any bodies produced
during the intersection operation.
Table 18: Pseudo code
for C++ driver; evaluate_overlaps() function.
|
CubitStatus
evaluate_overlaps() { // evaluate
overlaps by intersecting bodies pairwise GeometryTool
*gti = GeometryTool::instance(); // make a copy
of the body list for use in this function DLBodyList
all_bodies, all_new_bodies; gti->bodies(all_bodies); // step
backward on this list, extracting the last body and using it // as a tool
for remaining bodies for (i =
all_bodies.size(); i > 0; i--) { all_bodies.last(); Body *tool =
all_bodies.remove(); // intersect
the tool with remaining bodies; make sure and keep old // bodies,
since we're not using copies; save new bodies for evaluation // later DLBodyList
new_bodies; CubitStatus
status = gti->intersect(tool, all_bodies, new_bodies, CUBIT_TRUE); all_new_bodies
+= new_bodies; } // count number
of geometric entities in new bodies; if there are no // overlaps,
this number should be zero DLRefEntityList
child_entities, temp_children; // first get
all child entities of the new bodies for (i =
all_new_bodies.size(); i > 0; i--) { all_new_bodies.get_and_step()->get_all_child_ref_entities(temp_children); child_entities
+= temp_children; temp_children.clean_out(); } // then filter
the list, keeping only unique entities temp_children.clean_out(); temp_children.merge_unique(child_entities); // now report if
(temp_children.size() != 0) { // check
vertices //
temp_entities is cleaned out in the CAST_LIST macro, so no need // to do that
here DLRefEntityList
temp_entities; CAST_LIST(temp_children,
temp_entities, RefVertex); if
(temp_entities.size() > 0) PRINT_INFO("
Vertices: %d\n", temp_entities.size()); … // now delete
all the bodies produced by the intersections DLBodyList
new_bodies; CAST_LIST(temp_children,
new_bodies, Body); for (i =
new_bodies.size(); i > 0; i--) gti->delete_Body(new_bodies.get_and_step()); } // we're done return
CUBIT_SUCCESS; } |
Bodies are
imprinted in mergechk in the imprint_bodies() function, shown in Table 19.
Geometry entities can merge together only if they have like topology and
geometry. Typically, models coming from CAD packages like Pro/Engineer or
SolidWorks do not have topology imprints on neighboring bodies. Imprinting is
accomplished using the GeometryTool::imprint() function. Since imprint will
always change the number of topological entities in a model, numbers of
entities before and after the imprint can be compared to determine whether any
imprints were made.
Table 19: Pseudo code
for C++ driver; imprint_bodies() function.
|
CubitStatus
imprint_bodies() { // imprint all
the bodies together, and report number of new // entities
formed GeometryTool
*gti = GeometryTool::instance(); // first, count
old entities int
num_vertices = gti->num_ref_vertices(); int num_edges =
gti->num_ref_edges(); int num_faces =
gti->num_ref_faces(); int num_volumes
= gti->num_ref_volumes(); int num_bodies
= gti->num_bodies(); // imprint the
bodies together, discarding old bodies DLBodyList
old_bodies, new_bodies; gti->bodies(old_bodies); gti->imprint(old_bodies,
new_bodies); // now count
new numbers of entities, subtracting old numbers num_vertices =
gti->num_ref_vertices() - num_vertices; num_edges =
gti->num_ref_edges() - num_edges; num_faces =
gti->num_ref_faces() - num_faces; num_volumes =
gti->num_ref_volumes() - num_volumes; num_bodies =
gti->num_bodies() - num_bodies; // report
results if
(!num_vertices && !num_edges && !num_faces &&
!num_volumes && !num_bodies) PRINT_INFO("Imprinting
resulted in no new entities.\n"); else { PRINT_INFO("Imprinting
resulted in the following numbers of new entities:\n"); if
(num_vertices) PRINT_INFO(" %d vertices.\n"); if (num_edges)
PRINT_INFO(" %d edges.\n"); if (num_faces)
PRINT_INFO(" %d faces.\n"); if
(num_volumes) PRINT_INFO(" %d volumes.\n"); if (num_bodies)
PRINT_INFO(" %d bodies.\n"); } // ok, we're
done return
CUBIT_SUCCESS; } |
5. Merging and printing results
In the main
function, after the call to imprint_bodies() (see Table 16), all bodies in the
model are checked for potential merges by calling
MergeTool::merge_all_bodies(). This function uses a pre-defined tolerance while
checking for coincident geometry; this tolerance can be changed using other
functions in MergeTool.
After merging, the
unmerged surfaces can be determined by counting the number of parent volumes.
The print_unmerged_surfaces() demonstrates stepping through the global list of
surfaces using the GeometryTool::get_first_refface() and
GeometryTool::get_next_refface() functions (see Table 20). The
CubitUtil::list_entity_ids() function takes a list of CubitEntity objects and prints
the id numbers of those objects in a "pretty" format.
Table 20: Pseudo code
for C++ driver; print_unmerged_surfaces() function.
|
CubitStatus
print_unmerged_surfaces() { // Print number
and ids of non-shared surfaces // Non-shared
surfaces are one with < 2 volumes connected to them, // or less than
two parent entities RefFace *face =
GeometryTool::instance()->get_first_ref_face(); DLRefEntityList
parents; DLRefFaceList
unmerged_faces; for (i = 0; i
< GeometryTool::instance()->num_ref_faces(); i++) { // get the
parent volumes parents.clean_out(); face->get_parent_ref_entities(parents); if
(parents.size() < 2) unmerged_faces.append(face); face =
GeometryTool::instance()->get_next_ref_face(); } // now print
information on unmerged surfaces // first cast
faces to a cubit entity list; use cast_list_to_parent, // since it's
much more efficient DLCubitEntityList
temp_entities; CAST_LIST_TO_PARENT(unmerged_faces,
temp_entities); PRINT_INFO("There
were %d unmerged surfaces; their ids are:\n", unmerged_faces.size()); CubitUtil::list_entity_ids("\0",
temp_entities); // now we're
done return
CUBIT_SUCCESS; } |
The output of the
driver program applied to a simple file is shown in Table 21. By default, the
output from the calls to PRINT_INFO and PRINT_WARNING go to standard out;
output from PRINT_ERROR goes to standard error. The definitions of these print
macros are located in CubitMessage.hpp, and can be changed in cases where
printed information is not desired. Alternatively, there are functions in
CubitMessage which can be used to turn off printed output.
Table 21: Output of
mergechk driver application.
|
|
The driver
application described in this section shows how CGM can be used to import,
modify and query solid geometry. Initialization and termination of the package
are simplified through simple API calls to singleton classes. A large variety
of functions can be accessed through this interface, without needing to know
the details of how those functions are implemented.
This same driver
program has also been implemented in C, to demonstrate the use of C-language
API functions written for CGM. The C-language version of mergechk is of
comparable complexity to the C++-language version. Using the C interface,
programs written in C or FORTRAN can also access and use CGM for solid geometry
evaluation.
5. Application Developer's Guide
There are two types
of applications of CGM; the first type is where an application relies entirely
on CGM for its geometry functionality, and does not require addition of data to
CGM geometry objects or extension of their functional capabilities. Another use
of CGM, and one which is much more powerful, is to use CGM geometry objects as
foundation classes, upon which additional functionality is implemented. For
example, classes can be derived from the CGM basic geometry entity types
(RefVertex, RefEdge, etc.) which not only provide geometry functionality, but
which also (for example) provide mesh-related functions and datastructure.
CGM has been
carefully designed to support applications of the latter type. In fact, the
CUBIT application is built on CGM by deriving CUBIT-specific geometry classes,
which enrich the CGM geometry classes with functions for generating and storing
mesh. Since class derivation is used to extend the CGM classes, these extended
classes must be created and used in place of the unenriched CGM classes, even
if that creation is taking place from within CGM. This is accomplished using
Factory classes and other advanced designed techniques; these techniques are
described in this section.
This section begins
by describing the CGM geometry entities which can be enriched using
application-specific subclasses. The Factory class used to create geometry
entities is then described, along with how this class can be extended to create
application-specific geometry entities. Section describes geometry attributes,
which are used to associate application-specific data to geometry entities.
Section describes the ToolData mechanism, which can be used to associate
application-specific data to many types of data including geometry entities.
Section describes the CubitObserver mechanism, which is used to notify
application-specific objects of changes to geometry entities and other
observable objects. This chapter concludes with specific information on how
CUBIT uses these mechanisms to implement meshing functionality on top of the
CGM classes.
2. RefEntityFactory: Topological Entity Construction
As described
earlier, the children of the RefEntity class are RefVertex, RefEdge, RefFace,
RefVolume, Body, RefGroup, RefPart, RefAssembly, and RefCoordSys. These
entities are most useful for implementing applications using CGM, and are
therefore the entities from which child classes can be derived. In the future,
derivation of classes from other entities, like Loops and CoEdges, may be
allowed.
Also described
earlier was the relationship between entities in the solid model file and the
corresponding CGM entities; for example, each FACE in an ACIS model has a
corresponding RefFace in the CGM model. When geometry is imported or created,
the CGM entities are constructed for each entity in the solid model; this
construction is implemented in CGM code, in particular in GeometryTool.
However, if an application derives child classes from the CGM entities, the
application-specific objects must be constructed instead. Constructors for the
application-specific child classes cannot be called from the CGM code, since
this would make CGM dependent on those applications. Therefore, the
construction of RefEntity's in the geometric model is implemented in a
singleton factory; CGM is designed to allow this factory to be replaced with an
application-specific factory, which constructs application-specific derived
class objects and returns them as CGM objects.
Consider first the
CGM factory, implemented in the RefEntityFactory class. Following the singleton
pattern, there is only a single RefEntityFactory object created for the
application; this object is created the first time RefEntityFactory::instance()
is called. Afterwards, this function simply returns a pointer to the singleton
factory instance (a static pointer to the instance is maintained in
RefEntityFactory). CGM requires that all RefEntity's be constructed using
RefEntityFactory (this is accomplished by making the constructors of RefEntity
leaf classes inaccessible from other classes). RefEnityFactory declares virtual
functions for constructing geometric entities, e.g.
RefEntityFactory::construct_RefFace, RefEntityFactory::construct_RefVolume,
etc.
To substitute an
application-specific factory for RefEntityFactory, an application simply derives
that factory from RefEntityFactory, and constructs that application-specific
factory before RefEntityFactory::instance() is called (this is typically done
just before initializing GeometryTool). A pointer to the application-specific
factory is still stored in RefEntityFactory, and returned from
RefEntityFactory::instance(). However, since RefEntityFactory::construct_xxx
functions were declared virtual, the application-specific factory can
substitute alternative implementations for these functions; these
implementations simply construct application-specific entities, passing them
back as the parent class pointer.
For example, say an
application wants to extend the functionality of vertices only. The application
would write a derived entity factory, ARefEntityFactory:
Class ARefEntityFactory : public
RefEntityFactory
{
public:
ARefEntityFactory *instance()
{
if (instance_ == NULL) instance_
= new ARefEntityFactory;
return instance_;
}
Virtual RefVertex
*construct_RefVertex(…);
}
Any CGM code
calling RefEntityFactory::construct_RefVertex would actually call the function
in ARefEntityFactory, thanks to the virtual function mechanism in C++. This
way, an application-specific vertex is created instead of an unextended vertex
object. Note that, since the extended vertex is derived from RefVertex, all
functions defined for RefVertex and its parent classes can be called directly
from ARefVertex. Therefore, the ARefVertex objects can be used for normal
CGM-type topology traversal, as well as for application-specific uses.
Details on how
CUBIT uses this mechanism to implement meshing functionality on geometry
objects are given in the next chapter.
3. Application-Specific Attributes
Geometry attributes
are used to store application-specific data directly on objects to which they
are associated. This data is saved to and restored from the solid model files
automatically. Geometry attributes used by CGM include entity names, ids, group
membership, and others. CGM also provides a means for applications to define
their own attributes, which are then managed by CGM along with and in the same
way as CGM attributes.
Similar to how
application-specific RefEntity objects are managed, CGM utilizes a factory
pattern for the creation of attributes. A pure virtual base class, named
CubitAttribFactory, is defined with two pure virtual functions, both named
create_cubit_attrib; these functions create an application-specific attribute
from an attribute type and a simple attribute pointer, respectively.
The process for
defining an application-specific attribute factory is as follows. First, the
application-specific attribute classes are designed and written (see other
attributes like CAEntityName and CAEntityId for guidelines on writing
attributes). Then, an application-specific attribute factory is named and
written (in CUBIT, this class is named CAFactory); this factory is derived from
CubitAttribFactory:
Class AppCAFactory : public
CubitAttribFactory
{…}
Two functions need
to be declared and defined:
CubitAttrib
*AppCAFactory::create_cubit_attrib(const int attrib_type,
RefEntity
*owner)
{…}
CubitAttrib
*AppCAFactory::create_cubit_attrib(CubitSimpleAttrib *csa_ptr,
RefEntity
*owner)
{…}
Both these
functions must be defined, since they are called from CubitAttrib.
The constructor for
CubitAttribFactory is declared to be protected, which means only child and
friend classes of CubitAttribFactory can construct this class. Typically, an
application-defined attribute factory will be written with a static member
which constructs the factory. Inside that function, the factory must be
constructed and then passed to CubitAttrib, which stores a pointer to it:
static AppCAFactory *create_factory() {
ApplicationCAFactory *factory = new ApplicationCAFactory();
CubitAttrib::set_cubit_attrib_factory(factory);
}
This factory should
be created before the application imports any solid model files, otherwise any
application-specific attributes in those files will not be initialized
automatically.
There are several
static variables and one field in an enum corresponding to each type of
attribute. These variables are described in Table 22. Currently, these
variables and the enum are defined in CubitAttrib.hpp, inside the CGM code.
This requires changes to CGM if applications choose to add more attributes.
This will be changed eventually to allow the definition of these variables in
the application-specific attribute factory.
Table 22: Variables
and enum defined for each attribute. Currently, these variables are defined in
CubitAttrib.hpp.
|
Variable / Enum |
Description |
|
AutoActuateFlag[t], autoUpdateFlag[t] |
When true, attributes of type t actuate/update automatically after restoration / before saving from/to solid model file. |
|
AutoReadFlag[t], AutoWriteFlag[t] |
When true, attributes of type t are read from/written to the solid model file automatically. |
|
ActuateInConstructor[t] |
When true, attributes of type t are actuated from a call inside the object constructor; otherwise, they are attributed after all entities have been read from the solid model file in which the attributes resided. |
|
ActuateAfterGeomChanges[t] |
If true, attributes of type t are not actuated until after any changes to the geometry resulting from other attributes have taken place. |
|
CubitAttributeType |
Enum of attribute types; new attributes should be defined before CA_LAST_CA in CubitAttrib.hpp. |
|
AttTypeName[t] |
User-visible attribute type name of attribute t. |
|
AttInternalName[t] |
Internal name for attribute type t (written to solid model file for identification purposes). |
When an attribute's
AutoActuateFlag flag is set to true, attributes of that type are actuated
automatically when the solid model file is imported. If the attribute's
ActuateInConstructor flag is set to true, the attribute is actuated from inside
the constructor of the geometry entity to which it is associated, otherwise it
is actuated after the entire solid model is imported and the corresponding
CUBIT geometry entities created. Actuation of application-specific attributes
should be done carefully, especially if those attributes rely on functions or
data stored on application-specific geometric entities derived from the
RefEntity leaf classes (RefVertex, RefEdge, etc.). In this situation, the
actuate() function for these attributes will be called from the RefEntity leaf
class constructor, before the application-specific entity has been created, so
casting to an application-specific object will fail. Application-specific
attribute actuate functions should check to make sure they are applied to the
correct type of entity, and if not, should return CUBIT_FAILURE. The actuate
function can be called again from the application-specific entity constructor
by calling CubitAttribUser::auto_actuate_cubit_attrib(), at which time the
attribute can actuate correctly.
4. ToolData: Application-Specific Data
There are times
when applications need to store data on geometric entities that is of a
transient nature. For example, the mapping algorithm in CUBIT stores angle
information on the vertices bounding surfaces and volumes. This data is no
longer used after the meshing algorithm is finished generating mesh, and so
there is no need to store it in the datastructure of the geometric entity. CGM
provides a mechanism for storing transient data on entities; this mechanism is
referred to as the ToolData capability.
This capability
consists of two base classes, ToolData and ToolDataUser. ToolDataUser is a base
class of RefEntity, and is used to manage a list of ToolData objects.
ToolDataUser implements functions for adding, removing, and returning a list of
ToolData objects. Classes derived from ToolData are used to store the transient
information which should be associated with the ToolDataUser. ToolData also
stores a pointer to the next ToolData in a list. Thus, ToolData objects are stored
in a singly linked list, the head of which is pointed to by ToolDataUser.
Implementing new
types of ToolData objects is quite simple. ToolData-derived classes are
typically named TDSomeName, and are required to implement an identification
function:
int TDSomeName::is_some_name()
{return
(get_address(TDSomeName_TYPE) ? CUBIT_TRUE : CUBIT_FALSE);}
This function is
called from ToolDataUser to identify ToolData types:
TDSomeName my_td =
(TDSomeName *)
tool_data_user->get_TD(&ToolData::is_some_name);
See documentation
on ToolData, ToolDataUser, and some of the CGM ToolData classes (TDCompare,
TDUniqueId) for more details.
5. CubitObserver: Application-Specific Observation of
Entities
One of the classic problems in
object-oriented design is how to allow one class (the observer)
"observe" another class (the observable), without making the
implementation of observable know about the implementation of the observer.
This problem is addressed by using the well-known Observer pattern in C++. CGM's
implementation of observers, in CubitObserver and CubitObservable, is modeled
directly after that pattern.
To implement an application-specific
observer, the developer needs to do the following:
The EventType enum is defined in
CubitDefines.h.
There is also a mechanism in CubitObserver
for implementing "static" observers, that is observers which observe
all events. Static observers can be registered by calling
CubitObserver::register_static_observer and CubitObserver::unregister_static_observer.
This mechanism is used in CUBIT to implement some graphics functions and to
manage some global entity lists, for example.
Applications are also free to implement their
own observables as well as observers to observe them. In fact, there are some
classes in CGM which are derived from both CubitObservable and CubitObserver.
For example, RefGroup is both a CubitObserver, to keep track of entities which
are in the group but which should get removed upon deletion, as well as a
CubitObservable, since it too can be contained in groups.
1. Example: CUBIT Implementation Using CGM
In this example,
details are given about how CUBIT is implemented on top of CGM. This purpose of
this example is to show how an application might extend the capabilities of CGM
by deriving classes from the CGM classes. It also serves as an introduction to
CUBIT developers of the CUBIT database.
1. Derived topological entities
In addition to
representing geometry, geometric entities in CUBIT must also be able to store
mesh, represent finite element boundary conditions, and serve as a point of
access to meshing algorithms assigned to those entities. This is done using
inherited classes.
The inheritance
hierarchy used in CUBIT is shown in . As the figure shows, from each RefXxx leaf
class in CGM (RefVertex, RefEdge, RefFace, RefVolume, Body and RefGroup), CUBIT
derives a corresponding MRefXxx class (MRefVertex, MRefEdge, etc.). CUBIT also
defines a parent class for these leaf classes, which is named MRefEntity.
MRefEntity serves as a common base class for meshable entities in CUBIT, and is
decorated by (i.e. derived from) various classes which define certain
meshing-related functions. The parent classes of MRefEntity are MeshContainer,
used to store mesh on an MRefEntity, and MeshToolUser, used to associate
meshing tool data with the entity, including the meshing scheme assigned to the
entity.
Using a common
MRefEntity base class simplifies the design of the MRefXxx classes by allowing
the implementation of meshing functionality at a higher level in the derivation
hierarchy. However, it also complicates things because of the use of multiple
inheritance. Much of the parsing code in CUBIT operates on lists of
MRefEntity's, since the parsing involves setting meshing parameters, but it
also needs to access functions defined above RefEntity, list
CubitEntity::class_name and CubitEntity::id. For these reasons, most of the commonly-used
functions in RefEntity and above are defined as virtual functions, and can be
overridden in derived classes. Using this technique, common functions like
class_name and id are defined as virtual functions in MRefEntity as well, so
that they can be used with MRefEntity objects. These functions are defined in
the MRefXxx leaf classes, and call the CubitEntity functions explicitly.
CGM applications
not needing a common base class for derived geometric entities need not
implement one. If an application does define a common base class like that, but
does not implement those common functions, those functions are only accessible
from objects of the leaf classes or from CGM objects.
3. Topology traversal functions
One of the most
common operations on geometric entities is to access topologically connected
entities, for example finding all the edges containing a given vertex. These
functions are implemented in TopologyEntity, and can be called directly from
RefXxx objects. However, these functions pass back lists of RefXxx objects,
which may still need to be casted to MRefXxx objects. To solve this problem, a
similar set of topology traversal functions is implemented in MRefEntity which
return lists of MRefXxx objects; these functions have the same name as those in
TopologyEntity, except with an 'm' prepended to their names, e.g. mref_edges,
mref_faces, etc. To access the ref_xxx functions directly from an MRefEntity
object, the MRefEntity::topology_entity() function should be used to first cast
the entity to a TopologyEntity, on which the ref_xxx() function can be called
directly.
As described in the
previous chapter, a factory class is used to create geometric entities in CGM.
This allows the definition of application-specific geometric entities, which
are created from an application-specific factory. CUBIT defines the MRefEntity
class for this purpose. Note that this factory class must be instantiated
before the creation of any geometry in the application; in CUBIT, this is done
by calling MRefEntityFactory::instance() just before the first call to
GeometryTool::instance().
If an
application-specific factory is implemented, it is also responsible for the
storage of global lists of geometric entities; global list functions like
ref_volumes(), ref_faces(), etc. must also be defined (these functions are
declared virtual in RefEntityFactory for this purpose). CUBIT implements this
by defining pointers to global lists in RefEntityFactory. These lists are
created in the first call to RefEntityFactory::instance(), just after calling
the RefEntityFactory constructor; if an application-specific entity factory has
already been created, this part of RefEntityFactory::instance() is never
executed, and the list pointers remain NULL.
5. AttribFactory for CUBIT-specific attributes
As described in the
previous chapter, applications can define their own attributes by providing an
application-specific attribute factory. This factory should be instantiated
before any attributes are created. In CUBIT, the CAFactory serves this purpose,
and is instantiated before the first call to GeometryTool::instance().
CAFactory is derived from CubitAttribFactory, which is itself declared in the
CubitAttrib class header.
Currently,
CUBIT-specific attribute types are part of the CubitAttributeType enum, defined
in CGM. Technically, this should not be done, since it requires modification of
CGM to add application-specific attributes. Other applications can easily
bypass this by defining their own use of the CubitAttributeType values above the
ones used in CGM (those currently end at CA_DEFERRED_ATTRIB in
CubitAttrib.hpp). This will be changed eventually to allow the definition of
arbitrary attribute types (identified by an integer).
The DrawingTool
class in CUBIT is used to implement graphics functionality for the entire code.
As such, this class depends on virtually all of the datastructure classes in
CUBIT, and many of the datastructure classes depend on DrawingTool. Eventually,
this backward dependence will be removed and replaced entirely by the static
observer mechanism described earlier. That implementation will be done
incrementally. In the meantime, the GdrawingTool class in CGM serves as a
parent class of DrawingTool, and simply provides empty functions for those
DrawingTool functions which are called from within CGM. Applications are
welcome to derive their own drawing mechanism from GDrawingTool (see
GDrawingTool for more details).
It has proven
useful in CUBIT to have a single point of access to all the geometry, mesh and
boundary condition data created during the meshing process; the Model class
serves that purpose in CUBIT. While the actual data may not be stored there,
Model acts as a common point of reference. The functions in Model then call the
appropriate functions in MeshContainer, CGM, or wherever else to retrieve the
necessary data. Other applications can easily port to using CGM by retaining
their equivalent of the Model class, and modifying it to provide access to CGM
functions.
8. Adding mesh data to MRefGroup
In addition to adding various types of
decoration to the basic geometry capabilities in the RefEntity classes,
sometimes there is a need to modify the fundamental purpose of one of those
datastructures. In the CUBIT application, RefGroup has been modified to store
not only geometry entities but also mesh entities. This required the addition
of functions necessary for adding, removing and accessing the mesh stored in a
group. MRefGroup is still derived from RefGroup (as well as MRefEntity), but
also extends the functional interface of RefGroup by defining MRefGroup
functions and datastructure. Thus, applications can use a combination of class
decoration and class extension to add functionality to the basic CGM geometric
entities.
The structure of the MRefEntity class is
shown in . Note that MRefEntity is not derived from RefEntity. While virtual
inheritance could have been used to derive MRefEntity from RefEntity, this was
not done for reasons of efficiency, and because of the already complicated
inheritance hierarchy below the TopologyEntity and RefEntity classes in CGM.
The MRefXxx leaf classes derive from both MRefEntity and their RefXxx
counterparts.
Since the parsing code works extensively with
lists of MRefEntity objects, many of the functions defined in RefEntity,
TopologyEntity and CubitEntity have been re-defined in MRefEntity. In order to
avoid ambiguities when calling those member functions on MRefXxx objects,
definitions in the MRefXxx classes were needed as well. Further complicating
this problem, some of those functions were already re-defined in some of the
RefXxx classes (e.g. bounding_box()). To simplify definition of these
functions, macros were written and are stored in MRefFuncs.hpp. Four macros are
defined:
In addition to including these macros in the
MRefXxx.hpp and MRefXxx.cpp files, definitions are added for the functions in
the MREFFUNCS_DIFFERENT_IMPLEMENTATIONS macro.
Care should be taken when adding a function
to those declared/defined in these macros, to make sure that both the
definitions are added (to MREFFUNCS_DEFINE1 and MREFFUNCS_DEFINE2) as well as
the declaration to MREFFUNCS_DECLARE. It should be relatively rare that
functions are added to these macros at all.
When debugging these functions, the debugger
will not be able to step into the functions defined in these macros. However,
if the "step into" debugger function is used, the debugger will step
into the definition of the function in CGM, i.e. in RefEntity, CubitEntity, or
wherever else it is defined.
Because of the
extensibility features built into CGM, and the extension of those classes by
CUBIT, the initialization of CGM is a bit more complicated for CUBIT. In
particular, CUBIT must initialize MRefEntityFactory and CAFactory before
creating or using GeometryTool. This is most easily done by calling
MRefEntityFactory::instance() and CAFactory::create_factory before calling
GeometryTool::instance() for the first time. Also, there are several tools
which use mesh-defined geometry, in particular CompositeToolMesh and
PartitionToolMesh; these classes are derived from the virtual geometry classes
CompositeTool and PartitionTool, respectively, and also must be initialized
before creating any virtual geometry. The initialization of these tools is done
in main(), before calling GeometryTool::instance().
The mechanics of
how these CUBIT-specific tools are stored and used inside CGM varies a bit.
MRefEntity, CompositeToolMesh and PartitionToolMesh are all derived from their
respective parents who are singleton classes. Singleton classes store a static
pointer to the (single) instance of that class; the child classes simply create
a child object and store a pointer to the child in the parent's static pointer.
Thus, when the parent class's instance() function is used, it returns the
pointer to the child class. CAFactory, and other classes derived from
CubitAttribFactory, work slightly differently. CubitAttrib keeps a static
pointer to a CubitAttribFactory object; this pointer is initialized to NULL,
but gets assigned inside CAFactory::create_factory to point to the CAFactory
object. This object is used from CubitAttrib if attributes unknown to
CubitAttrib are encountered.
As stated earlier,
pointers to global lists of entities are maintained in both RefEntityFactory
and MRefEntityFactory. Only one set of those pointers is non-NULL; in the case
of CUBIT, the lists are non-NULL in MRefEntityFactory. Thus, global lists are
stored and accessed from MRefEntityFactory; the functions for accessing global
lists in RefEntityFactory (e.g. ref_faces(), ref_volumes()) are declared
virtual, and are overridden in MRefEntityFactory.
Although the global
list access functions are declared public in MRefEntityFactory, in most cases
these lists should be accessed through either Model or GeometryTool. In fact,
for most of the code in CUBIT (i.e. the code outside CGM), these lists should
be accessed through Model. Model provides a common access point for most of the
datastructure in CUBIT (geometry, mesh and boundary conditions). The functions
in Model are declared with the same names as those in MRefEntityFactory, to
minimize confusion. Functions are provided for returning list copies
(mref_faces, etc.), for returning a single entity of a given type (mref_face(),
etc.), or for direct (but read-only) manipulation of the lists
(get_first_mref_face(), get_next_mref_face(), get_last_mref_face(), etc.).
Similar functions are declared in GeometryTool, for accessing global lists of
RefXxx entities.
In rare cases,
efficiency concerns may be of such concern that direct access to the global
lists is necessary. There are functions defined in MRefEntityFactory which
return the pointers to the global entity lists, which can be used to modify the
lists directly. These functions should only be used when the functions in Model
or GeometryTool cannot be used, for efficiency or other reasons. See the
documentation for MRefEntityFactory for a description of these functions.
One of the most
common types of geometry query in CUBIT is topological queries, e.g. return a
list of edges bounding a face. Since lists of entities do not derive from one
another based on the inheritance hierarchy of the entities in the list, we need
functions which return lists of MRefXxx objects that are distinct from the corresponding
topological query functions defined in TopologyEntity. That is, we need
functions like mref_edges(), mref_faces(), etc. that return lists of MRefEdge's
and MRefFace's, respectively. These functions are defined in MRefEntity, and so
can be used with any object derived from MRefEntity.
3. MRefEntity Construction/Destruction
Because of the
extensibility features of CGM and the use of factories to construct geometric
entities, the implementation of the MRefEntity constructors and destructors can
be rather complicated. This section describes these implementations in more
detail.
The constructors of
the RefXxx classes are all private. This is to prevent any code from
constructing RefXxx objects without going through the RefEntityFactory. This ensures
that any code in CGM requesting a new RefXxx object will actually get an
application-specific RefXxx object (e.g. MRefXxx object in CUBIT) in return.
Constructors in the MRefXxx classes are private for the same reason. Because
all these constructors are private, the RefXxx and MRefXxx classes declare
RefEntityFactory and MRefEntityFactory, respectively, as friend classes; this
allows the factory classes to call those private constructors.
Tools needing to
construct new geometric entities must therefore call functions in the
appropriate factory class. For example,
RefEntityFactory::construct_RefEdge(Curve *) constructs and returns a RefEdge
given a Curve pointer, while MRefEntityFactory::construct_MRefEdge(Curve *)
constructs an MRefEdge and returns a pointer to an MRefEdge.

Figure 1: Pseudo code
for deleting geometric entities.
Destruction of
geometric entities in CUBIT was quite complicated before CGM, and remains so.
At the highest level, the GeometryTool::delete_Body(Body *) function should be
used to delete bodies. This function implements the logic necessary to delete
the body, the solid model entities represented by the body, and any mesh that
may exist on the body that isn't shared by geometric entities not being
deleted.
Unfortunately, it
is often necessary to understand what is happening during the entity deletion
process. Pseudo code for this process, as implemented when this manual was
written, is shown in Figure 1. The most important thing to derive from this
information is that the destructors for the geometric entities are called
directly from CDODAGNODE::~CDODAGNODE().
3. Construction/Destruction and Multiple Inheritance
By definition, when
a class object inherits from multiple base classes, the base class constructors
are called before that of the child class, in the order in which they appear in
the child class declaration. Destructors are called in reverse order. As
implemented in CUBIT, the MRefXxx classes inherit from RefXxx first, then from
MRefEntity. This has important implications on what happens in the MRefXxx and
MRefEnity constructors and destructors.
The primary thing
to remember about constructors is that during the construction of an MRefXxx
object, inside the MRefEntity constructor, the object does not yet know that it
is also a RefEntity. This is because the leaf class, MRefXxx, which is the link
between the two sets of parent classes, has not yet been constructed. So, if
inside the MRefEntity constructor, an attempt is made to call ref_entity(),
e.g. for the purpose of traversing the topology, that function will return
NULL. Thus, any work that needs to be done in the constructor that requires
topology traversal should be done in the leaf classes, not in MRefEntity.
Likewise, during
the destruction process, the leaf class MRefXxx is destroyed before calling the
MRefEntity destructor. Any call to ref_entity() inside the MRefEntity
destructor will also return NULL. Therefore, any destruction-related code which
requires topology traversal should be called from the MRefXxx destructors. For
example, this requires that CubitObservable::remove_from_observers() be called
from the MRefXxx destructors, since observers sometimes perform topology
traversal from the entity they are un-observing.
In order to remove
dependency of CGM on CUBIT classes like DrawingTool and Model, while still
being able to notify those classes of changes to the geometry, a general
observer mechanism was implemented. Using this mechanism, objects derived from
CubitObserver can "observe" objects derived from CubitObservable, and
can be notified when these objects are modified or destroyed. The observer
mechanism works by keeping, for every observable, a list of observers which get
notified upon changes to the observable. Although this same mechanism could be
used to implement the observation of entities by DrawingTool and Model, this
would be inefficient, since most mesh and geometry entities would keep pointers
to these classes. Instead, a static observer mechanism was also implemented,
where certain classes can register themselves to observe all events from all
observables.
Static observers
register themselves by calling
CubitObserver::register_static_observer(CubitObserver*). A pointer to each
static observer is kept on a static observer list in CubitObserver (actually,
CubitObserver just points to that list, creating it from CubitApp::instance()
upon startup). Typically, static observers are also singleton classes, which
means they can be registered inside either the constructor or the part of the
instance() function which calls the constructor (the latter method is used when
there may be child classes derived from those singleton classes, to avoid the
same object being registered twice). The current static observers in CUBIT are
DrawingTool and Model.
Static observers
are notified of events by calling CubitObserver::notify_static_observers(EventType).
When entities are constructed, several things
are done at the global level. The entity must be added to global lists, and if
graphics are active, the entity is added to the graphics display list. In
CUBIT, there are specific events which accomplish these and other things; these
events are listed in Table 23.
Note that
notify_static_observers(MODEL_ENTITY_CONSTRUCTED) is called from both the
MRefXxx and RefXxx constructors. The implementation of this notify function is
done carefully, to avoid adding a given entity to the global lists twice. This
is accomplished in the following way. First, inside the Model::notify_observer
function, the CubitObservable argument is cast to an MRefEntity; if that cast
is successful, the Model::notify(MRefEntity*, EventType) function is called
with that entity as an argument. If the result of the cast is NULL, the
Model::notify_observer function returns without doing anything further. During
the construction of an MRefXxx object, CubitObserver::notify_static_observers
gets called twice, once from the RefXxx::RefXxx constructor, and once from
MRefXxx::MRefXxx. The first call does nothing, while the second call results in
the entity being added to the global entity list.
Table 23: Events passed to
CubitObserver::notify_static_observers(EventType) from various functions in
CUBIT.
|
· Called from… |
· Action |
|
· VGI_BODY_CONSTRUCTED |
· GeometryTool::make_Body |
· Adds Body and its descendents to graphics display lists |
|
· FREE_REF_ENTITY_CONSTRUCTED |
·
GeometryTool::make_Xxx · (Xxx = Face, Edge, Vertex) |
· Adds entity (Face, Edge, etc.) to graphics, then calls DrawingTool::make_topmost for that entity |
|
· MODEL_ENTITY_CONSTRUCTED |
·
MRefXxx::MRefXxx, ·
RefXxx::RefXxx · (constructors) |
· Adds entity
to global entity list |
When an entity is destructed, its observers
must be notified so that they can take the proper actions prior to that entity
being removed. For example, when an entity which is contained in a group is
destroyed, that entity must be removed from the group. Also, static observers
should be notified of the entity being destroyed. Inside each MRefXxx::~MRefXxx
function are calls to CubitObservable::remove_from_observers() and
CubitObserver::notify_static_observers(this, MODEL_ENTITY_DESTRUCTED). The
first function removes the observable from any observers that are observing it,
while the second notifies static observers that the entity is about to be
destroyed. The RefXxx::~RefXxx destructors do not need to call
remove_from_observers(), since this function is called from
CubitObservable::~CubitObservable(). This function must be called in the
MRefXxx destructors because some observers need to know that the observable is
an MRefEntity. If the remove_from_observers() function was not called in the
leaf class destructors, by the time the function was called in the
CubitObservable destructor the link between MRefEntity and RefEntity would be
lost.

Figure 2: Class
hierarchy for CGM tool/engine (top) and topology entity (bottom) classes. These
classes stored in SOURCE_DIR/geom subdirectory.

Figure 3: Class
hierarchy for CGM geometry classes. These classes stored in SOURCE_DIR/geom
subdirectory.

Figure 4: Class hierarchy
for CGM geometry representation classes. These classes stored in
SOURCE_DIR/geom subdirectory.

Figure 5: Class
hierarchy for CUBIT factory and entity classes. These classes stored in
SOURCE_DIR directory.
CGM is written in
C++, and is based on ACIS, which is also written in C++. To support
applications written in C and FORTRAN, a C API has been written for most of the
public functions in CGM classes. Using these functions, an application written
in C or FORTRAN can do the same operations possible from C++, with the
exception of deriving application-specific classes from the CGM counterparts.
The C interface to
CGM is implemented as a set of API functions for each class in CGM, where each
public function in the C++ class has a corresponding C function. The API
functions differ from their C++ counterparts in how C++ objects are handled,
both as function arguments and as return types. The following section describes
these differences. This section is followed by a description of a simple C
driver code which performs the same operations as those in the C++ driver code
described earlier.
2. CGM C++ Object Handling Conventions
Although C++ classes are very similar to C
structures, they cannot be handled as structures because of complications that
arise with multiple inheritance, function overloading, and other C++ language
features. Coplien recommends an approach to interfacing C++ to C code that
strikes a balance between code duplication and portability; this is the
approach taken with the CGM C API, hereafter referred to as CGM/CCAPI or CCAPI.
CCAPI functions are implemented using a
convention that allows simple identification of the class and the arguments of
the corresponding C++ CGM member function. Arguments are either converted in
the implementation of CCAPI functions or passed directly to the CGM function.
There are four primary object handling conventions defined for passing data
between CGM and CCAPI; they are: