How to create readers and writers in Axl

Anais Ducoffe

There are two main kinds of readers/writers in axel software: the ones for axel data which can read/write “.axl” files and the others that can read/write other extension, for instance “.off”, “.iges”, etc… Every reader (resp. writer) implemented in Axl software inherits from dtkAbstractDataReader (resp. dtkAbstractDataWriter). But only the readers/writers for axel data inherit from axlAbstractDataReader/axlAbstractDataWriter. This text explains how to implement your readers and writers in axel, to import or export data obtained with another software or simply save and read some axel data.

Readers and writers for axel data

Readers and writers are classes, which allows to read and write Axl files (with the extension “.axl”). The format is based on XML, with appropriate node and attribute tags.

Structure of an axel writer/reader

As previously said, every writer of axel data inherits from axlAbstractDataWriter, which structure is as follow:

class AXLCORE_EXPORT axlAbstractDataWriter : public dtkAbstractDataWriter
{
Q_OBJECT

public:
   axlAbstractDataWriter(void);
   virtual ~axlAbstractDataWriter(void);

public:
   virtual bool accept (dtkAbstractData *data) = 0;
   virtual bool reject (dtkAbstractData *data) = 0;

   virtual QDomElement write(QDomDocument *doc, dtkAbstractData *data) = 0;

   void setName   (QDomElement& e, axlAbstractData *data);
   void setColor  (QDomElement& e, axlAbstractData *data);
   void setSize   (QDomElement& e, axlAbstractData *data);
   void setShader (QDomElement& e, axlAbstractData *data);

};

The three virtual methods have to be implemented in your axel writer.

  • The accept method is here to check if the writer is able to read the object you want to save. An example:

    bool axlPointWriter::accept(dtkAbstractData *data)
    {
      axlPoint *point = dynamic_cast<axlPoint *>(data);
      if(point)
         return true;
    
      return false;
    }
    
  • The reject method is the opposite of the accept method. Usually it is implemented like in the following example:

    bool axlPointWriter::reject(dtkAbstractData *data)
    {
      return !this->accept(data);
    }
    
  • And the most important method is write(QDomDocument *doc, dtkAbstractData *data) which enables to write a description of the data according to some rules. Those rules are explained later in this part. It is exactly the same idea for the readers except that there is a read method and the contents of the accept method is a little different.

In order to register the writer or reader in the factory one should also add the registered() method to the writer/reader class, as it is explained in the second part.

Now to understand better the contents of the accept method for readers, and what represent methods that were not explained, let’s see how are written axel data in a file .axl.

Axl file structure

Let’s see an example to understand the structure:

<axl>
  <surface type="bspline" color="255 100 100" name="MyBSpline">
    <dimension>3</dimension>
    <number>4 4</number>
    <order>4 4</order>
    <knots>0 0 0 0  1 1 1 1</knots>
    <knots>0 0 0 0  1 1 1 1</knots>
    <points>
    1.4 0.0 2.4
    1.4 -0.784 2.4
    0.784 -1.4 2.4
    0.0 -1.4 2.4
    1.3375 0.0 2.53125
    1.3375 -0.749 2.53125
    0.749 -1.3375 2.53125
    0.0 -1.3375 2.53125
    1.4375 0.0 2.53125
    1.4375 -0.805 2.53125
    0.805 -1.4375 2.53125
    0.0 -1.4375 2.53125
    1.5 0.0 2.4
    1.5 -0.84 2.4
    0.84 -1.5 2.4
    0.0 -1.5 2.4
    </points>
  </surface>
</axl>

The contents above describes a bspline surface. The information are given as an xml tree which is of type QDomElement (See qdom.h and QXml; https://qt-project.org/doc/qt-4.8/qdomelement.html). Every .axl file starts with <axl> and finishes with </axl>. In between, the data saved are described with the same idea. Here the tag to inform that we save a surface is <surface>. When its description is entirely done, we close it by using </surface>. This tag will be analyzed in the accept method of the reader to know if the reader is able to read the description of the axel object. For each axel data, there is at least three default attributes that are used by the axlAbstractDataReader (written by axlAbstractDataWriter): the object name, its color, its size and whether it has a shader applied on it. In other words, these attributes are attributes of axlAbstractData. That’s what we found in the second line of the .axl file: <surface type="bspline" color="255 100 100" name="MyBSpline" shader=""> the surface which is a bspline surface has no shader, its name is MyBSpline, its color is given as an RGB color etc… To write those information, the method setName, setColor, setSize and setShader are used. You can also add some more particular description items for this kind of axel object by adding some child node to the QDomElement that describes the axel object. In the example dimension, order points etc … are particular description of the axel bspline object. To see some more examples go to the axel data page : http://axl.inria.fr/axel_data

Structure for other extension files.

While developping your plugin for axel, you may need to import some data described in a special format, OFF format for instance. Or you might need to export your results in a special file extension to use it later with another software. That’s why the axel structure enables you to create special readers and/or writers. Here is described the structure that you need to follow for creating your reader or your writer.

Writers to export your data created in axel to another format.

The structure of your writer yourWriter should be as follow:

class YOURPLUGIN_EXPORT yourWriter : public dtkAbstractDataWriter
{
Q_OBJECT

public:
   yourWriter(void);
   ~yourWriter(void);

public:
   QString identifier(void) const;
   QString description(void) const;
   QStringList handled(void) const;

   static bool registered(void);

public slots:
   bool canWrite(const QString& file);
   bool write(const QString& file);
}
dtkAbstractDataWriter *createyourWriter(void);

Here are some explanations about the different methods you have to implement:

  • The identifier method is the same method than you will find in any axel classes. It returns the class name. For instance if the name of your writer class is yourWriter, then the identifier of your writer will be a QString equals to yourWriter. It enables to find your writer into the writer factory after it will be registered.

  • The description is here to describe your object. The same thing for all axel data.

  • The handled method must return the extension file your writer is to write. For instance, for a writer of OFF files:

    //! Return the appropriate extension of files (.off) written by this writer.
    QStringList axlOFFWriter::handled(void) const
    {
       return QStringList() <<".off";
    }
    
  • The registered static method is used to register your writer in the factory. For instance:

    //! Register the writer in the factory.
    bool axlOFFWriter::registered(void)
    {
      return dtkAbstractDataFactory::instance()->registerDataWriterType("axlOFFWriter",
                                                                        QStringList(),
                                                                        createaxlOFFWriter);
    }
    
  • The canWrite method is here to determine if the writer can write the file. In other words if it’s possible to write than kind of file with your writer. A simple test might be that you check you want to create a file with the appropriate extension.

  • The write method enables to write your file. It returns true if the file is correctly written, else returns false:

    CreateyourWriter(void) returns a : new yourWriter().
    

Readers to import into axel software some data described in another format than axel files

The structure of your reader yourReader should be as follows:

class YOURPLUGIN_EXPORT yourReader : public dtkAbstractDataReader
{
  Q_OBJECT

public :
    yourReader(void);
   ~yourReader(void);

public:
    QString identifier(void) const;
    QString description(void) const;
    QStringList handled(void) const;

    static bool registered(void);

public slots:
    bool canRead(const QString& file);
    bool read(const QString& file);
};
dtkAbstractDataReader *createyourReader(void);

The explanations of the different methods are similar to the previous ones for writers. Except that you must also insert the object created in the reader as a data of the reader in the read method. For that you have to call setData method of dtkAbstractDataReader. The object that will be sent to the axel view is the return value of the dtkAbstractDataReadet.data() method.