In this tutorial, we create an importer and an exporter for the xyz format.
Start by creating a new SAMSON Element called XYZ with the SAMSON Element Generator, and add an importer class (called SEXYZImporter) and an exporter class (SEXYZExporter).
Despite what the names suggest, this tutorial is rated E, for Everyone.
Parsing a xyz file
In SAMSON, an importer is created by programming three functions: getExtension, getFilter and importFromFile. Since you might have an xyz importer already, we’re actually going to read files with a xyzt extension (t for tutorial), even though the contents will still correspond to the xyz format.
Thus, modify the getExtension function in the SEXYZImporter.cpp file as follows:
std::string SEXYZImporter::getExtension() const { return std::string("xyzt"); } |
and the getFilter function as follows:
std::string SEXYZImporter::getFilter() const { return std::string("XYZT format (*.xyzt)"); } |
We now have to code the importFromFile function to read a molecule from a xyzt 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:
#include "SAMSON.hpp" #include "SBIFileReader.hpp" #include "SBMStructuralModel.hpp" #include "SBMStructuralModelNodeRoot.hpp" #include "SBMStructuralModelNodeAtom.hpp" |
We now code inside the importFromFile function. We first begin by opening 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 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | // read atoms 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)); structuralModel->getStructuralRoot()->addChild(newAtom); } } |
We’re almost ready to add the structural model to the document. We find covalent bonds:
structuralModel->createCovalentBonds(); |
and we add the model to the document:
1 2 3 4 5 6 7 8 9 10 11 | SAMSON::beginHolding("Import XYZ model"); // add the model to the data graph, and make this undoable SAMSON::hold(structuralModel); structuralModel->create(); if (preferredLayer == 0) SAMSON::getActiveLayer()->addChild(structuralModel); else preferredLayer->addChild(structuralModel); SAMSON::endHolding(); |
Lines 1 and 11 turn the undo system on and off, respectively. Line 5 tells SAMSON to acquire ownership of the structural model and its contents. Line 6 sets the model to a created state (i.e. not erased). Finally, lines 8 and 9 implement the expected behavior of an importer, which is to import to the active layer by default, unless another layer (preferedLayer) has been specified.
Finally, we signal that we had no issue with the document (in this version of the importer, we do not deal with potential reading errors):
return true; |
Exporting
Exporting is the reverse operation: we are given a list of selected nodes, and we export them to a file.
First, modify the getExtension and getFilter functions in the SEXYZExporter.cpp file as you did in the SEXYZImporter.cpp file (same extension, same filter).
Then, include the header files that we will need to code the exportToFile function:
#include <fstream> #include "SAMSON.hpp" #include "SBIFileReader.hpp" #include "SBMStructuralModel.hpp" #include "SBMStructuralModelNodeRoot.hpp" #include "SBMStructuralModelNodeAtom.hpp" |
where fstream will be used to create the file.
We can now add code to the exportToFile function. First, we find all atoms in the nodes that were passed to the exportToFile function:
// retrieve all atoms SBNodeIndexer atomIndexer; SB_FOR(SBNode* node, nodeIndexer) node->getNodes(atomIndexer, SBNode::IsType(SBNode::Atom)); |
Recall that SBNode::IsType(SBNode::Atom) is a predicate, passed to the getNodes function, that returns true for nodes that are atoms.
Then, we create the file and write its header:
// create file ofstream file; file.open(fileName.c_str()); // write the header file << atomIndexer.size() << std::endl; // number of atoms file << "SAMSON Export" << std::endl; |
and we then write the description of each atom to the file:
1 2 3 4 5 6 7 8 9 10 11 12 13 | // write atoms for (unsigned int i = 0; i < atomIndexer.size(); i++) { SBAtom* currentAtom = static_cast<SBAtom*>(atomIndexer[i]); SBPosition3 const& currentAtomPosition = currentAtom->getPosition(); file << SAMSON::getElementSymbol(currentAtom->getElementType()) << " " << SBQuantity::angstrom(currentAtomPosition.v[0]).getValue() << " " << SBQuantity::angstrom(currentAtomPosition.v[1]).getValue() << " " << SBQuantity::angstrom(currentAtomPosition.v[2]).getValue() << "\n"; } |
Line 5 casts the current node pointer to a pointer to an atom, so we can use the functionality of SBAtom (i.e. the getPosition and getElementType functions). Casting is safe, since the predicate we used guarantees that all nodes in atomIndexer are indeed atoms.
Finally, we signal that everything went fine (at least we believe so, since we didn’t test for errors in this tutorial):
return true; |
That’s it for the importer and the exporter. Since the xyz format is simple, we can actually remove the options window that appears immediately before importing or exporting, by replacing the constructor of SEXYZImporter as follows:
SEXYZImporter::SEXYZImporter() { propertyDialog = 0; } |
and the constructor of SEXYZExporter as follows:
SEXYZExporter::SEXYZExporter() { propertyDialog = 0; } |
We also need to empty the destructors of both classes:
SEXYZImporter::~SEXYZImporter() { } |
and:
SEXYZExporter::~SEXYZExporter() { } |
We may now export to an xyzt file and import back from it.