Chapter 2. Writing Your First Molecular Inventor Application

This chapter gives you the opportunity to write your first, simple Molecular Inventor application, called helloMI. (It is also included in the source code in /usr/share/src/MolInventor/examples/helloMI so you can just follow along if you prefer.) helloMI renders molecular data using MI default settings.

To understand the structure of helloMI, this chapter begins with a brief discussion of MI scene graphs and nodes.

The chapter concludes with a description of a more sophisticated MI application, called miApp. The source code for miApp is included in /usr/share/src/MolInventor/examples/miApp.

This chapter contains the following sections:

The following chapters describe in greater depth the classes and methods mentioned in this chapter.

Molecular Inventor Scene Graph Requirements

A scene graph in Open Inventor consists of one or more nodes, each of which are instances of Open Inventor classes. These nodes define, among other things, the data to be rendered and the display parameters, such as color, that affect the rendering. There are other types of nodes, such as grouping nodes, that connect children nodes into a hierarchical scene graph.

For more general information about scene graphs and scene graph components, see Chapter 3 of The Inventor Mentor.

Molecular Inventor Scene Graphs

MI requires that you include at least one of each of the following nodes in every MI application:

ChemBaseData 


Provides access to molecular data. Because you cannot use ChemBaseData directly, you must derive a class to access molecular data. ChemData is a ChemBaseData-derived node supplied in the software distribution.

ChemDisplayParam 


Sets the style of rendering, for example, whether or not labels are rendered, whether atoms and bonds are represented as spheres and cylinders, or whether fog is used to enhance a sense of depth.

ChemColor 

Sets the colors used to render atoms, bonds, and labels.

ChemRadii 

Sets the size of the atoms when they are rendered as spheres.

ChemDisplay 

Renders the chemical system based on information obtained from the other nodes.

The following figure is a diagram of the simplest MI scene graph.

Figure 2-1. Simplest MI Scene Graph


In this figure, the order of the nodes is unimportant except that the ChemDisplay node must come last. Only nodes to the left of a ChemDisplay instance influence the rendering of data by that instance.

Using Node Kits

Open Inventor contains a mechanism for organizing nodes into commonly used subgraphs. These groupings are referred to as node kits. MI provides two node kits, ChemDisplayKit and ChemKit.

ChemDisplayKit groups together a ChemDisplayParam, ChemRadii, ChemColor and ChemDisplay node. ChemKit groups a ChemData node with a ChemDisplayKit node kit. The following figure shows the simplest MI scene graph that uses node kits.

Figure 2-2. Simplest MI Scene Graph Using Node Kits


For more information regarding node kits, see Chapter 14 in The Inventor Mentor.

For more information about ChemBaseData, see Chapter 3, “Supplying Chemical System Data.” For more information about ChemDisplayParam, ChemRadii, and ChemColor, see Chapter 4, “Controlling the Display Parameters.” For more information about ChemDisplay, see Chapter 5, “Rendering Chemical System Data.”

Writing a Simple Molecular Inventor Application

The following list shows you the steps you take to write a simple MI application, such as helloMI.

  1. Initialize Inventor and MI.

  2. Create the root node and all required MI nodes.

  3. Attach the MI nodes to the scene graph.

  4. Read the molecular data.

  5. Create a viewer and attach the scene graph.

  6. Make the viewer and X Window visible.

  7. Handle mouse events.

The following sections explain these steps in detail. “helloMI Code Example” shows the entire code sample of helloMI. If the sample source code was installed as part of the installation of the Molecular Inventor Development Kit, the source code for helloMI can also be found in the /usr/share/src/MolInventor/examples/helloMI directory.

The Include Files

MI applications include Open Inventor and MI classes. Typically, you need to include in an application a header file for each Open Inventor and MI class used. In helloMI, the Open Inventor classes:

  • Initialize the Open Inventor database and the X toolkit.

  • Specify a viewer in which to render the data.

  • Declare the class used as the root node of the scene graph.

helloMI includes the following Open Inventor header files:

#include <Inventor/Xt/SoXt.h>
#include <Inventor/Xt/viewers/SoXtExaminerViewer.h>
#include <Inventor/nodes/SoSeparator.h>

You must also include a header file for each MI class that you use. These classes access molecular data and characterize the style of the display. helloMI includes the following MI header files:

#include <ChemKit/ChemColor.h>
#include <ChemKit/ChemData.h>
#include <ChemKit/ChemDisplay.h>
#include <ChemKit/ChemDisplayParam.h>
#include <ChemKit/ChemInit.h>
#include <ChemKit/ChemRadii.h>
#include <ChemKit/ChemUI.h>

Initialize Inventor and Molecular Inventor

Initializing the Open Inventor and MI database is a requirement for all MI applications; typically, it is the first step you take in writing an MI application.

The following lines create the top-level shell widget, myWindow, and initialize Open Inventor so that it interacts properly with the X toolkit.

Widget myWindow = SoXt::init(argv[0]); 
if (myWindow == NULL) exit(1);

The argv[0] argument passes the name of the application to SoXt::init(). SoXt::init() returns the top level shell widget. If one cannot be created, the program terminates. For more information about initializing the Open Inventor database, see the section “The Scene Database,” in Chapter 3 of The Inventor Mentor.

Initializing MI informs Open Inventor of the classes that MI defines. The following line initializes MI:

ChemInit::initClasses();

Create the Root Node and All Required MI Nodes

The scene graph for helloMI is described by the following figure.

Figure 2-3. helloMI scene graph


Every scene graph has a root node. The root node is the head node in a scene graph.

To create a root node, use lines similar to the following:

SoSeparator *root = new SoSeparator;
root->ref();

Root nodes can be of any grouping type, for example, SoSeparator or SoGroup.

Although you create a node by using the new directive, you do not deallocate it using the delete directive. An instance of a node can be referenced by other nodes in a scene graph. Open Inventor keeps track of the number of times a node is referenced, for example, if a root node has three children, its reference count is three. If the reference count for a node reaches zero, Open Inventor deallocates the node. For more information about allocating nodes, see the section, “References and Deletion,” in Chapter 3 of The Inventor Mentor.

The second line in the sample code references the root node. It is important to reference the root node once, otherwise, Open Inventor may deallocate it.

Create the Necessary Molecular Inventor Nodes

Each node in an MI scene graph either provides access to, manipulates, or displays molecular data. helloMI creates the following nodes:

ChemData *chemData                 = new ChemData;
ChemDisplayParam *chemDisplayParam = new ChemDisplayParam;
ChemUI *chemUI                     = new ChemUI;
ChemRadii *chemRadii               = new ChemRadii;
ChemColor *chemColor               = new ChemColor;
ChemDisplay *chemDisplay           = new ChemDisplay;

The ChemData node contains methods that access molecular data. The ChemDisplayParam node contains information relating to the display of molecular data. ChemUI is a user-interface node that manipulates the various parameters in ChemDisplayParam. ChemRadii specifies the radii to use when displaying atoms as spheres. ChemColor specifies the colors of the atoms, bonds, and labels. ChemDisplay renders the molecular structures and labels based on the information contained in the other nodes.

The value of using a ChemUI node in this application is that you can avoid writing X window code to create a simple user interface. ChemUI is most often used when displaying chemical data in applications that have no knowledge of molecular displays.

Figure 2-4. ChemUI Interface Widget


For more information about displaying chemical systems in non-chemical applications, see “Using MI Nodes Within Inventor Files”.

Attach the Molecular Inventor Nodes to the Scene Graph

Now that all of the MI nodes are created, you attach them to the root node, called root, using the addChild() method of SoSeparator.

root->addChild(chemData);
root->addChild(chemDisplayParam);
root->addChild(chemUI); 
root->addChild(chemRadii);
root->addChild(chemColor);
root->addChild(chemDisplay);

The order in which you attach the nodes to the root node is arbitrary with the following exceptions:

  • ChemUI must follow ChemDisplayParam because a ChemUI node affects the ChemDisplayParam node directly preceding it.

  • The ChemDisplay node must be the last node in the scene graph so that it can use the information in all of the nodes that precede it.

Read the Molecular Data

ChemBaseData is the base class for all derived data classes. Because the base class does not contain a constructor, your application cannot use it directly; your application must use a derived data class. ChemData is the sample ChemBaseData-derived class included with MI that provides access to molecular data.

The ChemBaseData class specifies the information that is required for displaying a molecule, for example, the coordinates and atomic numbers of the atoms, and the number of atoms and bonds in a molecule.

ChemBaseData also specifies the get...() methods a derived class must implement to provide access to chemical system data.

Because you can use any file format or database to store chemical system data, helloMI encapsulates the specifics of reading the chemical system data in the following function:

readFile(chemData);

For more information about readFile(), see “Filling ChemData Fields Quickly.”

For more information about deriving a class from ChemBaseData, see Chapter 3, “Supplying Chemical System Data.”.

Create a Viewer and Attach the Scene Graph

A viewer, as shown in Figure 2-2, contains a pane in which a scene graph is rendered. It adds a camera and headlight to the scene graph, if necessary, so that the scene can be viewed. The position of the camera is controlled either by mouse movements or the widgets around the window, such as sliders and thumb wheels.

Open Inventor supplies several different viewers. The one used in helloMI is the SoXtExaminerViewer. For more information on Open Inventor viewers, see Chapter 16 in The Inventor Mentor.

SoXtExaminerViewer *myViewer = new SoXtExaminerViewer(myWindow);

This line of code creates the viewer within the top level shell widget, myWindow.

Attach the Root Node to the Viewer

To view the scene graph created above, you tell the viewer which scene graph to render.

myViewer->setSceneGraph(root);

Set the Title on the Viewer

As an option, you can set the title displayed on the title bar of the viewer.

myViewer->setTitle(“Examiner Viewer”);

In this case, the title is set to “Examiner Viewer.”

Make the Viewer and X Window Visible

The following line makes the SoXtExaminerViewer (myViewer) visible within the top level shell widget:

myViewer->show();

The following line specifies that the top level shell widget should also be visible:

SoXt::show(myWindow);

Handle Mouse Events

Looping the application allows it to repeatedly look for mouse events and to change the rendered scene accordingly.

SoXt::mainLoop();

helloMI Code Example

The following example contains the complete code for helloMI.

Example 2-1. helloMI Code


// Include necessary Inventor stuff
#include <Inventor/Xt/SoXt.h>
#include <Inventor/Xt/viewers/SoXtExaminerViewer.h>
#include <Inventor/nodes/SoSeparator.h>

// Include necessarry Molecular Inventor stuff
#include <ChemKit/ChemColor.h>
#include <ChemKit/ChemData.h>
#include <ChemKit/ChemDisplay.h>
#include <ChemKit/ChemDisplayParam.h>
#include <ChemKit/ChemInit.h>
#include <ChemKit/ChemRadii.h>
#include <ChemKit/ChemUI.h>

void
main(int , char **argv)
{
   // Initialize Inventor. This returns a main window to use.
   // If unsuccessful, exit.
   Widget myWindow = SoXt::init(argv[0]); // pass the app name
   if (myWindow == NULL) exit(1);

   // Initialize Molecular Inventor
   ChemInit::initClasses();

   // Make the root node of the scene graph
   SoSeparator *root = new SoSeparator;
   root->ref();

   // Make the necessary Molecular Inventor nodes
   ChemData *chemData                 = new ChemData;
   ChemDisplayParam *chemDisplayParam = new ChemDisplayParam;
   ChemUI *chemUI                     = new ChemUI;
   ChemRadii *chemRadii               = new ChemRadii;
   ChemColor *chemColor               = new ChemColor;
   ChemDisplay *chemDisplay           = new ChemDisplay;

   // Attach these to the root node
   root->addChild(chemData);
   root->addChild(chemDisplayParam);
   root->addChild(chemUI);
   root->addChild(chemRadii);
   root->addChild(chemColor);
   root->addChild(chemDisplay);

   // Read the file and put info into chemData
   readFile(chemData);

   // Create the viewer
   SoXtExaminerViewer *myViewer = new SoXtExaminerViewer(myWindow);

   // Attach the root node to the viewer
   myViewer->setSceneGraph(root);

   // Set the title on the viewer window
   myViewer->setTitle(“Examiner Viewer”);

   // Tell the viewer to show itself
   myViewer->show();

   SoXt::show(myWindow);
   SoXt::mainLoop();
}

Using MI Nodes Within Inventor Files

Inventor files contain enough information to display 3D objects in Inventor-based applications. You can also write, or generate from C++ code, Inventor files which contain MI nodes. The following example shows the Inventor file equivalent of the helloMI application shown in Example 2-1.

Example 2-2. An Excerpt From an MI Inventor File


#Inventor V2.1 ascii

Separator {
    ChemData {

...
        }
    ChemDisplayParam {
        displayStyle DISPLAY_CPK
    }
    ChemUI { }
    ChemRadii { }
    ChemColor { }
    ChemDisplay { }
}

In this example, the contents of the fields in the ChemData node were omitted. For the remaining nodes, except ChemDisplayParam, all fields contain default values, therefore none are listed. For ChemDisplayParam, the displayStyle field is set to DISPLAY_CPK.

In an Inventor file, the nodes appear in the same order they appear in their corresponding scene graph. Inventor files use the following syntax for the elements of a node:

  • Each node name is displayed and followed by an open curly brace ({), for example, ChemColor{.

  • Each field of the node with a modified value is displayed along with its value; unmodified fields (fields that use the default value) are not included.

  • Children of the node (if any) are then listed.

  • A close curly brace (}) ends the description of the node.

For more information about the syntax of ASCII versions of scene graphs, refer to Chapter 11 in The Inventor Mentor.

Creating an ASCII Version of an Inventor Scene Graph

You can create an Inventor file of a scene graph in two ways. First, you can use a text editor, such as vi, to type in ASCII text, such as that shown in Example 2-2. It is customary to use .iv as the extension when naming the Inventor file.

Second, from within an application, you can write a scene graph to a file by applying an SoWriteAction to the root node. This action writes all of the field values in the node to a file using the syntax described in “Using MI Nodes Within Inventor Files.” If the action is applied to the root node of the scene graph, an Inventor file version of the entire scene graph is written to a file.

Example 2-3. Writing an Inventor Version of a Scene Graph to a File


SoWriteAction myAction;

myAction.getOutput()->openFile(“myFile.iv”);
myAction.getOutput()->setBinary(FALSE);
myAction.apply(root);
myAction.getOutput()->closeFile();

In this code fragment, an SoWriteAction node, myAction, is created. A file is opened named myFile.iv. The setBinary() function is set to FALSE so the information written into myFile.iv is ASCII (not binary). myAction is applied to the root of the scene graph so the values in all of the fields in all of the nodes in the scene graph are written to myFile.iv. Finally, myFile.iv is closed.

Displaying an Inventor File

You can display an Inventor file using any of the tools included with Open Inventor, for example, you could use the following command to view an Inventor file:

% ivview myInventorFile

where myInventorFile is the name of the Inventor file.

Writing More Robust Molecular Inventor Applications

In the helloMI application, the ChemUI node was introduced as a simple, easy-to-use, ready-built user interface that works even in non-chemical applications. For most applications, however, you will need to offer a more complete user interface (UI). Although the virtue of helloMI is its simplicity, its functionality is limited, for example, helloMI does not allow you to render various portions of a chemical system with different display parameters. More robust MI applications provide a more complete UI with greater control over the display of chemical system data.

Creating a More Complete User Interface

The MI software distribution includes a more sophisticated MI sample application called miApp. miApp, found in /usr/share/src/MolInventor/examples/miApp, includes all of the functionality of helloMI, but uses the X toolkit and Motif to create a more complete user interface. Like ChemUI, this user interface essentially allows the user of miApp to set the values of fields within ChemDisplayParam using menus and dialog boxes. However, the appearance to the user is that of a more traditional application with a menu bar and pulldown menus. miApp also allows users to write the scene graph to an Inventor file, select various portions of the molecular display using a ChemSelection node, copy all or portions of the scene graph to the X clipboard, drag and drop the scene graph into a second instance of miApp, and send the display of the chemical system to a printer or a file.

miApp is composed of four source files: main.c++, readPDB.c++, widget.c++, and scene.c++, plus a Makefile.

main.c++ 

Initializes X windows, Open Inventor, and Molecular Inventor, then parses the command line for arguments, and finally main.c++ enters the main event loop which handles user input.

readPDB.c++ 

Reads data from a file stored in the Protein Data Bank (.pdb) format. It stores this data in an instance of ChemData.

widget.c++ 

Builds the user interface for the application. It also handles the user interaction with menus (in the menuCB() function) and preferences dialog boxes (the pref...CB() functions). Most of this file consists of X windows code.

scene.c++ 

Creates and maintains the nodes in a scene graph. It generates a default scene graph (in createDefaultScene()) and updates this scene graph when a file is read (readFromFile()). It handles drag, drop, and copying to the clipboard (copySelection()). It also handles the case where the user has dragged the icon of a Protein Data Bank file (*.pdb) onto the miApp icon using the Indigo Magic desktop (parseIcon()). If Edit > Select or Edit > DeselectAll are selected, scene.c++ selects specific atoms (mySelect()) or deselects all atoms (myDeselectAll()), respectively. If File > Write is selected, writeToFile() writes the scene graph to an Open Inventor ASCII file. File > Print invokes myPrint() which uses SoXtPrintDialog() to execute a print job.


Note: scene.c++ automatically selects atoms indexed 10 through 19 when Edit > Select is used; if you do not have at least 20 atoms in the molecule, Edit > Select fails.

Refer to the code in each of these files for examples of how to implement the advanced features found in miApp.

While miApp uses the X toolkit and Motif to control many of the capabilities provided by Molecular Inventor, its scene graph differs very little from the one used for helloMI. The two differences are that miApp does not use ChemUI but it does use an additional ChemSelection node.

To gain greater control over the display of various portions of the chemical system data, you must use scene graphs of greater complexity than previously described.

Using More Complicated Scene Graphs

Figure 2-1 presents the simplest possible MI scene graph. It allows one set of data to be rendered in one display style. There are times, however, when you might want to

  • render portions of the same data set in more than one display style

  • render two or more data sets in different display styles

  • utilize Open Inventor nodes in conjunction with MI

These tasks can be accomplished by using more complicated scene graphs than that shown in Figure 2-1. It is important to be familiar with the manner in which scene graphs are traversed. This information is presented in Chapter 3 of The Inventor Mentor.

Rendering Portions of the Same Data Set in More Than One Display Style

If you want to render portions of the chemical system data in more than one display style, you need to include multiple ChemDisplay nodes each of which renders only a portion of the data. In order to render part of the data as wireframe and part as ball-and-stick, you might have a scene graph similar to the following:

Figure 2-5. Using Multiple ChemDisplay Nodes


Notice that there are two ChemDisplayParam and two ChemDisplay nodes in this figure. The first ChemDisplay (going from left to right) renders its data according to the values in the property nodes to its left. Therefore, if the first ChemDisplayParam node is set to render in wireframe, the data rendered by the first ChemDisplay appears as lines. The second ChemDisplay (the one on the far right) also uses the values set in the ChemBaseData, ChemRadii and ChemColor nodes, but the ChemDisplayParam immediately to its left overrides the values of the first ChemDisplayParam. Therefore, if this instance of ChemDisplayParam is set to render using ball-and-stick mode, the second ChemDisplay displays its data as spheres and cylinders.

Rendering Multiple Data Sets in Different Display Styles

While the previous example kept all of the chemical system data in a single instance of a ChemBaseData-derived node, you might find it advantageous to keep data sets separate. The following figure shows the display of an MI application where the hydrogen bonds (yellow lines), the other molecular data (red, white, and gray lines), and the box (blue lines) are maintained as three separate sets of data.

Figure 2-6. Three Sets of Data


The following diagram could represent the scene graph used in this display. In this diagram, Molecules, HBonds, and the Box are instances of a grouping node such as SoSeparator.

Figure 2-7. A More Complicated Scene Graph


This scene graph also shows that standard Open Inventor nodes can be mixed with Molecular Inventor nodes within an application in order to build a view of chemical data.