Plugins [OpendTect Programmer's manual V4.2]

OpendTect Plugins

Intro | Concept | Hello World | Menus and Dialogs | The Tutorial plugin | Help Doc Creation | Installation and auto-loading

Intro

Background

Making your own software within OpendTect is in principle pretty easy. You could change the software by modifying existing classes and functions, and adding your own stuff to the libs. The advantage is total control. The problem with this approach, however, is that you have to keep the OpendTect sources in sync with new releases. Furthermore, if you cannot convince the opendtect.org people to also make those changes, OpendTect users may not be happy with your work.

An easy way to overcome this is to make your own plugins. Plugins make use of all the facilities of OpendTect but are loaded at run-time and can therefore be developed in a completely independent way. If you then find things that can't be done without modifying the OpendTect environment, it should be much easier to convince the opendtect.org people to take over or even implement those things themselves.

One thing you cannot do, is use another compiler than gcc/g++. OpendTect is built with it, if you want to use another compiler (why?) you'll have to make all libs and supporting libs (Qt, COIN, fftw) yourself. The make itself should be pretty easy to get started, but there will probably be some porting to do, too.



The concept

Dynamic loading

All modern Operating systems nowadays have ways to dynamically load libraries into a running program. The basic idea is:

As an example, a 'hello world' program could conceptually look like this:



dynlib = OpenDynamicLib( "libc.so" );
PrintfTypeFunction fn = (PrintfTypeFunction)GetDynLibFn( dynlib, "printf" );
fn( "Hello world\n" );

In this very simple case calling the function has no other effect than printing a string. In OpendTect, you'd want to add an attribute, create a menu item in the Opendtect menu, start horizon tracking, that sort of thing.

OpendTect plugins

In OpendTect, all of the dynamic lib querying etc. is already programmed. A plugin just needs to contain a few standard functions that will be called automatically when the dynamic library is loaded. There are three functions, of which only one is really required. Let's say the name of the plugin is MyMod, these functions will be:

GetMyModPluginType
GetMyModPluginInfo
InitMyModPlugin

Only the last one is required. The first one, Get...PluginType determines whether the plugin can be used for the auto-load, and if so, when. 'When' means: before or after the program has created the OpendTect GUI objects (and always after the static objects are initialised). The second function simply provides info for the users of your plugin.

Note that the standard plugin functions must be declared 'extern "C"', as you can see in the first example plugin "Hello":

#include <iostream>
extern "C" const char* InitHelloPlugin( int, char** )
{
    std::cout << "Hello world" << std::endl;
    return 0; // All OK - no error messages
}

You'll find this example plugin in the plugins directory of your work environment. Those 6 lines are enough to constitute a plugin for OpendTect. The Makefile to build the plugin looks like this:

SRC.cc := hellopi.cc
PLUGIN := yes
include make.od.Defaults
include make.Targets

First try make -n (make sure you have Pmake initialised!). Notice that Pmake makes an awful lot of commands with a small specification. This is because Pmake includes loads of make-code.

An important remark here is that Pmake supports plugin generation fully when the subdirectory name is the same as the plugin (base-)name. Thus, subdirectory MyPlugin generates libMyPlugin.so (or libMyPlugin.dylib or MyPlugin.dll) which should contain an InitMyPlugin() function.

Anyway, after typing make the plugin should be generated. The new plugin can be loaded from within OpendTect (Menu Utilities-Plugins; press the 'Load new' button). At that point in time, the message 'Hello world' should appear on stdout (on Windows, stdout messages appear on the OpendTect console window).

To make this a UI - based program we'll need to use functionality from the uiBase module. In this case, we use the uiMSG() utility:

#include "uimsg.h"
extern "C" const char* InituiHelloPlugin( int*, char** )
{
    uiMSG().message( "Hello world" );
    return 0; // All OK - no error messages
}

Again 6 lines, but now, when you load it, you'll get a popup message with an OK button.

In this case we actually use stuff from OpendTect libraries, so the Makefile needs to change. We need to add the line:

MODDEP := uiBase 
Notice that this is added before the make inclusions: It's a directive to establish all kinds of variables in the make includes. By typing make -n again you'll see that Pmake added some directives for the compiler.

If you really want to see this simple thing working, make sure you add: OWNC++FLAGS := -DPLAN_A to the make file (or specify make OWNC++FLAGS=-DPLAN_A on the command line).


Realistic examples

Menu and Dialog

Intro

Adding menu items popping up your own dialogs is pretty easy once you get the hang of it. Although we fear that you might be scared by this code when you first see it, we hope that after the explanations below you will see some beauty in it and start to like it.

Look in uihellopi.cc - the else of the PLAN_A part. First of all, we nicely define the other standard plugin functions:

extern "C" int GetuiHelloPluginType()
{
    return PI_AUTO_INIT_LATE;
}

extern "C" PluginInfo* GetuiHelloPluginInfo()
{
    static PluginInfo retpi = {
        "uiHello plugin - plan B",
        "Bert",
        "1.1.1",
        "This is the more extensive variant of the uiHello example.\n"
        "See the plugin manual for details." };
    return &retpi;
}
The plugin will now
auto-load when added to the auto-loaded plugins.

The idea is to add a menu item 'Display Hello Message ...' to the 'Utilities' menu of OpendTect. The Makefile and the example code are already prepared for this. The MODDEP := uiODMain tells Pmake that the uiODMain headers, and the headers uiODMain depends on will be used. That is much more than the original uiBase dependencies. uiODMain is the top level of OpendTect.

If "make" does not compile anything (because a uiHello.so already exists), you can try "make uihellopi.rmlib" first.

Problems

The two problems we are faced with are:
  1. How to add a new menu item
  2. How to create a dialog

The real problem behind the first is not so much how to make the item itself, but how to get hold of the parent menu it must be inserted into. This is a threshold that needs to be conquered for every new system: you'll need an entry point, a seed to get you going. In OpendTect, the entry point is the global instance of the uiODMain class that can be accessed through the uiODMain* ODMainWin() global function. Once you have that, you can reach any object you need.

To see where things start, go to the InituiHelloPlugin function at the bottom of uihellopi.cc to find:

extern "C" const char* InituiHelloPlugin( int, char** )
{
    (void)new uiHelloMgr( ODMainWin() );
    return 0; // All OK - no error messages
}

As you can see, the ODMainWin() instance is passed to a new instance of uiHelloMgr which is programmed in the lines above it. This Manager object is needed as you will find that most non-trivial GUI plugin will need some kind of top-level 'application management' object to drive it.

The uiHelloMgr

An important part of any GUI system is callback handling. In OpendTect, callbacks are basic objects used throughout the system, not only in the User Interface. In any case, because callbacks can only be sent to objects that are CallBackers, we have made uiHelloMgr a descendent of CallBacker, so it can receive the callback from the menu. When the uiHelloMgr is constructed, it will add a menu item to the OpendTect menu:

    uiMenuItem* newitem = new uiMenuItem( "&Diplay Hello Message ...",
                                          mCB(this,uiHelloMgr,dispMsg) );
    appl.menuMgr().utilMnu()->insertItem( newitem );

The new menu item is defined by the display text (the '&' makes the 'D' the shortcut key) and the CallBack that needs to be called when the item is activated (by the user). Note the mCB which is a macro that makes the rather difficult C++ notation easy to understand. See callback.h in the Basic include directory if you're really interested.

The next line inserts the item into the 'Utilities' menu. You'll find a bunch of methods in the uiODMenuMgr class to access all menus and toolbars.

The uiHelloMsgBringer

Most dialog windows in OpendTect are objects of type uiDialog. uiDialog inherits from uiMainWin, the base class without Ok and Cancel buttons, a window title, and so forth (all of those are optional BTW). The uiHelloMsgBringer class is such a uiDialog. The class contains a field for user input of the string to display as message. To make the example a bit lively, we added a field whether the message should be, eh, just a message, or a warning. You can see the uiGenInput class will serve both input fields nicely, you just need to define another InpSpec instance:

txtfld = new uiGenInput( this, "Hello message",
                         StringInpSpec("Hello world") );
typfld = new uiGenInput( this, "Message type",
                         BoolInpSpec(true,"Info","Warning") );
typfld->attach( alignedBelow, txtfld );

Note that "Hello world" is the inital string displayed in txtfld. Another feature of the OpendTect UI is the automatic layout: we program the positions of the various fields by attaching them in certain ways - most commonly alignedBelow.

The actual message is displayed when the user presses Ok on the dialog that pops up. When nothing is filled in, we give the user the finger with the "Please type a text" message, and we do not accept the Ok, i.e. the dialog remains on the screen and the user can either be a good girl (or boy) and fill in something or press cancel.

if ( ! *typedtxt )
{
    uiMSG().error( "Please type a message text" );
    return false;
}

When a message is present, we do as we're told:

if ( typfld->getBoolValue() )
    uiMSG().message( typedtxt );
else
    uiMSG().warning( typedtxt );
return true;

and the dialog disappears immediately after the message appears on the screen and the user clicks OK.



The Tutorial plugin

Intro

Now that we've seen some basic plugin stuff, and some basic GUI stuff, we can move on to a real plugin that looks like something that could actually be useful. For this purpose, we have created the Tutorial plugins. As is common in OpendTect, there is a plugin 'Tut' for non-ui, real-work stuff, and the 'uiTut' for the GUI part.

The idea of the tutorial plugins is to show a variety of common things that one might want to do, rather than make something useful for end-users. For that we'll make the following tools:

In the process, we'll see how to:

The uiTut plugin

In the uiHello plugin, you have already seen how to add a menu item and how to create a dialog. In uiTut, the GUI consists of two parts. One is similar to that in uiHello and deals with opening an independent dialog box via a menu item in the 'Utilities' menu. The other part gets the 'Tutorial' attribute listed in the 'Edit Attributes' dialog and creates the input fields in the same dialog box. It also sends the input parameters to Tutorial for attribute computation

Let us first have a look at the independent dialog part which in turn has two parts -- one for seismic tools and the other for horizon tools. the insertion of menu item and opening of dialog is similar to that in uiHello. The only interesting part is the uiIOObjSel class which allows you to select an item from a set -- a horizon or a seismic cube ( subclass uiSeisSel is used for seismic cube selection).

Both uiSeisTools and uiHorTools use the class uiTaskRunner, which triggers the Executor's in the Tut plugin. The class uiTaskRunner also displays a progress bar which keeps the user informed about the progress of the process.

Now we come to the attribute part. In the uitutorialattrib.cc file we see that although uiAttrDescEd is not a uiDialog like the the uiHello example, it still is a valid parent (being a uiGroup) for the various UI elements. A nice feature of OpendTect is clear from the first line in the constructor: the inpfld is a special Attribute UI class which is handled just like any pre-defined uiBase or uiTools class. This illustrates that in the OpendTect GUI system, not only pre-made GUI elements are 'first class' - new objects with different shape and behavior attached will be usable transparently by any other GUI class.

Coming to the plugin 'main' file uitutpi.cc, you will find it very similar to uihellopi.cc. Like any typical UI plugin, uiTut is a LATE plugin, which means that it will be loaded only after the rest of the UI is already in place.

extern "C" int GetuiTutPluginType()
{
    return PI_AUTO_INIT_LATE;
}

Then comes the second 'special' plugin function GetxxxPluginInfo(). You may want to refer to the definition of the class PluginInfo for a better understanding of the above function. It allows the plugin manager to make this info available to the world.

extern "C" PluginInfo* GetuiTutPluginInfo()
{
    static PluginInfo retpi = { 
        "Tutorial plugin development",
        "dGB (Raman/Bert)",
        "4.2",
        "Shows some simple plugin basics." }; 
    return &retpi;
}

And the last 'special' function is the one which gets things going:

extern "C" const char* InituiTutPlugin( int, char** )
{
    static uiTutMgr* mgr = 0; if ( mgr ) return 0;
    mgr = new uiTutMgr( ODMainWin() );

    uiTutorialAttrib::initClass();
    return 0;
}

The Tut plugin

The responsibility of uiTut is limited to talking to the user and getting the input parameters. The real work is done behind the scene by the non-UI Tut plugin. And that is the reason why it is of type EARLY. This particular plugin tells OpendTect's application manager that it wants to be loaded early - i.e. before any build of tables, data structures or user interfaces are made. That is typical of 'Real Work' plugins. The alternatives are NONE (which is default so in that case a GetxxxxPluginType() need not be defined) and LATE, which is typical for UI plugins that want to start working when all objects have already been created.

extern "C" int GetTutPluginType()
{
    return PI_AUTO_INIT_EARLY;
}

SeisTools

Let us first look at the direct seismic operations, that are handled by the class SeisTools, which in turn is a subclass of class Executor. 'Real work' is done by the function nextStep() which is typical of class Executor. Here, three different operations are possible: Scaling, where you can multiply the data values by a certain factor and apply a shift; Squaring, where as the name suggests, you can take a square of the data values; and Smoothening, where you can take the arithmatic average of 3 or 5 samples depending on the filter strength. Traces are read one-by-one by a SeisTrcReader and supplied to the function handleTrace() where the actual computation is done. Then a SeisTrcWriter writes the output traces on-by-one to the output cube.

int Tut::SeisTools::nextStep()
{
    if ( !rdr_ )
        return createReader() ? Executor::MoreToDo : Executor::ErrorOccurred;

    int rv = rdr_->get( trcin_.info() );
    if ( rv < 0 )
        { errmsg_ = rdr_->errMsg(); return Executor::ErrorOccurred; }
    else if ( rv == 0 )
        return Executor::Finished;
    else if ( rv == 1 )
    {
        if ( !rdr_->get(trcin_) )
            { errmsg_ = rdr_->errMsg(); return Executor::ErrorOccurred; }

        trcout_ = trcin_;
        handleTrace();

        if ( !wrr_ && !createWriter() )
            return Executor::ErrorOccurred;
        if ( !wrr_->put(trcout_) )
            { errmsg_ = wrr_->errMsg(); return Executor::ErrorOccurred; }
    }

    return Executor::MoreToDo;
}

Scaling and squaring are single-sample operations. But as you can see in the implementation of the function handleTrace() below, smoothening involves multi-sample computation. It requires separate input and output traces. Otherwise, if we did the operation on the same trace, we would be taking the modified values of samples preceding the current sample. For the sake of simplicity, we make a copy of the input trace to store the output values. This is not a good practice as it results in duplication of data. But since it is a tutorial, our aim is to keep the code as simple as possible and leave the efficiency part for serious programming.

void Tut::SeisTools::handleTrace()
{
    switch ( action_ )
    {

    case Scale: {
        SeisTrcPropChg stpc( trcout_ );
        stpc.scale( factor_, shift_ );
    } break;

    case Square: {
        for ( int icomp=0; icomp < trcin_.nrComponents(); icomp++ )
        {
            for ( int idx=0; idx < trcin_.size(); idx++ )
            {
                const float v = trcin_.get( idx, icomp );
                trcout_.set( idx, v*v, icomp );
            }
        }
    } break;

    case Smooth: {
        const int sgate = weaksmooth_ ? 3 : 5;
        const int sgate2 = sgate/2;
        for ( int icomp=0; icomp < trcin_.nrComponents(); icomp++ )
        {
            for ( int idx=0; idx < trcin_.size(); idx++ )
            {
                float sum = 0;
                int count = 0;
                for( int ismp=idx-sgate2; ismp <= idx+sgate2; ismp++)
                {
                    const float val = trcin_.get( ismp, icomp );
                    if ( !mIsUdf(val) )
                    {
                        sum += val;
                        count++;
                    }
                }
                if ( count )
                    trcout_.set( idx, sum/count, icomp );
            }
        }

    } break;

    }

    nrdone_++;
}

HorTool

Similar to SeisTools, HorTool performs some simple operations on horizons: thickness computation and smoothening. Each of these operations is handled by a subclass of HorTool which is a subclass of Executor and as expected the computation is performed by the function nextStep(). You may notice here that no object of class HorTool is defined anywhere. It is only used as the base class for classes ThicknessCalculator and HorSmoothener. Let us have a look at the nextStep() function in class ThicknessCalculator to see how the data values are accessed in a Horizon3D:

int Tut::ThicknessCalculator::nextStep()
{
    if ( !iter_->next(bid_) )
        return Executor::Finished();

    int nrsect = horizon1_->nrSections();
    if ( horizon2_->nrSections() < nrsect ) nrsect = horizon2_->nrSections();

    for ( EM::SectionID isect=0; isectgetPos( isect, subid ).z;
        const float z2 = horizon2_->getPos( isect, subid ).z;

        float val = mUdf(float);
        if ( !mIsUdf(z1) && !mIsUdf(z2) )
            val = fabs( z2 - z1 ) * usrfac_;

        posid_.setSubID( subid );
        posid_.setSectionID( isect );
        horizon1_->auxdata.setAuxDataVal( dataidx_, posid_, val );
    }

    nrdone_++;
    return Executor::MoreToDo();
}

Please note the difference in the function dataSaver in the two classes. In ThicknessCalculator, it saves the auxilary data, whereas in HorSmoothener, it saves the geometry.

The Tutorial Attribute

We have seen the direct seismic approach to simple operations on seismic data in SeisTools. For our purpose, it suits well. But the main problem with this approach is the difficulty in multi-trace handling. Moreover, for large seismic volumes, handling each trace one-by-one may slow down the process. This brings us to another approach called Attributes. In this example, we define the Tutorial attribute to do things once done by SeisTools. As we discuss different aspects of making an attribute, we will also discuss its advantages over the direct seismic approach.

The main plugin file "tutpi.cc" makes a call to Tutorial::initClass(). The class Tutorial ( tutorialattrib.h ) is defined as a subclass of Attrib::Provider class. Every attribute is a provider, each can thus be used as input for another attribute.

Steering

A Steering cube, as the name suggests, works as a guiding cube. It stores the Inline dip and Crossline dip at each point, which guides the attribute engine in multi-trace computations. In case of our Tutorial attribute, we can use the steering data for horizontal smoothening. The key function is initSteering() which makes the steering data available in the form of shifts relative to the central trace. To understand how this shift is used during computation, please refer to the horizontal smoothening section in the function computeData().

Some fundamental attribute functions are listed here:

initClass()

This static function initializes the attribute: sets up the parameters and the number and type of the inputs and outputs. You can compare this to what you see in Opendtect in the attribute definition window after loading the uiTut plugin.

If you look at the parts of the implementation carefully, ( tutorialattrib.cc ) you'll see that each parameter is built up following this example:

EnumParam* action = new EnumParam( actionStr() );
    action->addEnum( "Scale" );
    action->addEnum( "Square" );
    action->addEnum( "Smooth" );
    desc->addParam( action );

Every parameter is required by default, to overrule this use setRequired(false)"

initClass() also adds the attribute to the attribute factory. In this case, as every attribute is a provider, the Tutorial attribute is added to PF() (the Attrib::ProviderFactory singleton access function).

createInstance()

This function is standard for every attribute, here is the attribute constructor called.

updateDesc()

Will be used not only to update the parameters but also the number and type of the outputs and to add or disable some inputs. If you look at the implementation for the tutorial attribute, this function just allows to enable or disable the inputs ( factor, shift and smooth ) according to the action chosen by the user

getInputOutput()

we need to define this initialization function because we have Steering. Steering always carries two outputs and we need them both.

initSteering()

If we are using steering data, this function prepares the steering input for use in computation. A subvolume is generated around the central trace, with the size of the subvolume specified by the stepout. This data contains the shifts in terms of number of samples for each trace in the subvolume relative to the central trace.

void Tutorial::initSteering()
{
    if ( inputs[1] && inputs[1]->getDesc().isSteering() )
        inputs[1]->initSteering( stepout_ );
}
getInputData()

Before the work can be done, some input has to be given. This function is the place where you specify how to get your input data. For the Tutorial this is the seismic data. But it can also be Steering Data or any other attribute.

bool Tutorial::getInputData( const BinID& relpos, int zintv )
{
    if ( inpdata_.isEmpty() )
        inpdata_ += 0;
    const DataHolder* data = inputs[0]->getData( relpos, zintv );
    if ( !data ) return false;
    inpdata_.replace( 0, data);


    if ( action_ ==2 && horsmooth_ )
    {
        steeringdata_ = inputs[1] ? inputs[1]->getData( relpos, zintv ) : 0;
        const int maxlength  = mMAX(stepout_.inl, stepout_.crl)*2 + 1;
        while ( inpdata_.size() < maxlength * maxlength )
            inpdata_ += 0;

        for ( int idx=0; idxgetData( relpos + posandsteeridx_.pos_[idx] );
            if ( !data ) continue;
            inpdata_.replace( posandsteeridx_.steeridx_[idx], data);
        }
    }

    dataidx_ = getDataIndex( 0 );

    return true;

}

You will notice from here that the calculation of the attributes is not done on traces but using a different object, the DataHolder. The dataholder contains a set of ValueSeries which holds the value of every sample of the SeisTrc. Advantage: in case of an attribute which has other attributes as inputs, data is available in the corresponding dataholders, it thus saves a lot of time ( easier and much faster to read some floats in a ValueSeries than to get values from a SeisTrc ). Stored data are read from cubes of seismic traces and written the same way.

The DataHolder is also carrying some specific information about the trace to be processed, like the start sample number and the number of samples you wish to calculate.

Another important remark: calculation is made using sample numbers, not time or depth

Most of the rest of the methods are there to comply with the Attrib::Provider interface - see the Attrib::Provider documentation. The basic idea is that for each sample of each trace one or more attribute values can be calculated. The number of attribute values (or outputs) is defined in the initClass() function. If your input requires additional samples (timegate) or neighbouring traces (stepout), you will have to define reqZMargin() and reqStepout() respectively.

computeData()

When we want to look at the actual work, the place to be is the computeData() method. This is the place where you define the mathematics for calculating the attribute. This function is called for each trace of your output cube.

In the computeData() method, we are faced with a number of Z ranges. To be able to support multi-threading, computeData must be ready to only process part of the trace. Then, also, we can have input cubes that are larger than requested or desired, or smaller than that. This delivers a rather nasty picture of Z indexes that we really cannot circumvent. To make things at least clear, the indexes are all related to the the absolute Z=0. This is where everything refers to. Then, we have different start Z indexes for each of the input cubes and the output cube. These are named 'z0_' in the corresponding DataHolders.

Explaining Z0

Let us have a look at the Tutorial::computeData function and compare it with the code in SeisTools. The algorithm for actual computation is the same in both the cases, but there is a marked difference in the manner in which seismic data is accessed in each case.

bool Tutorial::computeData( const DataHolder& output, const BinID& relpos,
                           int z0, int nrsamples, int threadid ) const
{
    for ( int idx=0; idx < nrsamples; idx++ )
    {
        float outval = 0;
        if ( action_==0 || action_==1 )
        {
            const float trcval = getInputValue( *inputdata_, dataidx_,
                                                idx, z0 );
            outval = action_==0 ? trcval * factor_ + shift_ :
                                        trcval * trcval;
        }
	else if ( action_==2 && !horsmooth_ )
        {
            float sum = 0;
            int count = 0;
            for ( int isamp=sampgate_.start; isamp <= sampgate_.stop; isamp++ )
            {
                const float curval = getInputValue( *inpdata_[0], dataidx_,
                                        idx + isamp, z0 );
                if ( !mIsUdf(curval) )
                {
                    sum += curval;
                    count ++;
                }
            }
            outval = sum / count;
        }
        else if (action_ == 2 && horsmooth_ )
        {
            float sum = 0;
            int count = 0;
            for ( int posidx=0; posidx < inpdata_.size(); posidx++ )
            {
                if ( !inpdata_[posidx] ) continue;
                const float shift = steeringdata_ ?
                        getInputValue( *steeringdata_,posidx, idx, z0 ) : 0;
                const int sampidx = idx + ( mIsUdf(shift) ? 0 : mNINT(shift) );
                if ( sampidx < 0 || sampidx >= nrsamples ) continue;
                const float val = getInputValue( *inpdata_[posidx], 
                                        dataidx_, sampidx, z0 ); 
                if ( !mIsUdf(val) )
                {
                    sum += val;
                    count ++;
                }
            }
            outval = sum / count;
        }

        setOutputValue( output, 0, idx, z0, outval );
    }

    return true;

}


Creating the Help Documention

The help system

Like any other commercial application, our plugin also needs a help document which a user can see by clicking on a button in the user interface. In OpendTect, the help documents are stored in $WORK/doc/User/### directories. In case of our Tutorial plugin, the directory is called tut. There are 4 key files in this directory:

The codes of these help documents as mentioned in the WindowLinkTable.txt can be passed as a parameter during creation of the UI dialog. For example,

uiTutSeisTools::uiTutSeisTools( uiParent* p )
        : uiDialog( p, Setup( "Tut seismic tools",
                              "Specify process parameters",
                              "tut:105.0.1") )
In the above code, "tut:105.0.1" is passed as the help id, where tut refers to the directory and 105.0.1 is the window link code of the corresponding help document.

The 'credits' documentation

From 3.3.2, OpendTect has the 'Credits' documentation, for aknowledging helpers, sponsors, idols, .... The structure is different from the user documentation described earlier. The base directory is $WORK/doc/Credits.

If you want to tie certain windows to specific credits (as we want in OpendTect base), you can use the Help ID as reference. This should then go in the index.txt file. All files are opened by the browser via that key. It's easy to see, for example in Credits/tut/index.txt how this works. Note that the IDs in index.txt need no scope, the scope is already used to for the subdirectory (no scope is as usual 'base'). Then, and this is essential, you'll have to use setHaveCredits( true ); in the constructor of your dialog. This will add the 'credits' button to the dialog.

If you want to add credits, you should at least make an index.html. If it is present, an entry will be made in the top-level Help-Credits menu.

Installation and auto-loading

Once you have made your own plugin, you probably would like it to be loaded automatically whenever OpendTect is started. OpendTect provides some facilities that do just that.

Preparing a plugin for auto-load

If you want to prepare your plugin for auto-loading at startup, you basically just have to define when it should be loaded. This is done by implementing the GetxxxxPluginType() function, for example:


#include "plugins.h"

extern "C" int GetTutPluginType()
{
    return PI_AUTO_INIT_EARLY;
}

#include "plugins.h" is needed for the PluginInfo structure and the PI_AUTO_INIT_xxx defines.

The GetxxxxPluginType() specifies when a plugin is loaded:

  • PI_AUTO_INIT_EARLY : Plugin is loaded before construction of main window
  • PI_AUTO_INIT_LATE : Plugin is loaded after construction of main window
  • Installing plugins for auto-load

    The auto-load tool of OpendTect looks for plugins to load in two places:

  • The plugins/platform_dir of the installation directory ($DTECT_APPL/plugins/$HDIR)
  • The '.od' directory in your 'Personal directory' ($HOME/.od/plugins/$HDIR on Unix)
  • On Windows, your 'Personal directory' is located at $HOME if this is defined. Otherwise, $USERPROFILE is used. Also see the specific notes in the windows documentation.

    Using .alo files

    Once you have located (and/or made) the directory where the plugins should be stored, you have to make a subdir 'libs' to contain the plugins and use .alo files.

    Auto LOad files are simple text files that tell a program which plugins it is supposed to load from the 'libs' directory. Since OpendTect contains multiple programs, each program has its own set of .alo files '<program name>.*.alo', while the plugins can be shared between multiple programs. OpendTect will scan for any file with this naming convention. So od_main.john.alo is perfectly OK.

    Since there are multiple vendors and/or plugin sets, each vendor can make his own .alo files. od_main, for example, will look at any file named od_main.*.alo. For this example, the default plugins are specified by od_main.base.alo, while dgb's plugins are specified by od_main.dgb.alo. This way, each vendor can make his own .alo files, without interfering with others.

    A .alo file is nothing more then a simple list of plugins, without extensions. For example, here is part of the od_main.base.alo file:

    
    Annotations
    Madagascar
    uiMadagascar
    CmdDriver
    GMT
    uiGMT
    

    Note that for each platform, a specific .alo file must be created. Usually, they will be the same, but some plugins might not be relevant or supported on all platforms.

    The plugins in the .alo files are loaded in the order as specified in the file. The alo files themselves are handled in alphabetical order.


    Distributing your plugins:

    The publishing and distribution of OpendTect plugins is pretty straightforward. The .alo files can be installed in the plugins/platform ($DTECT_APPL/plugins/$HDIR) directory, while the actual plugins come in the 'libs' sub-directory.

    On Unix, this means that you can make a .tar.gz file containing the plugins in a directory structure as described above, which can be extracted into the existing OpendTect installation directory.

    On Windows this is also possible, but it is more common to use an auto-extracting installer to do this. For more info on this, see the windows documentation.


    Index | Overview | Pmake | UNIX | MS Windows | opendtect.org