SEARCH
TOOLBOX
LANGUAGES
PluginCreation

PluginCreation

From SOFAWiki

Jump to: navigation, search
Writing a Plugin for SOFA

Contents

Writing a plugin using the svn public branch

The public branch does not yet benefit from the recent changes in the SOFA buildsystem, so if you are developping for SOFA with this branch you should follow these guidelines instead !

Converting your plugins to the new buildsystem

To convert your plugins to the new buildsystem, follow the guidelines explained here. Buildsystem_switch

Plugins in SOFA

In SOFA, a plugin is a list of components that can be integrated in the scene graph via the factory. A plugin contains a set of classes derivated from sofa components. You can also put external classes used by your components. The purpose is to add your own components into sofa without have to integrate them into the heart of Sofa directories. Optionnally you can also provide custom widgets to represent your component datas in the GUI. A plugin is actually a shared library, loaded at runtime by Sofa.

There are some plugins delivered with SOFA source code. Through this tutorial we'll follow the steps that were required to create the plugin PluginExample you can find in Sofa/applications/plugins/PluginExample/ directory.

The plugin directory

The first step is to create a new directory named after your project in Sofa/applications/plugins. This directory will contain the source code related to your plugin.

Inside Sofa/applications/plugins/PluginExample/ You can find most notably these files :

  • a configuration file for qmake to generate the makefile : PluginExample.pro
  • a dependency file for qmake : PluginExample-dependencies.prf
  • the source code for the plugin interface : initPlugin.h initPlugin.cpp
  • the actual source code for components : MyBehaviorModel, MyMappingPendulumInPlane, MyProjectiveConstraintSet

The configuration file (PluginExample.pro)

This file is used by QMake to generate the Makefiles.

This first part is dedicated to the plugin configuration You need to adapt the parameter passed to the defineAsPlugin and the value of the TARGET to your plugin name. This is mandatory, all plugin should begin with such definitions.

load(sofa/pre)
defineAsPlugin(PluginExample)
 
TARGET = PluginExample
TEMPLATE = lib


This part is for the export macro used on windows to make the dll symbol visibles to the outside.

 
DEFINES += SOFA_BUILD_PLUGINEXAMPLE

Then comes the list of source and header files required to build this plugin.

SOURCES = MyBehaviorModel.cpp \
          MyDataWidgetUnsigned.cpp \
          MyProjectiveConstraintSet.cpp \
          MyMappingPendulumInPlane.cpp \
          initPlugin.cpp
 
HEADERS = MyBehaviorModel.h \
          MyDataWidgetUnsigned.h \
          MyMappingPendulumInPlane.h \
          MyMappingPendulumInPlane.inl \
          MyProjectiveConstraintSet.h \
          MyProjectiveConstraintSet.inl \
		  initPlugin.h

This part contains a post link step to copy a readme file in the destination folder for libraries. This is optional but nice to have in a plugin.

README_FILE = PluginExample.txt
 
#TODO: add an install target for README files
 
unix : QMAKE_POST_LINK = cp $$SRC_DIR/$$README_FILE $$LIB_DESTDIR 
win32 : QMAKE_POST_LINK = copy \"$$toWindowsPath($$SRC_DIR/$$README_FILE)\" \"$$LIB_DESTDIR"

Finally the mandatory step which finishes the configuration of the plugin according to the variables defined so far in this .pro

load(sofa/post)

The dependency file (PluginExample-dependencies.prf)

You need to create a dependency file for your plugin. You should name it pluginname-dependencies.prf and pluginname should be unique to avoid collision with other plugins when they will be installed (because all dependency files will be copied into the same folder).

It should contain a declare instruction for each library within the plugin, and an enable instruction (which must not be surrounded by a conditionnal block, otherwise building the plugin separately will be impossible). Here is the example of the PluginExample plugin (PluginExample-dependencies.prf) :

Note that the first parameter in the declare function as well as the one used in the enable function corresponds to the TARGET in your .pro file. In the case of PluginExample the TARGET variable happens to have the same name as the directory it resides, but it is not always the case!

 
includeOnce($$_FILE_) {
	declare(PluginExample, applications/plugins/PluginExample, sofaguiqt)
 
	enable(PluginExample)
}

The plugin interface (initPlugin.h)

This file contains the dll export / dll import macro definitions which is used on the windows platform to tell which symbols should be visible outside the scope of the dll curently built.

#ifndef INITMyPluginExample_H
#define INITMyPluginExample_H
 
 
#include <sofa/helper/system/config.h>
 
#ifdef SOFA_BUILD_PLUGINEXAMPLE
#define SOFA_MyPluginExample_API SOFA_EXPORT_DYNAMIC_LIBRARY
#else
#define SOFA_MyPluginExample_API  SOFA_IMPORT_DYNAMIC_LIBRARY
#endif
 
/** \mainpage
  This is a simple example plugin.
  */
 
#endif // INITMyPluginExample_H

You can find more information on that particular aspect on the msdn website : link dllexport,dllimport

The plugin interface (initMyPlugin.cpp)

This file declares the interface of the plugin, ie the function signatures that the PluginManager seeks when it attempts to load a dynamic library.

//Here are just several convenient functions to help user to know what contains the plugin
 
	extern "C" {
                SOFA_MyPluginExample_API void initExternalModule(); // Mandatory you should simply use the same body function for your plugin as the one use for PluginExample
                SOFA_MyPluginExample_API const char* getModuleName(); 
                SOFA_MyPluginExample_API const char* getModuleVersion();
                SOFA_MyPluginExample_API const char* getModuleLicense();
                SOFA_MyPluginExample_API const char* getModuleDescription();
                SOFA_MyPluginExample_API const char* getModuleComponentList();
	}
 
        // Simply copy paste this function in your own plugin. 
	void initExternalModule()
	{
		static bool first = true;
		if (first)
		{
			first = false;
		}
	}

The last part are the macro that link your components to the Sofa factory :

SOFA_LINK_CLASS(MyMappingPendulumInPlane)
SOFA_LINK_CLASS(MyBehaviorModel)
SOFA_LINK_CLASS(MyProjectiveConstraintSet)

More information about the registration of your components inside the sofa::core::ObjectFactory here

Writing custom widgets for your component

By default Sofa will generate an edition dialog box when you double click in your component in the scene or visual graph to modify its data fields. However you can also register your own widget to do that data edition operation. In MyFakeComponentDataWidgets.h and .cpp files you have an example of a custom widget used to represent a data declared in MyFakeComponent. Basically you need to write a class which derives from TDataWidget and implement methods describing what widgets you want to create, how they can read from a Data, and how they can write into it.

Writing a custom widget for a Data<unsigned>

  /**
  *\brief Customization of the representation of Data<unsigned> types
  * in the gui. In the .cpp file this widget is registered to represent
  * myData from MyFakeComponent in the gui.
  **/
  class CustomDataUnsignedWidget : public TDataWidget<unsigned>
  {
    Q_OBJECT
  public :
    ///The class constructor takes a TData<unsigned> since it creates 
    ///a widget for a that particular data type.
    CustomDataUnsignedWidget(QWidget* parent, const char* name, core::objectmodel::TData<unsigned>* data):
         TDataWidget<unsigned>(parent,name,data){};
    ///In this method we  create the widgets and perform the signal / slots 
    ///connections. 
    virtual bool createWidgets(); 
  protected slots:
    void change();
  protected:
    ///Implements how update the widgets knowing the data value.
    virtual void readFromData();
    ///Implements how to update the data, knowing the widget value.
    virtual void writeToData();
    QSlider* qslider;
    QLabel*  label1;
    QLabel*  label2;
  };
 
}

Registration in the factory.

   /* 
  register this new class in the DataWidgetFactory. 
  The factory key is the Data widget property 
  (see MyFakeComponent constructor) 
  */
  helper::Creator<DataWidgetFactory,CustomDataUnsignedWidget> DW_myData("widget_myData",false);

Make use of this widget to represent a data containing an unsigned by using the setWidget method of a Data object.

MyFakeComponent::MyFakeComponent()
: customUnsignedData( initData(&customUnsignedData,(unsigned)1,"Custom Unsigned Data","Example of unsigned data with custom widget") ),
regularUnsignedData( initData(&regularUnsignedData,(unsigned)1,"Unsigned Data","Example of unsigned data with standard widget") )
{
  customUnsignedData.setWidget("widget_myData"); 
}

Image:DatawidgetDialog.png

Build and load the plugin

To build :

  1. For windows users, you have to run projectVC8.bat or projectVC9.bat depending on your version of Microsoft Visual Studio C++.
      For the others, simply run qmake at the Sofa main directory
  2. Then launch the compilation.

To load :

When using GUI

  1. Launch the excutable runSofa (or the modeler)
  2. Go to the menu Edit > Plugin Manager...
      Image:PluginManagerMenu.png


  3. Add you plugin by selecting it In the browser. Plugins should be in Sofa/lib/sofa-plugins directory. Extension is .dll on Windows, .so on linux, and .dylib on MacOs
  Image:PluginManagerWindow.png

On batch mode

 Use the -l option, specifying the path to your plugin library, for example : 
     ./bin/runSofa -g batch -l lib/sofa-plugins/libPluginExample.so examples/Demos/liver.scn

Quick GUI run

It is possible to avoid to manually load the plugin, using the batch command, by customizing the run command in your favorite IDE.

For example, the following image shows a project customization for QtCreator on Linux. The run command and the LD_LIBRARY_PATH are customized.

Image:pluginExampleQtCreatorConfig.png

Using this, simply run your project in the IDE. The following image shows the result of running the application scene graph

Image:pluginExampleRunTest.png