/********************************************************************** AutoOptTool - Automatic Optimization Tool for Avogadro Copyright (C) 2007,2008 by Marcus D. Hanwell Copyright (C) 2007 by Geoffrey R. Hutchison Copyright (C) 2007 by Benoit Jacob Copyright (C) 2008 by Tim Vandermeersch This file is part of the Avogadro molecular editor project. For more information, see Some code is based on Open Babel For more information, see This program 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 version 2 of the License. This program 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. ***********************************************************************/ #include "autoopttool.h" #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 { AutoOptTool::AutoOptTool(QObject *parent) : Tool(parent), m_clickedAtom(0), m_leftButtonPressed(false), m_midButtonPressed(false), m_rightButtonPressed(false), m_running(false), m_block(false), m_setupFailed(false), m_timerId(0) ,m_toolGroup(0), m_settingsWidget(0), m_lastEnergy(0.0) { QAction *action = activateAction(); action->setIcon(QIcon(QString::fromUtf8(":/autoopttool/autoopttool.png"))); action->setToolTip(tr("Auto Optimization Tool\n\n" "Navigation Functions when Clicking in empty space.\n" "Left Mouse: Rotate Space\n" "Middle Mouse: Zoom Space\n" "Right Mouse: Move Space\n\n" "Extra Function when running\n" "Left Mouse: Click and drag atoms to move them")); m_forceField = OBForceField::FindForceField( "UFF" ); // Check that the force field exists and was initialised OK if (!m_forceField) { // We can't do anything is the force field cannot be found - OB issue emit setupFailed(); return; } m_thread = new AutoOptThread; connect(m_thread, SIGNAL(finished(bool)), this, SLOT(finished(bool))); connect(m_thread, SIGNAL(setupDone()), this, SLOT(setupDone())); connect(m_thread, SIGNAL(setupFailed()), this, SLOT(setupFailed())); connect(m_thread, SIGNAL(setupSucces()), this, SLOT(setupSucces())); OBPlugin::ListAsVector("forcefields", "ids", m_forceFieldList); //action->setShortcut(Qt::Key_F10); } AutoOptTool::~AutoOptTool() { if (m_thread) { m_thread->exit(); m_thread->wait(); delete m_thread; m_thread = 0; } } int AutoOptTool::usefulness() const { return 10; } void AutoOptTool::translate(GLWidget *widget, const Eigen::Vector3d &what, const QPoint &from, const QPoint &to) const { // Translate the selected atoms in the x and y sense of the view Vector3d fromPos = widget->camera()->unProject(from, what); Vector3d toPos = widget->camera()->unProject(to, what); Vector3d atomTranslation = toPos - fromPos; if (widget->selectedPrimitives().size()) { foreach(Primitive *p, widget->selectedPrimitives()) { if (p->type() == Primitive::AtomType) { Atom *a = static_cast(p); a->setPos(atomTranslation + *a->pos()); a->update(); } } } if (m_clickedAtom) { m_clickedAtom->setPos(atomTranslation + *m_clickedAtom->pos()); m_clickedAtom->update(); } } QUndoCommand* AutoOptTool::mousePressEvent(GLWidget *widget, QMouseEvent *event) { m_glwidget = widget; m_lastDraggingPosition = event->pos(); m_leftButtonPressed = (event->buttons() & Qt::LeftButton && event->modifiers() == Qt::NoModifier); // On the Mac, either use a three-button mouse // or hold down the Shift key m_midButtonPressed = ((event->buttons() & Qt::MidButton) || (event->buttons() & Qt::LeftButton && event->modifiers() & Qt::ShiftModifier)); // Hold down the Command key (ControlModifier in Qt notation) for right button m_rightButtonPressed = ((event->buttons() & Qt::RightButton) || (event->buttons() & Qt::LeftButton && (event->modifiers() == Qt::ControlModifier || event->modifiers() == Qt::MetaModifier))); m_clickedAtom = widget->computeClickedAtom(event->pos()); if(m_clickedAtom != 0 && m_leftButtonPressed && m_running) { event->accept(); if (m_forceField->GetConstraints().IsIgnored(m_clickedAtom->index()+1) && !m_ignoredMovable->isChecked() ) m_clickedAtom = 0; else if (m_forceField->GetConstraints().IsFixed(m_clickedAtom->index()+1) && !m_fixedMovable->isChecked() ) m_clickedAtom = 0; if (m_clickedAtom) { m_forceField->SetFixAtom(m_clickedAtom->index()+1); } } widget->update(); return 0; } QUndoCommand* AutoOptTool::mouseReleaseEvent(GLWidget *widget, QMouseEvent *) { m_glwidget = widget; m_leftButtonPressed = false; m_midButtonPressed = false; m_rightButtonPressed = false; m_clickedAtom = 0; m_forceField->UnsetFixAtom(); widget->update(); return 0; } QUndoCommand* AutoOptTool::mouseMoveEvent(GLWidget *widget, QMouseEvent *event) { m_glwidget = widget; if(!widget->molecule()) { return 0; } // Get the currently selected atoms from the view PrimitiveList currentSelection = widget->selectedPrimitives(); QPoint deltaDragging = event->pos() - m_lastDraggingPosition; // Manipulation can be performed in two ways - centred on an individual atom if (m_clickedAtom && m_running) { if (m_leftButtonPressed) { event->accept(); // translate the molecule following mouse movement Vector3d begin = widget->camera()->project(*m_clickedAtom->pos()); QPoint point = QPoint(begin.x(), begin.y()); translate(widget, *m_clickedAtom->pos(), point/*m_lastDraggingPosition*/, event->pos()); } } m_lastDraggingPosition = event->pos(); widget->update(); return 0; } QUndoCommand* AutoOptTool::wheelEvent(GLWidget*, QWheelEvent*) { return 0; } bool AutoOptTool::paint(GLWidget *widget) { QPoint labelPos(10, 10); //QPoint debugPos(10, 50); glColor3f(1.0,1.0,1.0); if (m_running) { if (m_setupFailed) { widget->painter()->drawText(labelPos, tr("AutoOpt: Could not setup force field....")); } else { double energy = m_forceField->Energy(false); widget->painter()->drawText(labelPos, tr("AutoOpt: E = %1 %2 (dE = %3)").arg(energy). arg(m_forceField->GetUnit().c_str()). arg( fabs(m_lastEnergy - energy) )); //widget->painter()->drawText(debugPos, // tr("Num Constraints: %1").arg(m_forceField->GetConstraints().Size())); m_lastEnergy = energy; } } m_glwidget = widget; if(m_leftButtonPressed) { if(m_running && m_clickedAtom) { // Don't highlight the atom on right mouse unless there is a selection double renderRadius = widget->radius(m_clickedAtom); renderRadius += 0.10; glEnable( GL_BLEND ); widget->painter()->setColor(1.0, 0.3, 0.3, 0.7); widget->painter()->drawSphere(m_clickedAtom->pos(), renderRadius); glDisable( GL_BLEND ); } } return true; } QWidget* AutoOptTool::settingsWidget() { if(!m_settingsWidget) { m_settingsWidget = new QWidget; QLabel* labelFF = new QLabel(tr("Force Field:")); m_comboFF = new QComboBox(m_settingsWidget); for (unsigned int i = 0; i < m_forceFieldList.size(); ++i) m_comboFF->addItem(m_forceFieldList[i].c_str()); // find UFF for the default item int currentFF = m_comboFF->findText("UFF"); if (currentFF != -1) // couldn't find it, go for index 0 m_comboFF->setCurrentIndex(currentFF); QGridLayout* grid = new QGridLayout; grid->addWidget(labelFF, 0, 0, Qt::AlignRight); grid->addWidget(m_comboFF, 0, 1, Qt::AlignLeft); QLabel* labelSteps = new QLabel(tr("Steps per Update:")); labelSteps->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); m_stepsSpinBox = new QSpinBox(m_settingsWidget); m_stepsSpinBox->setMinimum(1); m_stepsSpinBox->setMaximum(100); m_stepsSpinBox->setValue(4); grid->addWidget(labelSteps, 1, 0, Qt::AlignRight); grid->addWidget(m_stepsSpinBox, 1, 1, Qt::AlignLeft); QLabel* labelAlg = new QLabel(tr("Algorithm:")); m_comboAlgorithm = new QComboBox(m_settingsWidget); m_comboAlgorithm->addItem(tr("Steepest Descent")); m_comboAlgorithm->addItem(tr("Conjugate Gradients")); m_comboAlgorithm->addItem(tr("Molecular Dynamics (300K)")); m_comboAlgorithm->addItem(tr("Molecular Dynamics (600K)")); m_comboAlgorithm->addItem(tr("Molecular Dynamics (900K)")); m_buttonStartStop = new QPushButton(tr("Start"), m_settingsWidget); m_fixedMovable = new QCheckBox(tr("Fixed atoms are movable"), m_settingsWidget); m_ignoredMovable = new QCheckBox(tr("Ignored atoms are movable"), m_settingsWidget); QVBoxLayout* layout = new QVBoxLayout(); layout->addLayout(grid); // force field, steps and labels layout->addWidget(labelAlg); layout->addWidget(m_comboAlgorithm); layout->addWidget(m_fixedMovable); layout->addWidget(m_ignoredMovable); layout->addWidget(m_buttonStartStop); layout->addStretch(1); m_settingsWidget->setLayout(layout); // Connect the start/stop button connect(m_buttonStartStop, SIGNAL(clicked()), this, SLOT(toggle())); connect(m_settingsWidget, SIGNAL(destroyed()), this, SLOT(settingsWidgetDestroyed())); // Check the force field is there, if not disable the start button if (!m_forceField) m_buttonStartStop->setEnabled(false); } return m_settingsWidget; } void AutoOptTool::settingsWidgetDestroyed() { m_settingsWidget = 0; } void AutoOptTool::toggle() { // Toggle the timer on and off if (m_running) { disable(); } else { enable(); } } void AutoOptTool::enable() { // If the force field is false we have nothing and so should return if (!m_forceField) return; if(!m_running) { connect(m_glwidget->molecule(), SIGNAL(destroyed()), this, SLOT(abort())); m_thread->setup(m_glwidget->molecule(), m_forceField, m_comboAlgorithm->currentIndex(), m_stepsSpinBox->value()); m_thread->start(); m_running = true; m_buttonStartStop->setText(tr("Stop")); QUndoStack *stack = m_glwidget->undoStack(); AutoOptCommand *cmd = new AutoOptCommand(m_glwidget->molecule(),this,0); if(stack && cmd) { stack->push(cmd); } else { delete cmd; } } } void AutoOptTool::abort() { killTimer(m_timerId); m_thread->quit(); m_running = false; } void AutoOptTool::disable() { if(m_running) { if(m_timerId) { killTimer(m_timerId); m_timerId = 0; } m_thread->quit(); m_running = false; m_setupFailed = false; m_buttonStartStop->setText(tr("Start")); m_glwidget->update(); // redraw AutoOpt label m_clickedAtom = 0; m_forceField->UnsetFixAtom(); m_leftButtonPressed = false; m_midButtonPressed = false; m_rightButtonPressed = false; } } void AutoOptTool::timerEvent(QTimerEvent*) { if(m_block || m_glwidget->molecule()->numAtoms() < 2) return; else m_block = true; m_forceField = OBForceField::FindForceField(m_forceFieldList[m_comboFF->currentIndex()]); // Check that we can find our force field - if not return if (!m_forceField) { emit setupFailed(); return; } m_thread->setup(m_glwidget->molecule(), m_forceField, m_comboAlgorithm->currentIndex(), m_stepsSpinBox->value()); m_thread->update(); } void AutoOptTool::finished(bool calculated) { if (m_running && calculated) { OBMol mol = m_glwidget->molecule()->OBMol(); m_forceField->GetCoordinates( mol ); QList atoms = m_glwidget->molecule()->atoms(); double *coordPtr = mol.GetCoordinates(); foreach(Atom* atom, atoms) { atom->setPos(Eigen::Vector3d(coordPtr)); coordPtr += 3; } if(m_clickedAtom && m_leftButtonPressed) { Vector3d begin = m_glwidget->camera()->project(*m_clickedAtom->pos()); QPoint point = QPoint(begin.x(), begin.y()); translate(m_glwidget, *m_clickedAtom->pos(), point, m_lastDraggingPosition); } } m_glwidget->molecule()->update(); m_glwidget->update(); m_block = false; } void AutoOptTool::setupDone() { if(!m_timerId) { m_timerId = startTimer(50); } } void AutoOptTool::setupFailed() { m_setupFailed = true; } void AutoOptTool::setupSucces() { m_setupFailed = false; } AutoOptThread::AutoOptThread(QObject*) { m_stop = false; m_velocities = false; } void AutoOptThread::setup(Molecule *molecule, OpenBabel::OBForceField* forceField, int algorithm, int steps) { //cout << "start AutoOptThread::setup()" << endl; m_mutex.lock(); m_molecule = molecule; m_forceField = forceField; m_algorithm = algorithm; m_steps = steps; m_stop = false; m_velocities = false; m_mutex.unlock(); emit setupDone(); //cout << "stop AutoOptThread::setup()" << endl; } void AutoOptThread::run() { exec(); } void AutoOptThread::update() { // If the force field is false we have nothing and so should return if (!m_forceField) return; m_mutex.lock(); m_forceField->SetLogFile(NULL); m_forceField->SetLogLevel(OBFF_LOGLVL_NONE); OBMol mol = m_molecule->OBMol(); if ( !m_forceField->Setup( mol ) ) { m_stop = true; emit setupFailed(); emit finished(false); m_mutex.unlock(); return; } else { emit setupSucces(); } m_forceField->SetConformers( mol ); switch(m_algorithm) { case 0: m_forceField->SteepestDescent(m_steps); break; case 1: m_forceField->ConjugateGradients(m_steps); break; case 2: m_forceField->MolecularDynamicsTakeNSteps(m_steps, 300, 0.001); break; case 3: m_forceField->MolecularDynamicsTakeNSteps(m_steps, 600, 0.001); break; case 4: m_forceField->MolecularDynamicsTakeNSteps(m_steps, 900, 0.001); break; } m_mutex.unlock(); emit finished(m_stop ? false : true); } void AutoOptThread::stop() { m_stop = true; } AutoOptCommand::AutoOptCommand(Molecule *molecule, AutoOptTool *tool, QUndoCommand *parent) : QUndoCommand(parent), m_molecule(0) { // Store the original molecule before any modifications are made setText(QObject::tr("AutoOpt Molecule")); m_moleculeCopy = *molecule; m_molecule = molecule; m_tool = tool; } void AutoOptCommand::redo() { m_moleculeCopy = *m_molecule; } void AutoOptCommand::undo() { if(m_tool) { m_tool->disable(); } *m_molecule = m_moleculeCopy; } bool AutoOptCommand::mergeWith (const QUndoCommand *) { // Just return true to repeated calls - we have stored the original molecule return true; } int AutoOptCommand::id() const { return 1311387; } void AutoOptTool::writeSettings(QSettings &settings) const { Tool::writeSettings(settings); settings.setValue("forceField", m_comboFF->currentIndex()); settings.setValue("algorithm", m_comboAlgorithm->currentIndex()); settings.setValue("steps", m_stepsSpinBox->value()); settings.setValue("fixedMovable", m_fixedMovable->checkState()); settings.setValue("ignoredMovable", m_ignoredMovable->checkState()); } void AutoOptTool::readSettings(QSettings &settings) { Tool::readSettings(settings); if(m_comboFF) { int currentFF = settings.value("forceField", -1).toInt(); if (currentFF == -1) { // haven't set a default // find UFF for default currentFF = m_comboFF->findText("UFF"); if (currentFF == -1) // couldn't find it, go for index 0 currentFF = 0; } m_comboFF->setCurrentIndex(currentFF); } if(m_comboAlgorithm) { m_comboAlgorithm->setCurrentIndex(settings.value("algorithm", 0).toInt()); } if(m_stepsSpinBox) { m_stepsSpinBox->setValue(settings.value("steps", 4).toInt()); } if(m_fixedMovable) { m_fixedMovable->setCheckState((Qt::CheckState)settings.value("fixedMovable", 2).toInt()); } if(m_ignoredMovable) { m_ignoredMovable->setCheckState((Qt::CheckState)settings.value("ignoredMovable", 2).toInt()); } } } #include "autoopttool.moc" Q_EXPORT_PLUGIN2(autoopttool, Avogadro::AutoOptToolFactory)