Loading...
Searching...
No Matches
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, 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 Extension created in this tutorial can be found at https://github.com/1A-OneAngstrom/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):

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:

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)
This class describes a document folder.
Definition: SBDDocumentFolder.hpp:43

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;
}
static void setStatusMessage(const QString &message, int time=0)
Shows a message in the status bar.
Definition: SAMSON.cpp:1647

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);
virtual void setName(const std::string &name)
Sets the name of the node.
Definition: SBDDataGraphNode.cpp:480
This class describes a structural model.
Definition: SBMStructuralModel.hpp:21

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
if (element != SBElement::Unknown) {
SBAtom* newAtom = new SBAtom(element,
newAtom->setSerialNumber(currentSerialNumber++);
structuralModel->getStructuralRoot()->addChild(newAtom);
}
}
SBMStructuralModelNodeAtom SBAtom
The short name of SBMStructuralModelNodeAtom.
Definition: SBMStructuralModelNodeAtom.hpp:666
static SBElement::Type getElementTypeBySymbol(char *elementSymbol)
Returns the periodic table element type corresponding to a given symbol
Definition: SAMSON.cpp:846
This template class defines physical quantity types.
Definition: SBDQuantityType.hpp:43
Type
The element type.
Definition: SBMElement.hpp:31
@ Unknown
The type of an unknown element.
Definition: SBMElement.hpp:402
SBMStructuralModelNodeRoot * getStructuralRoot() const
Definition: SBMStructuralModel.cpp:456
This class describes an atom in a structural model.
Definition: SBMStructuralModelNodeAtom.hpp:34
void setSerialNumber(const int &serialNumber)
Sets the atom's serial number.
virtual bool addChild(SBNode *node, SBNode *nextNode=nullptr) override
Adds a child to the node.
Definition: SBMStructuralModelNodeGroup.cpp:169

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

// create covalent bonds
structuralModel->createCovalentBonds();
void createCovalentBonds()
Builds covalent bonds for the atoms belonging to the structural model according to the inter-atomic d...
Definition: SBMStructuralModel.cpp:1164

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
preferredFolder->addChild(structuralModel);
else // if there is no preferred folder, add to the active document
SAMSON::getActiveDocument()->addChild(structuralModel);
SAMSON::endHolding(); // end the undoable operation
static void endHolding()
Ends holding.
Definition: SAMSON.cpp:2141
static void beginHolding(const std::string &name)
Begins holding.
Definition: SAMSON.cpp:2138
static void hold(void *object)
Holds an object.
Definition: SAMSON.cpp:2143
static SBDDocument * getActiveDocument()
Returns a pointer to SAMSON's active document.
Definition: SAMSON.cpp:738
void create()
Creates the node.
Definition: SBDDataGraphNode.cpp:2312
virtual bool addChild(SBNode *node, SBNode *nextNode=nullptr) override
Adds a child to the folder.
Definition: SBDDocumentFolder.cpp:406

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:

Add QCheckBox

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

void SEEXYZImporterGUI::saveSettings( SBGSettings *settings ) {
if ( settings == NULL ) return;
settings->saveValue("checkBoxCreateCovalentBonds", ui.checkBoxCreateCovalentBonds->isChecked());
}
This class is used to save and load settings.
Definition: SBGSettings.hpp:17
void saveValue(const QString &name, QVariant value)
Saves a value.
Definition: SBGSettings.cpp:58

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;
ui.checkBoxCreateCovalentBonds->setChecked( settings->loadBoolValue("checkBoxCreateCovalentBonds", true) );
}
bool loadBoolValue(const QString &name, bool defaultValue=0)
Loads a boolean.
Definition: SBGSettings.cpp:74

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);
}
// 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 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);
}
}
This class represents values.
Definition: SBCMetaValue.hpp:23

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.