diff --git a/avogadro/core/crystaltools.cpp b/avogadro/core/crystaltools.cpp index dd2658d8d..a799639db 100644 --- a/avogadro/core/crystaltools.cpp +++ b/avogadro/core/crystaltools.cpp @@ -672,7 +672,6 @@ bool CrystalTools::setCellMatrix(Molecule &molecule, const Matrix3 &newCellColMatrix, Options opt) { - if (opt & TransformAtoms && molecule.unitCell()) { const Matrix3 xform(newCellColMatrix * molecule.unitCell()->cellMatrix().inverse()); diff --git a/avogadro/qtgui/rwmolecule.cpp b/avogadro/qtgui/rwmolecule.cpp index 94d41219a..b68703289 100644 --- a/avogadro/qtgui/rwmolecule.cpp +++ b/avogadro/qtgui/rwmolecule.cpp @@ -21,11 +21,15 @@ #include #include +#include + namespace Avogadro { namespace QtGui { using Core::Array; using Core::AtomHybridization; +using Core::CrystalTools; +using Core::UnitCell; // Base class for all undo commands used by this class. // Used to expose molecule internals without needing to add explicit friendships @@ -907,6 +911,303 @@ bool RWMolecule::setBondPair(Index bondId, const std::pair &pair) return true; } +namespace { +class AddUnitCellCommand : public RWMolecule::UndoCommand +{ + UnitCell m_newUnitCell; +public: + AddUnitCellCommand(RWMolecule &m, const UnitCell &newUnitCell) + : UndoCommand(m), m_newUnitCell(newUnitCell) + { + } + + void redo() AVO_OVERRIDE + { + m_mol.molecule().setUnitCell(new UnitCell(m_newUnitCell)); + } + + void undo() AVO_OVERRIDE + { + m_mol.molecule().setUnitCell(NULL); + } +}; +} // end anon namespace + +void RWMolecule::addUnitCell() +{ + // If there is already a unit cell, there is nothing to do + if (m_molecule.unitCell()) + return; + + UnitCell *cell = new UnitCell; + cell->setCellParameters(static_cast(3.0), + static_cast(3.0), + static_cast(3.0), + static_cast(90.0) * DEG_TO_RAD, + static_cast(90.0) * DEG_TO_RAD, + static_cast(90.0) * DEG_TO_RAD); + m_molecule.setUnitCell(cell); + + AddUnitCellCommand *comm = new AddUnitCellCommand(*this, + *m_molecule.unitCell()); + comm->setText(tr("Add Unit Cell")); + m_undoStack.push(comm); + emitChanged(Molecule::UnitCell | Molecule::Added); +} + +namespace { +class RemoveUnitCellCommand : public RWMolecule::UndoCommand +{ + UnitCell m_oldUnitCell; +public: + RemoveUnitCellCommand(RWMolecule &m, const UnitCell &oldUnitCell) + : UndoCommand(m), m_oldUnitCell(oldUnitCell) + { + } + + void redo() AVO_OVERRIDE + { + m_mol.molecule().setUnitCell(NULL); + } + + void undo() AVO_OVERRIDE + { + m_mol.molecule().setUnitCell(new UnitCell(m_oldUnitCell)); + } +}; +} // end anon namespace + +void RWMolecule::removeUnitCell() +{ + // If there is no unit cell, there is nothing to do + if (!m_molecule.unitCell()) + return; + + RemoveUnitCellCommand *comm = new RemoveUnitCellCommand( + *this, + *m_molecule.unitCell()); + comm->setText(tr("Remove Unit Cell")); + m_undoStack.push(comm); + + m_molecule.setUnitCell(NULL); + emitChanged(Molecule::UnitCell | Molecule::Removed); +} + +namespace { +class ModifyMoleculeCommand : public RWMolecule::UndoCommand +{ + Molecule m_oldMolecule; + Molecule m_newMolecule; +public: + ModifyMoleculeCommand(RWMolecule &m, + const Molecule &oldMolecule, + const Molecule &newMolecule) + : UndoCommand(m), m_oldMolecule(oldMolecule), m_newMolecule(newMolecule) + { + } + + void redo() AVO_OVERRIDE + { + m_mol.molecule() = m_newMolecule; + } + + void undo() AVO_OVERRIDE + { + m_mol.molecule() = m_oldMolecule; + } +}; +} // end anon namespace + +void RWMolecule::modifyMolecule(const Molecule &newMolecule, + Molecule::MoleculeChanges changes, + const QString &undoText) +{ + ModifyMoleculeCommand *comm = new ModifyMoleculeCommand(*this, + m_molecule, + newMolecule); + + comm->setText(undoText); + m_undoStack.push(comm); + + m_molecule = newMolecule; + emitChanged(changes); +} + +void RWMolecule::editUnitCell(Matrix3 cellMatrix, + CrystalTools::Options options) +{ + // If there is no unit cell, there is nothing to do + if (!m_molecule.unitCell()) + return; + + // Make a copy of the molecule to edit so we can store the old one + // If the user has "TransformAtoms" set in the options, then + // the atom positions will move as well. + Molecule newMolecule = m_molecule; + CrystalTools::setCellMatrix(newMolecule, cellMatrix, options); + + // We will just modify the whole molecule since there may be many changes + Molecule::MoleculeChanges changes = Molecule::UnitCell | Molecule::Modified; + // If TransformAtoms is set in the options, then the atoms may be modified + // as well. + if (options & CrystalTools::TransformAtoms) + changes |= Molecule::Atoms | Molecule::Modified; + QString undoText = tr("Edit Unit Cell"); + + modifyMolecule(newMolecule, changes, undoText); +} + +void RWMolecule::wrapAtomsToCell() +{ + // If there is no unit cell, there is nothing to do + if (!m_molecule.unitCell()) + return; + + Core::Array oldPos = m_molecule.atomPositions3d(); + CrystalTools::wrapAtomsToUnitCell(m_molecule); + Core::Array newPos = m_molecule.atomPositions3d(); + + SetPositions3dCommand *comm = new SetPositions3dCommand(*this, oldPos, + newPos); + comm->setText(tr("Wrap Atoms to Cell")); + m_undoStack.push(comm); + + Molecule::MoleculeChanges changes = Molecule::Atoms | Molecule::Modified; + emitChanged(changes); +} + +void RWMolecule::setCellVolume(double newVolume, + CrystalTools::Options options) +{ + // If there is no unit cell, there is nothing to do + if (!m_molecule.unitCell()) + return; + + // Make a copy of the molecule to edit so we can store the old one + // The unit cell and atom positions may change + Molecule newMolecule = m_molecule; + + CrystalTools::setVolume(newMolecule, newVolume, options); + + // We will just modify the whole molecule since there may be many changes + Molecule::MoleculeChanges changes = Molecule::UnitCell | Molecule::Modified; + if (options & CrystalTools::TransformAtoms) + changes |= Molecule::Atoms | Molecule::Modified; + QString undoText = tr("Scale Cell Volume"); + + modifyMolecule(newMolecule, changes, undoText); +} + +void RWMolecule::buildSupercell(unsigned int a, unsigned int b, unsigned int c) +{ + // If there is no unit cell, there is nothing to do + if (!m_molecule.unitCell()) + return; + + // Make a copy of the molecule to edit so we can store the old one + // The unit cell and atom positions may change + Molecule newMolecule = m_molecule; + + CrystalTools::buildSupercell(newMolecule, a, b, c); + + // We will just modify the whole molecule since there may be many changes + Molecule::MoleculeChanges changes = Molecule::UnitCell | Molecule::Modified | + Molecule::Atoms | Molecule::Added; + QString undoText = tr("Build Super Cell"); + + modifyMolecule(newMolecule, changes, undoText); +} + +void RWMolecule::niggliReduceCell() +{ + // If there is no unit cell, there is nothing to do + if (!m_molecule.unitCell()) + return; + + // Make a copy of the molecule to edit so we can store the old one + // The unit cell and atom positions may change + Molecule newMolecule = m_molecule; + + // We need to perform all three of these operations... + CrystalTools::niggliReduce(newMolecule, CrystalTools::TransformAtoms); + CrystalTools::rotateToStandardOrientation(newMolecule, + CrystalTools::TransformAtoms); + CrystalTools::wrapAtomsToUnitCell(newMolecule); + + // We will just modify the whole molecule since there may be many changes + Molecule::MoleculeChanges changes = Molecule::UnitCell | Molecule::Atoms | + Molecule::Modified; + QString undoText = tr("Niggli Reduction"); + + modifyMolecule(newMolecule, changes, undoText); +} + +void RWMolecule::rotateCellToStandardOrientation() +{ + // If there is no unit cell, there is nothing to do + if (!m_molecule.unitCell()) + return; + + // Store a copy of the old molecule + // The atom positions may move as well. + Molecule newMolecule = m_molecule; + + CrystalTools::rotateToStandardOrientation(newMolecule, + CrystalTools::TransformAtoms); + + // Since most components of the molecule will be modified (atom positions + // and the unit cell), we will just modify the whole thing... + Molecule::MoleculeChanges changes = Molecule::UnitCell | Molecule::Atoms | + Molecule::Modified; + QString undoText = tr("Rotate to Standard Orientation"); + + modifyMolecule(newMolecule, changes, undoText); +} + +bool RWMolecule::reduceCellToPrimitive(double cartTol) +{ + // If there is no unit cell, there is nothing to do + if (!m_molecule.unitCell()) + return false; + + // Make a copy of the molecule to edit so we can store the old one + // The unit cell, atom positions, and numbers of atoms may change + Molecule newMolecule = m_molecule; + + if (!Core::AvoSpglib::reduceToPrimitive(newMolecule, cartTol)) + return false; + + // Since most components of the molecule will be modified, + // we will just modify the whole thing... + Molecule::MoleculeChanges changes = Molecule::UnitCell | Molecule::Atoms | + Molecule::Added; + QString undoText = tr("Reduce to Primitive"); + + modifyMolecule(newMolecule, changes, undoText); + return true; +} + +bool RWMolecule::conventionalizeCell(double cartTol) +{ + // If there is no unit cell, there is nothing to do + if (!m_molecule.unitCell()) + return false; + + // Make a copy of the molecule to edit so we can store the old one + // The unit cell, atom positions, and numbers of atoms may all change + Molecule newMolecule = m_molecule; + + if (!Core::AvoSpglib::conventionalizeCell(newMolecule, cartTol)) + return false; + + Molecule::MoleculeChanges changes = Molecule::UnitCell | Molecule::Atoms | + Molecule::Added; + QString undoText = tr("Conventionalize Cell"); + + modifyMolecule(newMolecule, changes, undoText); + return true; +} + void RWMolecule::emitChanged(unsigned int change) { m_molecule.emitChanged(change); diff --git a/avogadro/qtgui/rwmolecule.h b/avogadro/qtgui/rwmolecule.h index 394fb2620..f27de5ff0 100644 --- a/avogadro/qtgui/rwmolecule.h +++ b/avogadro/qtgui/rwmolecule.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -399,6 +400,86 @@ class AVOGADROQTGUI_EXPORT RWMolecule : public QObject */ bool setBondPair(Index bondId, const std::pair &pair); + /** + * Add a default unit cell to the molecule. Does nothing if there already + * is a unit cell. Changes are emitted. + */ + void addUnitCell(); + + /** + * Remove the unit cell from the molecule. Does nothing if there is + * no unit cell. Changes are emitted. + */ + void removeUnitCell(); + + /** + * Generic edit that changes the current molecule to be @a newMolecule. + * Also sets the text for the undo command to be @a undoText. Changes are + * emitted. + * @param newMolecule The new molecule to be set. + * @param changes The changes to be emitted. + * @param undoText The text description for the undo command. + */ + void modifyMolecule(const Molecule &newMolecule, + Molecule::MoleculeChanges changes, + const QString &undoText = "Modify Molecule"); + + /** + * Edit the unit cell by replacing the current cell matrix with a new cell + * matrix. Changes are emitted. + * @param cellMatrix The new cell matrix to be set. + * @param opts If TransformAtoms is specified, the atoms in @a molecule are + * adjusted so that their fractional (lattice) coordinates are preserved. This + * option is ignored if the input molecule has no unit cell. + */ + void editUnitCell(Matrix3 cellMatrix, Core::CrystalTools::Options opts); + + /** + * Wrap atoms to the unit cell. Changes are emitted. + */ + void wrapAtomsToCell(); + + /** + * Rotate cell to standard orientation. Changes are emitted. + */ + void rotateCellToStandardOrientation(); + + /** + * Scale a cell's volume. Changes are emitted. + * @param newVolume The new volume to be set. + * @param options If CrystalTools::TransformAtoms is set, then + * the atoms will be transformed during the scaling. + */ + void setCellVolume(double newVolume, Core::CrystalTools::Options options); + + /** + * Build a supercell. Changes are emitted. + * @param a The final number of units along the A vector (at least 1). + * @param b The final number of units along the B vector (at least 1). + * @param c The final number of units along the C vector (at least 1). + */ + void buildSupercell(unsigned int a, unsigned int b, unsigned int c); + + /** + * Perform a Niggli reduction on the cell. Changes are emitted. + */ + void niggliReduceCell(); + + /** + * Use spglib to reduce the cell to its primitive form. Changes are emitted. + * @param cartTol Cartesian tolerance for primitive reduction. + * @return True if the algorithm succeeded, and false if it failed. + */ + bool reduceCellToPrimitive(double cartTol = 1e-5); + + /** + * Use spglib to convert the cell to its conventional form. Changes are + * emitted. + * @param cartTol Cartesian tolerance for conventionalization. + * @return True if the algorithm succeeded, and false if it failed. + */ + bool conventionalizeCell(double cartTol = 1e-5); + /** * @brief Begin or end an interactive edit. * diff --git a/avogadro/qtplugins/crystal/crystal.cpp b/avogadro/qtplugins/crystal/crystal.cpp index a6f2a3514..a8b76494b 100644 --- a/avogadro/qtplugins/crystal/crystal.cpp +++ b/avogadro/qtplugins/crystal/crystal.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include @@ -179,9 +180,10 @@ void Crystal::importCrystalClipboard() Core::Molecule m; if (d.importCrystalClipboard(m)) { // If we succeeded, update m_molecule - *m_molecule = m; - m_molecule->emitChanged(Molecule::Added | - Molecule::Atoms | Molecule::UnitCell); + Molecule::MoleculeChanges changes = Molecule::Added | + Molecule::Atoms | Molecule::UnitCell; + QString undoText = tr("Import Crystal from Clipboard"); + m_molecule->undoMolecule()->modifyMolecule(m, changes, undoText); } } @@ -198,10 +200,7 @@ void Crystal::editUnitCell() void Crystal::buildSupercell() { SupercellDialog d; - if (d.buildSupercell(*m_molecule)) { - m_molecule->emitChanged(Molecule::Modified | - Molecule::Atoms | Molecule::UnitCell); - } + d.buildSupercell(*m_molecule); } void Crystal::niggliReduce() @@ -213,12 +212,7 @@ void Crystal::niggliReduce() QMessageBox::Ok); return; } - CrystalTools::niggliReduce(*m_molecule, CrystalTools::TransformAtoms); - CrystalTools::rotateToStandardOrientation(*m_molecule, - CrystalTools::TransformAtoms); - CrystalTools::wrapAtomsToUnitCell(*m_molecule); - m_molecule->emitChanged(Molecule::Modified - | Molecule::Atoms | Molecule::UnitCell); + m_molecule->undoMolecule()->niggliReduceCell(); } void Crystal::scaleVolume() @@ -232,46 +226,30 @@ void Crystal::scaleVolume() if (reply != QDialog::Accepted) return; - CrystalTools::setVolume(*m_molecule, dlg.newVolume(), - dlg.transformAtoms() ? CrystalTools::TransformAtoms - : CrystalTools::None); - m_molecule->emitChanged(Molecule::Modified | Molecule::UnitCell - | (dlg.transformAtoms() ? Molecule::Atoms - : Molecule::NoChange)); + m_molecule->undoMolecule()->setCellVolume( + dlg.newVolume(), dlg.transformAtoms() ? CrystalTools::TransformAtoms + : CrystalTools::None); } void Crystal::standardOrientation() { - CrystalTools::rotateToStandardOrientation(*m_molecule, - CrystalTools::TransformAtoms); - m_molecule->emitChanged(Molecule::Modified - | Molecule::Atoms | Molecule::UnitCell); + m_molecule->undoMolecule()->rotateCellToStandardOrientation(); } void Crystal::toggleUnitCell() { if (m_molecule->unitCell()) { - m_molecule->setUnitCell(NULL); - m_molecule->emitChanged(Molecule::UnitCell | Molecule::Removed); + m_molecule->undoMolecule()->removeUnitCell(); } else { - UnitCell *cell = new UnitCell; - cell->setCellParameters(static_cast(3.0), - static_cast(3.0), - static_cast(3.0), - static_cast(90.0) * DEG_TO_RAD, - static_cast(90.0) * DEG_TO_RAD, - static_cast(90.0) * DEG_TO_RAD); - m_molecule->setUnitCell(cell); - m_molecule->emitChanged(Molecule::UnitCell | Molecule::Added); + m_molecule->undoMolecule()->addUnitCell(); editUnitCell(); } } void Crystal::wrapAtomsToCell() { - CrystalTools::wrapAtomsToUnitCell(*m_molecule); - m_molecule->emitChanged(Molecule::Atoms | Molecule::Modified); + m_molecule->undoMolecule()->wrapAtomsToCell(); } } // namespace QtPlugins diff --git a/avogadro/qtplugins/crystal/supercelldialog.cpp b/avogadro/qtplugins/crystal/supercelldialog.cpp index 3befa7055..c2c952b1c 100644 --- a/avogadro/qtplugins/crystal/supercelldialog.cpp +++ b/avogadro/qtplugins/crystal/supercelldialog.cpp @@ -20,6 +20,9 @@ #include #include +#include +#include + using Avogadro::Core::CrystalTools; namespace Avogadro { @@ -37,7 +40,7 @@ SupercellDialog::~SupercellDialog() delete m_ui; } -bool SupercellDialog::buildSupercell(Avogadro::Core::Molecule &mol) +bool SupercellDialog::buildSupercell(Avogadro::QtGui::Molecule &mol) { // If the user rejected, just return false if (this->exec() == QDialog::Rejected) @@ -53,7 +56,8 @@ bool SupercellDialog::buildSupercell(Avogadro::Core::Molecule &mol) return true; // Run the supercell-building tool - return CrystalTools::buildSupercell(mol, a, b, c); + mol.undoMolecule()->buildSupercell(a, b, c); + return true; } } // namespace QtPlugins diff --git a/avogadro/qtplugins/crystal/supercelldialog.h b/avogadro/qtplugins/crystal/supercelldialog.h index b2fc58d38..8e74c9ee3 100644 --- a/avogadro/qtplugins/crystal/supercelldialog.h +++ b/avogadro/qtplugins/crystal/supercelldialog.h @@ -23,7 +23,7 @@ namespace Avogadro { -namespace Core { +namespace QtGui { class Molecule; } @@ -45,7 +45,7 @@ class SupercellDialog : public QDialog SupercellDialog(QWidget *p = 0); ~SupercellDialog(); - bool buildSupercell(Avogadro::Core::Molecule &mol); + bool buildSupercell(Avogadro::QtGui::Molecule &mol); void displayInvalidFormatMessage(); diff --git a/avogadro/qtplugins/crystal/unitcelldialog.cpp b/avogadro/qtplugins/crystal/unitcelldialog.cpp index a1a3bb615..c6f954801 100644 --- a/avogadro/qtplugins/crystal/unitcelldialog.cpp +++ b/avogadro/qtplugins/crystal/unitcelldialog.cpp @@ -18,6 +18,7 @@ #include "ui_unitcelldialog.h" #include +#include #include #include @@ -143,14 +144,9 @@ void UnitCellDialog::apply() break; default: { Core::CrystalTools::Options options = Core::CrystalTools::None; - Molecule::MoleculeChanges changes = Molecule::UnitCell | Molecule::Modified; - if (m_ui->transformAtoms->isChecked()) { + if (m_ui->transformAtoms->isChecked()) options |= Core::CrystalTools::TransformAtoms; - changes |= Molecule::Atoms | Molecule::Modified; - } - Core::CrystalTools::setCellMatrix(*m_molecule, m_tempCell.cellMatrix(), - options); - m_molecule->emitChanged(changes); + m_molecule->undoMolecule()->editUnitCell(m_tempCell.cellMatrix(), options); break; } } diff --git a/avogadro/qtplugins/spacegroup/spacegroup.cpp b/avogadro/qtplugins/spacegroup/spacegroup.cpp index ad2403e99..7b47407ee 100644 --- a/avogadro/qtplugins/spacegroup/spacegroup.cpp +++ b/avogadro/qtplugins/spacegroup/spacegroup.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -190,7 +191,7 @@ void SpaceGroup::reduceToPrimitive() setTolerance(); // Primitive reduction! - bool success = AvoSpglib::reduceToPrimitive(*m_molecule, m_spgTol); + bool success = m_molecule->undoMolecule()->reduceCellToPrimitive(m_spgTol); if (!success) { // Print an error message. @@ -200,10 +201,6 @@ void SpaceGroup::reduceToPrimitive() "with a different tolerance.")); retMsgBox.exec(); } - else { - m_molecule->emitChanged(Molecule::Added | - Molecule::Atoms | Molecule::UnitCell); - } } void SpaceGroup::conventionalizeCell() @@ -219,7 +216,7 @@ void SpaceGroup::conventionalizeCell() setTolerance(); // Conventionalize the cell! - bool success = AvoSpglib::conventionalizeCell(*m_molecule, m_spgTol); + bool success = m_molecule->undoMolecule()->conventionalizeCell(m_spgTol); if (!success) { // Print an error message. @@ -229,10 +226,6 @@ void SpaceGroup::conventionalizeCell() "with a different tolerance.")); retMsgBox.exec(); } - else { - m_molecule->emitChanged(Molecule::Added | - Molecule::Atoms | Molecule::UnitCell); - } } void SpaceGroup::setTolerance()