1 /******************************************************************************
2 
3   This source file is part of the Avogadro project.
4 
5   Copyright 2013-2015 Kitware, Inc.
6 
7   This source code is released under the New BSD License, (the "License").
8 
9   Unless required by applicable law or agreed to in writing, software
10   distributed under the License is distributed on an "AS IS" BASIS,
11   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   See the License for the specific language governing permissions and
13   limitations under the License.
14 
15 ******************************************************************************/
16 
17 #include "rwmolecule.h"
18 
19 #include <QtWidgets/QUndoCommand>
20 
21 #include <algorithm>
22 #include <cassert>
23 
24 #ifdef USE_SPGLIB
25 #include <avogadro/core/avospglib.h>
26 #endif
27 #include <avogadro/core/spacegroups.h>
28 #include <avogadro/qtgui/hydrogentools.h>
29 
30 namespace Avogadro {
31 namespace QtGui {
32 
33 using Core::Array;
34 using Core::AtomHybridization;
35 using Core::CrystalTools;
36 using Core::UnitCell;
37 
38 // Base class for all undo commands used by this class.
39 // Used to expose molecule internals without needing to add explicit friendships
40 // between all undo commands and the container.
41 // Subclasses that want to support automatic merging should use the
42 // MergeUndoCommand interface below.
43 // Subclass implementations are inline in the code below. Specific undo
44 // operations are kept near the RWMolecule methods that use them.
45 class RWMolecule::UndoCommand : public QUndoCommand
46 {
47 public:
UndoCommand(RWMolecule & m)48   UndoCommand(RWMolecule& m) : QUndoCommand(tr("Modify Molecule")), m_mol(m) {}
49 
50 protected:
atomUniqueIds()51   Array<Index>& atomUniqueIds() { return m_mol.m_molecule.atomUniqueIds(); }
bondUniqueIds()52   Array<Index>& bondUniqueIds() { return m_mol.m_molecule.bondUniqueIds(); }
atomicNumbers()53   Array<unsigned char>& atomicNumbers()
54   {
55     return m_mol.m_molecule.atomicNumbers();
56   }
positions3d()57   Array<Vector3>& positions3d() { return m_mol.m_molecule.atomPositions3d(); }
hybridizations()58   Array<AtomHybridization>& hybridizations()
59   {
60     return m_mol.m_molecule.hybridizations();
61   }
formalCharges()62   Array<signed char>& formalCharges()
63   {
64     return m_mol.m_molecule.formalCharges();
65   }
colors()66   Array<Vector3ub>& colors() { return m_mol.m_molecule.colors(); }
bondPairs()67   Array<std::pair<Index, Index>>& bondPairs()
68   {
69     return m_mol.m_molecule.bondPairs();
70   }
bondOrders()71   Array<unsigned char>& bondOrders() { return m_mol.m_molecule.bondOrders(); }
forceVectors()72   Array<Vector3>& forceVectors() { return m_mol.m_molecule.forceVectors(); }
73   RWMolecule& m_mol;
74 };
75 
76 namespace {
77 enum MergeIds
78 {
79   SetPositions3dMergeId = 0,
80   SetPosition3dMergeId,
81   SetForceVectorMergeId,
82   SetBondOrderMergeId
83 };
84 
85 // Base class for undo commands that can be merged together, overriding the
86 // "after" state of an old command with that of the new command.
87 // Intended for use with RWMolecule's interactive mode.
88 // To add a new class, add a new entry to the MergeIds enum above and use that
89 // symbolic value as the template parameter. Implement the mergeWith() method
90 // to update the "after" state. See SetPositions3dCommand for an example.
91 template <int Id>
92 class MergeUndoCommand : public RWMolecule::UndoCommand
93 {
94   bool m_canMerge;
95 
96 public:
MergeUndoCommand(RWMolecule & m)97   MergeUndoCommand(RWMolecule& m) : UndoCommand(m), m_canMerge(false) {}
setCanMerge(bool merge)98   void setCanMerge(bool merge) { m_canMerge = merge; }
canMerge() const99   bool canMerge() const { return m_canMerge; }
id() const100   int id() const override { return m_canMerge ? Id : -1; }
101 };
102 } // namespace
103 
RWMolecule(Molecule & mol,QObject * p)104 RWMolecule::RWMolecule(Molecule& mol, QObject* p) : QObject(p), m_molecule(mol)
105 {}
106 
~RWMolecule()107 RWMolecule::~RWMolecule() {}
108 
109 namespace {
110 class AddAtomCommand : public RWMolecule::UndoCommand
111 {
112   unsigned char m_atomicNumber;
113   bool m_usingPositions;
114   Index m_atomId;
115   Index m_uniqueId;
116 
117 public:
AddAtomCommand(RWMolecule & m,unsigned char aN,bool usingPositions,Index atomId,Index uid)118   AddAtomCommand(RWMolecule& m, unsigned char aN, bool usingPositions,
119                  Index atomId, Index uid)
120     : UndoCommand(m), m_atomicNumber(aN), m_usingPositions(usingPositions),
121       m_atomId(atomId), m_uniqueId(uid)
122   {}
123 
redo()124   void redo() override
125   {
126     assert(atomicNumbers().size() == m_atomId);
127     atomicNumbers().push_back(m_atomicNumber);
128     if (m_usingPositions)
129       positions3d().push_back(Vector3::Zero());
130     if (m_uniqueId >= atomUniqueIds().size())
131       atomUniqueIds().resize(m_uniqueId + 1, MaxIndex);
132     atomUniqueIds()[m_uniqueId] = m_atomId;
133   }
134 
undo()135   void undo() override
136   {
137     assert(atomicNumbers().size() == m_atomId + 1);
138     atomicNumbers().pop_back();
139     if (m_usingPositions)
140       positions3d().resize(atomicNumbers().size(), Vector3::Zero());
141     atomUniqueIds()[m_uniqueId] = MaxIndex;
142   }
143 };
144 } // namespace
145 
addAtom(unsigned char num,bool usingPositions)146 RWMolecule::AtomType RWMolecule::addAtom(unsigned char num, bool usingPositions)
147 {
148   Index atomId = static_cast<Index>(m_molecule.m_atomicNumbers.size());
149   Index atomUid = static_cast<Index>(m_molecule.m_atomUniqueIds.size());
150 
151   AddAtomCommand* comm =
152     new AddAtomCommand(*this, num, usingPositions, atomId, atomUid);
153   comm->setText(tr("Add Atom"));
154   m_undoStack.push(comm);
155   return AtomType(this, atomId);
156 }
157 
addAtom(unsigned char num,const Vector3 & position3d)158 RWMolecule::AtomType RWMolecule::addAtom(unsigned char num,
159                                          const Vector3& position3d)
160 {
161   // We will combine the actions in this command.
162   m_undoStack.beginMacro(tr("Add Atom"));
163   AtomType atom = addAtom(num);
164   setAtomPosition3d(atomCount() - 1, position3d);
165   m_undoStack.endMacro();
166   return atom;
167 }
168 
atomCount(unsigned char num) const169 Index RWMolecule::atomCount(unsigned char num) const
170 {
171   return m_molecule.atomCount(num);
172 }
173 
174 namespace {
175 class RemoveAtomCommand : public RWMolecule::UndoCommand
176 {
177   Index m_atomId;
178   Index m_atomUid;
179   unsigned char m_atomicNumber;
180   Vector3 m_position3d;
181 
182 public:
RemoveAtomCommand(RWMolecule & m,Index atomId,Index uid,unsigned char aN,const Vector3 & pos)183   RemoveAtomCommand(RWMolecule& m, Index atomId, Index uid, unsigned char aN,
184                     const Vector3& pos)
185     : UndoCommand(m), m_atomId(atomId), m_atomUid(uid), m_atomicNumber(aN),
186       m_position3d(pos)
187   {}
188 
redo()189   void redo() override
190   {
191     assert(m_atomUid < atomUniqueIds().size());
192     atomUniqueIds()[m_atomUid] = MaxIndex;
193 
194     // Move the last atom to the removed atom's position:
195     Index movedId = m_mol.atomCount() - 1;
196     if (m_atomId != movedId) {
197       atomicNumbers()[m_atomId] = atomicNumbers().back();
198       if (positions3d().size() == atomicNumbers().size())
199         positions3d()[m_atomId] = positions3d().back();
200 
201       // Update any bond pairs that have changed:
202       Array<RWMolecule::BondType> atomBonds = m_mol.bonds(movedId);
203       for (Array<RWMolecule::BondType>::const_iterator it = atomBonds.begin(),
204                                                        itEnd = atomBonds.end();
205            it != itEnd; ++it) {
206         std::pair<Index, Index>& bondPair = bondPairs()[it->index()];
207         if (bondPair.first == movedId)
208           bondPair.first = m_atomId;
209         else
210           bondPair.second = m_atomId;
211       }
212 
213       // Update the moved atom's uid
214       Index movedUid = m_mol.atomUniqueId(movedId);
215       assert(movedUid != MaxIndex);
216       atomUniqueIds()[movedUid] = m_atomId;
217     }
218 
219     // Resize the arrays:
220     if (positions3d().size() == atomicNumbers().size())
221       positions3d().resize(movedId, Vector3::Zero());
222     atomicNumbers().resize(movedId, 0);
223   }
224 
undo()225   void undo() override
226   {
227     // Append removed atom's info to the end of the arrays:
228     if (positions3d().size() == atomicNumbers().size())
229       positions3d().push_back(m_position3d);
230     atomicNumbers().push_back(m_atomicNumber);
231 
232     // Swap the moved and unremoved atom data if needed
233     Index movedId = m_mol.atomCount() - 1;
234     if (m_atomId != movedId) {
235       using std::swap;
236       if (positions3d().size() == atomicNumbers().size())
237         swap(positions3d()[m_atomId], positions3d().back());
238       swap(atomicNumbers()[m_atomId], atomicNumbers().back());
239 
240       // Update any bond pairs that have changed:
241       Array<RWMolecule::BondType> atomBonds(m_mol.bonds(m_atomId));
242       for (Array<RWMolecule::BondType>::iterator it = atomBonds.begin(),
243                                                  itEnd = atomBonds.end();
244            it != itEnd; ++it) {
245         std::pair<Index, Index>& bondPair = bondPairs()[it->index()];
246         if (bondPair.first == m_atomId)
247           bondPair.first = movedId;
248         else
249           bondPair.second = movedId;
250       }
251 
252       // Update the moved atom's UID
253       Index movedUid = m_mol.atomUniqueId(m_atomId);
254       assert(movedUid != MaxIndex);
255       atomUniqueIds()[movedUid] = movedId;
256     }
257 
258     // Update the removed atom's UID
259     atomUniqueIds()[m_atomUid] = m_atomId;
260   }
261 };
262 } // namespace
263 
removeAtom(Index atomId)264 bool RWMolecule::removeAtom(Index atomId)
265 {
266   if (atomId >= atomCount())
267     return false;
268 
269   Index uniqueId = findAtomUniqueId(atomId);
270   if (uniqueId == MaxIndex)
271     return false;
272 
273   // Lump all operations into a single undo command:
274   m_undoStack.beginMacro(tr("Remove Atom"));
275 
276   // Remove any bonds containing this atom first.
277   Array<BondType> atomBonds = bonds(atomId);
278   while (atomBonds.size()) {
279     // Ensure that indices aren't invalidated as we remove them:
280     assert("atomBonds have ascending indices" &&
281            (atomBonds.size() == 1 ||
282             ((atomBonds.end() - 2)->index() < (atomBonds.end() - 1)->index())));
283     removeBond(atomBonds.back());
284     atomBonds.pop_back();
285   }
286 
287   RemoveAtomCommand* comm = new RemoveAtomCommand(
288     *this, atomId, uniqueId, atomicNumber(atomId), atomPosition3d(atomId));
289   comm->setText(tr("Remove Atom"));
290 
291   m_undoStack.push(comm);
292 
293   m_undoStack.endMacro();
294   return true;
295 }
296 
clearAtoms()297 void RWMolecule::clearAtoms()
298 {
299   m_undoStack.beginMacro(tr("Clear Atoms"));
300 
301   while (atomCount() != 0)
302     removeAtom(0);
303 
304   m_undoStack.endMacro();
305 }
306 
adjustHydrogens(Index atomId)307 void RWMolecule::adjustHydrogens(Index atomId)
308 {
309   RWAtom atom = this->atom(atomId);
310   if (atom.isValid()) {
311     m_undoStack.beginMacro(tr("Adjust Hydrogens"));
312     QtGui::HydrogenTools::adjustHydrogens(atom);
313     m_undoStack.endMacro();
314   }
315 }
316 
adjustHydrogens(const Core::Array<Index> & atomIds)317 void RWMolecule::adjustHydrogens(const Core::Array<Index>& atomIds)
318 {
319   m_undoStack.beginMacro(tr("Adjust Hydrogens"));
320   for (Index i = 0; i < atomIds.size(); ++i) {
321     adjustHydrogens(atomIds[i]);
322   }
323   m_undoStack.endMacro();
324 }
325 
326 namespace {
327 class SetAtomicNumbersCommand : public RWMolecule::UndoCommand
328 {
329   Core::Array<unsigned char> m_oldAtomicNumbers;
330   Core::Array<unsigned char> m_newAtomicNumbers;
331 
332 public:
SetAtomicNumbersCommand(RWMolecule & m,const Core::Array<unsigned char> & oldAtomicNumbers,const Core::Array<unsigned char> & newAtomicNumbers)333   SetAtomicNumbersCommand(RWMolecule& m,
334                           const Core::Array<unsigned char>& oldAtomicNumbers,
335                           const Core::Array<unsigned char>& newAtomicNumbers)
336     : UndoCommand(m), m_oldAtomicNumbers(oldAtomicNumbers),
337       m_newAtomicNumbers(newAtomicNumbers)
338   {}
339 
redo()340   void redo() override { atomicNumbers() = m_newAtomicNumbers; }
341 
undo()342   void undo() override { atomicNumbers() = m_oldAtomicNumbers; }
343 };
344 } // namespace
345 
setAtomicNumbers(const Core::Array<unsigned char> & nums)346 bool RWMolecule::setAtomicNumbers(const Core::Array<unsigned char>& nums)
347 {
348   if (nums.size() != m_molecule.m_atomicNumbers.size())
349     return false;
350 
351   SetAtomicNumbersCommand* comm =
352     new SetAtomicNumbersCommand(*this, m_molecule.m_atomicNumbers, nums);
353   comm->setText(tr("Change Elements"));
354   m_undoStack.push(comm);
355   return true;
356 }
357 
358 namespace {
359 class SetAtomicNumberCommand : public RWMolecule::UndoCommand
360 {
361   Index m_atomId;
362   unsigned char m_oldAtomicNumber;
363   unsigned char m_newAtomicNumber;
364 
365 public:
SetAtomicNumberCommand(RWMolecule & m,Index atomId,unsigned char oldAtomicNumber,unsigned char newAtomicNumber)366   SetAtomicNumberCommand(RWMolecule& m, Index atomId,
367                          unsigned char oldAtomicNumber,
368                          unsigned char newAtomicNumber)
369     : UndoCommand(m), m_atomId(atomId), m_oldAtomicNumber(oldAtomicNumber),
370       m_newAtomicNumber(newAtomicNumber)
371   {}
372 
redo()373   void redo() override { atomicNumbers()[m_atomId] = m_newAtomicNumber; }
374 
undo()375   void undo() override { atomicNumbers()[m_atomId] = m_oldAtomicNumber; }
376 };
377 } // namespace
378 
setAtomicNumber(Index atomId,unsigned char num)379 bool RWMolecule::setAtomicNumber(Index atomId, unsigned char num)
380 {
381   if (atomId >= atomCount())
382     return false;
383 
384   SetAtomicNumberCommand* comm = new SetAtomicNumberCommand(
385     *this, atomId, m_molecule.m_atomicNumbers[atomId], num);
386   comm->setText(tr("Change Element"));
387   m_undoStack.push(comm);
388   return true;
389 }
390 
391 namespace {
392 class SetPositions3dCommand : public MergeUndoCommand<SetPositions3dMergeId>
393 {
394   Core::Array<Vector3> m_oldPositions3d;
395   Core::Array<Vector3> m_newPositions3d;
396 
397 public:
SetPositions3dCommand(RWMolecule & m,const Core::Array<Vector3> & oldPositions3d,const Core::Array<Vector3> & newPositions3d)398   SetPositions3dCommand(RWMolecule& m,
399                         const Core::Array<Vector3>& oldPositions3d,
400                         const Core::Array<Vector3>& newPositions3d)
401     : MergeUndoCommand<SetPositions3dMergeId>(m),
402       m_oldPositions3d(oldPositions3d), m_newPositions3d(newPositions3d)
403   {}
404 
redo()405   void redo() override { positions3d() = m_newPositions3d; }
406 
undo()407   void undo() override { positions3d() = m_oldPositions3d; }
408 
mergeWith(const QUndoCommand * other)409   bool mergeWith(const QUndoCommand* other) override
410   {
411     const SetPositions3dCommand* o =
412       dynamic_cast<const SetPositions3dCommand*>(other);
413     if (o) {
414       m_newPositions3d = o->m_newPositions3d;
415       return true;
416     }
417     return false;
418   }
419 };
420 } // namespace
421 
setAtomPositions3d(const Core::Array<Vector3> & pos,const QString & undoText)422 bool RWMolecule::setAtomPositions3d(const Core::Array<Vector3>& pos,
423                                     const QString& undoText)
424 {
425   if (pos.size() != m_molecule.m_atomicNumbers.size())
426     return false;
427 
428   SetPositions3dCommand* comm =
429     new SetPositions3dCommand(*this, m_molecule.m_positions3d, pos);
430   comm->setText(undoText);
431   comm->setCanMerge(m_interactive);
432   m_undoStack.push(comm);
433   return true;
434 }
435 
436 namespace {
437 class SetPosition3dCommand : public MergeUndoCommand<SetPosition3dMergeId>
438 {
439   Array<Index> m_atomIds;
440   Array<Vector3> m_oldPosition3ds;
441   Array<Vector3> m_newPosition3ds;
442 
443 public:
SetPosition3dCommand(RWMolecule & m,Index atomId,const Vector3 & oldPosition3d,const Vector3 & newPosition3d)444   SetPosition3dCommand(RWMolecule& m, Index atomId,
445                        const Vector3& oldPosition3d,
446                        const Vector3& newPosition3d)
447     : MergeUndoCommand<SetPosition3dMergeId>(m), m_atomIds(1, atomId),
448       m_oldPosition3ds(1, oldPosition3d), m_newPosition3ds(1, newPosition3d)
449   {}
450 
redo()451   void redo() override
452   {
453     for (size_t i = 0; i < m_atomIds.size(); ++i)
454       positions3d()[m_atomIds[i]] = m_newPosition3ds[i];
455   }
456 
undo()457   void undo() override
458   {
459     for (size_t i = 0; i < m_atomIds.size(); ++i)
460       positions3d()[m_atomIds[i]] = m_oldPosition3ds[i];
461   }
462 
mergeWith(const QUndoCommand * o)463   bool mergeWith(const QUndoCommand* o) override
464   {
465     const SetPosition3dCommand* other =
466       dynamic_cast<const SetPosition3dCommand*>(o);
467     if (!other)
468       return false;
469 
470     size_t numAtoms = other->m_atomIds.size();
471     if (numAtoms != other->m_oldPosition3ds.size() ||
472         numAtoms != other->m_newPosition3ds.size()) {
473       return false;
474     }
475 
476     for (size_t i = 0; i < numAtoms; ++i) {
477       const Index& atomId = other->m_atomIds[i];
478       const Vector3& oldPos = other->m_oldPosition3ds[i];
479       const Vector3& newPos = other->m_newPosition3ds[i];
480 
481       Array<Index>::const_iterator idsBegin = m_atomIds.begin();
482       Array<Index>::const_iterator idsEnd = m_atomIds.end();
483       Array<Index>::const_iterator match = std::find(idsBegin, idsEnd, atomId);
484 
485       if (match == idsEnd) {
486         // Append a new atom:
487         m_atomIds.push_back(atomId);
488         m_oldPosition3ds.push_back(oldPos);
489         m_newPosition3ds.push_back(newPos);
490       } else {
491         // Overwrite the existing movement:
492         size_t offset = std::distance(idsBegin, match);
493         assert(m_atomIds[offset] == atomId);
494         m_newPosition3ds[offset] = newPos;
495       }
496     }
497 
498     return true;
499   }
500 };
501 } // namespace
502 
setAtomPosition3d(Index atomId,const Vector3 & pos,const QString & undoText)503 bool RWMolecule::setAtomPosition3d(Index atomId, const Vector3& pos,
504                                    const QString& undoText)
505 {
506   if (atomId >= atomCount())
507     return false;
508 
509   if (m_molecule.m_positions3d.size() != m_molecule.m_atomicNumbers.size())
510     m_molecule.m_positions3d.resize(m_molecule.m_atomicNumbers.size(),
511                                     Vector3::Zero());
512 
513   SetPosition3dCommand* comm = new SetPosition3dCommand(
514     *this, atomId, m_molecule.m_positions3d[atomId], pos);
515   comm->setText(undoText);
516   comm->setCanMerge(m_interactive);
517   m_undoStack.push(comm);
518   return true;
519 }
520 
setAtomSelected(Index atomId,bool selected)521 void RWMolecule::setAtomSelected(Index atomId, bool selected)
522 {
523   // FIXME: Add in an implementation (and use it from the selection tool).
524   m_molecule.setAtomSelected(atomId, selected);
525 }
526 
atomSelected(Index atomId) const527 bool RWMolecule::atomSelected(Index atomId) const
528 {
529   return m_molecule.atomSelected(atomId);
530 }
531 
532 namespace {
533 class SetAtomHybridizationCommand : public RWMolecule::UndoCommand
534 {
535   Index m_atomId;
536   Core::AtomHybridization m_oldHybridization;
537   Core::AtomHybridization m_newHybridization;
538 
539 public:
SetAtomHybridizationCommand(RWMolecule & m,Index atomId,Core::AtomHybridization oldHybridization,Core::AtomHybridization newHybridization)540   SetAtomHybridizationCommand(RWMolecule& m, Index atomId,
541                               Core::AtomHybridization oldHybridization,
542                               Core::AtomHybridization newHybridization)
543     : UndoCommand(m), m_atomId(atomId), m_oldHybridization(oldHybridization),
544       m_newHybridization(newHybridization)
545   {}
546 
redo()547   void redo() override { hybridizations()[m_atomId] = m_newHybridization; }
548 
undo()549   void undo() override { hybridizations()[m_atomId] = m_oldHybridization; }
550 };
551 } // namespace
552 
setHybridization(Index atomId,Core::AtomHybridization hyb)553 bool RWMolecule::setHybridization(Index atomId, Core::AtomHybridization hyb)
554 {
555   if (atomId >= atomCount())
556     return false;
557 
558   SetAtomicNumberCommand* comm = new SetAtomicNumberCommand(
559     *this, atomId, m_molecule.hybridization(atomId), hyb);
560   comm->setText(tr("Change Atom Hybridization"));
561   m_undoStack.push(comm);
562   return true;
563 }
564 
565 namespace {
566 class SetAtomFormalChargeCommand : public RWMolecule::UndoCommand
567 {
568   Index m_atomId;
569   signed char m_oldCharge;
570   signed char m_newCharge;
571 
572 public:
SetAtomFormalChargeCommand(RWMolecule & m,Index atomId,signed char oldCharge,signed char newCharge)573   SetAtomFormalChargeCommand(RWMolecule& m, Index atomId, signed char oldCharge,
574                              signed char newCharge)
575     : UndoCommand(m), m_atomId(atomId), m_oldCharge(oldCharge),
576       m_newCharge(newCharge)
577   {}
578 
redo()579   void redo() override { formalCharges()[m_atomId] = m_newCharge; }
580 
undo()581   void undo() override { formalCharges()[m_atomId] = m_oldCharge; }
582 };
583 } // namespace
584 
setFormalCharge(Index atomId,signed char charge)585 bool RWMolecule::setFormalCharge(Index atomId, signed char charge)
586 {
587   if (atomId >= atomCount())
588     return false;
589 
590   SetAtomFormalChargeCommand* comm = new SetAtomFormalChargeCommand(
591     *this, atomId, m_molecule.formalCharge(atomId), charge);
592   comm->setText(tr("Change Atom Formal Charge"));
593   m_undoStack.push(comm);
594   return true;
595 }
596 
597 namespace {
598 class SetAtomColorCommand : public RWMolecule::UndoCommand
599 {
600   Index m_atomId;
601   Vector3ub m_oldColor;
602   Vector3ub m_newColor;
603 
604 public:
SetAtomColorCommand(RWMolecule & m,Index atomId,Vector3ub oldColor,Vector3ub newColor)605   SetAtomColorCommand(RWMolecule& m, Index atomId, Vector3ub oldColor,
606                       Vector3ub newColor)
607     : UndoCommand(m), m_atomId(atomId), m_oldColor(oldColor),
608       m_newColor(newColor)
609   {}
610 
redo()611   void redo() override { colors()[m_atomId] = m_newColor; }
612 
undo()613   void undo() override { colors()[m_atomId] = m_oldColor; }
614 };
615 } // end namespace
616 
setColor(Index atomId,Vector3ub color)617 bool RWMolecule::setColor(Index atomId, Vector3ub color)
618 {
619   if (atomId >= atomCount())
620     return false;
621 
622   SetAtomColorCommand* comm =
623     new SetAtomColorCommand(*this, atomId, m_molecule.color(atomId), color);
624   comm->setText(tr("Change Atom Color"));
625   m_undoStack.push(comm);
626   return true;
627 }
628 
629 namespace {
630 class AddBondCommand : public RWMolecule::UndoCommand
631 {
632   unsigned char m_bondOrder;
633   std::pair<Index, Index> m_bondPair;
634   Index m_bondId;
635   Index m_uniqueId;
636 
637 public:
AddBondCommand(RWMolecule & m,unsigned char order,const std::pair<Index,Index> & bondPair,Index bondId,Index uid)638   AddBondCommand(RWMolecule& m, unsigned char order,
639                  const std::pair<Index, Index>& bondPair, Index bondId,
640                  Index uid)
641     : UndoCommand(m), m_bondOrder(order), m_bondPair(bondPair),
642       m_bondId(bondId), m_uniqueId(uid)
643   {}
644 
redo()645   void redo() override
646   {
647     assert(bondOrders().size() == m_bondId);
648     assert(bondPairs().size() == m_bondId);
649     bondOrders().push_back(m_bondOrder);
650     bondPairs().push_back(m_bondPair);
651     if (m_uniqueId >= bondUniqueIds().size())
652       bondUniqueIds().resize(m_uniqueId + 1, MaxIndex);
653     bondUniqueIds()[m_uniqueId] = m_bondId;
654   }
655 
undo()656   void undo() override
657   {
658     assert(bondOrders().size() == m_bondId + 1);
659     assert(bondPairs().size() == m_bondId + 1);
660     bondOrders().pop_back();
661     bondPairs().pop_back();
662     bondUniqueIds()[m_uniqueId] = MaxIndex;
663   }
664 };
665 
666 // Make an std::pair where the lower index is always first in the pair. This
667 // offers us the guarantee that any given pair of atoms will always result in
668 // a pair that is the same no matter what the order of the atoms given.
makeBondPair(Index a,Index b)669 inline std::pair<Index, Index> makeBondPair(Index a, Index b)
670 {
671   return a < b ? std::make_pair(a, b) : std::make_pair(b, a);
672 }
673 } // namespace
674 
addBond(Index atom1,Index atom2,unsigned char order)675 RWMolecule::BondType RWMolecule::addBond(Index atom1, Index atom2,
676                                          unsigned char order)
677 {
678   if (atom1 == atom2 || std::max(atom1, atom2) >= atomCount())
679     return BondType();
680 
681   Index bondId = bondCount();
682   Index bondUid = static_cast<Index>(m_molecule.m_bondUniqueIds.size());
683 
684   AddBondCommand* comm = new AddBondCommand(
685     *this, order, makeBondPair(atom1, atom2), bondId, bondUid);
686   comm->setText(tr("Add Bond"));
687   m_undoStack.push(comm);
688   return BondType(this, bondId);
689 }
690 
bond(Index atom1,Index atom2) const691 RWMolecule::BondType RWMolecule::bond(Index atom1, Index atom2) const
692 {
693   Molecule::BondType b = m_molecule.bond(atom1, atom2);
694   if (b.isValid())
695     return BondType(const_cast<RWMolecule*>(this), b.index());
696   else
697     return BondType();
698 }
699 
700 namespace {
701 class RemoveBondCommand : public RWMolecule::UndoCommand
702 {
703   Index m_bondId;
704   Index m_bondUid;
705   std::pair<Index, Index> m_bondPair;
706   unsigned char m_bondOrder;
707 
708 public:
RemoveBondCommand(RWMolecule & m,Index bondId,Index bondUid,const std::pair<Index,Index> & bondPair,unsigned char bondOrder)709   RemoveBondCommand(RWMolecule& m, Index bondId, Index bondUid,
710                     const std::pair<Index, Index>& bondPair,
711                     unsigned char bondOrder)
712     : UndoCommand(m), m_bondId(bondId), m_bondUid(bondUid),
713       m_bondPair(bondPair), m_bondOrder(bondOrder)
714   {}
715 
redo()716   void redo() override
717   {
718     // Clear removed bond's UID
719     bondUniqueIds()[m_bondUid] = MaxIndex;
720 
721     // Move the last bond's data to the removed bond's index:
722     Index movedId = m_mol.bondCount() - 1;
723     if (m_bondId != movedId) {
724       bondOrders()[m_bondId] = bondOrders().back();
725       bondPairs()[m_bondId] = bondPairs().back();
726 
727       // Update moved bond's UID
728       Index movedUid = m_mol.bondUniqueId(movedId);
729       assert(movedUid != MaxIndex);
730       bondUniqueIds()[movedUid] = m_bondId;
731     }
732     bondOrders().pop_back();
733     bondPairs().pop_back();
734   }
735 
undo()736   void undo() override
737   {
738     // Push the removed bond's info to the end of the arrays:
739     bondOrders().push_back(m_bondOrder);
740     bondPairs().push_back(m_bondPair);
741 
742     // Swap with the bond that we moved in redo():
743     Index movedId = m_mol.bondCount() - 1;
744     if (m_bondId != movedId) {
745       using std::swap;
746       swap(bondOrders()[m_bondId], bondOrders().back());
747       swap(bondPairs()[m_bondId], bondPairs().back());
748 
749       // Update moved bond's UID
750       Index movedUid = m_mol.bondUniqueId(m_bondId);
751       assert(movedUid != MaxIndex);
752       bondUniqueIds()[movedUid] = movedId;
753     }
754 
755     // Restore the removed bond's UID
756     bondUniqueIds()[m_bondUid] = m_bondId;
757   }
758 };
759 } // namespace
760 
removeBond(Index bondId)761 bool RWMolecule::removeBond(Index bondId)
762 {
763   if (bondId >= bondCount())
764     return false;
765 
766   Index bondUid = findBondUniqueId(bondId);
767   if (bondUid == MaxIndex)
768     return false;
769 
770   RemoveBondCommand* comm = new RemoveBondCommand(
771     *this, bondId, bondUid, m_molecule.m_bondPairs[bondId],
772     m_molecule.m_bondOrders[bondId]);
773   comm->setText(tr("Removed Bond"));
774   m_undoStack.push(comm);
775   return true;
776 }
777 
clearBonds()778 void RWMolecule::clearBonds()
779 {
780   m_undoStack.beginMacro(tr("Clear Bonds"));
781 
782   while (bondCount() != 0)
783     removeBond(0);
784 
785   m_undoStack.endMacro();
786 }
787 
788 namespace {
789 class SetBondOrdersCommand : public RWMolecule::UndoCommand
790 {
791   Array<unsigned char> m_oldBondOrders;
792   Array<unsigned char> m_newBondOrders;
793 
794 public:
SetBondOrdersCommand(RWMolecule & m,const Array<unsigned char> & oldBondOrders,const Array<unsigned char> & newBondOrders)795   SetBondOrdersCommand(RWMolecule& m, const Array<unsigned char>& oldBondOrders,
796                        const Array<unsigned char>& newBondOrders)
797     : UndoCommand(m), m_oldBondOrders(oldBondOrders),
798       m_newBondOrders(newBondOrders)
799   {}
800 
redo()801   void redo() override { bondOrders() = m_newBondOrders; }
802 
undo()803   void undo() override { bondOrders() = m_oldBondOrders; }
804 };
805 } // namespace
806 
setBondOrders(const Core::Array<unsigned char> & orders)807 bool RWMolecule::setBondOrders(const Core::Array<unsigned char>& orders)
808 {
809   if (orders.size() != m_molecule.m_bondOrders.size())
810     return false;
811 
812   SetBondOrdersCommand* comm =
813     new SetBondOrdersCommand(*this, m_molecule.m_bondOrders, orders);
814   comm->setText(tr("Set Bond Orders"));
815   m_undoStack.push(comm);
816   return true;
817 }
818 
819 namespace {
820 class SetBondOrderCommand : public MergeUndoCommand<SetBondOrderMergeId>
821 {
822   Index m_bondId;
823   unsigned char m_oldBondOrder;
824   unsigned char m_newBondOrder;
825 
826 public:
SetBondOrderCommand(RWMolecule & m,Index bondId,unsigned char oldBondOrder,unsigned char newBondOrder)827   SetBondOrderCommand(RWMolecule& m, Index bondId, unsigned char oldBondOrder,
828                       unsigned char newBondOrder)
829     : MergeUndoCommand<SetBondOrderMergeId>(m), m_bondId(bondId),
830       m_oldBondOrder(oldBondOrder), m_newBondOrder(newBondOrder)
831   {}
832 
redo()833   void redo() override { bondOrders()[m_bondId] = m_newBondOrder; }
834 
undo()835   void undo() override { bondOrders()[m_bondId] = m_oldBondOrder; }
836 
mergeWith(const QUndoCommand * other)837   bool mergeWith(const QUndoCommand* other) override
838   {
839     const SetBondOrderCommand* o =
840       dynamic_cast<const SetBondOrderCommand*>(other);
841     // Only merge when the bondIds match.
842     if (o && o->m_bondId == this->m_bondId) {
843       this->m_newBondOrder = o->m_newBondOrder;
844       return true;
845     }
846     return false;
847   }
848 };
849 } // namespace
850 
setBondOrder(Index bondId,unsigned char order)851 bool RWMolecule::setBondOrder(Index bondId, unsigned char order)
852 {
853   if (bondId >= bondCount())
854     return false;
855 
856   SetBondOrderCommand* comm = new SetBondOrderCommand(
857     *this, bondId, m_molecule.m_bondOrders[bondId], order);
858   comm->setText(tr("Change Bond Order"));
859   // Always allow merging, but only if bondId is the same.
860   comm->setCanMerge(true);
861   m_undoStack.push(comm);
862   return true;
863 }
864 
865 namespace {
866 class SetBondPairsCommand : public RWMolecule::UndoCommand
867 {
868   Array<std::pair<Index, Index>> m_oldBondPairs;
869   Array<std::pair<Index, Index>> m_newBondPairs;
870 
871 public:
SetBondPairsCommand(RWMolecule & m,const Array<std::pair<Index,Index>> & oldBondPairs,const Array<std::pair<Index,Index>> & newBondPairs)872   SetBondPairsCommand(RWMolecule& m,
873                       const Array<std::pair<Index, Index>>& oldBondPairs,
874                       const Array<std::pair<Index, Index>>& newBondPairs)
875     : UndoCommand(m), m_oldBondPairs(oldBondPairs), m_newBondPairs(newBondPairs)
876   {}
877 
redo()878   void redo() override { bondPairs() = m_newBondPairs; }
879 
undo()880   void undo() override { bondPairs() = m_oldBondPairs; }
881 };
882 } // namespace
883 
setBondPairs(const Array<std::pair<Index,Index>> & pairs)884 bool RWMolecule::setBondPairs(const Array<std::pair<Index, Index>>& pairs)
885 {
886   if (pairs.size() != m_molecule.m_bondPairs.size())
887     return false;
888 
889   // Correct any pairs that are ordered improperly:
890   typedef std::pair<Index, Index> BondPair;
891   Array<BondPair> p(pairs);
892   // Use for reading to prevent copies unless needed (Array is copy-on-write):
893   const Array<BondPair>& p_const = p;
894   using std::swap;
895   for (size_t i = 0; i < p.size(); ++i)
896     if (p_const[i].first > p_const[i].second)
897       swap(p[i].first, p[i].second);
898 
899   SetBondPairsCommand* comm =
900     new SetBondPairsCommand(*this, m_molecule.m_bondPairs, p);
901   comm->setText(tr("Update Bonds"));
902   m_undoStack.push(comm);
903   return true;
904 }
905 
906 namespace {
907 class SetBondPairCommand : public RWMolecule::UndoCommand
908 {
909   Index m_bondId;
910   std::pair<Index, Index> m_oldBondPair;
911   std::pair<Index, Index> m_newBondPair;
912 
913 public:
SetBondPairCommand(RWMolecule & m,Index bondId,const std::pair<Index,Index> & oldBondPair,const std::pair<Index,Index> & newBondPair)914   SetBondPairCommand(RWMolecule& m, Index bondId,
915                      const std::pair<Index, Index>& oldBondPair,
916                      const std::pair<Index, Index>& newBondPair)
917     : UndoCommand(m), m_bondId(bondId), m_oldBondPair(oldBondPair),
918       m_newBondPair(newBondPair)
919   {}
920 
redo()921   void redo() override { bondPairs()[m_bondId] = m_newBondPair; }
922 
undo()923   void undo() override { bondPairs()[m_bondId] = m_oldBondPair; }
924 };
925 } // namespace
926 
setBondPair(Index bondId,const std::pair<Index,Index> & pair)927 bool RWMolecule::setBondPair(Index bondId, const std::pair<Index, Index>& pair)
928 {
929   if (bondId >= bondCount() || pair.first == pair.second)
930     return false;
931 
932   SetBondPairCommand* comm = nullptr;
933   if (pair.first < pair.second) {
934     comm = new SetBondPairCommand(*this, bondId, m_molecule.m_bondPairs[bondId],
935                                   pair);
936   } else {
937     comm = new SetBondPairCommand(*this, bondId, m_molecule.m_bondPairs[bondId],
938                                   makeBondPair(pair.first, pair.second));
939   }
940   comm->setText(tr("Update Bond"));
941   m_undoStack.push(comm);
942   return true;
943 }
944 
945 namespace {
946 class AddUnitCellCommand : public RWMolecule::UndoCommand
947 {
948   UnitCell m_newUnitCell;
949 
950 public:
AddUnitCellCommand(RWMolecule & m,const UnitCell & newUnitCell)951   AddUnitCellCommand(RWMolecule& m, const UnitCell& newUnitCell)
952     : UndoCommand(m), m_newUnitCell(newUnitCell)
953   {}
954 
redo()955   void redo() override
956   {
957     m_mol.molecule().setUnitCell(new UnitCell(m_newUnitCell));
958   }
959 
undo()960   void undo() override { m_mol.molecule().setUnitCell(nullptr); }
961 };
962 } // namespace
963 
addUnitCell()964 void RWMolecule::addUnitCell()
965 {
966   // If there is already a unit cell, there is nothing to do
967   if (m_molecule.unitCell())
968     return;
969 
970   UnitCell* cell = new UnitCell;
971   cell->setCellParameters(
972     static_cast<Real>(3.0), static_cast<Real>(3.0), static_cast<Real>(3.0),
973     static_cast<Real>(90.0) * DEG_TO_RAD, static_cast<Real>(90.0) * DEG_TO_RAD,
974     static_cast<Real>(90.0) * DEG_TO_RAD);
975   m_molecule.setUnitCell(cell);
976 
977   AddUnitCellCommand* comm =
978     new AddUnitCellCommand(*this, *m_molecule.unitCell());
979   comm->setText(tr("Add Unit Cell"));
980   m_undoStack.push(comm);
981   emitChanged(Molecule::UnitCell | Molecule::Added);
982 }
983 
984 namespace {
985 class RemoveUnitCellCommand : public RWMolecule::UndoCommand
986 {
987   UnitCell m_oldUnitCell;
988 
989 public:
RemoveUnitCellCommand(RWMolecule & m,const UnitCell & oldUnitCell)990   RemoveUnitCellCommand(RWMolecule& m, const UnitCell& oldUnitCell)
991     : UndoCommand(m), m_oldUnitCell(oldUnitCell)
992   {}
993 
redo()994   void redo() override { m_mol.molecule().setUnitCell(nullptr); }
995 
undo()996   void undo() override
997   {
998     m_mol.molecule().setUnitCell(new UnitCell(m_oldUnitCell));
999   }
1000 };
1001 } // namespace
1002 
removeUnitCell()1003 void RWMolecule::removeUnitCell()
1004 {
1005   // If there is no unit cell, there is nothing to do
1006   if (!m_molecule.unitCell())
1007     return;
1008 
1009   RemoveUnitCellCommand* comm =
1010     new RemoveUnitCellCommand(*this, *m_molecule.unitCell());
1011   comm->setText(tr("Remove Unit Cell"));
1012   m_undoStack.push(comm);
1013 
1014   m_molecule.setUnitCell(nullptr);
1015   emitChanged(Molecule::UnitCell | Molecule::Removed);
1016 }
1017 
1018 namespace {
1019 class ModifyMoleculeCommand : public RWMolecule::UndoCommand
1020 {
1021   Molecule m_oldMolecule;
1022   Molecule m_newMolecule;
1023 
1024 public:
ModifyMoleculeCommand(RWMolecule & m,const Molecule & oldMolecule,const Molecule & newMolecule)1025   ModifyMoleculeCommand(RWMolecule& m, const Molecule& oldMolecule,
1026                         const Molecule& newMolecule)
1027     : UndoCommand(m), m_oldMolecule(oldMolecule), m_newMolecule(newMolecule)
1028   {}
1029 
redo()1030   void redo() override { m_mol.molecule() = m_newMolecule; }
1031 
undo()1032   void undo() override { m_mol.molecule() = m_oldMolecule; }
1033 };
1034 } // namespace
1035 
modifyMolecule(const Molecule & newMolecule,Molecule::MoleculeChanges changes,const QString & undoText)1036 void RWMolecule::modifyMolecule(const Molecule& newMolecule,
1037                                 Molecule::MoleculeChanges changes,
1038                                 const QString& undoText)
1039 {
1040   ModifyMoleculeCommand* comm =
1041     new ModifyMoleculeCommand(*this, m_molecule, newMolecule);
1042 
1043   comm->setText(undoText);
1044   m_undoStack.push(comm);
1045 
1046   m_molecule = newMolecule;
1047   emitChanged(changes);
1048 }
1049 
appendMolecule(const Molecule & mol,const QString & undoText)1050 void RWMolecule::appendMolecule(const Molecule& mol, const QString& undoText)
1051 {
1052   // We add atoms and bonds, nothing else
1053   Molecule::MoleculeChanges changes =
1054     (Molecule::Atoms | Molecule::Bonds | Molecule::Added);
1055 
1056   beginMergeMode(undoText);
1057   // loop through and add the atoms
1058   Index offset = atomCount();
1059   for (size_t i = 0; i < mol.atomCount(); ++i) {
1060     Core::Atom atom = mol.atom(i);
1061     addAtom(atom.atomicNumber(), atom.position3d());
1062     setAtomSelected(atomCount() - 1, true);
1063   }
1064   // now loop through and add the bonds
1065   for (size_t i = 0; i < mol.bondCount(); ++i) {
1066     Core::Bond bond = mol.bond(i);
1067     addBond(bond.atom1().index() + offset, bond.atom2().index() + offset,
1068             bond.order());
1069   }
1070   endMergeMode();
1071   emitChanged(changes);
1072 }
1073 
editUnitCell(Matrix3 cellMatrix,CrystalTools::Options options)1074 void RWMolecule::editUnitCell(Matrix3 cellMatrix, CrystalTools::Options options)
1075 {
1076   // If there is no unit cell, there is nothing to do
1077   if (!m_molecule.unitCell())
1078     return;
1079 
1080   // Make a copy of the molecule to edit so we can store the old one
1081   // If the user has "TransformAtoms" set in the options, then
1082   // the atom positions will move as well.
1083   Molecule newMolecule = m_molecule;
1084   CrystalTools::setCellMatrix(newMolecule, cellMatrix, options);
1085 
1086   // We will just modify the whole molecule since there may be many changes
1087   Molecule::MoleculeChanges changes = Molecule::UnitCell | Molecule::Modified;
1088   // If TransformAtoms is set in the options, then the atoms may be modified
1089   // as well.
1090   if (options & CrystalTools::TransformAtoms)
1091     changes |= Molecule::Atoms | Molecule::Modified;
1092   QString undoText = tr("Edit Unit Cell");
1093 
1094   modifyMolecule(newMolecule, changes, undoText);
1095 }
1096 
wrapAtomsToCell()1097 void RWMolecule::wrapAtomsToCell()
1098 {
1099   // If there is no unit cell, there is nothing to do
1100   if (!m_molecule.unitCell())
1101     return;
1102 
1103   Core::Array<Vector3> oldPos = m_molecule.atomPositions3d();
1104   CrystalTools::wrapAtomsToUnitCell(m_molecule);
1105   Core::Array<Vector3> newPos = m_molecule.atomPositions3d();
1106 
1107   SetPositions3dCommand* comm =
1108     new SetPositions3dCommand(*this, oldPos, newPos);
1109   comm->setText(tr("Wrap Atoms to Cell"));
1110   m_undoStack.push(comm);
1111 
1112   Molecule::MoleculeChanges changes = Molecule::Atoms | Molecule::Modified;
1113   emitChanged(changes);
1114 }
1115 
setCellVolume(double newVolume,CrystalTools::Options options)1116 void RWMolecule::setCellVolume(double newVolume, CrystalTools::Options options)
1117 {
1118   // If there is no unit cell, there is nothing to do
1119   if (!m_molecule.unitCell())
1120     return;
1121 
1122   // Make a copy of the molecule to edit so we can store the old one
1123   // The unit cell and atom positions may change
1124   Molecule newMolecule = m_molecule;
1125 
1126   CrystalTools::setVolume(newMolecule, newVolume, options);
1127 
1128   // We will just modify the whole molecule since there may be many changes
1129   Molecule::MoleculeChanges changes = Molecule::UnitCell | Molecule::Modified;
1130   if (options & CrystalTools::TransformAtoms)
1131     changes |= Molecule::Atoms | Molecule::Modified;
1132   QString undoText = tr("Scale Cell Volume");
1133 
1134   modifyMolecule(newMolecule, changes, undoText);
1135 }
1136 
buildSupercell(unsigned int a,unsigned int b,unsigned int c)1137 void RWMolecule::buildSupercell(unsigned int a, unsigned int b, unsigned int c)
1138 {
1139   // If there is no unit cell, there is nothing to do
1140   if (!m_molecule.unitCell())
1141     return;
1142 
1143   // Make a copy of the molecule to edit so we can store the old one
1144   // The unit cell and atom positions may change
1145   Molecule newMolecule = m_molecule;
1146 
1147   CrystalTools::buildSupercell(newMolecule, a, b, c);
1148 
1149   // We will just modify the whole molecule since there may be many changes
1150   Molecule::MoleculeChanges changes =
1151     Molecule::UnitCell | Molecule::Modified | Molecule::Atoms | Molecule::Added;
1152   QString undoText = tr("Build Super Cell");
1153 
1154   modifyMolecule(newMolecule, changes, undoText);
1155 }
1156 
niggliReduceCell()1157 void RWMolecule::niggliReduceCell()
1158 {
1159   // If there is no unit cell, there is nothing to do
1160   if (!m_molecule.unitCell())
1161     return;
1162 
1163   // Make a copy of the molecule to edit so we can store the old one
1164   // The unit cell and atom positions may change
1165   Molecule newMolecule = m_molecule;
1166 
1167   // We need to perform all three of these operations...
1168   CrystalTools::niggliReduce(newMolecule, CrystalTools::TransformAtoms);
1169   CrystalTools::rotateToStandardOrientation(newMolecule,
1170                                             CrystalTools::TransformAtoms);
1171   CrystalTools::wrapAtomsToUnitCell(newMolecule);
1172 
1173   // We will just modify the whole molecule since there may be many changes
1174   Molecule::MoleculeChanges changes =
1175     Molecule::UnitCell | Molecule::Atoms | Molecule::Modified;
1176   QString undoText = tr("Niggli Reduction");
1177 
1178   modifyMolecule(newMolecule, changes, undoText);
1179 }
1180 
rotateCellToStandardOrientation()1181 void RWMolecule::rotateCellToStandardOrientation()
1182 {
1183   // If there is no unit cell, there is nothing to do
1184   if (!m_molecule.unitCell())
1185     return;
1186 
1187   // Store a copy of the old molecule
1188   // The atom positions may move as well.
1189   Molecule newMolecule = m_molecule;
1190 
1191   CrystalTools::rotateToStandardOrientation(newMolecule,
1192                                             CrystalTools::TransformAtoms);
1193 
1194   // Since most components of the molecule will be modified (atom positions
1195   // and the unit cell), we will just modify the whole thing...
1196   Molecule::MoleculeChanges changes =
1197     Molecule::UnitCell | Molecule::Atoms | Molecule::Modified;
1198   QString undoText = tr("Rotate to Standard Orientation");
1199 
1200   modifyMolecule(newMolecule, changes, undoText);
1201 }
1202 
reduceCellToPrimitive(double cartTol)1203 bool RWMolecule::reduceCellToPrimitive(double cartTol)
1204 {
1205   // If there is no unit cell, there is nothing to do
1206   if (!m_molecule.unitCell())
1207     return false;
1208 
1209   // Make a copy of the molecule to edit so we can store the old one
1210   // The unit cell, atom positions, and numbers of atoms may change
1211   Molecule newMolecule = m_molecule;
1212 #ifdef USE_SPGLIB
1213   if (!Core::AvoSpglib::reduceToPrimitive(newMolecule, cartTol))
1214     return false;
1215 #else
1216   return false;
1217 #endif
1218 
1219   // Since most components of the molecule will be modified,
1220   // we will just modify the whole thing...
1221   Molecule::MoleculeChanges changes =
1222     Molecule::UnitCell | Molecule::Atoms | Molecule::Added;
1223   QString undoText = tr("Reduce to Primitive");
1224 
1225   modifyMolecule(newMolecule, changes, undoText);
1226   return true;
1227 }
1228 
conventionalizeCell(double cartTol)1229 bool RWMolecule::conventionalizeCell(double cartTol)
1230 {
1231   // If there is no unit cell, there is nothing to do
1232   if (!m_molecule.unitCell())
1233     return false;
1234 
1235   // Make a copy of the molecule to edit so we can store the old one
1236   // The unit cell, atom positions, and numbers of atoms may all change
1237   Molecule newMolecule = m_molecule;
1238 
1239 #ifdef USE_SPGLIB
1240   if (!Core::AvoSpglib::conventionalizeCell(newMolecule, cartTol))
1241     return false;
1242 #else
1243   return false;
1244 #endif
1245 
1246   Molecule::MoleculeChanges changes =
1247     Molecule::UnitCell | Molecule::Atoms | Molecule::Added;
1248   QString undoText = tr("Conventionalize Cell");
1249 
1250   modifyMolecule(newMolecule, changes, undoText);
1251   return true;
1252 }
1253 
symmetrizeCell(double cartTol)1254 bool RWMolecule::symmetrizeCell(double cartTol)
1255 {
1256   // If there is no unit cell, there is nothing to do
1257   if (!m_molecule.unitCell())
1258     return false;
1259 
1260   // Make a copy of the molecule to edit so we can store the old one
1261   // The unit cell, atom positions, and numbers of atoms may all change
1262   Molecule newMolecule = m_molecule;
1263 
1264 #ifdef USE_SPGLIB
1265   if (!Core::AvoSpglib::symmetrize(newMolecule, cartTol))
1266     return false;
1267 #else
1268   return false;
1269 #endif
1270 
1271   Molecule::MoleculeChanges changes =
1272     Molecule::UnitCell | Molecule::Atoms | Molecule::Added;
1273   QString undoText = tr("Symmetrize Cell");
1274 
1275   modifyMolecule(newMolecule, changes, undoText);
1276   return true;
1277 }
1278 
fillUnitCell(unsigned short hallNumber,double cartTol)1279 bool RWMolecule::fillUnitCell(unsigned short hallNumber, double cartTol)
1280 {
1281   // If there is no unit cell, there is nothing to do
1282   if (!m_molecule.unitCell())
1283     return false;
1284 
1285   // Make a copy of the molecule to edit so we can store the old one
1286   // The atom positions and numbers of atoms may change
1287   Molecule newMolecule = m_molecule;
1288 
1289   Core::SpaceGroups::fillUnitCell(newMolecule, hallNumber, cartTol);
1290 
1291   Molecule::MoleculeChanges changes = Molecule::Added | Molecule::Atoms;
1292   QString undoText = tr("Fill Unit Cell");
1293 
1294   modifyMolecule(newMolecule, changes, undoText);
1295   return true;
1296 }
1297 
reduceCellToAsymmetricUnit(unsigned short hallNumber,double cartTol)1298 bool RWMolecule::reduceCellToAsymmetricUnit(unsigned short hallNumber,
1299                                             double cartTol)
1300 {
1301   // If there is no unit cell, there is nothing to do
1302   if (!m_molecule.unitCell())
1303     return false;
1304 
1305   // Make a copy of the molecule to edit so we can store the old one
1306   // The atom positions and numbers of atoms may change
1307   Molecule newMolecule = m_molecule;
1308 
1309   Core::SpaceGroups::reduceToAsymmetricUnit(newMolecule, hallNumber, cartTol);
1310 
1311   Molecule::MoleculeChanges changes = Molecule::Removed | Molecule::Atoms;
1312   QString undoText = tr("Reduce Cell to Asymmetric Unit");
1313 
1314   modifyMolecule(newMolecule, changes, undoText);
1315   return true;
1316 }
1317 
emitChanged(unsigned int change)1318 void RWMolecule::emitChanged(unsigned int change)
1319 {
1320   m_molecule.emitChanged(change);
1321 }
1322 
findAtomUniqueId(Index atomId) const1323 Index RWMolecule::findAtomUniqueId(Index atomId) const
1324 {
1325   return m_molecule.findAtomUniqueId(atomId);
1326 }
1327 
findBondUniqueId(Index bondId) const1328 Index RWMolecule::findBondUniqueId(Index bondId) const
1329 {
1330   return m_molecule.findBondUniqueId(bondId);
1331 }
1332 
1333 namespace {
1334 class SetForceVectorCommand : public MergeUndoCommand<SetForceVectorMergeId>
1335 {
1336   Array<Index> m_atomIds;
1337   Array<Vector3> m_oldForceVectors;
1338   Array<Vector3> m_newForceVectors;
1339 
1340 public:
SetForceVectorCommand(RWMolecule & m,Index atomId,const Vector3 & oldForceVector,const Vector3 & newForceVector)1341   SetForceVectorCommand(RWMolecule& m, Index atomId,
1342                         const Vector3& oldForceVector,
1343                         const Vector3& newForceVector)
1344     : MergeUndoCommand<SetForceVectorMergeId>(m), m_atomIds(1, atomId),
1345       m_oldForceVectors(1, oldForceVector), m_newForceVectors(1, newForceVector)
1346   {}
1347 
redo()1348   void redo() override
1349   {
1350     for (size_t i = 0; i < m_atomIds.size(); ++i)
1351       forceVectors()[m_atomIds[i]] = m_newForceVectors[i];
1352   }
1353 
undo()1354   void undo() override
1355   {
1356     for (size_t i = 0; i < m_atomIds.size(); ++i)
1357       forceVectors()[m_atomIds[i]] = m_oldForceVectors[i];
1358   }
1359 
mergeWith(const QUndoCommand * o)1360   bool mergeWith(const QUndoCommand* o) override
1361   {
1362     const SetForceVectorCommand* other =
1363       dynamic_cast<const SetForceVectorCommand*>(o);
1364     if (!other)
1365       return false;
1366 
1367     size_t numAtoms = other->m_atomIds.size();
1368     if (numAtoms != other->m_oldForceVectors.size() ||
1369         numAtoms != other->m_newForceVectors.size()) {
1370       return false;
1371     }
1372 
1373     for (size_t i = 0; i < numAtoms; ++i) {
1374       const Index& atomId = other->m_atomIds[i];
1375       const Vector3& oldPos = other->m_oldForceVectors[i];
1376       const Vector3& newPos = other->m_newForceVectors[i];
1377 
1378       Array<Index>::const_iterator idsBegin = m_atomIds.begin();
1379       Array<Index>::const_iterator idsEnd = m_atomIds.end();
1380       Array<Index>::const_iterator match = std::find(idsBegin, idsEnd, atomId);
1381 
1382       if (match == idsEnd) {
1383         // Append a new atom:
1384         m_atomIds.push_back(atomId);
1385         m_oldForceVectors.push_back(oldPos);
1386         m_newForceVectors.push_back(newPos);
1387       } else {
1388         // Overwrite the existing movement:
1389         size_t offset = std::distance(idsBegin, match);
1390         assert(m_atomIds[offset] == atomId);
1391         m_newForceVectors[offset] = newPos;
1392       }
1393     }
1394 
1395     return true;
1396   }
1397 };
1398 } // namespace
1399 
setForceVector(Index atomId,const Vector3 & forces,const QString & undoText)1400 bool RWMolecule::setForceVector(Index atomId, const Vector3& forces,
1401                                 const QString& undoText)
1402 {
1403   if (atomId >= atomCount())
1404     return false;
1405 
1406   if (m_molecule.m_positions3d.size() != m_molecule.m_atomicNumbers.size())
1407     m_molecule.m_positions3d.resize(m_molecule.m_atomicNumbers.size(),
1408                                     Vector3::Zero());
1409 
1410   SetForceVectorCommand* comm = new SetForceVectorCommand(
1411     *this, atomId, m_molecule.m_positions3d[atomId], forces);
1412   comm->setText(undoText);
1413   comm->setCanMerge(m_interactive);
1414   m_undoStack.push(comm);
1415   return true;
1416 }
1417 
1418 } // namespace QtGui
1419 } // namespace Avogadro
1420