The structure of a plugin

M. Perrinel

Axl is a cross platform sofware composed of a main application and several plugins. In this article, we describe the structure of the code required for an Axl plugin. A plugin consists of a set of classes and some configurations files. Let’s see an example of a plugin generated by the axel-plugin project wizard (named myTools) with data (named myData) and a process (named myProcess). I’ll explain (for each of the following classes) the important things to respect if you want to create your own plugin:

  • myProcess
  • myProcessDialog
  • myData
  • myDataDialog
  • myDataReader
  • myDataWriter
  • myDataConverter
  • myDataCreatorProcessDialog
  • myToolsPlugin
  • myToolsExport
  • CMakeList.txt

Before analyzing in detail each class, we first have a look at the taxonomy of the plugin:

  • myProcess and myProcessDialog correspond to the process named myProcess.
  • myData, myDataDialog, myDataReader, myDataWriter, myDataConverter, myDataCreatorProcessDialog for the data named myData.
  • myToolsPlugin and myToolsExport files are for the plugin named myTools.
  • CMakeList.txt is for the configuration with cmake.

As you can see, a plugin for axel consists of a lot of classes. If the plugin config pattern is always necessary and unique, data pattern or process pattern isn’t. You can create a plugin just for one or more processes and/or one ore more data. In addition, you probably already know that Axl directly depends of dtk library. Most of Axl classes inherit from one of the dtk classes. you will find more information about dtk library there.

I’ll continue on this example to explain plugin classes. In this documentation, you will see how to generate a first plugin based on this example. I recommend you to follow explanations of classes with the example opened in QtCreator.

Process Pattern

  • myProcess: this class inherits from dtkAbstractProcess. You will create the core of your process there. For that you need to override or implement these methods:

    virtual QString description (void) const;
    /* The description of your process */
    
    virtual QString identifier (void) const;
    /* return the name of the class: myProcess */
    
    int update (void);
    /* use input data or parameters to create your output. This method is the core
    of the process */
    
    static bool registered (void);
    /* register the process into the dtkAbstractProcessFactory */
    
    void setInput (dtkAbstractData *data, int channel);
    /* To set you attribute input data, you need to override from dtkAbstractProcess
    all virtual methods you need */
    
    dtkAbstractData *output (void);
    /* To get you attribute output data, you need to override from dtkAbstractProcess
    all virtual methods you need */
    
  • myProcessDialog: this class inherits from axlInspectorToolInterface. You will create the GUI of your process there that you can see on Axl if you select you process from the tool inspector (see Apply a process on a selected data if you don’t know what I mean by tool inspector). For that you need to implement these methods and declare a signal:

    int run (void);
    /* this method is called when user click on run button in Axl. Using widgets of
    GUI defined into myProcessDialog constructors, myProcess methods are called to set
    process attributes and run its update method, then you will use the dataInserted
    signal to emit new data created if existed */
    
    static bool registered (void);
    /* register the process into the dtkAbstractProcessFactory */
    
    signals:
    void dataInserted (axlAbstractData *data);
    /* Use it in run method of myProcessDialog to insert new data in Axl */
    

Data Pattern

  • myData: this class inherits from axlAbstractData. You will create the core of your data there. For that you need to override or implement virtual methods of axlAbstractData:

    virtual QString description (void) const;
    // The description of your data
    
    virtual QString identifier (void) const;
    // return the name of the class: myData
    
    virtual QString objectType (void) const;
    // return the type of the class: axlAbstractData
    
    static bool registered (void);
    // register the process into the dtkAbstractDataFactory
    

    Note

    When you add a data in your plugin, you have to extend one of the abstract data class given into the abstract taxonomy of Axl data. For instance, BSpline surface data implementation given with BSplineTools plugin extend axkAbstractSurfaceBSpline directly instead of axlAbstractData that is more abstract. That mean you have to override all virtual method of axlAbstractSurfaceBSpline and its parents classes in addition.

  • myDataDialog: this class inherits from axlInspectorObjectInterface. You will create the GUI of your data there.that you can see on Axl if you select you process from the object inspector (see Apply a process on a selected data if you don’t know what I mean by object inspector). For that you need to add all GUI elements you need as attributes. Some of there come from parents classes and other come from your specific data.

    static bool registered (void);
    // register the process into the axlInspectorObjectFactory
    
    public slots:
    void setData (dtkAbstractData *data);
    // Initialize your data attributes and run the initWidget
    
    private:
    void initWidget (void);
    // The most important method of the class.
    // All axlAbstractData have some basic properties:
    
    // color, opacity, size and shader. Using the data and other private method of
    the class, GUI elements are initialized.
    
    // In addition, initialize new GUI elements you added depending of your specific
    // data.
    
    // Then Create all connections needed to link in real time your
    // data logic representation with data graphic representation
    

    Note

    There are some signals and other private methods I didn’t mentionned there, because they are already implemented. There are usefull for one part of initWidget method which will initialize axlAbstractData properties. Moreover, their connections are already created to. If you create by your self and from scratch this class, you have to add them manually and as identical. In other way, if you use axel-plugin wizard, the myDataDialog will be generated with them.

  • myDataReader: this class inherits from axlAbstractDataReader. If it is not necessary to create a data plugin, it permits to create data by the reading an axl file. You will find more complements informations there. Let’s see what methods you have to implements:

    virtual QString description (void) const;
    // The description of your reader
    
    virtual QString identifier (void) const;
    // return the name of the class: myDataReader
    
    virtual QStringList handled (void) const;
    // return list of data identifier that it can handle
    
    static bool registered (void);
    // register the process into the dtkAbstractDataFactory
    
    public:
    bool accept (const QDomNode& node);
    // condition of QDomNode to recognize your data type
    bool reject (const QDomNode& node);
    // return ! (this->accept);
    
    dtkAbstractData *read (const QDomNode& node);
    // If QDomNode is accepted,
    // you have to use the parsed informations to create you data
    
  • myDataWriter: this class inherits from axlAbstractDataWriter. If it not necessary to create a data plugin, it permits to create an axl file from your data opened in Axl. You will find more complements informations there. Let’s see what methods you have to implements:

    virtual QString description (void) const;
    // The description of your reader
    
    virtual QString identifier (void) const;
    // return the name of the class: myDataWriter
    
    virtual QStringList handled (void) const;
    // return list of data identifier that it can handle
    
    static bool registered (void);
    // register the process into the dtkAbstractDataFactory
    
    public:
    bool accept (dtkAbstractData *data);
    // return true if we can dynamic_cast<myData *> (data)
    
    bool reject (dtkAbstractData *data); // return ! (this->accept);
    
    QDomElement write (QDomDocument *doc, dtkAbstractData *data);
    // if the data is accepted
    // you have to write data attributes into a QDomElement
    
  • myDataConverter: this class inherits from axlAbstractDataConverter. You have already create the data, but how Axl can show your data inside the view ? It’s possible because of this class. You will convert there your myData into an axlMesh which can be displayed inside Axl view. For that you have to implements:

    virtual QString description (void) const;
    // The description of your converter
    
    virtual QString toType (void) const;
    // return the type of the output converter (here axlMesh)
    
    virtual QStringList fromTypes (void) const;
    // return list of data types that your inherit
    
    static bool registered (void);
    // register the process into the dtkAbstractDataFactory
    
    public:
    
    axlMesh* toMesh (void);
    // you have to implement it to create a mesh
    // from your data and use axlMesh to store it
    
  • myDataCreatorProcessDialog: this class inherits from axlInspectorToolInterface and respect pattern of processDialog. You will create the GUI necessary to create a myData, that you can see on Axl if you select your generator from the tool inspector (see Open a data from a generator tool. if you don’t know how to do. For this processDialog, you don’t have to create a class myDataCreatorProcess, because the Dialog will do that for you. For that you need to implement these methods and declare a signal:

    int run (void);
    // this method is called when the user clicks on run button in Axl.
    // using widgets of GUI defined into myDataCreatorProcessDialog constructors,
    // myData will be created then inserted by the dataInserted signa,
    
    static bool registered (void);
    // register the process into the dtkAbstractProcessFactory
    
    signals:
    void dataInserted (axlAbstractData *data);
    // Use it in run method of myProcessDialog to insert new data in Axl
    

Plugin Pattern

  • myToolsPlugin: this class inherits from dtkPlugin. You will include all files of process pattern and data pattern. Then in the initialize method, you will call for each of them their register method. In addition, static variables will be defined to access singleton instance of used factories in your plugin:

    virtual QString description (void) const;
    // The description of your plugin
    
    virtual QString name (void) const;
    // return the name of the class: myTools
    
    virtual QStringList types (void) const;
    // List data and process classes embedded in your plugin
    
    virtual bool initialize (void);
    // call static registered method of all data and process clases
    // and initialize static variables to their correspondant factory
    
    public:
    static dtkAbstractDataFactory *dataFactSingleton;
    static dtkAbstractProcessFactory *processFactSingleton;
    

    Note

    Two other methods need to be implemented but we still not give them uses in axel-plugin. So just implement them as you want for the moment

    virtual QStringList tags (void) const;
    
    virtual bool uninitialize (void);
    
  • myToolsExport: In fact, there, you have only one file myToolsExport.h and it’s not a class. This files contain some preprocessor directives that permit to you plugin to get the cross compilation on windows with static compilation. Then for each class header, you add the preprocessor value (initialized by this file). for instance:

    class myTools: public dtkPlugin
    

    becomes

    class PLUGPLUGIN_EXPORT myTools: public dtkPlugin
    
  • myToolsPlugin.json: a json file for the indentification of the plugin (name, version, dependencies), in the plugin manager.

    {
               "name" : "myToolsPlugin",
            "version" : "0.0.1",
       "dependencies" : []
    }