Using the viewer in a Qt application

VisIt is composed of several programs that cooperate: The GUI, CLI, viewer, metadata server, and compute engine. The viewer program is the central program to which all others talk. The viewer maintains the state of the entire application, draws the graphics, and commands the metadata server and compute engine in order to process data. The GUI is a Qt client application that exposes windows that let the user control all aspects of VisIt. The CLI client provides a Python scripting interface for controlling VisIt. Of course, both clients really let the user control the viewer. As implemented both the GUI and the CLI start the viewer and the viewer creates its own separate windows. Sometimes, however, users want to embed the viewer graphics in their own Qt application window. This can be achieved by using the viewer as a component in another application instead of as an independent program.

VisItViewer class

The VisItViewer class is a class in the viewer that provides methods for controlling the viewer. By using the VisItViewer class in an application, one can control VisIt's viewer. In fact, VisIt's viewer just uses VisItViewer class.

#ifndef VISIT_VIEWER_H
#define VISIT_VIEWER_H
#include <viewer_exports.h>
#include <string>
#include <qstring.h>

// Objects that we need publicly
#include <ViewerMethods.h>
#include <ViewerState.h>
#include <vtkQtRenderWindow.h>
#include <avtDatabaseMetaData.h>

// Private objects
class ViewerSubject;

// ****************************************************************************
// Class: VisItViewer
//
// Purpose:
//   This class provides an interface for controlling VisIt's viewer as an
//   embedded component in another application. There is limited access to 
//   various viewer pieces to ensure they remain encapsulated.
//
// Notes:      
//
// Programmer: Brad Whitlock
// Creation:   Mon Aug 18 11:43:42 PDT 2008
//
// Modifications:
//   
// ****************************************************************************

class VIEWER_API VisItViewer
{
public:
    // Initialize the viewer environment.
    static void Initialize(int *argc, char ***argv);

    // Construct the viewer.
    VisItViewer();

    // Destroy the viewer.
    ~VisItViewer();

    // Set the method for determining VISITHOME. VISITHOME is used to determine
    // where VisIt is installed so the application writer has the option of
    // telling the VisItViewer application where VisIt is located since the
    // application will not be located with VisIt itself except for when
    // the VisItViewer class is used in VisIt's own viewer application.
    typedef enum
    {
        FromEnvironment, // Get VISITHOME from environment (default)

        FromArgv0,       // Get VISITHOME from argv[0] path minus last path
                         // /path/to/app/bin/appname -> /path/to/app

        UserSpecified    // Set VISITHOME to user-specified path.
    } VISITHOME_METHOD;

    // Set the method used for determining VISITHOME [optional]
    // Must be called before ProcessCommandLine.
    void SetVISITHOMEMethod(VISITHOME_METHOD m);

    // Set the path to VISITHOME [optional]
    // Must be called before ProcessCommandLine.
    void SetVISITHOME(const std::string &);

    // Set viewer options from the command line [optional]
    void ProcessCommandLine(int argc, char **argv);

    // Connect back to the client that launched the viewer [optional]
    void Connect(int *argc, char ***argv);

    // Finish setting up the viewer
    void Setup();

    // Removes the crash recovery file.
    void RemoveCrashRecoveryFile() const;

    // Returns whether the viewer was started with the -nowin flag.
    bool GetNowinMode() const;

    // Access the viewer's methods at once. The methods block until finished
    ViewerMethods *Methods() const;

    // Access the viewer's methods but call them in a delayed manner from the 
    // main event loop. Call methods through this object if you are doing an
    // operation that can be posted for execution but executed later. Use this 
    // For example, use this method if you are setting plot attributes or 
    // something that causes recalculation on the compute engine. Methods called
    // through this interface return immediately but are not executed immediately.
    ViewerMethods *DelayedMethods() const;

    // Access the viewer's state. No Notify is needed.
    ViewerState   *State() const;

    // Access the viewer's state. Changes to these state objects require a 
    // Notify() to post them into the event loop where they can be safely
    // used.
    ViewerState   *DelayedState() const;

    // Get a file's metadata.
    const avtDatabaseMetaData *GetMetaData(const std::string &hostDB, int ts=-1);

    // Set a window creation callback function. Note that you only need to
    // supply this function if you want to embed vtkQtRenderWindow windows
    // into your own GUI interface.
    void SetWindowCreationCallback(vtkQtRenderWindow* (*wcc)(void *),
                                   void *wccdata);

    // Send an error message
    void Error(const QString &);

    // Send a warning
    void Warning(const QString &);

    // Methods to query plot plugin names.
    int         GetNumPlotPlugins() const;
    std::string GetPlotName(int index) const;
    int         GetPlotIndex(const std::string &plotName) const;

    // Methods to query operator plugin names.
    int         GetNumOperatorPlugins() const;
    std::string GetOperatorName(int index) const;
    int         GetOperatorIndex(const std::string &operatorName) const;

    // Get the VisIt command
    std::string GetVisItCommand() const;

    // Finalize the viewer environment.
    static void Finalize();

private:
    std::string GetVisItHome() const;

    VISITHOME_METHOD visitHomeMethod;
    std::string      visitHome;
    ViewerSubject   *viewer;
};

#endif

Using the VisItViewer class

The VisItViewer class provides methods for controlling various aspects of the viewer. Relatively few methods need to be called in order to set up the viewer. This section talks about some of the common methods needed to set up and use VisItViewer. The order of functions in the table indicate the order in which they should be called in a main program.

VisItViewer Method Purpose Optional (yes/no)
VisItViewer::Initialize(&argc, &argv) Pass the main function's argc,argv to the VisItViewer class to perform one time initialization such as setting up VisIt's debugging logs. This function must be called prior to instantiating a VisItViewer object. no
SetVISITHOMEMethod()

When you're using the VisItViewer class in your own application, it means that some extra information is needed in order to set up the VisIt environment. Normally, VisIt's launch scripts initialize the VISITHOME and VISITPLUGINDIR environment variables. The default behavior of VisItViewer is to assume that both of those environment variables are set up, which works in VisIt's viewer but probably won't work for you.

You have the option of using either FromArgv0 or UserSpecified. If you use FromArgv0, VisItViewer assumes will take argv0 and remove the last directory from it, using the resulting value for VISITHOME. This means that the resulting directory better contain bin and plugins directories. If you use the UserSpecified option then you must pass the path for VISITHOME to VisItViewer using the SetVISITHOME() method. If the path to the VisIt launch script is: /usr/local/apps/visit/bin/visit, you would call SetVISITHOME("/usr/local/apps/visit").

yes
Connect(&argc, &argv) This method tells the VisItViewer object to connect back to a client program such as VisIt's GUI or CLI. This method does not need to be called unless you expect your application to always be launched by a VisIt client. Most applications will not call this method. yes
ProcessCommandLine(argc, argv) This method processes any command line options supported by VisIt's viewer (e.g. -nowin, -noconfig, -locale, ...) This method does not need to be called but it is recommended. yes
Create your QApplication. If you plan to embed VisItViewer's vtkQtRenderWindows in your own widget layout then you must do that before Setup() is called.
Setup() This method performs the rest of the viewer's setup, creating the first visualization window. no
qApp->exec(); // The Qt event loop
VisItViewer::Finalize() This function performs one time clean up for the VisItViewer class. no

Example main program

Here is an example main program that can be used as a template for other applications that want to use VisItViewer. The order of calls to VisItViewer is significant for this example but there is some freedom in the order in which you can mix Qt functions and VisItViewer functions.

#include <qapplication.h>

#include <visitstream.h>
#include <VisItViewer.h>
#include <VisItException.h>

#include <SimpleVisApp.h>

int
main(int argc, char *argv[])
{
    int retval = 0;

    VisItViewer::Initialize(&argc, &argv);

    TRY
    {
        VisItViewer viewer;
        viewer.ProcessCommandLine(argc, argv);

        QApplication *mainApp = new QApplication(argc, argv);

        //
        // Create our visualization app. We have to do it before the call to Setup()
        // since we're embedding vis windows.
        //
        SimpleVisApp *visapp = new SimpleVisApp(&viewer);

        viewer.Setup();
        visapp->show();
        retval = mainApp->exec();
    }
    CATCH2(VisItException, e)
    {
        cerr << "VisIt's viewer encountered the following fatal "
                "initialization error: " << endl
             << e.Message().c_str() << endl;
        retval = -1;
    }
    ENDTRY

    VisItViewer::Finalize();

    return retval;
}

Finding VisIt

The VisItViewer class needs to know where VisIt is located so it can find VisIt's plugins and other components such as the metadata server and compute engine. By default, the VisItViewer class assumes that the VISITHOME environment variable will be set. This is adequate for VisIt itself but that's not likely to be the case for other applications that use the VisItViewer class. If you set the method for determining VISITHOME, you must do it before calling VisItViewer::Setup().

The VisItViewer class provides different options for finding the value for VISITHOME.

VISITHOME_METHOD Description
FromEnvironment Get VISITHOME from environment variable (default)
FromArgv0 Get VISITHOME from argv[0] path minus last path: /path/to/app/bin/appname -> /path/to/app. This means that VisItViewer will assume that the lib and plugins directories will be peer to the bin directory in the path as they would be in a VisIt installation.
UserSpecified The calling application provides the path to the directory that contains VisIt's bin, lib, plugins directories.

Building the main program

VisIt's viewer uses many libraries from VisIt and VTK. This table lists some of the variables that you will need to use in your Makefile in order to build your application with support for VisItViewer.

Makefile variable Value
VISITARCH Path to the installed VisIt architecture directory. Example: /usr/apps/visit/1.10.0/linux-intel
CXXFLAGS -I$VISITARCH/include -I$VISITARCH/include/visit -I$VISITARCH/include/vtk
LDFLAGS -L$VISITARCH/lib -Wl,-rpath,$VISITARCH/lib
VTK_LIBS -lvtkCommon -lvtkDICOMParser -lvtkFiltering -lvtkGraphics -lvtkHybrid -lvtkIO -lvtkImaging -lvtkRendering -lvtkzlib -lvtkexpat -lvtkjpeg -lvtkpng -lvtktiff -lvtkftgl -lvtkfreetype -lvtksys -lvtkMPEG2Encode -lOSMesa -lMesaGL -lGL -lGLEW
LIBS -lavtdatabase_ser -lavtdbatts -lavtfilters_ser -lavtivp -lavtmath -lavtmir_ser -lavtpipeline_ser -lavtplotter_ser -lavtqtviswindow -lavtview -lavtviswindow_ser -lavtwriter_ser -lengineproxy -lenginerpc -llightweight_visit_vtk -lmdserverproxy -lmdserverrpc -lvclproxy -lvclrpc -lviewer -lviewerrpc -lvisit_verdict -lvisit_vtk -lvtkqt ${VTK_LIBS} -lz -lm -lpthread

Simple application

Now that we've discussed some of how the VisItViewer class is created, let's use it to create a simple application that can plot scalar variables using the Pseudocolor plot or the Contour plot. The visualization window will be embedded in the application's main window, there will be controls for selecting the plotted variable, and for setting the number of contours. Look here for a code listing for the example program.

SimpleVisApp.png

Embedding the visualization window

Normally, VisIt creates top level windows for its visualization windows. It is possible to embed the visualization windows in your own widget layout. You can create your own instances of vtkQtRenderWindow and then register a window creation callback function with VisItViewer. When the window creation callback has been provided, VisIt will call that callback function when it needs to create windows. Your job as the callback function writer is to return your instance of vtkQtRenderWindow so that VisIt will associate its visualization window with your widget. As long as you do not delete visualization windows, they are requested sequentially from the callback function: 1, 2, 3, ...

The create window callback function is registered using the VisItViewer::CreateWindowCallback method. The CreateWindowCallback method must be called before the VisItViewer::Setup method in order to register the window creation callback before the first visualization window is created in the Setup() method.

// Excerpt from SimpleVisApp constructor
SimpleVisApp::SimpleVisApp(VisItViewer *v)
{
    viewer = v;

    // Create the vis window directly.
    viswin = new vtkQtRenderWindow(central);
    viswin->setMinimumSize(QSize(500,500));
    hLayout->addWidget(viswin, 100);
 
    //
    // Register a window creation function (before Setup) that will
    // return the vtkQtRenderWindow objects that we've already
    // parented into our interface.
    //
    viewer->SetWindowCreationCallback(ReturnVisWin, (void *)this);
}

// Static method that serves as the window creation callback function.
// Note that the "viswin" pointer is returned as the vtkQtRenderWindow
// object that VisIt will use. We created the "viswin" vtkQtRenderWindow
// ourselves as a widget in our window and we return its pointer when
// VisIt needs a new visualization window.
vtkQtRenderWindow *
SimpleVisApp::ReturnVisWin(void *data)
{
    SimpleVisApp *This = (SimpleVisApp *)data;
    return This->viswin;
}

Showing the visualization window

Now that you can embed a visualization window in your own widgets, there's still another step for making VisIt actually use the visualization windows. There's still the matter of telling VisIt to "show" its windows, which performs the last stages of window initialization for VisIt. The missing function call is to the ShowAllWindows() viewer method. In addition, you may optionally want to hide all of VisIt's toolbars before showing your application window in order to maximize the size of the visualization given the available space for the visualization window widget.

It's best to include this code in your application window's show() method.

void
SimpleVisApp::show()
{
    // Tell the viewer to hide toolbars in all windows.
    viewer->Methods()->HideToolbars(true);
    // Tell the viewer to show all windows. This does not show our windows
    // since our windows are embedded but it does do some extra setup for
    // the windows and thus needs to be called.
    viewer->Methods()->ShowAllWindows();

    QMainWindow::show();
}

Embedding multiple windows

Embedding multiple visualization windows is little different than embedding a single visualization window but there are some additional issues to be aware of. First of all, you need to create multiple vtkQtRenderWindows and make sure your window creation callback returns them. It's best to associate each vtkQtRenderWindow instance with a meaningful identifier that you can use later in VisIt's SetActiveWindow method. In the example below, there are 2 embedded visualization windows and we choose 1-origin integer identifiers that are used in the SetActiveWindow method as well as to index into the viswindows array that contains the pointers to the vtkQtRenderWindow objects. The window creation callback, ReturnVisWin, takes care to return one vtkQtRenderWindow pointer after another.

Next, the window creation callback is called the total number of NWINDOWS times by adding NWINDOWS-1 calls to the AddWindow() method in the application's show() method.

Finally, when there are multiple visualization windows in the application, operations destined for a particular visualization window should be preceded by a call to SetActiveWindow() to ensure that they go to the right visualization window. Note how in the onSelectVariable method, we set the active window using the same PSEUDOCOLOR_WINDOW and VOLUME_WINDOW identifiers before actually creating plots for the respective windows.

See the sources at:

Using ViewerMethods and ViewerState

Like the ViewerProxy class used to create new VisIt client applications, the VisItViewer class provides access to ViewerMethods and ViewerState classes. These two classes are used to control the viewer by providing methods that can be called and state objects whose values can be set.

The ViewerMethods object is used for making method calls on the viewer such as: OpenDatabase, AddPlot, DrawPlots. For more information on the available methods and their usage you can look at the Java API documentation for ViewerMethods, which is nearly identical to the C++ interface.

The ViewerState object is a collection of other state objects, each of which is used to set values that VisIt uses during its operations. State objects are also useful for observation since they implement the Subject interface. This means that you can create objects or Windows that implement the Observer interface and attach them to state objects. When a state object is modified, it will notify its observers, which would let your object or window update itself using the state object's new values.

Immediate versus Delayed

The VisItViewer class provides two ways to access ViewerMethods and ViewerState: immediate and delayed. More on this can be seen by looking at the Viewer's design. When you use the immediate version of the objects, the operations or setting of state takes place immediately. For example, when you call ViewerMethods through the immediate interface using the VisItViewer::Methods() method, called methods will usually block until the operation is complete. There are instances however where further event processing can occur as a result of calling an immediate method. For example, when you call a method that results in the execution of a plot on the compute engine, events are processed while the compute engine executes in order for your application to continue to function and not appear hung.

Of course, since events are being processed, this means that while your application is waiting for the ViewerMethods to return, you could actually interact with other widgets in your program and possibly enter another slot function that can cause a complex operation. This is very bad and will likely cause the compute engine and VisIt viewer interface to deadlock.

To avoid the problem of deadlock, sometimes you will want to execute viewer methods in a delayed manner. That is, you'll want to execute the methods but have them be scheduled for execution when VisIt deems it safe. The VisItViewer class provides the DelayedMethods and DelayedState methods for accessing the delayed versions of the ViewerMethods and ViewerState objects. When you use the delayed versions of those objects, the following are true:

  • ViewerMethods do not block until complete; they are scheduled to run later when it is safe
  • Setting state object values requires a Notify() method to be called in order for the state to be scheduled to be set later.

The combination of immediate and delayed methods in a single function is not well understood since the delayed methods are called at VisIt's convenience.

Setting plot attributes

Setting plot attributes is a good example of an operation that can cause a plot to be reexecuted on the compute engine. Operations that cause the plot to be reexecuted can sometimes take a long time and thus can allow event processing during their execution. To be safe, use the delayed version of ViewerMethods to prevent any problems with recursively calling the compute engine.

Plot attributes are state objects the same as other state objects in VisIt. In other VisIt clients such as the GUI and CLI, there are GUI and CLI plugins that allow plot attributes to be directly manipulated using their exact C++ type. Since our application is not manipulating state objects from within a plugin, it is not safe to use plugin state object types explicitly. One must instead set plot and operator state object values via the AttributeSubject base class, which provides various SetValue methods for setting state object attributes given an attribute's name.

Setting plot attributes is done using the SetPlotOptions method in ViewerMethods. The plot is identified using its integer index in the plugin manager. This index can be called using VisItViewer::GetPlotIndex and passing the name of the plugin of interest.

void
SimpleVisApp::setNContours(int nc)
{
    // Get the plot index from the name "Contour"
    int Contour = viewer->GetPlotIndex("Contour");

    // Get the plot's delayed state object.
    AttributeSubject *contourAtts = viewer->DelayedState()->GetPlotAttributes(Contour);
    if(contourAtts != 0)
    {
        // Set the "contourLevels" field in the state object to the new number of contours
        contourAtts->SetValue("contourNLevels", nc);

        // Insert the state into the queue so it will be set when appropriate. This is
        // only necessary because we're using the delayed state.
        contourAtts->Notify();

        // Set the plot options for the Contour plot.
        viewer->DelayedMethods()->SetPlotOptions(Contour);
    }
}