This tutorial explains how to create a new vIST/e plugin. This includes setting up CMake, creating the necessary files and creating the skeleton code. Throughout the tutorial a number of placeholders are used. These are enclosed in \<brackets>. The following table explains these placeholders and their meaning.
| Placeholder | Description | Example |
|---|---|---|
| \<SrcDir> | Root folder of the vIST/e source code tree | C:\vISTe\subversion\trunk |
| \<PluginName> | Name of the plugin without spaces | DoSomethingPlugin |
| \<PluginNameSpaces> | Name of the plugin with (optional) spaces | Do Something Plugin |
| \<PLUGINNAME> | Name of the plugin with capital letters and no spaces | DOSOMETHINGPLUGIN |
| \<YourName> | The name of the plugin folder, usually your first name | Steve |
Before you start, you should determine what features your plugin will support. Specifically, you need to answer the following questions:
Advanced plugins have more control over the tool and can, for example, implement new controls in the rendering area. However, you should only use advanced plugins if you are absolutely sure that you needs functionality beyond what is exposed inside the plugin.
Add a new folder for the plugin in the source code tree. By default, you should use \<SrcDir>\plugins\testing\\<YourName>\\<PluginName>. Open the CMakeLists.txt file in \<SrcDir>\plugins\testing and add ADD_SUBDIRECTORY(\<YourName>) at the end of the file.
Go to \<SrcDir>\plugins\testing\\<YourName> and create a new CMakeLists.txt file.
In the CMakeLists.txt file add the line ADD_SUBDIRECTORY(\<PluginName>).
In \<SrcDir>\plugins\testing\\<YourName>\\<PluginName> create the following two files: \<PluginName>.cxx (e.g. DoSomethingPlugin.cxx) and \<PluginName>.h (e.g. DoSomethingPlugin.h).
In the same folder create a new CMakeLists.txt file with the following content:
FIND_PACKAGE(VTK REQUIRED)
INCLUDE(${VTK_USE_FILE})
FIND_PACKAGE(Qt4 REQUIRED)
INCLUDE(${QT_USE_FILE})
SET (SRCS
<PluginName>.cxx
<PluginName>.h
)
Whenever you add source or header files to the plugin, you should add their names to the SRCS set. If your plugin also requires additional libraries from the \<SrcDir>\libs directory, add the following lines after the SRCS:
INCLUDE_DIRECTORIES(${BMIA_LIBS_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
LINK_DIRECTORIES(${BMIA_LIBS_BINARY_DIR})
The ${CMAKE_CURRENT_BINARY_DIR} item is optional and only required if your plugin implements a GUI. However, this will be the case for most plugins. If your plugin implements a GUI also the following lines to the CMakeLists.txt file:
QT4_WRAP_UI(UiHeaders <PluginName>.ui)
SET_SOURCE_FILES_PROPERTIES(<PluginName>.cxx PROPERTIES OBJECT_DEPENDS "${UiHeaders}")
These commands will generate boiler-plate C++ code for the GUI of your plugin. Note that upon first execution of CMake there will be an error, because the \<PluginName>.ui file does not yet exist. This will be explained in the next section (Step 3). Add the following lines in the CMakeLists.txt file:
QT4_WRAP_CPP(MOC_SRCS <PluginName>.h)
ADD_LIBRARY(<PluginName> SHARED ${SRCS} ${MOC_SRCS})
The QT4_WRAP_CPP command generates Qt UI files from the plugin header. The ADD_LIBRARY command adds the plugin to the project as a shared DLL. Then add the following line to your CMakeLists.txt file:
TARGET_LINK_LIBRARIES(<PluginName> bmia_core bmia_plugin bmia_data)
The TARGET_LINK_LIBRARIES command specifies which libraries (DLLs) your plugin relies on. These are always the standard vIST/e libraries such as bmia_core, bmia_plugin and bmia_data. If your plugin also requires additional libraries from the \<SrcDir>\libs directory, you can add their names after the bmia_data library. You can find the name of a library by opening its corresponding CMakeLists.txt file and looking at the first parameter of the ADD_LIBRARY command.
If your plugin does not have a GUI, you can skip this step. Launch Qt Designer. You should be able to find it in the bin folder of your Qt installation (see elsewhere in this WIKI how to install Qt). Select File -> New..., and under templates\forms select Widget. Then select Create.
In the Object Inspector (right side of the screen) double-click on the Form object of type QWidget and rename it to \<pluginname\>Form**. With the form selected, go to the **Property Editor** (bottom right of the screen) and set **Geometry > Width</pluginname\> and Geometry > Height to 290 and 600 respectively. These are more or less the minimum dimensions of the GUI area in vIST/e. You can now add controls to the widget. After adding the first control, select the form again and choose Lay Out Vertically** (in the toolbar, the symbol with three horizontal bars). Additionally, you will want to add a vertical spacer below your controls to ensure that all controls are top aligned and not spread out across the vertical axis of your widget. When you are done, save the GUI as \<PluginName>.ui in your plugin source folder.
You can now run CMake (either through the CMake GUI or directly from Visual Studio by rebuilding your project). Configuration The new plugin should show up in the module list with two empty source files, \<PluginName>.cxx and \<PluginName>.h. We will not create skeleton code for the header file.
As with all headers, you should include preprocessor directives that prevent multiple inclusions of the header. So at the very top of the \<PluginName>.h header file add the following lines:
#ifndef bmia_<PluginName>_h
#define bmia_<PluginName>_h
...
#endif
Make sure to close the block with a #endif at the bottom of the header file. Then, just below the #define statement add the following:
#include "DTITool.h"
This header file contains basic includes of vIST/e that every plugin requires. Of course, any additional headers your plugin needs (such as VTK headers), should be included as well. For example, if your plugin implements a GUI, add the boiler-plate header file you created in Step 3. Also add an additional namespace for the GUI:
#include "ui_<PluginName>.h"
namespace Ui {
class <PluginName>Form;
}
After this, the actual plugin header code will come. Start with the BMIA namespace as follows:
namespace bmia {
...
} // namespace bmia
It is always wise to add a little comment specifying which command the ending curly brace belongs to (in this case the namespace command). Between the namespace block, start with the class definition:
namespace bmia {
class <PluginName> : public <Parent1>, public <Parent2>, ... {
...
};}
The <parent...> terms refer to parent classes from which your plugin inherits (or extends). The possible values are listed below. Note that Plugin and AdvancedPlugin are mutually exclusive. Either the Plugin or AdvancedPlugin is mandatory and should come first in the list. The order of the other types is irrelevant.</parent...>
| Class | Description |
|---|---|
| plugin::Plugin | Base plugin parent class, except if AdvancedPlugin is used |
| plugin::AdvancedPlugin | Base plugin parent class for advanced plugins |
| plugin::Visualization | Interface for plugins implementing visualization features |
| plugin::GUI | Interface for plugins implementing a GUI |
| data::Reader | Interface for plugins that read/write datasets |
| data::Consumer | Interface for plugins that use datasets (loaded by Reader plugins) |
As the first lines inside the class definition write the following:
namespace bmia {
class <PluginName> : public plugin::Plugin, ... {
Q_OBJECT
Q_INTERFACES(bmia::plugin::Plugin)
Q_INTERFACES(...)
...
};}
Note that Plugin and AdvancedPlugin are not mutually exclusive inside the Q_INTERFACES definition. The bmia::plugin::Plugin definition is always required, even if your plugin inherits only from plugin::AdvancedPlugin. In that case, you would have:
namespace bmia {
class <PluginName> : public plugin::AdvancedPlugin {
Q_OBJECT
Q_INTERFACES(bmia::plugin::Plugin)
Q_INTERFACES(bmia::plugin::AdvancedPlugin)
...
};}
In the following piece of code we will assume you have a plugin that inherits all interfaces, that is, the Visualization, Reader, Consumer and GUI interfaces. You can leave out what you do not need.
namespace bmia {
class PluginName : public plugin::Plugin, public plugin::Visualization,
public plugin::GUI, public data::Reader, public data::Consumer {
Q_OBJECT
Q_INTERFACES(bmia::plugin::Plugin)
Q_INTERFACES(bmia::plugin::GUI)
Q_INTERFACES(bmia::data::Reader)
Q_INTERFACES(bmia::data::Consumer)
Q_INTERFACES(bmia::plugin::Visualization)
public:
QString getPluginVersion() { return "1.0.0"; }
<PluginName>();
virtual ~<PluginName>();
virtual void init();
// If plugin inherits from plugin::Visualization
vtkProp * getVtkProp();
// If plugin inherits from plugin::GUI
QWidget * getGUI();
// If plugin inherits from data::Consumer
void dataSetAdded(data::DataSet * d);
void dataSetChanged(data::DataSet * d);
void dataSetRemoved(data::DataSet * d);
// If plugin inherits from data::Reader
QStringList getSupportedFileExtensions();
QStringList getSupportedFileDescriptions();
void loadDataFromFile(QString fileName);
private:
// If plugin inherits from plugin::Visualization
vtkActor * actor;
// If plugin inherits from plugin::GUI
QWidget * widget;
Ui::<PluginName>Form * form;
// If plugin inherits from data::Consumer
QList<data::DataSet *> datasets;
};}
Now that we have the plugin header file, we can start on the skeleton code for the plugin source file \<PluginName>.cxx. As in the above example, we will just show the skeleton code as a whole and use code comments to explain what is what.
#include "<PluginName>.h"
namespace bmia {
// Plugin constructor
<PluginName>::<PluginName>() : plugin::Plugin("<PluginName>") {
this->actor = NULL;
this->widget = NULL;
this->form = NULL;
}
// Plugin desctructor
<PluginName>::~<PluginName() {
this->actor->Delete();
delete this->widget;
delete this->form;
}
// Plugin initialization
void <PluginName>::init() {
this->actor = vtkActor::New();
this->widget = new QWidget();
this->form = new Ui::<PluginName>Form();
this->form->setupUi(this->widget);
}
// Returns visualization component as VTK object
vtkProp * <PluginName>::getVtkProp() {
return this->actor;
}
// Returns GUI component as Qt widget
QWidget * <PluginName>::getGUI() {
return this->widget;
}
// Called whenever a dataset is loaded into the vIST/e repository
void <PluginName>::dataSetAdded(data::DataSet * d) {
// Check if dataset kind is supported
if(d->getKind() != "someKindSupportedByThisPlugin")
return;
...
// Keep track of the datasets used by this plugin
this->datasets.append(d);
}
// Called whenever a dataset is updated within vIST/e (e.g., by other plugins)
void <PluginName>::dataSetChanged(data::DataSet * d) {
// Check if dataset kind is supported
if(d->getKind() != "someKindSupportedByThisPlugin")
return;
// Check if dataset is known
if(this->datasets.contains(d) == false)
return;
...
}
// Called whenever a dataset is removed
void <PluginName>::dataSetRemoved(data::DataSet * d) {
// Check if dataset kind is supported
if(d->getKind() != "someKindSupportedByThisPlugin")
return;
// Check if dataset is known
if(this->datasets.contains(d) == false)
return;
...
}
// Returns the supported file extensions of this plugin
QStringList <PluginName>::getSupportedFileExtensions() {
QStringList list;
list.append("jpg");
list.append("png");
return list;
}
// Returns the supported file descriptions
QStringList <PluginName>::getSupportedFileDescriptions() {
QStringList list;
list.append("JPG Images");
list.append("PNG Images");
return list;
}
// Loads the dataset specified by the given filename
void <PluginName>::loadDataFromFile(QString fileName) {
}
}
Q_EXPORT_PLUGIN2(lib<PluginName>, bmia::<PluginName>)
Note in the above code that datasets created by the plugin (e.g. if it is a Reader plugin) should not be deleted in the destructor because other plugins still may have a reference to these datasets. Filters, however, that were used to create these datasets can be deleted.
Note that dataSetRemoved() can be implemented in different ways, depending on the plugin. Sometimes it may be enough to simply remove any reference to a dataset. In other cases, you delete all filters, mappers and/or output datasets associated with the input dataset. In any case, do not delete the VTK object associated with the dataset itself. This is done automatically by the destructor of the dataset.
Note that reader plugins (implementing loadDataFromFile()) will create a new dataset for the loaded data add it to the data repository of vIST/e. In this case, other plugins are notified of it and can do something with the data if they want.
You should now be able to compile the new plugin inside the overall vIST/e project. (There may be other library link settings (ie. opengl32.lib) not in CMakeLists.txt files and set directly from Visual Studio, since CMakeLists.txt has been run again, you must add this to "additional input" settings to the linkers of 3 GpuGlyphsPlugin, FiberVisualizationPlugin, RayCastPlugin projects) In order to test it, add it to a profile in the Profile Manager. For details on creating a profile, see the WIKI pages here.