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:
Modify the getFilter
function to return the filter of the importer as follows (it should be in the Qt format):
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:
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):
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:
Give a meaningful name to the check box: checkBoxCreateCovalentBonds
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):
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):
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.