diff --git a/avogadro/core/atom.h b/avogadro/core/atom.h index 7867ef2a95..a176c301a1 100644 --- a/avogadro/core/atom.h +++ b/avogadro/core/atom.h @@ -185,6 +185,9 @@ class AtomTemplate */ void setForceVector(const Vector3& force); Vector3 forceVector() const; + + void setLabel(const std::string& label); + std::string label() const; /** @} */ private: @@ -365,6 +368,18 @@ Vector3 AtomTemplate::forceVector() const : Vector3::Zero(); } +template +void AtomTemplate::setLabel(const std::string& label) +{ + m_molecule->setLabel(m_index, label); +} + +template +std::string AtomTemplate::label() const +{ + return m_molecule->label(m_index); +} + } // namespace Core } // namespace Avogadro diff --git a/avogadro/core/molecule.cpp b/avogadro/core/molecule.cpp index 9f2d4aaf9d..e00cf79960 100644 --- a/avogadro/core/molecule.cpp +++ b/avogadro/core/molecule.cpp @@ -31,8 +31,8 @@ Molecule::Molecule() Molecule::Molecule(const Molecule& other) : m_data(other.m_data), m_customElementMap(other.m_customElementMap), m_positions2d(other.m_positions2d), m_positions3d(other.m_positions3d), - m_coordinates3d(other.m_coordinates3d), m_timesteps(other.m_timesteps), - m_hybridizations(other.m_hybridizations), + m_label(other.m_label), m_coordinates3d(other.m_coordinates3d), + m_timesteps(other.m_timesteps), m_hybridizations(other.m_hybridizations), m_formalCharges(other.m_formalCharges), m_colors(other.m_colors), m_vibrationFrequencies(other.m_vibrationFrequencies), m_vibrationIntensities(other.m_vibrationIntensities), @@ -63,6 +63,7 @@ Molecule::Molecule(Molecule&& other) noexcept m_customElementMap(std::move(other.m_customElementMap)), m_positions2d(std::move(other.m_positions2d)), m_positions3d(std::move(other.m_positions3d)), + m_label(std::move(other.m_label)), m_coordinates3d(std::move(other.m_coordinates3d)), m_timesteps(std::move(other.m_timesteps)), m_hybridizations(std::move(other.m_hybridizations)), @@ -94,6 +95,7 @@ Molecule& Molecule::operator=(const Molecule& other) m_customElementMap = other.m_customElementMap; m_positions2d = other.m_positions2d; m_positions3d = other.m_positions3d; + m_label = other.m_label; m_coordinates3d = other.m_coordinates3d; m_timesteps = other.m_timesteps; m_hybridizations = other.m_hybridizations; @@ -144,6 +146,7 @@ Molecule& Molecule::operator=(Molecule&& other) noexcept m_customElementMap = std::move(other.m_customElementMap); m_positions2d = std::move(other.m_positions2d); m_positions3d = std::move(other.m_positions3d); + m_label = std::move(other.m_label); m_coordinates3d = std::move(other.m_coordinates3d); m_timesteps = std::move(other.m_timesteps); m_hybridizations = std::move(other.m_hybridizations); @@ -397,6 +400,7 @@ void Molecule::clearAtoms() { m_positions2d.clear(); m_positions3d.clear(); + m_label.clear(); m_hybridizations.clear(); m_formalCharges.clear(); m_colors.clear(); diff --git a/avogadro/core/molecule.h b/avogadro/core/molecule.h index 49d751de18..9eb624ddb6 100644 --- a/avogadro/core/molecule.h +++ b/avogadro/core/molecule.h @@ -233,6 +233,10 @@ class AVOGADROCORE_EXPORT Molecule */ bool setAtomPosition3d(Index atomId, const Vector3& pos); + std::string label(Index atomId) const; + bool setLabel(const Core::Array& label); + bool setLabel(Index atomId, const std::string& label); + /** * Set whether the specified atom is selected or not. */ @@ -664,6 +668,7 @@ class AVOGADROCORE_EXPORT Molecule CustomElementMap m_customElementMap; Array m_positions2d; Array m_positions3d; + Array m_label; Array> m_coordinates3d; // Used for conformers/trajectories. Array m_timesteps; Array m_hybridizations; @@ -857,6 +862,31 @@ inline bool Molecule::setAtomPosition3d(Index atomId, const Vector3& pos) return false; } +inline std::string Molecule::label(Index atomId) const +{ + return atomId < m_label.size() ? m_label[atomId] : ""; +} + +inline bool Molecule::setLabel(const Core::Array& label) +{ + if (label.size() == atomCount() || label.size() == 0) { + m_label = label; + return true; + } + return false; +} + +inline bool Molecule::setLabel(Index atomId, const std::string& label) +{ + if (atomId < atomCount()) { + if (atomId >= m_label.size()) + m_label.resize(atomCount(), ""); + m_label[atomId] = label; + return true; + } + return false; +} + inline void Molecule::setAtomSelected(Index atomId, bool selected) { if (atomId < atomCount()) { diff --git a/avogadro/io/cjsonformat.cpp b/avogadro/io/cjsonformat.cpp index cff1f49e2a..d6dfc4348c 100644 --- a/avogadro/io/cjsonformat.cpp +++ b/avogadro/io/cjsonformat.cpp @@ -143,6 +143,13 @@ bool CjsonFormat::read(std::istream& file, Molecule& molecule) } } + // todo? 2d position + // labels + json labels = atoms["labels"]; + for (size_t i = 0; i < atomCount; ++i) { + molecule.atom(i).setLabel(labels[i]); + } + // Check for coordinate sets, and read them in if found, e.g. trajectories. json coordSets = atoms["coords"]["3dSets"]; if (coordSets.is_array() && coordSets.size()) { @@ -232,8 +239,9 @@ bool CjsonFormat::read(std::istream& file, Molecule& molecule) newResidue.addResidueAtom(item.key(), atom); } } - - // todo colors + json color = residue["color"]; + Vector3ub col = Vector3ub(color[0], color[1], color[2]); + newResidue.setColor(col); } } @@ -725,6 +733,13 @@ bool CjsonFormat::write(std::ostream& file, const Molecule& molecule) } } + // labels + json labels; + for (size_t i = 0; i < molecule.atomCount(); ++i) { + labels.push_back(molecule.label(i)); + } + root["atoms"]["labels"] = labels; + auto layer = LayerManager::getMoleculeInfo(&molecule)->layer; if (layer.atomCount()) { json atomLayer; diff --git a/avogadro/qtgui/rwmolecule.cpp b/avogadro/qtgui/rwmolecule.cpp index e59ad969a5..ed3eddb6e2 100644 --- a/avogadro/qtgui/rwmolecule.cpp +++ b/avogadro/qtgui/rwmolecule.cpp @@ -168,6 +168,15 @@ bool RWMolecule::setAtomPositions3d(const Core::Array& pos, return true; } +bool RWMolecule::setLabel(Index atomId, const std::string& label, + const QString& undoText) +{ + ModifyLabelCommand* comm = new ModifyLabelCommand(*this, atomId, label); + comm->setText(undoText); + m_undoStack.push(comm); + return true; +} + bool RWMolecule::setAtomPosition3d(Index atomId, const Vector3& pos, const QString& undoText) { diff --git a/avogadro/qtgui/rwmolecule.h b/avogadro/qtgui/rwmolecule.h index e11d1e030f..8cdaba3aa8 100644 --- a/avogadro/qtgui/rwmolecule.h +++ b/avogadro/qtgui/rwmolecule.h @@ -214,6 +214,9 @@ class AVOGADROQTGUI_EXPORT RWMolecule : public QObject */ Vector3 atomPosition3d(Index atomId) const; + std::string label(Index atomId) const; + bool setLabel(Index atomId, const std::string& label, + const QString& undoText = QStringLiteral("Change Atom Label")); /** * Replace the current array of 3D atomic coordinates. * @param pos The new coordinate array. Must be of length atomCount(). @@ -739,6 +742,11 @@ inline Vector3 RWMolecule::atomPosition3d(Index atomId) const return m_molecule.atomPosition3d(atomId); } +inline std::string RWMolecule::label(Index atomId) const +{ + return m_molecule.label(atomId); +} + inline Core::AtomHybridization RWMolecule::hybridization(Index atomId) const { return m_molecule.hybridization(atomId); diff --git a/avogadro/qtgui/rwmolecule_undo.h b/avogadro/qtgui/rwmolecule_undo.h index d87443b578..d4fd5f3578 100644 --- a/avogadro/qtgui/rwmolecule_undo.h +++ b/avogadro/qtgui/rwmolecule_undo.h @@ -624,6 +624,27 @@ class SetForceVectorCommand : public MergeUndoCommand } }; } // namespace + +namespace { +class ModifyLabelCommand : public RWMolecule::UndoCommand +{ + Index m_atomId; + std::string m_newLabel; + std::string m_oldLabel; + +public: + ModifyLabelCommand(RWMolecule& m, Index atomId, const std::string& label) + : UndoCommand(m), m_atomId(atomId), m_newLabel(label) + { + m_oldLabel = m_mol.molecule().label(m_atomId); + } + + void redo() override { m_mol.molecule().setLabel(m_atomId, m_newLabel); } + + void undo() override { m_mol.molecule().setLabel(m_atomId, m_oldLabel); } +}; +} // namespace + } // namespace QtGui } // namespace Avogadro #endif diff --git a/avogadro/qtplugins/CMakeLists.txt b/avogadro/qtplugins/CMakeLists.txt index facebec9d4..a5147d4bc5 100644 --- a/avogadro/qtplugins/CMakeLists.txt +++ b/avogadro/qtplugins/CMakeLists.txt @@ -106,6 +106,7 @@ add_subdirectory(fetchpdb) add_subdirectory(hydrogens) add_subdirectory(importpqr) add_subdirectory(insertfragment) +add_subdirectory(label) add_subdirectory(lammpsinput) add_subdirectory(lineformatinput) add_subdirectory(manipulator) diff --git a/avogadro/qtplugins/label/CMakeLists.txt b/avogadro/qtplugins/label/CMakeLists.txt new file mode 100644 index 0000000000..ff1b75f8ef --- /dev/null +++ b/avogadro/qtplugins/label/CMakeLists.txt @@ -0,0 +1,11 @@ + +avogadro_plugin(Label + "Labels rendering scheme" + ScenePlugin + label.h + Label + label.cpp + "") + + +target_link_libraries(Label LINK_PRIVATE AvogadroRendering) diff --git a/avogadro/qtplugins/label/label.cpp b/avogadro/qtplugins/label/label.cpp new file mode 100644 index 0000000000..5db7070957 --- /dev/null +++ b/avogadro/qtplugins/label/label.cpp @@ -0,0 +1,263 @@ +/****************************************************************************** + This source file is part of the Avogadro project. + This source code is released under the 3-Clause BSD License, (see "LICENSE"). +******************************************************************************/ + +#include "label.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace Avogadro { +namespace QtPlugins { + +using Avogadro::Rendering::TextLabel3D; +using Core::Array; +using Core::Atom; +using Core::Elements; +using Core::Molecule; +using QtGui::PluginLayerManager; +using Rendering::GeometryNode; +using Rendering::GroupNode; +using std::map; + +typedef Array NeighborListType; + +namespace { +TextLabel3D* createLabel(const std::string& text, const Vector3f& pos, + float radius) +{ + Rendering::TextProperties tprop; + tprop.setAlign(Rendering::TextProperties::HCenter, + Rendering::TextProperties::VCenter); + tprop.setFontFamily(Rendering::TextProperties::SansSerif); + + tprop.setColorRgb(255, 255, 255); + TextLabel3D* label = new TextLabel3D; + label->setText(text); + label->setRenderPass(Rendering::OpaquePass); + label->setTextProperties(tprop); + label->setRadius(radius); + label->setAnchor(pos); + return label; +} +} // namespace + +struct LayerLabel : Core::LayerData +{ + QWidget* widget; + bool atomLabel; + bool residueLabel; + float radiusScalar; + + LayerLabel() + { + widget = nullptr; + QSettings settings; + atomLabel = settings.value("label/atomLabel", true).toBool(); + residueLabel = settings.value("label/residueLabel", false).toBool(); + radiusScalar = settings.value("label/radiusScalar", 0.5).toDouble(); + } + + ~LayerLabel() + { + if (widget) + widget->deleteLater(); + } + + std::string serialize() override final + { + return boolToString(atomLabel) + " " + boolToString(residueLabel) + " " + + std::to_string(radiusScalar); + } + void deserialize(std::string text) override final + { + std::stringstream ss(text); + std::string aux; + ss >> aux; + atomLabel = stringToBool(aux); + ss >> aux; + residueLabel = stringToBool(aux); + ss >> aux; + radiusScalar = std::stof(aux); + } + + void setupWidget(Label* slot) + { + if (!widget) { + widget = new QWidget(qobject_cast(slot->parent())); + QVBoxLayout* v = new QVBoxLayout; + + // radius scalar + QDoubleSpinBox* spin = new QDoubleSpinBox; + spin->setRange(0.0, 1.5); + spin->setSingleStep(0.1); + spin->setDecimals(1); + spin->setValue(radiusScalar); + QObject::connect(spin, SIGNAL(valueChanged(double)), slot, + SLOT(setRadiusScalar(double))); + QFormLayout* form = new QFormLayout; + form->addRow(QObject::tr("Distance from center:"), spin); + v->addLayout(form); + + // residue or atoms? + QCheckBox* check = new QCheckBox(QObject::tr("Atom Labels")); + check->setChecked(atomLabel); + QObject::connect(check, &QCheckBox::clicked, slot, &Label::atomLabel); + v->addWidget(check); + + check = new QCheckBox(QObject::tr("Residue Labels")); + check->setChecked(residueLabel); + QObject::connect(check, &QCheckBox::clicked, slot, &Label::residueLabel); + v->addWidget(check); + + v->addStretch(1); + widget->setLayout(v); + } + } +}; + +Label::Label(QObject* parent_) : QtGui::ScenePlugin(parent_) +{ + m_layerManager = PluginLayerManager(m_name); + m_layerManager.load(); +} + +Label::~Label() {} + +void Label::process(const Core::Molecule& molecule, Rendering::GroupNode& node) +{ + for (size_t layer = 0; layer < m_layerManager.layerCount(); ++layer) { + LayerLabel& interface = m_layerManager.getSetting(layer); + if (interface.residueLabel) { + processResidue(molecule, node, layer); + } + if (interface.atomLabel) { + processAtom(molecule, node, layer); + } + } +} + +void Label::processResidue(const Core::Molecule& molecule, + Rendering::GroupNode& node, size_t layer) +{ + GeometryNode* geometry = new GeometryNode; + node.addChild(geometry); + + for (const auto& residue : molecule.residues()) { + Atom caAtom = residue.getAtomByName("CA"); + if (!caAtom.isValid() || + !m_layerManager.atomEnabled(layer, caAtom.index())) { + continue; + } + auto text = residue.residueName(); + const auto atoms = residue.residueAtoms(); + Vector3f pos = Vector3f::Zero(); + for (const auto& atom : atoms) { + pos += atom.position3d().cast(); + } + pos /= static_cast(atoms.size()); + + float radius = 0.0f; + for (const auto& atom : atoms) { + unsigned char atomicNumber = atom.atomicNumber(); + float auxR = static_cast(Elements::radiusVDW(atomicNumber)); + auxR += (atom.position3d().cast() - pos).norm(); + if (auxR > radius) { + auxR = radius; + } + } + + TextLabel3D* residueLabel = createLabel(text, pos, radius); + geometry->addDrawable(residueLabel); + } +} + +void Label::processAtom(const Core::Molecule& molecule, + Rendering::GroupNode& node, size_t layer) +{ + GeometryNode* geometry = new GeometryNode; + node.addChild(geometry); + + std::map atomCount; + for (Index i = 0; i < molecule.atomCount(); ++i) { + Core::Atom atom = molecule.atom(i); + + unsigned char atomicNumber = atom.atomicNumber(); + if (atomCount.find(atomicNumber) == atomCount.end()) { + atomCount[atomicNumber] = 1; + } else { + ++atomCount[atomicNumber]; + } + + if (!m_layerManager.atomEnabled(layer, i)) { + continue; + } + + auto text = atom.label(); + if (text == "") { + text = Elements::symbol(atomicNumber) + + std::to_string(atomCount[atomicNumber]); + } + const Vector3f pos(atom.position3d().cast()); + LayerLabel& interface = m_layerManager.getSetting(layer); + float radius = static_cast(Elements::radiusVDW(atomicNumber)) * + interface.radiusScalar; + + TextLabel3D* atomLabel = createLabel(text, pos, radius); + geometry->addDrawable(atomLabel); + } +} + +void Label::atomLabel(bool show) +{ + LayerLabel& interface = m_layerManager.getSetting(); + if (show != interface.atomLabel) { + interface.atomLabel = show; + emit drawablesChanged(); + } + QSettings settings; + settings.setValue("label/atomLabel", show); +} + +void Label::residueLabel(bool show) +{ + LayerLabel& interface = m_layerManager.getSetting(); + if (show != interface.residueLabel) { + interface.residueLabel = show; + emit drawablesChanged(); + } + QSettings settings; + settings.setValue("label/residueLabel", show); +} + +void Label::setRadiusScalar(double radius) +{ + LayerLabel& interface = m_layerManager.getSetting(); + interface.radiusScalar = float(radius); + emit drawablesChanged(); + + QSettings settings; + settings.setValue("label/radiusScalar", interface.radiusScalar); +} + +QWidget* Label::setupWidget() +{ + LayerLabel& interface = m_layerManager.getSetting(); + interface.setupWidget(this); + return interface.widget; +} + +} // namespace QtPlugins +} // namespace Avogadro diff --git a/avogadro/qtplugins/label/label.h b/avogadro/qtplugins/label/label.h new file mode 100644 index 0000000000..952c16b7b7 --- /dev/null +++ b/avogadro/qtplugins/label/label.h @@ -0,0 +1,53 @@ +/****************************************************************************** + This source file is part of the Avogadro project. + This source code is released under the 3-Clause BSD License, (see "LICENSE"). +******************************************************************************/ + +#ifndef AVOGADRO_QTPLUGINS_LABEL_H +#define AVOGADRO_QTPLUGINS_LABEL_H + +#include + +namespace Avogadro { +namespace QtPlugins { + +/** + * @brief Render labels to each atom. + */ +class Label : public QtGui::ScenePlugin +{ + Q_OBJECT + +public: + explicit Label(QObject* parent = nullptr); + ~Label() override; + + QString name() const override { return tr(m_name.c_str()); } + + QString description() const override + { + return tr("Display labels on ball and stick style."); + } + + QWidget* setupWidget() override; + void process(const Core::Molecule& molecule, Rendering::GroupNode& node); + +public slots: + void atomLabel(bool show); + void residueLabel(bool show); + void setRadiusScalar(double radius); + +private: + void processAtom(const Core::Molecule& molecule, Rendering::GroupNode& node, + size_t layer); + void processResidue(const Core::Molecule& molecule, + Rendering::GroupNode& node, size_t layer); + + Rendering::GroupNode* m_group; + std::string m_name = "Labels"; +}; + +} // end namespace QtPlugins +} // end namespace Avogadro + +#endif // AVOGADRO_QTPLUGINS_LABEL_H