Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial port of align tool from Avogadro 1.x code #1364

Merged
merged 5 commits into from
Oct 10, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions avogadro/qtplugins/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
22 changes: 22 additions & 0 deletions avogadro/qtplugins/aligntool/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
Binary file added avogadro/qtplugins/aligntool/align.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added avogadro/qtplugins/aligntool/[email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
362 changes: 362 additions & 0 deletions avogadro/qtplugins/aligntool/aligntool.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,362 @@
/******************************************************************************
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 <avogadro/core/vector.h>

#include <avogadro/qtgui/molecule.h>
#include <avogadro/qtgui/rwmolecule.h>

#include <avogadro/qtopengl/glwidget.h>

#include <avogadro/rendering/camera.h>
#include <avogadro/rendering/geometrynode.h>
#include <avogadro/rendering/glrenderer.h>
#include <avogadro/rendering/groupnode.h>
#include <avogadro/rendering/scene.h>
#include <avogadro/rendering/textlabel2d.h>
#include <avogadro/rendering/textlabel3d.h>
#include <avogadro/rendering/textproperties.h>

#include <QtCore/QDebug>
#include <QtGui/QIcon>
#include <QtGui/QKeyEvent>
#include <QtGui/QMouseEvent>
#include <QtGui/QWheelEvent>
#include <QtWidgets/QAction>
#include <QtWidgets/QComboBox>
#include <QtWidgets/QGridLayout>
#include <QtWidgets/QHBoxLayout>
#include <QtWidgets/QLabel>
#include <QtWidgets/QPushButton>

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_renderer(nullptr),
m_alignType(0), m_axis(0)
{
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);
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<Vector3>& coords = m_molecule->atomPositions3d();
Core::Array<Vector3> 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<Vector3>& coords = m_molecule->atomPositions3d();
Core::Array<Vector3> 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<unsigned char>(255 - input);
else
result[i] = static_cast<unsigned char>(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<Vector3> 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<float>());
label->setRadius(
static_cast<float>(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.

if (command == "centerAtom") {
if (options.contains("id")) {
Index atomIndex = options["id"].toInt();
if (atomIndex >= 0 && atomIndex < m_molecule->atomCount())
Fixed Show fixed Hide fixed
shiftAtomToOrigin(atomIndex);
return true;
} else if (options.contains("index")) {
Index atomIndex = options["index"].toInt();
if (atomIndex >= 0 && atomIndex < m_molecule->atomCount())
Fixed Show fixed Hide fixed
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())
Fixed Show fixed Hide fixed
alignAtomToAxis(atomIndex, axis);
return true;
} else if (options.contains("index")) {
Index atomIndex = options["index"].toInt();
if (atomIndex >= 0 && atomIndex < m_molecule->atomCount())
Fixed Show fixed Hide fixed
alignAtomToAxis(atomIndex, axis);
return true;
}
}

return true;
}
}
Fixed Show fixed Hide fixed

} // namespace Avogadro::QtPlugins
Loading
Loading