Importing data

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 (e for element), even though the contents will still correspond to the xyz format. Therefore, to test this importer, you can export (File menu > Save as) any system from SAMSON in the XYZ format and change the extension from .xyz to .exyz.

The source code for an Element created in this tutorial can be found at https://github.com/1A-OneAngstrom/SAMSON-Developer-Tutorials.

# Generating an Element

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

The SAMSON Element 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:

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 "SBStructuralModel.hpp"
#include "SBAtom.hpp"

Let's now code inside the importFromFile function:

bool SEEXYZImporter::importFromFile(const std::string& fileName, const SBList<std::string>* 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.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;

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:

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
if (element != SBElement::Unknown) {
SBAtom* newAtom = new SBAtom(element,
newAtom->setSerialNumber(currentSerialNumber++);
}
}

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:

SAMSON::beginHolding("Import EXYZ model"); // start the undoable operation
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
else // if there is no preferred folder, add to the active document
SAMSON::endHolding(); // end the undoable operation

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 section Getting parameters, 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 = 0;
}
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:

Give a meaningful name to the check box: checkBoxCreateCovalentBonds

Rename the check box

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 Element is being destructed. The loadSettings function is invoked in the constructor of a SAMSON Element.

void SEEXYZImporterGUI::saveSettings( SBGSettings *settings ) {
if ( settings == NULL ) 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 == NULL ) return;
}

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 SBList<std::string>* parameters); ///< Initializes import parameters
void parseParameters(const SBList<std::string>* parameters); ///< Parse the parameters list

Implement the initializeParameters function that initializes parameters for the parser.

void SEEXYZImporter::initializeParameters(const SBList<std::string>* parameters) {
const int nparams = 1; // total number of parameters
// no parameters or only one parameter "default" => take from form
if (!parameters || (parameters->size() == 1 && (*parameters->begin()) == "default") || parameters->size() != nparams) {
// get parameters of the parser from the window
static_cast<SEEXYZImporterGUI*>(propertyDialog)->getParameters(createCovalentBonds);
}
// analyze parameters
else {
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 SBList<std::string>* cparameters) {
SBList<std::string> *parameters = 0;
if (cparameters) parameters = new SBList<std::string>(*cparameters);
if ((*parameters->begin()) == "1")
createCovalentBonds = true;
else
createCovalentBonds = false;
parameters->pop_front();
if (parameters) delete parameters;
}

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 SBList<std::string>* 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.