Chapter 3. Supplying Chemical System Data

This chapter describes how to provide access to chemical system data. The Molecular Inventor software distribution includes the ChemData class, which is derived from the base data class, ChemBaseData. You can use an instance of ChemData directly in your application or you can use it as a model for deriving your own data node.

This chapter contains the following sections:

If you are deriving your own data node, you should read, The Inventor Toolmaker, which is included with the Open Inventor Development Kit.


Note: In this guide, the term “data node” refers to any ChemBaseData-derived class, such as ChemData.


Deriving a Data Node

ChemBaseData is a base Molecular Inventor (MI) class from which you must derive your own data class. MI uses the derived data class to access the chemical system data.

MI requires that the derived class contain, at a minimum, the following information:

  • the number of atoms

  • the number of bonds

  • a series of get functions, supplied by the developer, that allow MI to access the chemical system data

  • a constructor

Because MI uses get...() methods supplied by the ChemBaseData-derived class to access chemical system data, MI makes no requirements on the data structures that store your chemical system data. The ChemData class provided with MI, for example, stores chemical system data in Open Inventor fields.

Maintaining Data

The fields maintained by the data node are the number of atoms and bonds in the chemical system data, and any associated data.

SoSFInt32           numberOfAtoms;
SoSFInt32           numberOfBonds;
SoSFNode            associatedData;

The optional field, associatedData, contains an instance of the ChemAssociatedData class. This class holds ASCII or binary information that is descriptive of the chemical system but which is not required by MI for rendering. Often the associatedData field is used as a mechanism for transferring additional information about a chemical system between applications.

For more information about ChemAssociatedData, see “Maintaining Additional Information About a Chemical System.”

The Get Methods

When you derive your own data class from ChemBaseData, you must implement the following methods that provide access to chemical system data:

virtual short             getAtomicNumber(int32_t index) const;
virtual int32_t           getAtomId(int32_t index) const;
virtual const SbString &  getAtomName(int32_t index) const;
virtual int32_t           getAtomIndex(int32_t index) const;
virtual const SbVec3f &   getAtomCoordinates(int32_t index) const;
virtual int32_t           getBondFrom(int32_t index) const;
virtual int32_t           getBondTo(int32_t index) const;
virtual int               getBondType(int32_t index) const;
virtual int32_t           getBondIndex(int32_t index) const;

The following table defines these methods.

Table 3-1. ChemDisplay Methods

Method

Description

getAtomicNumber()

returns the atomic number of the atom with the specified index. The value returned is used for labeling atoms and for optionally turning off the display of hydrogens.

getAtomId()

returns the ID of the atom with the specified index; the ID is a numeric identifier. The value returned is used for labeling atoms.

getAtomName()

returns the name of the atom with the specified index. The value returned is used for labeling atoms.

getAtomIndex()

returns the entry to use with the ChemColor and ChemRadii tables for the atom with the specified index if ATOM_PER_ATOM_INDEXED is used as the value for the atomRadiiBinding or atomColorBinding fields in ChemRadii or ChemColor, respectively.

getAtomCoordinates()

returns the coordinates of the atom with the specified index.

getBondFrom()

returns the entry for the “from” atom in the bond with the specified bond index. Each bond is composed of a “from” and “to” atom. The returned value is used as the argument for the getAtom*() methods.

getBondTo()

returns the entry for the “to” atom in the bond with the specified bond index. The returned value is used as the argument for the getAtom*() methods.

getBondType()

returns the type of bond with the given index; it must be one of the enum values of BondType in ChemBaseData, such as, SINGLE_BOND, DOUBLE_BOND, or TRIPLE_BOND.

getBondIndex()

returns the entry to use with the ChemColor bondColor table if BOND_PER_BOND_INDEXED is used as the value of bondColorBinding in the ChemColor node

All of these methods use an index number as an argument. If you think of the data accessed by the get...() methods as being stored in arrays, the index is the particular element in the array. For example, if atomicNumber contained the values listed below, chemData->getAtomicNumber(2) would return the value 8, which is the value of the third (numbering starts at 0) element the atomic number array.

atomicNumber        [ 7, 6, 8, 7, 6, 8, 6, 6,
                      6, 6, 6, 6, 7, 7, 7, 1,
                      6, 6, 8, 1, 8, 1, 1, 1,
                      1, 1, 1, 1, 1, 1, 1, 1,
                      1 ]

The Action Methods

Once a scene graph is created, actions are applied to the scene graph to cause something to happen. Examples of actions include SoGLRenderAction, that causes the scene graph to be rendered, SoWriteAction, that causes the scene graph to be written to a file, and SoGetBoundingBoxAction, that computes a 3D bounding box for all of the objects in the scene graph. Actions are applied to each node in the scene graph by invoking methods within a node. These methods are either inherited from the base Open Inventor classes or they are supplied by a specific class.

Actions invoked on a grouping node spread to its children nodes in a defined order. For more information about how actions are carried out in a scene graph, see Chapter 3 of The Inventor Mentor.

ChemBaseData-derived nodes must handle the following primary actions: SoGLRenderAction, SoCallbackAction, SoGetBoundingBoxAction and SoPickAction. It is the ChemDisplay node that renders or generates information in response to these actions. ChemDisplay, however, needs the information contained in the data node, the ChemDisplayParam node, the ChemRadii node, and the ChemColor node to handle these actions. Consequently, all of the nodes must respond appropriately to these actions by making their information available to the ChemDisplay node.

A ChemBaseData-derived node makes its information available by placing a pointer to itself in the traversal state. This functionality is provided by the ChemBaseDataElement class. Therefore, in response to the actions described above, a data node just needs to invoke the set() method of the ChemBaseDataElement class. The ChemData class provides an example of this implementation.


Note: If your ChemBaseData-derived class does not store its data in Open Inventor field types, you must implement methods that handle the writing of data, in response to a SoWriteAction, and the reading of data from an Inventor file.

For more information regarding actions, refer to Chapter 9 of The Inventor Mentor. For more information about creating nodes, refer to The Inventor Toolmaker.

Maintaining Additional Information About a Chemical System

Sometimes you might like to maintain more information about a chemical system than is required by MI for rendering. To maintain this non-rendering information, you can either create your own ChemBaseData-derived node which contains fields to hold this information, or you can use the associatedData field which is defined in the ChemBaseData class.

To make use of this field, your application must create an instance of ChemAssociatedData and then set the value of the associatedData field in a data node to the instance of ChemAssociatedData, for example:

ChemData *chemData = new ChemData;

ChemAssociatedData *cad = new ChemAssociatedData;
...
chemData->associatedData.setValue((SoNode *)cad);

In this example, cad is a pointer to a newly-created instance of ChemAssociatedData. The last line of code sets the value of chemData's associatedData field to the instance of ChemAssociatedData.


Note: The setValue() method increments the reference count on cad so that it is not deallocated by Open Inventor.

If the associatedData field contains data, that data is written to an Inventor file when an SoWriteAction is applied to the data node. The field thereby provides a mechanism to easily transfer data between applications. If you do not use the associatedData field but store the additional information within Open Inventor fields, the information is also written to an Inventor file upon invocation of a SoWriteAction. If you do not use the associatedData field and do not use Open Inventor fields to store the additional information, you must provide a write() method for your data node. For more information, see The Inventor Toolmaker.


Note: The remainder of this chapter explores the ChemData class. Use its explanation as a guide for creating your own ChemBaseData-derived data node.


Exploring ChemData.h

The software distribution includes ChemData, which is a class derived from ChemBaseData. You can use ChemData directly in your application or you can use it as a model for deriving your own data node.

This section describes the header file for the ChemData class found in /usr/include/ChemKit/ChemData.h.

The Include Files

ChemData.h includes the ChemBaseData.h and ChemDefs.h header files. ChemDefs.h contains definitions which allow MI to be compatible with versions 2.0 and 2.1 of Open Inventor.

#include <ChemKit/ChemDefs.h>
#include <ChemKit/ChemBaseData.h>

The remaining header files include declarations of the Open Inventor fields used by ChemData and the declaration of SoSubNode from which all Open Inventor nodes are derived.


#ifdef IV2_0
#include <Inventor/fields/SoSFLong.h>
#include <Inventor/fields/SoMFLong.h>
#else
#include <Inventor/fields/SoSFInt32.h>
#include <Inventor/fields/SoMFInt32.h>
#endif

#include <Inventor/fields/SoMFEnum.h>
#include <Inventor/fields/SoMFShort.h>
#include <Inventor/fields/SoMFString.h>
#include <Inventor/fields/SoMFVec3f.h>
#include <Inventor/nodes/SoSubNode.h>

Declaring the Class and Its Data Members

ChemData.h uses the following lines to create a class named ChemData which is derived from ChemBaseData:

class ChemData : public ChemBaseData {
    SO_NODE_HEADER(ChemData);

The following lines declare the fields used in ChemData:

public:

    // Fields - Atoms
    SoMFShort   atomicNumber;
    SoMFInt32   atomId;
    SoMFString  atomName;
    SoMFInt32   atomIndex;
    SoMFVec3f   atomCoordinates;

    // Fields - Bonds
    SoMFInt32   bondFrom;
    SoMFInt32   bondTo;
    SoMFEnum    bondType;
    SoMFInt32   bondIndex;

The following table describes the contents of these fields.

Table 3-2. ChemData Fields

ChemData Fields

Description

atomicNumber

is the atomic number of an atom. ChemDisplay renders the atomic numbers of atoms if this is specified in ChemDisplayParam.

atomId

is the unique, numeric ID for each atom in the chemical system. ChemDisplay renders the IDs of the atoms if this is specified in ChemDisplayParam.

atomName

is the name of each atom in the chemical system. ChemDisplay renders the names of the atoms if this is specified in ChemDisplayParam.

atomIndex

contains lookup information to find the color and radii of an atom in tables defined in the ChemColor and ChemRadii nodes.

atomCoordinates

are the coordinates for each atom in a chemical system.

bondFrom

is used as an index into the atom SoMFFields in order to access information about the “from” atom. Each bond is composed of a “from” and “to” atom.

bondTo

is used as an index into the atom SoMFFields in order to access information about the “to” atom. Each bond is composed of a “from” and “to” atom.

bondType

is the type of bond; it must be one of the enum values of BondType in ChemBaseData, such as, SINGLE_BOND, DOUBLE_BOND, or TRIPLE_BOND.

bondIndex

contains lookup information to find the color of a bond in the bond-color table defined in the ChemColor node.


Declaring Methods

ChemData.h provides declarations and implementations of all of the get...() methods in ChemBaseData, as described in “The Get Methods.” In addition, ChemData defines the following methods.

Table 3-3. ChemData Methods

Method

Description

ChemData()

is the class constructor; it creates an instance of ChemData with default settings.

~ChemData()

is the class destructor which is empty.

reset()

empties all of the SoMFFields so the node can be used for a different chemical system.

callback()

is invoked when a SoCallbackAction is applied to the node. It in turn invokes the doAction() method.

GLRender()

is invoked when a SoGLRenderAction is applied to the node. It in turn invokes the doAction() method.

pick()

is invoked when a SoPickAction is applied to the node. It in turn invokes the doAction() method.

getBoundingBox()

is invoked when a SoGetBoundingBoxAction is applied to the node. It in turn invokes the doAction() method.

doAction()

invokes the ChemBaseDataElement->set() method that places a pointer to the node in the traversal state


Exploring ChemData.c++

The implementation of a data node depends on the data structures used for storing chemical system data. There are, however, parts of ChemData.c++ that any derived class must implement. The following sections describe those parts.

Including Files

Every data node must include the header files for the classes it uses. Your data node might include header files similar to ChemData.c++. The ChemData.h header file contains the declarations of the ChemData class.

#include <ChemKit/ChemData.h>

When actions are applied to the data node, the set() method of the ChemBaseDataElement class is ultimately invoked. Therefore, the header file for ChemBaseDataElement must be included as must the header files for the actions handled by the data node.

#include <ChemKit/ChemBaseDataElement.h>
#include <Inventor/actions/SoGLRenderAction.h>
#include <Inventor/actions/SoCallbackAction.h>
#include <Inventor/actions/SoGetBoundingBoxAction.h>
#include <Inventor/actions/SoPickAction.h>

Creating the Data Node and Its Fields

Every data node must declare itself as a node, provide a constructor and a destructor, and, if Open Inventor fields are used to hold the chemical system data, declare and provide default values for each of its fields. The constructor for the ChemData class is shown in the following section of ChemData.c++:

SO_NODE_SOURCE(ChemData);

ChemData::ChemData()
{
    SO_NODE_HEADER(ChemData);
    SO_NODE_CONSTRUCTOR(ChemData);

    SO_NODE_ADD_FIELD(atomicNumber, (0));
    SO_NODE_ADD_FIELD(atomId, (0));
    SO_NODE_ADD_FIELD(atomName, (””));
    SO_NODE_ADD_FIELD(atomIndex, (0));
    SO_NODE_ADD_FIELD(atomCoordinates, (0.0, 0.0, 0.0));

    SO_NODE_ADD_FIELD(bondFrom, (0));
    SO_NODE_ADD_FIELD(bondTo, (0));
    SO_NODE_ADD_FIELD(bondType, (0));
    SO_NODE_ADD_FIELD(bondIndex, (0));
    SO_NODE_SET_MF_ENUM_TYPE(bondType, BondType);
}

For more information about creating nodes and fields within Open Inventor, see The Inventor Toolmaker.

Implementing the Methods

The ChemData header file declares and implements the get...() methods for the ChemData class. The implementations of these methods vary according to the manner in which data is accessed for your ChemBaseData-derived node.

The following methods in your class are invoked when actions, described in “The Action Methods,” are applied to a scene graph containing your node:

virtual void    callback(SoCallbackAction *action);
virtual void    GLRender(SoGLRenderAction *action);
virtual void    pick(SoPickAction *action);
virtual void    getBoundingBox(SoGetBoundingBoxAction *action);

In ChemData, all of these methods invoke the doAction() method which in turn invokes the set() method of ChemBaseDataElement. You can very likely use the following code from ChemData.c++ directly in your ChemBaseData-derived class after changing ChemData:: to the name of your class.

void
ChemData::doAction(SoAction *action)
{
    ChemBaseDataElement::set(action->getState(), this, this);
}   

void
ChemData::GLRender(SoGLRenderAction *action)
{
    ChemData::doAction(action);
}

void
ChemData::pick(SoPickAction *action)
{
    ChemData::doAction(action);
}
 
void
ChemData::callback(SoCallbackAction *action)
{
    ChemData::doAction(action);
}

void
ChemData::getBoundingBox(SoGetBoundingBoxAction *action)
{
    ChemData::doAction(action);
}

The Entire ChemData.c++ Source

Since the source file for the ChemData class is not included in the distribution, it is listed here in its entirety.

#include <ChemKit/ChemData.h>
#include <ChemKit/ChemBaseDataElement.h>

#include <Inventor/actions/SoGLRenderAction.h>
#include <Inventor/actions/SoCallbackAction.h>
#include <Inventor/actions/SoGetBoundingBoxAction.h>
#include <Inventor/actions/SoPickAction.h>


SO_NODE_SOURCE(ChemData);

ChemData::ChemData()
{
    SO_NODE_CONSTRUCTOR(ChemData);

    SO_NODE_ADD_FIELD(atomicNumber, (0));
    SO_NODE_ADD_FIELD(atomId, (0));
    SO_NODE_ADD_FIELD(atomName, (““));
    SO_NODE_ADD_FIELD(atomIndex, (0));
    SO_NODE_ADD_FIELD(atomCoordinates, (0.0, 0.0, 0.0));

    SO_NODE_ADD_FIELD(bondFrom, (0));
    SO_NODE_ADD_FIELD(bondTo, (0));
    SO_NODE_ADD_FIELD(bondType, (0));
    SO_NODE_ADD_FIELD(bondIndex, (0));

    SO_NODE_SET_MF_ENUM_TYPE(bondType, BondType);

    isBuiltIn = TRUE;

}

ChemData::~ChemData()
{
}

void
ChemData::doAction(SoAction *action)
{
    ChemBaseDataElement::set(action->getState(), this, this);
}

void
ChemData::GLRender(SoGLRenderAction *action)
{
    ChemData::doAction(action);
}

void
ChemData::pick(SoPickAction *action)
{
    ChemData::doAction(action);
}

void
ChemData::callback(SoCallbackAction *action)
{
    ChemData::doAction(action);
}

void
ChemData::getBoundingBox(SoGetBoundingBoxAction *action)
{
    ChemData::doAction(action);
}

void
ChemData::reset()
{
    atomicNumber.deleteValues(0,-1);
    atomId.deleteValues(0,-1);
    atomName.deleteValues(0,-1);
    atomIndex.deleteValues(0,-1);
    atomCoordinates.deleteValues(0,-1);

    bondFrom.deleteValues(0,-1);
    bondTo.deleteValues(0,-1);
    bondType.deleteValues(0,-1);
    bondIndex.deleteValues(0,-1);
}