From 2ec502cd0e54450bb4210d2f7ee5e82eeeec865c Mon Sep 17 00:00:00 2001 From: Geoff Hutchison Date: Sat, 7 Oct 2023 13:02:10 -0400 Subject: [PATCH] Add support for atom labels in the coordinate editor Suggest on the forum: https://discuss.avogadro.cc/t/atom-labeling-in-exported-xyz-file/4878 Signed-off-by: Geoff Hutchison --- avogadro/core/coordinateblockgenerator.cpp | 10 +++++++ avogadro/core/coordinateblockgenerator.h | 1 + .../coordinateeditordialog.cpp | 26 ++++++++++++++++++- .../coordinateeditordialog.ui | 2 +- 4 files changed, 37 insertions(+), 2 deletions(-) diff --git a/avogadro/core/coordinateblockgenerator.cpp b/avogadro/core/coordinateblockgenerator.cpp index ddf3c24314..d7ec38f3c8 100644 --- a/avogadro/core/coordinateblockgenerator.cpp +++ b/avogadro/core/coordinateblockgenerator.cpp @@ -42,6 +42,7 @@ std::string CoordinateBlockGenerator::generateCoordinateBlock() for (it = begin; it != end; ++it) { switch (*it) { case 'S': + case 'L': needElementSymbol = true; break; case 'N': @@ -76,6 +77,7 @@ std::string CoordinateBlockGenerator::generateCoordinateBlock() { atomicNumberPrecision = 0, atomicNumberWidth = 3, + atomicLabelWidth = 8, // 3 element symbol + 5 index coordinatePrecision = 6, coordinateWidth = 11, elementNameWidth = 13, // Currently the longest element name @@ -89,10 +91,14 @@ std::string CoordinateBlockGenerator::generateCoordinateBlock() // Use fixed number format. m_stream << std::fixed; + // Count the number for each element + std::vector elementCounts(Elements::elementCount(), 0); + // Iterate through the atoms for (Index atomI = 0; atomI < numAtoms; ++atomI) { atom = m_molecule->atom(atomI); atomicNumber = atom.atomicNumber(); + elementCounts[atomicNumber]++; if (needElementSymbol) symbol = Core::Elements::symbol(atomicNumber); if (needElementName) @@ -137,6 +143,10 @@ std::string CoordinateBlockGenerator::generateCoordinateBlock() case 'S': m_stream << std::left << std::setw(elementSymbolWidth) << symbol; break; + case 'L': + m_stream << std::left << symbol + << elementCounts[atomicNumber] << " "; + break; case 'N': m_stream << std::left << std::setw(elementNameWidth) << name; break; diff --git a/avogadro/core/coordinateblockgenerator.h b/avogadro/core/coordinateblockgenerator.h index 600240d9c2..0f5f8cea8f 100644 --- a/avogadro/core/coordinateblockgenerator.h +++ b/avogadro/core/coordinateblockgenerator.h @@ -47,6 +47,7 @@ class AVOGADROCORE_EXPORT CoordinateBlockGenerator * about each atom in the coordinate block. * - @c #: Atom index (one-based index) * - @c Z: Atomic number (e.g. "6" for carbon) + * - @c L: Atomic label (e.g., "C1" for first carbon, "H2" for second hydrogen") * - @c G: GAMESS-styled Atomic number (e.g. "6.0" for carbon) * - @c S: Element symbol (e.g. "C" for carbon) * - @c N: Element name (e.g. "Carbon") diff --git a/avogadro/qtplugins/coordinateeditor/coordinateeditordialog.cpp b/avogadro/qtplugins/coordinateeditor/coordinateeditordialog.cpp index 96b76c368d..085676426f 100644 --- a/avogadro/qtplugins/coordinateeditor/coordinateeditordialog.cpp +++ b/avogadro/qtplugins/coordinateeditor/coordinateeditordialog.cpp @@ -140,7 +140,7 @@ CoordinateEditorDialog::CoordinateEditorDialog(QWidget* parent_) SLOT(textModified(bool))); // Setup spec edit - QRegExp specRegExp("[#ZGSNabcxyz01_]*"); + QRegExp specRegExp("[#ZGSLNabcxyz01_]*"); auto* specValidator = new QRegExpValidator(specRegExp, this); m_ui->spec->setValidator(specValidator); connect(m_ui->presets, SIGNAL(currentIndexChanged(int)), @@ -385,6 +385,30 @@ void CoordinateEditorDialog::validateInputWorker() break; } + case 'L': { + // Validate label (symbol + number) + + QString cleanToken(tokenCursor.selectedText().toLower()); + if (!cleanToken.isEmpty()) + cleanToken.replace(0, 1, cleanToken[0].toUpper()); + + // Split the label into symbol and number + QRegExp labelSplitter("([A-Z][a-z]?)(\\d+)"); + if (labelSplitter.indexIn(cleanToken) == -1) { + m_ui->text->markInvalid(tokenCursor, tr("Invalid atom label.")); + break; + } + // check the symbol + std::string tokenStd(labelSplitter.cap(1).toStdString()); + atom.atomicNumber = Elements::atomicNumberFromSymbol(tokenStd); + if (atom.atomicNumber == Avogadro::InvalidElement) + m_ui->text->markInvalid(tokenCursor, tr("Invalid element symbol.")); + else + m_ui->text->markValid(tokenCursor, tr("Element symbol.")); + break; + + } + case '#': { // Validate integer: bool isInt; diff --git a/avogadro/qtplugins/coordinateeditor/coordinateeditordialog.ui b/avogadro/qtplugins/coordinateeditor/coordinateeditordialog.ui index fedf142f58..1862a884e3 100644 --- a/avogadro/qtplugins/coordinateeditor/coordinateeditordialog.ui +++ b/avogadro/qtplugins/coordinateeditor/coordinateeditordialog.ui @@ -68,7 +68,7 @@ - <html><head/><body><p>Specification of format. Each character indicates a value to write per atom:</p><p><span style=" font-weight:600;">#</span> - Atom index (1, 2, ..., numAtoms)<br/><span style=" font-weight:600;">Z</span> - Atomic number (e.g. &quot;6&quot; for carbon)<br/><span style=" font-weight:600;">G</span> - GAMESS-style atomic number (e.g. &quot;6.0&quot; for carbon)<br/><span style=" font-weight:600;">N</span> - Element name (e.g. &quot;Carbon&quot;)<br/><span style=" font-weight:600;">S</span> - Element symbol (e.g. &quot;C&quot; for carbon)<br/><span style=" font-weight:600;">x</span> - X position coordinate<br/><span style=" font-weight:600;">y</span> - Y position coordinate<br/><span style=" font-weight:600;">z</span> - Z position coordinate<br/><span style=" font-weight:600;">a</span> - 'a' lattice coordinate (crystals only)<br/><span style=" font-weight:600;">b</span> - 'b' lattice coordinate (crystals only)<br/><span style=" font-weight:600;">c</span> - 'c' lattice coordinate (crystals only)<br/><span style=" font-weight:600;">_</span> - A literal space (&quot; &quot;), useful for alignment<br/><span style=" font-weight:600;">0</span> - A literal 0 (&quot;0&quot;), useful for optimization flags<br/><span style=" font-weight:600;">1</span> - A literal 1 (&quot;1&quot;), useful for optimization flags<br/></p></body></html> + <html><head/><body><p>Specification of format. Each character indicates a value to write per atom:</p><p><span style=" font-weight:600;">#</span> - Atom index (1, 2, ..., numAtoms)<br/><span style=" font-weight:600;">Z</span> - Atomic number (e.g. &quot;6&quot; for carbon)<br/><span style=" font-weight:600;">G</span> - GAMESS-style atomic number (e.g. &quot;6.0&quot; for carbon)<br/><span style=" font-weight:600;">N</span> - Element name (e.g. &quot;Carbon&quot;)<br/><span style=" font-weight:600;">S</span> - Element symbol (e.g. &quot;C&quot; for carbon)<br/><span style=" font-weight:700;">L</span> - Atom label (e.g., &quot;C2&quot; for second carbon atom, &quot;H1&quot; for first hydrogen) <br/><span style=" font-weight:600;">x</span> - X position coordinate<br/><span style=" font-weight:600;">y</span> - Y position coordinate<br/><span style=" font-weight:600;">z</span> - Z position coordinate<br/><span style=" font-weight:600;">a</span> - 'a' lattice coordinate (crystals only)<br/><span style=" font-weight:600;">b</span> - 'b' lattice coordinate (crystals only)<br/><span style=" font-weight:600;">c</span> - 'c' lattice coordinate (crystals only)<br/><span style=" font-weight:600;">_</span> - A literal space (&quot; &quot;), useful for alignment<br/><span style=" font-weight:600;">0</span> - A literal 0 (&quot;0&quot;), useful for optimization flags<br/><span style=" font-weight:600;">1</span> - A literal 1 (&quot;1&quot;), useful for optimization flags<br/></p></body></html>