Extended CML to save display properties

As far as I understand, Avogadro saves all file formats through OB. However, there is need to store display properties somewhere, e.g. to save axes orientation in space, background color, settings of engines, in future - settings of GLWidget, to reproduce picture exactly when file is opened next time. I think extension of CML will work fine for this purpose, but it’ll require changes in file saving

  1. Do you like this idea?
  2. What is right place for changes: MoleculeFile::writeMolecule/readMolecule/readFile, MainWindow::saveFile/openFile or somewhere else?
  3. What name for new format will you propose (Avogadro Document, Extended CML, etc)? What file extension (.cml or other)?

    Regards,
    Konstantin
  1. Do you like this idea?

I worked on this for a while, but didn’t have the time to push the imported settings through GLWidget and all appropriate classes. I’ll explain below.

  1. What is right place for changes: MoleculeFile::writeMolecule/readMolecule/readFile, MainWindow::saveFile/openFile or somewhere else?

Well, the code was in MainWindow::loadFile(), but most of it probably makes more sense in MoleculeFile now. Then once the molecule is loaded, MainWindow will need to tell everyone to update their settings.

  1. What name for new format will you propose (Avogadro Document, Extended CML, etc)? What file extension (.cml or other)?

You actually don’t need a new format. CML and Open Babel have the capability of reading/writing arbitrary metadata through the OBPairData interface. So what I did was to create OBPairData with names that began with “Avogadro:” and imported/exported that into our QSettings. That much was easy. It leaves a “normal” CML file, but with Avogadro data in our own namespace.

So there were three problems with my approach:

  1. I only handled molecule-level custom data. I didn’t yet handle customizations for residues, bonds, or atoms, although that’s possible using OB.
  2. I didn’t realize that beyond just loading settings, you needed to tell GLWidget and all other objects to reload their settings.
  3. There’s no separation between “levels” of settings. I mean, we might want:
  • Global settings which can only be changed by the Settings window and Plugin manager
  • Document-level settings like the camera viewpoint, etc.
  • Per-atom level settings?

Anyway, here’s the code for reading from an older version of mainwindow.cpp:

  Molecule *mol = new Molecule;
  mol->setOBMol(obMolecule);
  setMolecule(mol);
  // Now unroll any settings we saved in the file
  if (obMolecule->HasData(OBGenericDataType::PairData)) {
    QSettings settings;
    // We've saved the settings with key Avogadro:blah as an OBPairData.
    std::vector<OBGenericData *> pairDataVector = obMolecule->GetAllData(OBGenericDataType::PairData);
    OBPairData *savedSetting;
    OBDataIterator i;
    QString attribute;

    for (i = pairDataVector.begin(); i != pairDataVector.end(); ++i) {
      savedSetting = dynamic_cast<OBPairData *>(*i);
      // Check to see if this is an Avogadro setting
      attribute = savedSetting->GetAttribute().c_str();
      if (attribute.startsWith("Avogadro:")) {
        attribute.remove("Avogadro:");
        settings.setValue(attribute, savedSetting->GetValue().c_str());
        // TODO: we should probably delete the entry from obMolecule now, but I'm going to play it safe first
      }
    }
  } // end reading OBPairData

Here’s the code for writing:

OBMol obmol = d->molecule->OBMol();

// We're going to wrap up the QSettings and save them to the CML file
if (QString(outFormat->GetID()).compare("cml", Qt::CaseInsensitive) == 0) {
  // First off, let's set some CML options
  conv.AddOption("p"); // add properties -- including OBPairData
  conv.AddOption("m"); // Dublin Core metadata
  conv.AddOption("a"); // array format for atoms & bonds

  QSettings settings;
  OBPairData *savedSetting;
  QString attribute, value;

  // Walk through all our settings (is there a more efficient way to do this?)
  foreach(QString key, settings.allKeys()) {
    // Ignore this key -- there are definitely some on Mac with Apple... or com/
    // There may be others to ignore on Linux and Windows, but I haven't tested those yet.
    if (key.startsWith("Apple") || key.startsWith("com/") || key.startsWith("NS"))
      continue;

    if (key.startsWith("enginesDock")) {
      continue; // TODO: this seems to kill the CML
    }

    // We're going to save all our settings as Avogadro:blah
    savedSetting = new OBPairData;
    attribute = "Avogadro:" + key;
    // Convert from QString to char*
    savedSetting->SetAttribute(attribute.toAscii().constData());

    value = settings.value(key).toString();
    // Convert from QString to char*
    savedSetting->SetValue(value.toAscii().constData());
    savedSetting->SetOrigin(userInput);
    obmol.SetData(savedSetting);
  }
} // end saving settings for CML files

if ( conv.Write( &obmol, &ofs ) ) {
  1. What name for new format will you propose (Avogadro Document, Extended CML, etc)? What file extension (.cml or other)?
    You actually don’t need a new format. CML and Open Babel have the capability of reading/writing arbitrary metadata through the OBPairData interface. So what I did was to create OBPairData with names that began with “Avogadro:” and imported/exported that into our QSettings. That much was easy. It leaves a “normal” CML file, but with Avogadro data in our own namespace.

That’s great! Nevertheless, I think addition new “format” will not be useless, e.g. to give a possibility to save CML with or without Avogadro’s metadata. CML is widely used, some programs (e.g., MarvinSketch) allow to write extended CML files and manages them as separate format.

So there were three problems with my approach:

  1. I only handled molecule-level custom data. I didn’t yet handle customizations for residues, bonds, or atoms, although that’s possible using OB.
  2. I didn’t realize that beyond just loading settings, you needed to tell GLWidget and all other objects to reload their settings.
  3. There’s no separation between “levels” of settings. I mean, we might want:
  • Global settings which can only be changed by the Settings window and Plugin manager
  • Document-level settings like the camera viewpoint, etc.
  • Per-atom level settings?

IMHO, per-atom settings must be in document level. For example, in one molecule, where Co(II) has tetrahedral coordination sphere, I’d like to fill it with violet, in another with octahedral coordination I’d like to use pink, another compound is yellow because it contains Co(III), etc.


Regards,
Konstantin

P.S. Please, set up reply to the list :slight_smile:

That’s great! Nevertheless, I think addition new “format” will not be useless, e.g. to give a possibility to save CML with or without Avogadro’s metadata. CML is widely used, some programs (e.g., MarvinSketch) allow to write extended CML files and manages them as separate format.

Part of the idea of CML (and XML in general) is that they are extensible, and that programs like Marvin or Avogadro can add metadata in their own namespaces. Other programs can either ignore this or attempt to read it as appropriate. Consider, for example, other programs which use libavogadro – wouldn’t they benefit from automatically reading our settings? Or what about Jmol? They might want to support custom colors.

Here’s another example… some computational chemistry packages are working on CML output. Obviously, not all programs would understand all of the data/metadata in the file. The point of using CML, however, is that you don’t need to write another output parser in Open Babel. :slight_smile:

As the lead developer on Open Babel, I’m also against the idea of creating new quasi-formats. CML is up to the task – it’s designed for exactly this purpose.

  • Global settings which can only be changed by the Settings window and Plugin manager
  • Document-level settings like the camera viewpoint, etc.
  • Per-atom level settings?

IMHO, per-atom settings must be in document level. For example, in one molecule, where Co(II) has tetrahedral coordination sphere, I’d like to fill it with violet, in another with octahedral coordination I’d like to use pink, another compound is yellow because it contains Co(III), etc.

I agree completely. My point, was that my previous code didn’t support per-atom (or other) settings. It stored everything in QSettings and the OBMol. Obviously, you might want to have per-atom settings, and those should be stored in the Atom/OBAtom code. It’s not a big code modification, it’s just that nothing in Avogadro supported per-atom customization at that point.

In any case, I’d be thrilled to help you get this working.

Hope that helps,
-Geoff