From ca5137c84ea7307c5dfad1ab3fadcf213a5132f2 Mon Sep 17 00:00:00 2001 From: Geoff Hutchison Date: Thu, 28 Jul 2022 13:02:24 -0400 Subject: [PATCH 1/5] Initial port of align tool from Avogadro 1.x code Signed-off-by: Geoff Hutchison --- avogadro/qtplugins/CMakeLists.txt | 1 + avogadro/qtplugins/aligntool/CMakeLists.txt | 22 ++ avogadro/qtplugins/aligntool/align.png | Bin 0 -> 3708 bytes avogadro/qtplugins/aligntool/align@2x.png | Bin 0 -> 5646 bytes avogadro/qtplugins/aligntool/aligntool.cpp | 318 ++++++++++++++++++++ avogadro/qtplugins/aligntool/aligntool.h | 96 ++++++ avogadro/qtplugins/aligntool/aligntool.qrc | 6 + 7 files changed, 443 insertions(+) create mode 100644 avogadro/qtplugins/aligntool/CMakeLists.txt create mode 100644 avogadro/qtplugins/aligntool/align.png create mode 100644 avogadro/qtplugins/aligntool/align@2x.png create mode 100644 avogadro/qtplugins/aligntool/aligntool.cpp create mode 100644 avogadro/qtplugins/aligntool/aligntool.h create mode 100644 avogadro/qtplugins/aligntool/aligntool.qrc diff --git a/avogadro/qtplugins/CMakeLists.txt b/avogadro/qtplugins/CMakeLists.txt index 49d7c5223b..481930e876 100644 --- a/avogadro/qtplugins/CMakeLists.txt +++ b/avogadro/qtplugins/CMakeLists.txt @@ -97,6 +97,7 @@ endfunction() # Now to make the plugins. add_subdirectory(3dmol) add_subdirectory(applycolors) +add_subdirectory(aligntool) add_subdirectory(bondcentrictool) add_subdirectory(bonding) add_subdirectory(cartoons) diff --git a/avogadro/qtplugins/aligntool/CMakeLists.txt b/avogadro/qtplugins/aligntool/CMakeLists.txt new file mode 100644 index 0000000000..6b579404d9 --- /dev/null +++ b/avogadro/qtplugins/aligntool/CMakeLists.txt @@ -0,0 +1,22 @@ +set(aligntool_srcs + aligntool.cpp +) + +set(aligntool_uis +) + +set(aligntool_rcs + aligntool.qrc +) + +avogadro_plugin(AlignTool + "AlignTool" + ToolPlugin + aligntool.h + AlignTool + "${aligntool_srcs}" + "${aligntool_uis}" + "${aligntool_rcs}" +) + +target_link_libraries(AlignTool PRIVATE Avogadro::QtOpenGL) diff --git a/avogadro/qtplugins/aligntool/align.png b/avogadro/qtplugins/aligntool/align.png new file mode 100644 index 0000000000000000000000000000000000000000..5bd38385833e9fabf21e2177f566b5b8d3db1c40 GIT binary patch literal 3708 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 literal 0 HcmV?d00001 diff --git a/avogadro/qtplugins/aligntool/align@2x.png b/avogadro/qtplugins/aligntool/align@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..ab671cbb6bc1f9051297ba7330d98eabafc32c29 GIT binary patch literal 5646 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 literal 0 HcmV?d00001 diff --git a/avogadro/qtplugins/aligntool/aligntool.cpp b/avogadro/qtplugins/aligntool/aligntool.cpp new file mode 100644 index 0000000000..c7109ba6a3 --- /dev/null +++ b/avogadro/qtplugins/aligntool/aligntool.cpp @@ -0,0 +1,318 @@ +/****************************************************************************** + This source file is part of the Avogadro project. + This source code is released under the 3-Clause BSD License, (see "LICENSE"). +******************************************************************************/ + +#include "aligntool.h" + +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using Avogadro::Core::Elements; +using Avogadro::QtGui::Molecule; +using Avogadro::Rendering::GeometryNode; +using Avogadro::Rendering::Identifier; +using Avogadro::Rendering::TextLabel3D; +using Avogadro::Rendering::TextProperties; + +namespace Avogadro::QtPlugins { + +using QtGui::Molecule; +using QtGui::RWAtom; + +AlignTool::AlignTool(QObject* parent_) + : QtGui::ToolPlugin(parent_), m_activateAction(new QAction(this)), + m_molecule(nullptr), m_toolWidget(nullptr) +{ + m_activateAction->setText(tr("Align")); + m_activateAction->setIcon(QIcon(":/icons/align.png")); + m_activateAction->setToolTip( + tr("Align Molecules\n\n" + "Left Mouse: \tSelect up to two atoms.\n" + "\tThe first atom is centered at the origin.\n" + "\tThe second atom is aligned to the selected axis.\n" + "Right Mouse: \tReset alignment.\n" + "Double-Click: \tCenter the atom at the origin.")); +} + +AlignTool::~AlignTool() +{ + if (m_toolWidget) + m_toolWidget->deleteLater(); +} + +QWidget* AlignTool::toolWidget() const +{ + if (!m_toolWidget) { + m_toolWidget = new QWidget; + + QLabel* labelAxis = new QLabel(tr("Axis:"), m_toolWidget); + labelAxis->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + labelAxis->setMaximumHeight(15); + + // Combo box to select desired aixs to align to + QComboBox* comboAxis = new QComboBox(m_toolWidget); + m_axis = 0; + comboAxis->addItem("x"); + comboAxis->addItem("y"); + comboAxis->addItem("z"); + comboAxis->setCurrentIndex(m_axis); + + // Button to actually perform actions + QPushButton* buttonAlign = new QPushButton(m_toolWidget); + buttonAlign->setText(tr("Align")); + connect(buttonAlign, SIGNAL(clicked()), this, SLOT(align())); + + QGridLayout* gridLayout = new QGridLayout(); + gridLayout->addWidget(labelAxis, 0, 0, 1, 1, Qt::AlignRight); + QHBoxLayout* hLayout = new QHBoxLayout; + hLayout->addWidget(comboAxis); + hLayout->addStretch(1); + gridLayout->addLayout(hLayout, 0, 1); + + QHBoxLayout* hLayout3 = new QHBoxLayout(); + hLayout3->addStretch(1); + hLayout3->addWidget(buttonAlign); + hLayout3->addStretch(1); + QVBoxLayout* layout = new QVBoxLayout(); + layout->addLayout(gridLayout); + layout->addLayout(hLayout3); + layout->addStretch(1); + m_toolWidget->setLayout(layout); + + connect(comboAxis, SIGNAL(currentIndexChanged(int)), this, + SLOT(axisChanged(int))); + + connect(m_toolWidget, SIGNAL(destroyed()), this, + SLOT(toolWidgetDestroyed())); + } + + return m_toolWidget; +} + +void AlignTool::axisChanged(int axis) +{ + // Axis to use - x=0, y=1, z=2 + m_axis = axis; +} + +void AlignTool::alignChanged(int align) +{ + // Type of alignment - 0=everything, 1=molecule + m_alignType = align; +} + +void AlignTool::align() +{ + if (m_atoms.size() == 0) + return; + + if (m_atoms.size() >= 1) + shiftAtomToOrigin(m_atoms[0].index); + if (m_atoms.size() == 2) + alignAtomToAxis(m_atoms[1].index, m_axis); + + m_atoms.clear(); +} + +void AlignTool::shiftAtomToOrigin(Index atomIndex) +{ + // Shift the atom to the origin + Vector3 shift = m_molecule->atom(atomIndex).position3d(); + const Core::Array& coords = m_molecule->atomPositions3d(); + Core::Array newCoords(coords.size()); + for (Index i = 0; i < coords.size(); ++i) + newCoords[i] = coords[i] - shift; + + m_molecule->setAtomPositions3d(newCoords, tr("Align at Origin")); + m_molecule->emitChanged(QtGui::Molecule::Atoms); +} + +void AlignTool::alignAtomToAxis(Index atomIndex, int axis) +{ + // Align the atom to the specified axis + Vector3 align = m_molecule->atom(atomIndex).position3d(); + const Core::Array& coords = m_molecule->atomPositions3d(); + Core::Array newCoords(coords.size()); + + double alpha, beta, gamma; + alpha = beta = gamma = 0.0; + + Vector3 pos = m_molecule->atom(atomIndex).position3d(); + pos.normalize(); + Vector3 axisVector; + + if (axis == 0) // x-axis + axisVector = Vector3(1., 0., 0.); + else if (axis == 1) // y-axis + axisVector = Vector3(0., 1., 0.); + else if (axis == 2) // z-axis + axisVector = Vector3(0., 0., 1.); + + // Calculate the angle of the atom from the axis + double angle = acos(axisVector.dot(pos)); + + // Get the vector for the rotation + axisVector = axisVector.cross(pos); + axisVector.normalize(); + + // Now to rotate the fragment + for (Index i = 0; i < coords.size(); ++i) + newCoords[i] = Eigen::AngleAxisd(-angle, axisVector) * coords[i]; + + m_molecule->setAtomPositions3d(newCoords, tr("Align to Axis")); + m_molecule->emitChanged(QtGui::Molecule::Atoms); +} + +void AlignTool::toolWidgetDestroyed() +{ + m_toolWidget = nullptr; +} + +QUndoCommand* AlignTool::mousePressEvent(QMouseEvent* e) +{ + // If the click is released on an atom, add it to the list + if (e->button() != Qt::LeftButton || !m_renderer) + return nullptr; + + Identifier hit = m_renderer->hit(e->pos().x(), e->pos().y()); + + // Now add the atom on release. + if (hit.type == Rendering::AtomType) { + if (toggleAtom(hit)) + emit drawablesChanged(); + e->accept(); + } + + return nullptr; +} + +QUndoCommand* AlignTool::mouseDoubleClickEvent(QMouseEvent* e) +{ + // Reset the atom list + if (e->button() == Qt::LeftButton && !m_atoms.isEmpty()) { + m_atoms.clear(); + emit drawablesChanged(); + e->accept(); + } + return nullptr; +} + +bool AlignTool::toggleAtom(const Rendering::Identifier& atom) +{ + int ind = m_atoms.indexOf(atom); + if (ind >= 0) { + m_atoms.remove(ind); + return true; + } + + if (m_atoms.size() >= 2) + return false; + + m_atoms.push_back(atom); + return true; +} + +inline Vector3ub AlignTool::contrastingColor(const Vector3ub& rgb) const +{ + // If we're far 'enough' (+/-32) away from 128, just invert the component. + // If we're close to 128, inverting the color will end up too close to the + // input -- adjust the component before inverting. + const unsigned char minVal = 32; + const unsigned char maxVal = 223; + Vector3ub result; + for (size_t i = 0; i < 3; ++i) { + unsigned char input = rgb[i]; + if (input > 160 || input < 96) + result[i] = static_cast(255 - input); + else + result[i] = static_cast(255 - (input / 4)); + + // Clamp to 32-->223 to prevent pure black/white + result[i] = std::min(maxVal, std::max(minVal, result[i])); + } + + return result; +} + +void AlignTool::draw(Rendering::GroupNode& node) +{ + if (m_atoms.size() == 0) + return; + + auto* geo = new GeometryNode; + node.addChild(geo); + + // Add labels, extract positions + QVector positions(m_atoms.size(), Vector3()); + + TextProperties atomLabelProp; + atomLabelProp.setFontFamily(TextProperties::SansSerif); + atomLabelProp.setAlign(TextProperties::HCenter, TextProperties::VCenter); + + for (int i = 0; i < m_atoms.size(); ++i) { + Identifier& ident = m_atoms[i]; + Q_ASSERT(ident.type == Rendering::AtomType); + Q_ASSERT(ident.molecule != nullptr); + + auto atom = m_molecule->atom(ident.index); + Q_ASSERT(atom.isValid()); + unsigned char atomicNumber(atom.atomicNumber()); + positions[i] = atom.position3d(); + + // get the color of the atom + const unsigned char* color = Elements::color(atomicNumber); + atomLabelProp.setColorRgb(contrastingColor(Vector3ub(color)).data()); + + auto* label = new TextLabel3D; + label->setText(QString("#%1").arg(i + 1).toStdString()); + label->setTextProperties(atomLabelProp); + label->setAnchor(positions[i].cast()); + label->setRadius( + static_cast(Elements::radiusCovalent(atomicNumber)) + 0.1f); + geo->addDrawable(label); + } +} + +void AlignTool::registerCommands() +{ + emit registerCommand("centerAtom", tr("Center the atom at the origin.")); + emit registerCommand( + "alignAtom", + tr("Rotate the molecule to align the atom to the specified axis.")); +} + +bool AlignTool::handleCommand(const QString& command, + const QVariantMap& options) +{ + if (m_molecule == nullptr) + return false; // No molecule to handle the command. + + return true; +} +} // namespace Avogadro::QtPlugins diff --git a/avogadro/qtplugins/aligntool/aligntool.h b/avogadro/qtplugins/aligntool/aligntool.h new file mode 100644 index 0000000000..1fb13d517e --- /dev/null +++ b/avogadro/qtplugins/aligntool/aligntool.h @@ -0,0 +1,96 @@ +/****************************************************************************** + This source file is part of the Avogadro project. + This source code is released under the 3-Clause BSD License, (see "LICENSE"). +******************************************************************************/ + +#ifndef AVOGADRO_QTPLUGINS_ALIGNTOOL_H +#define AVOGADRO_QTPLUGINS_ALIGNTOOL_H + +#include + +#include +#include + +namespace Avogadro { +namespace QtPlugins { + +/** + * @class AlignTool aligntool.h + * + * @brief The Align Tool class aligns molecules to a frame of reference. + * @author Geoffrey Hutchison + */ +class AlignTool : public QtGui::ToolPlugin +{ + Q_OBJECT +public: + explicit AlignTool(QObject* parent_ = nullptr); + ~AlignTool() override; + + QString name() const override { return tr("Align tool"); } + QString description() const override + { + return tr("Align molecules to a Cartesian axis"); + } + unsigned char priority() const override { return 90; } + QAction* activateAction() const override { return m_activateAction; } + QWidget* toolWidget() const override; + + void setMolecule(QtGui::Molecule* mol) override + { + if (mol) + m_molecule = mol->undoMolecule(); + } + + void setEditMolecule(QtGui::RWMolecule* mol) override { m_molecule = mol; } + + void setGLRenderer(Rendering::GLRenderer* renderer) override + { + m_renderer = renderer; + } + + QUndoCommand* mousePressEvent(QMouseEvent* e) override; + QUndoCommand* mouseDoubleClickEvent(QMouseEvent* e) override; + + void draw(Rendering::GroupNode& node) override; + + Vector3ub contrastingColor(const Vector3ub& rgb) const; + + void shiftAtomToOrigin(Index atomIndex); + void alignAtomToAxis(Index atomIndex, int axis); + + bool toggleAtom(const Rendering::Identifier& atom); + + bool handleCommand(const QString& command, + const QVariantMap& options) override; + + /** + * Called by the app to tell the tool to register commands. + * If the tool has commands, it should emit the registerCommand signals. + */ + void registerCommands() override; + +public Q_SLOTS: + void axisChanged(int axis); + void alignChanged(int align); + void align(); + +private: + QAction* m_activateAction; + QtGui::RWMolecule* m_molecule; + Rendering::GLRenderer* m_renderer; + QVector m_atoms; + + int m_axis; + int m_alignType; + + mutable QWidget* m_toolWidget; + +private Q_SLOTS: + void toolWidgetDestroyed(); +}; + +} // namespace QtPlugins +} // namespace Avogadro + +#endif // AVOGADRO_QTOPENGL_ALIGNTOOL_H diff --git a/avogadro/qtplugins/aligntool/aligntool.qrc b/avogadro/qtplugins/aligntool/aligntool.qrc new file mode 100644 index 0000000000..42a612d3b3 --- /dev/null +++ b/avogadro/qtplugins/aligntool/aligntool.qrc @@ -0,0 +1,6 @@ + + + align.png + align@2x.png + + From b945974c5a523d8c2c932c1a7c6bd2b9a0ad990f Mon Sep 17 00:00:00 2001 From: Geoff Hutchison Date: Mon, 9 Oct 2023 17:52:01 -0400 Subject: [PATCH 2/5] Fix compile error Signed-off-by: Geoff Hutchison --- avogadro/qtplugins/aligntool/aligntool.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/avogadro/qtplugins/aligntool/aligntool.cpp b/avogadro/qtplugins/aligntool/aligntool.cpp index c7109ba6a3..42d597937a 100644 --- a/avogadro/qtplugins/aligntool/aligntool.cpp +++ b/avogadro/qtplugins/aligntool/aligntool.cpp @@ -47,7 +47,8 @@ using QtGui::RWAtom; AlignTool::AlignTool(QObject* parent_) : QtGui::ToolPlugin(parent_), m_activateAction(new QAction(this)), - m_molecule(nullptr), m_toolWidget(nullptr) + m_molecule(nullptr), m_toolWidget(nullptr), m_renderer(nullptr), + m_alignType(0), m_axis(0) { m_activateAction->setText(tr("Align")); m_activateAction->setIcon(QIcon(":/icons/align.png")); @@ -77,7 +78,6 @@ QWidget* AlignTool::toolWidget() const // Combo box to select desired aixs to align to QComboBox* comboAxis = new QComboBox(m_toolWidget); - m_axis = 0; comboAxis->addItem("x"); comboAxis->addItem("y"); comboAxis->addItem("z"); From e046ba326b754e7ca5a34d28c3befc3644b8483f Mon Sep 17 00:00:00 2001 From: Geoff Hutchison Date: Mon, 9 Oct 2023 19:13:50 -0400 Subject: [PATCH 3/5] Implement script commands Signed-off-by: Geoff Hutchison --- avogadro/qtplugins/aligntool/aligntool.cpp | 47 +++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/avogadro/qtplugins/aligntool/aligntool.cpp b/avogadro/qtplugins/aligntool/aligntool.cpp index 42d597937a..84bd55efec 100644 --- a/avogadro/qtplugins/aligntool/aligntool.cpp +++ b/avogadro/qtplugins/aligntool/aligntool.cpp @@ -313,6 +313,51 @@ bool AlignTool::handleCommand(const QString& command, if (m_molecule == nullptr) return false; // No molecule to handle the command. - return true; + if (command == "centerAtom") { + if (options.contains("id")) { + Index atomIndex = options["id"].toInt(); + if (atomIndex >= 0 && atomIndex < m_molecule->atomCount()) + shiftAtomToOrigin(atomIndex); + return true; + } else if (options.contains("index")) { + Index atomIndex = options["index"].toInt(); + if (atomIndex >= 0 && atomIndex < m_molecule->atomCount()) + shiftAtomToOrigin(atomIndex); + return true; + } + return false; + } else if (command == "alignAtom") { + int axis = -1; + if (options.contains("axis") && options["axis"].type() == QVariant::Int) { + axis = options["axis"].toInt(); + } else if (options.contains("axis") && + options["axis"].type() == QVariant::String) { + QString axisString = options["axis"].toString(); + if (axisString == "x") + axis = 0; + else if (axisString == "y") + axis = 1; + else if (axisString == "z") + axis = 2; + } + + if (axis >= 0 && axis < 3) { + if (options.contains("id")) { + Index atomIndex = options["id"].toInt(); + if (atomIndex >= 0 && atomIndex < m_molecule->atomCount()) + alignAtomToAxis(atomIndex, axis); + return true; + } else if (options.contains("index")) { + Index atomIndex = options["index"].toInt(); + if (atomIndex >= 0 && atomIndex < m_molecule->atomCount()) + alignAtomToAxis(atomIndex, axis); + return true; + } + } + + return true; + } + } + } // namespace Avogadro::QtPlugins From 85b69a8a4257fc92c868a48d4a306f18a99be387 Mon Sep 17 00:00:00 2001 From: Geoff Hutchison Date: Mon, 9 Oct 2023 19:15:59 -0400 Subject: [PATCH 4/5] Fix formatting Signed-off-by: Geoff Hutchison --- avogadro/qtplugins/aligntool/aligntool.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/avogadro/qtplugins/aligntool/aligntool.cpp b/avogadro/qtplugins/aligntool/aligntool.cpp index 84bd55efec..e455050aa3 100644 --- a/avogadro/qtplugins/aligntool/aligntool.cpp +++ b/avogadro/qtplugins/aligntool/aligntool.cpp @@ -354,10 +354,9 @@ bool AlignTool::handleCommand(const QString& command, return true; } } - + return true; } - } } // namespace Avogadro::QtPlugins From c4cbdc1013c7ec947b5df23abf62ca01aef65bf8 Mon Sep 17 00:00:00 2001 From: Geoff Hutchison Date: Mon, 9 Oct 2023 21:28:13 -0400 Subject: [PATCH 5/5] Fix CodeQL issues Signed-off-by: Geoff Hutchison --- avogadro/qtplugins/aligntool/aligntool.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/avogadro/qtplugins/aligntool/aligntool.cpp b/avogadro/qtplugins/aligntool/aligntool.cpp index e455050aa3..3f12399de9 100644 --- a/avogadro/qtplugins/aligntool/aligntool.cpp +++ b/avogadro/qtplugins/aligntool/aligntool.cpp @@ -316,12 +316,12 @@ bool AlignTool::handleCommand(const QString& command, if (command == "centerAtom") { if (options.contains("id")) { Index atomIndex = options["id"].toInt(); - if (atomIndex >= 0 && atomIndex < m_molecule->atomCount()) + if (atomIndex < m_molecule->atomCount()) shiftAtomToOrigin(atomIndex); return true; } else if (options.contains("index")) { Index atomIndex = options["index"].toInt(); - if (atomIndex >= 0 && atomIndex < m_molecule->atomCount()) + if (atomIndex < m_molecule->atomCount()) shiftAtomToOrigin(atomIndex); return true; } @@ -344,19 +344,21 @@ bool AlignTool::handleCommand(const QString& command, if (axis >= 0 && axis < 3) { if (options.contains("id")) { Index atomIndex = options["id"].toInt(); - if (atomIndex >= 0 && atomIndex < m_molecule->atomCount()) + if (atomIndex < m_molecule->atomCount()) alignAtomToAxis(atomIndex, axis); return true; } else if (options.contains("index")) { Index atomIndex = options["index"].toInt(); - if (atomIndex >= 0 && atomIndex < m_molecule->atomCount()) + if (atomIndex < m_molecule->atomCount()) alignAtomToAxis(atomIndex, axis); return true; } } - return true; + return false; // invalid options } + + return true; // nothing to handle } } // namespace Avogadro::QtPlugins