Web Analytics Made Easy - Statcounter
Skip to content

Importing files#

In this tutorial, we will create an importer for the following XYZ format: The 1st line: the number of atoms in the file. The 2nd line: arbitraty header line. Next lines contain information on atoms in the following format (coordinates are in angstroms): element_symbol x-coordinate y-coordinate z-coordinate.

Example of the format:

10
Model name
C -6.644 9.967 5.557
C -7.934 9.831 4.773
C -7.934 10.816 3.617
C -8.098 8.407 4.266
N -7.404 9.603 7.84
C -6.46 9.161 6.834
C -5.09 9.488 7.383
O -5.008 10.375 8.245
H -8.382 9.481 7.521
H -7.182 10.599 8.021

Note

SAMSON already has an XYZ Importer which is installed by default, that's why we are actually going to read files with a exyz extension, even though the contents will still correspond to the xyz format. Therefore, to test this importer, you can export (Home > File > Save as) any system from SAMSON in the XYZ format and change the extension from .xyz to .exyz.

The source code for an Extension created in this tutorial can be found at SAMSON-Developer-Tutorials.

Generating an Extension#

We will start by creating a new SAMSON Extension called EXYZ thanks to the SAMSON Extension Generator (please, refer to the SAMSON Extension Generator tutorial for a reminder on how to use it):

  • launch SAMSON and run the SAMSON Extension Generator;
  • specify the path to a folder where you want to develop your SAMSON Extensions;
  • name the SAMSON Extension as EXYZ and add some description;
  • add an Importer class (called SEEXYZImporter);
  • generate the SAMSON Extension.

The SAMSON Extension Generator generates an importer class (derived from the SBIFileImporter class) and a GUI class that implements the user interface of the importer and is derived from the SBGFileImporter class.

Parsing a file#

In SAMSON, an importer is created by programming three functions in the importer class:

  • getExtension - specifies the extension of a file;
  • getFilter - specifies a filter in Qt format;
  • importFromFile - a function invoked by SAMSON to import a file with a suitable extension.

Let's open the SEEXYZImporter.cpp file and implement these functions.

Modify the getExtension function to return the extension of the importer (exyz) as follows:

std::string SEEXYZImporter::getExtension() const {

    return std::string("exyz");

}

Modify the getFilter function to return the filter of the importer as follows (it should be in the Qt format):

std::string SEEXYZImporter::getFilter() const {

    return std::string("EXYZ format (*.exyz)");

}

We now have to code the importFromFile function to read a molecule from a exyz file and create a structural model. To achieve this, we are going to use the SBIFileReader class to read a file, the SAMSON class to manage the undo mechanism and access the active document, and several structural classes. For that, include the following headers in the file:

#include "SAMSON.hpp"
#include "SBIFileReader.hpp"
#include "SBStructuralModel.hpp"
#include "SBAtom.hpp"

Let's now code inside the importFromFile function:

bool SEEXYZImporter::importFromFile(
    const std::string& fileName,
    const std::unordered_map<std::string, SBValue>* parameters,
    SBDDocumentFolder* preferredFolder)

We first begin by checking if the file exists and if it is readable:

    // check if file exists and valid

    QString qfileName = QString::fromStdString(fileName);
    QFileInfo checkFile(qfileName);

    // check the file

    if (!QFileInfo::exists(qfileName) || !checkFile.isFile() ||
        !checkFile.isReadable() || checkFile.size() == 0) {

        std::string msg = "EXYZImporter: Could not open EXYZ file " + fileName;

        if (!QFileInfo::exists(qfileName)) msg += " - file does not exist";
        else if (!checkFile.isFile())      msg += " - not a file";
        else if (!checkFile.isReadable())  msg += " - not readable";
        else if (checkFile.size() == 0)    msg += " - zero size";

        std::cerr << msg << "\n";
        SAMSON::setStatusMessage(QString::fromStdString(msg));

        return false;

    }

Now we open the file and store all its lines in a vector of strings:

// get file lines

std::vector<std::string> fileLineVector;
SBIFileReader::getFileLines(fileName, fileLineVector);

Then, we read the number of atoms in the file:

// read the number of atoms from the first line

unsigned int numberOfAtoms = 0;
std::stringstream numberOfAtomsParser(fileLineVector[0]);
numberOfAtomsParser >> numberOfAtoms;

We create a new structural model:

// create a new structural model

std::string name = fileLineVector[1];
SBMStructuralModel* structuralModel = new SBMStructuralModel();
structuralModel->setName(name);

And we read and create atoms that we add to the structural model:

// read atoms

int currentSerialNumber = 0;

for (unsigned int i = 2; i < fileLineVector.size(); i++) {

    // parse the current line

    double x, y, z;
    std::string atomType;

    std::stringstream atomParser(fileLineVector[i]);

    atomParser >> atomType >> x >> y >> z;

    // add a new atom to the model

    SBElement::Type element = SAMSON::getElementTypeBySymbol(atomType);
    if (element != SBElement::Unknown) {

        SBAtom* newAtom = new SBAtom(element,
                                        SBQuantity::angstrom(x),
                                        SBQuantity::angstrom(y),
                                        SBQuantity::angstrom(z));
        newAtom->setSerialNumber(currentSerialNumber++);
        structuralModel->getStructuralRoot()->addChild(newAtom);

    }

}

After adding atoms in the structural model we can create covalent bonds between these atoms:

// create covalent bonds

structuralModel->createCovalentBonds();

Now we can add the structural model to the document:

// start the undoable operation
SAMSON::beginHolding("Import EXYZ model");

SAMSON::hold(structuralModel);  // the undoable operation

// Create the structural model before adding it into the data graph.
// This will also create all its children (e.g., atoms).
structuralModel->create();

if (preferredFolder) {
    // add to the preferred folder
    preferredFolder->addChild(structuralModel);
}
else {
    // if there is no preferred folder, add to the active document
    SAMSON::getActiveDocument()->addChild(structuralModel);
}

// end the undoable operation
SAMSON::endHolding();

SAMSON::beginHolding and SAMSON::endHolding functions turn the undo system on and off, respectively. The SAMSON::hold function tells SAMSON to acquire ownership of the structural model and its contents. Line structuralModel->create(); sets the structural model to a created state (i.e. not erased). Then we implement the expected behavior of an importer, which is to import to the active document by default, unless a folder (preferedFolder) has been specified.

Finally, we signal that we had no issue with the file (in this version of the importer, we do not deal with potential reading errors):

    return true;

That's it, we have parsed the file, filled in a structural model, and added it into a SAMSON document.

If you want for the Importer to get some parameters/options from the user, please follow to the Getting parameters section, if not then you can simply disable the options window by replacing the constructor and destructor of SEEXYZImporter as follows:

SEEXYZImporter::SEEXYZImporter() {

    // remove the options window

    propertyDialog = nullptr;

}

SEEXYZImporter::~SEEXYZImporter() {

}

Getting parameters#

For some file formats it might be necessary for user to specify some parameters/options for an importer. Parameters can be received either from the GUI of an Importer or through the importFromFile function itself if it was invoked from the code. In this section we will show how to implement this.

Let's consider the possibility for the EXYZ Importer to import with or without creation of covalent bonds.

Open the SEEXYZImporterGUI.ui file in QtCreator. Remove the label with a tip and add a QCheckBox with a text as follows:

Add checkbox

Give a meaningful name to the check box: checkBoxCreateCovalentBonds

Change checkbox name

We want to save the GUI state (the state of the check box) from one session to the next. For that, we need to implement saveSettings and loadSettings functions. The saveSettings function is invoked when a SAMSON Extension is being destructed. The loadSettings function is invoked in the constructor of a SAMSON Extension.

void SEEXYZImporterGUI::saveSettings( SBGSettings *settings ) {

    if (settings == nullptr) return;

    settings->saveValue("checkBoxCreateCovalentBonds", 
                        ui.checkBoxCreateCovalentBonds->isChecked());

}

Here we save the current state of the check box in the variable checkBoxCreateCovalentBonds which will be stored thanks to the QSettings functionality.

void SEEXYZImporterGUI::loadSettings( SBGSettings *settings ) {

    if (settings == nullptr) return;

    ui.checkBoxCreateCovalentBonds->setChecked(
        settings->loadBoolValue("checkBoxCreateCovalentBonds", true));

}

Here we set the state of the check box to the saved one.

It is a good practice to separate GUI and non-GUI functionalities, e.g. do not access directly GUI elements from an another class. That is why we add the following function in the SEEXYZImporterGUI class (SEEXYZImporterGUI.hpp file):

void    getParameters(bool& createCovalentBonds);

This function will be used in the SEEXYZImporter to get parameters from the GUI class. Implement it as follows.

void SEEXYZImporterGUI::getParameters(bool &createCovalentBonds) {

    createCovalentBonds = ui.checkBoxCreateCovalentBonds->isChecked();

}

Add the following private variable in the SEEXYZImporter class (SEEXYZImporter.hpp file):

private:

    bool    createCovalentBonds;

Add the following functions in the SEEXYZImporter class (SEEXYZImporter.hpp file):

private:

    void    initializeParameters(const std::unordered_map<std::string, SBValue>* parameters);    ///< Initializes import parameters
    void    parseParameters(const std::unordered_map<std::string, SBValue>* parameters);         ///< Parse the parameters list

Implement the initializeParameters function that initializes parameters for the parser.

void SEEXYZImporter::initializeParameters(
    const std::unordered_map<std::string, SBValue>* parameters) {

    const int nparams = 1;    // total number of parameters

    // no parameters or only one parameter "default" => take from the form
    if (!parameters || (parameters->size() == 1 &&
        (parameters->find("default") != parameters->end()))) {

        // get parameters of the parser from the window
        static_cast<SEEXYZImporterGUI*>(propertyDialog)->getParameters(createCovalentBonds);

    }
    else {

        // parse parameters
        parseParameters(parameters);

    }

}

In this code, if no parameters has been specified (the parameters list is empty or has the wrong size) we take them from the GUI form, else we parse the parameters list for which we implement the parseParameters function as follows:

void SEEXYZImporter::parseParameters(
    const std::unordered_map<std::string, SBValue>* parameters) {

    // initialize with default values
    createCovalentBonds = true;

    if (parameters->find("createCovalentBonds") != parameters->end()) {

        const SBValue v = parameters->at("createCovalentBonds");
        if (SB_CAN_CAST<bool>(v))
            createCovalentBonds = SB_CAST<bool>(v);

    }

}

Here we go through the parameters list and read the parameters.

Now, invoke initializeParameters function in the beginning of the importFromFile function:

bool SEEXYZImporter::importFromFile(
    const std::string& fileName,
    const std::unordered_map<std::string, SBValue>* parameters,
    SBDDocumentFolder* preferredFolder) {

    initializeParameters(parameters);

    // ...

}

This allows both taking care of parameters specified through GUI or through direct invocation of the SAMSON::importFromFile function in the code.