/********************************************************************** AlignTool - AlignTool Tool for Avogadro Copyright (C) 2008 Marcus D. Hanwell This file is part of the Avogadro molecular editor project. For more information, see Avogadro is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Avogadro is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. **********************************************************************/ #include "aligntool.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; using namespace OpenBabel; using namespace Eigen; namespace Avogadro { AlignTool::AlignTool(QObject *parent) : Tool(parent), m_molecule(0), m_selectedAtoms(2), m_numSelectedAtoms(0), m_axis(2), m_alignType(0), m_settingsWidget(0) { QAction *action = activateAction(); action->setIcon(QIcon(QString::fromUtf8(":/align/align.png"))); action->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.")); action->setShortcut(Qt::Key_F12); // clear the selected atoms int size = m_selectedAtoms.size(); for(int i=0; imolecule(); if(!m_molecule) return 0; //! List of hits from initial click QList m_hits = widget->hits(event->pos().x()-2, event->pos().y()-2, 5, 5); // If there's a left button (and no modifier keys) continue adding to the list if(m_hits.size() && (event->buttons() & Qt::LeftButton && event->modifiers() == Qt::NoModifier)) { if(m_hits[0].type() != Primitive::AtomType) return 0; Atom *atom = m_molecule->atom(m_hits[0].name()); event->accept(); if(m_numSelectedAtoms < 2) { // Select another atom m_selectedAtoms[m_numSelectedAtoms++] = atom; widget->update(); } } // Right button or Left Button + modifier (e.g., Mac) else { event->accept(); m_numSelectedAtoms = 0; widget->update(); } return 0; } QUndoCommand* AlignTool::mouseMoveEvent(GLWidget*, QMouseEvent *) { return 0; } QUndoCommand* AlignTool::mouseReleaseEvent(GLWidget*, QMouseEvent*) { return 0; } QUndoCommand* AlignTool::wheelEvent(GLWidget *widget, QWheelEvent *event) { Q_UNUSED(widget); Q_UNUSED(event); return 0; } bool AlignTool::paint(GLWidget *widget) { if(m_numSelectedAtoms > 0) { Vector3d xAxis = widget->camera()->backTransformedXAxis(); Vector3d zAxis = widget->camera()->backTransformedZAxis(); // Check the atom is still around... if (m_selectedAtoms[0]) { glColor3f(1.0,0.0,0.0); widget->painter()->setColor(1.0, 0.0, 0.0); const Vector3d *pos = m_selectedAtoms[0]->pos(); // relative position of the text on the atom double radius = widget->radius(m_selectedAtoms[0]) + 0.05; Vector3d textRelPos = radius * (zAxis + xAxis); Vector3d textPos = *pos + textRelPos; widget->painter()->drawText(textPos, "*1"); widget->painter()->drawSphere(pos, radius); } if(m_numSelectedAtoms >= 2) { // Check the atom is still around... if (m_selectedAtoms[1]) { glColor3f(0.0,1.0,0.0); widget->painter()->setColor(0.0, 1.0, 0.0); const Vector3d *pos = m_selectedAtoms[1]->pos(); double radius = widget->radius(m_selectedAtoms[1]) + 0.05; widget->painter()->drawSphere(pos, radius); Vector3d textRelPos = radius * (zAxis + xAxis); Vector3d textPos = *pos + textRelPos; widget->painter()->drawText(textPos, "*2"); } } } return true; } void AlignTool::align() { // Check we have a molecule, otherwise we can't do anything if (m_molecule.isNull()) return; QList neighborList; if (m_numSelectedAtoms) { // Check the first atom still exists, return if not if (m_selectedAtoms[0].isNull()) return; // If m_alignType is 0 we want everything, otherwise just the fragment /// FIXME Add back fragment alignment too! /* if (m_alignType) { OBMolAtomDFSIter iter(m_molecule, m_selectedAtoms[0]->index()); Atom *tmpNeighbor; do { tmpNeighbor = static_cast(&*iter); neighborList.append(tmpNeighbor); } while ((iter++).next()); // this returns false when we are done } else { */ neighborList = m_molecule->atoms(); } // Align the molecule along the selected axis if (m_numSelectedAtoms >= 1) { // Translate the first selected atom to the origin Vector3d pos = *m_selectedAtoms[0]->pos(); foreach(Atom *a, neighborList) { if (a) { a->setPos(*a->pos() - pos); } } m_molecule->update(); } if (m_numSelectedAtoms >= 2) { // Check the second atom still exists, return if not if (m_selectedAtoms[1].isNull()) return; // Now line up the line from atom[0] to atom[1] with the axis selected double alpha, beta, gamma; alpha = beta = gamma = 0.0; Vector3d pos = *m_selectedAtoms[1]->pos(); pos.normalize(); Vector3d axis; if (m_axis == 0) // x-axis axis = Vector3d(1., 0., 0.); else if (m_axis == 1) // y-axis axis = Vector3d(0., 1., 0.); else if (m_axis == 2) // z-axis axis = Vector3d(0., 0., 1.); // Calculate the angle of the atom from the axis double angle = acos(axis.dot(pos)); // If the angle is zero then we don't need to do anything here if (angle > 0) { // Get the axis for the rotation axis = axis.cross(pos); axis.normalize(); // Now to rotate the fragment foreach(Atom *a, neighborList) { a->setPos(Eigen::AngleAxisd(-angle,axis) * *a->pos()); } m_molecule->update(); } } m_numSelectedAtoms = 0; } QWidget* AlignTool::settingsWidget() { if(!m_settingsWidget) { m_settingsWidget = new QWidget; QLabel *labelAxis = new QLabel(tr("Axis:")); labelAxis->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); labelAxis->setMaximumHeight(15); // Combo box to select desired aixs to align to QComboBox *comboAxis = new QComboBox(m_settingsWidget); comboAxis->addItem("x"); comboAxis->addItem("y"); comboAxis->addItem("z"); comboAxis->setCurrentIndex(2); QLabel *labelAlign = new QLabel(tr("Align:")); labelAlign->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); labelAlign->setMaximumHeight(15); // Combo to choose what should be aligned QComboBox *comboAlign = new QComboBox(m_settingsWidget); comboAlign->addItem(tr("Everything")); comboAlign->addItem(tr("Molecule")); // Button to actually perform actions QPushButton *buttonAlign = new QPushButton(m_settingsWidget); 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); gridLayout->addWidget(labelAlign, 1, 0, 1, 1, Qt::AlignRight); QHBoxLayout *hLayout2 = new QHBoxLayout; hLayout2->addWidget(comboAlign); hLayout2->addStretch(1); gridLayout->addLayout(hLayout2, 1, 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_settingsWidget->setLayout(layout); connect(comboAxis, SIGNAL(currentIndexChanged(int)), this, SLOT(axisChanged(int))); connect(comboAlign, SIGNAL(currentIndexChanged(int)), this, SLOT(alignChanged(int))); connect(m_settingsWidget, SIGNAL(destroyed()), this, SLOT(settingsWidgetDestroyed())); } return m_settingsWidget; } 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::settingsWidgetDestroyed() { m_settingsWidget = 0; } } #include "aligntool.moc" Q_EXPORT_PLUGIN2(aligntool, Avogadro::AlignToolFactory)