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