Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 0 additions & 12 deletions src/schrodinger/rdkit_extensions/helm/monomer_coordgen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,18 +50,6 @@ namespace rdkit_extensions

using schrodinger::rdkit_extensions::Direction;

// empty space gap between a monomer and the one following it in the chain. This
// will be the length of the visibile bond line connecting the two.
constexpr double SIDE_TO_SIDE_DISTANCE = 0.70;
// when a monomer size is not specified or its value is lower than this, this
// value is used as the minimum size
constexpr double MONOMER_MINIMUM_SIZE = 0.80;

// total distance from the center of one monomer to the center of the following
// in the chain
constexpr double MONOMER_BOND_LENGTH =
SIDE_TO_SIDE_DISTANCE + MONOMER_MINIMUM_SIZE;

// maximum allowed bond length as a multiple of the ideal bond length. Any bond
// longer than this will be considered "stretched"
constexpr double MAX_BOND_STRETCH = 3.0;
Expand Down
9 changes: 9 additions & 0 deletions src/schrodinger/rdkit_extensions/helm/monomer_coordgen.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ namespace rdkit_extensions
// measured in scene units
const std::string MONOMER_ITEM_SIZE{"monomerItemSize"};

// Empty-space gap between a monomer and the next in the chain (the length of
// the visible bond line connecting them).
constexpr double SIDE_TO_SIDE_DISTANCE = 0.70;
// Minimum monomer size, used when a size is unspecified or smaller than this.
constexpr double MONOMER_MINIMUM_SIZE = 0.80;
// Center-to-center distance between adjacent monomers in a chain.
constexpr double MONOMER_BOND_LENGTH =
SIDE_TO_SIDE_DISTANCE + MONOMER_MINIMUM_SIZE;

/**
* Information about a turn in a snaking or coiling chain layout.
*/
Expand Down
66 changes: 66 additions & 0 deletions src/schrodinger/sketcher/menu/monomer_context_menu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,50 @@ namespace schrodinger
namespace sketcher
{

// True when every selected monomer is part of a nucleic acid chain (no
// peptides, no CHEM) AND at least one is an NA base — the gate for the
// "Add Complementary Sequence" action visibility.
static bool na_selection_with_at_least_one_base(
const std::unordered_set<const RDKit::Atom*>& atoms)
{
// Any non-NA monomer type (peptide, CHEM, or an unknown future variant)
// disqualifies the whole selection.
auto is_na_monomer = [](const RDKit::Atom* a) {
switch (get_monomer_type(a)) {
case MonomerType::NA_BASE:
case MonomerType::NA_SUGAR:
case MonomerType::NA_PHOSPHATE:
return true;
default:
return false;
}
};
auto is_na_base = [](const RDKit::Atom* a) {
return get_monomer_type(a) == MonomerType::NA_BASE;
};
// any_of is false for an empty selection, so no explicit empty guard.
return std::ranges::all_of(atoms, is_na_monomer) &&
std::ranges::any_of(atoms, is_na_base);
}

// Filter the menu's selection down to NA bases whose symbol has a
// Watson-Crick complement. Used to drive both the "enabled" state of
// the action and the payload of the emitted signal.
static std::unordered_set<const RDKit::Atom*>
complementable_bases(const std::unordered_set<const RDKit::Atom*>& atoms)
{
std::unordered_set<const RDKit::Atom*> out;
for (const auto* a : atoms) {
if (get_monomer_type(a) != MonomerType::NA_BASE) {
continue;
}
if (na_base_has_complement(get_monomer_res_name(a))) {
out.insert(a);
}
}
return out;
}

MonomerContextMenu::MonomerContextMenu(QWidget* parent) :
AbstractContextMenu(parent)
{
Expand All @@ -25,6 +69,7 @@ MonomerContextMenu::MonomerContextMenu(QWidget* parent) :
createProtonateAction();
createMutateBaseSubMenu();
createSugarToggleAction();
createAddComplementaryStrandAction();
createDeleteAction();
}

Expand Down Expand Up @@ -174,6 +219,19 @@ void MonomerContextMenu::createSugarToggleAction()
});
}

void MonomerContextMenu::createAddComplementaryStrandAction()
{
m_add_complement_action =
addAction("Add Complementary Sequence", this, [this]() {
// Reuse the set computed in updateActions() rather than walking
// the selection a third time.
if (m_complement_bases.empty()) {
return;
}
emit addComplementaryStrandRequested(m_complement_bases);
});
}

void MonomerContextMenu::createDeleteAction()
{
addAction("Delete", this, [this]() { emit deleteRequested(m_atoms); });
Expand All @@ -194,6 +252,14 @@ void MonomerContextMenu::updateActions()
m_mutate_base_menu->menuAction()->setVisible(all_na_base);
m_sugar_toggle_action->setVisible(all_na_sugar);

m_complement_bases = complementable_bases(m_atoms);
const bool na_with_base = na_selection_with_at_least_one_base(m_atoms);
m_add_complement_action->setVisible(na_with_base);
// Enabled iff at least one selected base has a Watson-Crick complement
// symbol; DB-level validation happens model-side.
m_add_complement_action->setEnabled(na_with_base &&
!m_complement_bases.empty());

if (all_peptide) {
bool any_d_form_toggleable = false;
for (const auto* a : m_atoms) {
Expand Down
12 changes: 12 additions & 0 deletions src/schrodinger/sketcher/menu/monomer_context_menu.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ class SKETCHER_API MonomerContextMenu : public AbstractContextMenu
void mutateMonomerRequested(std::vector<MonomerMutation> mutations,
QString description);

// `bases` holds raw atom pointers into the live molecule, so the receiver
// must consume them before it starts mutating the molecule.
void addComplementaryStrandRequested(
const std::unordered_set<const RDKit::Atom*>& bases);

protected:
void updateActions() override;

Expand All @@ -56,13 +61,20 @@ class SKETCHER_API MonomerContextMenu : public AbstractContextMenu
void createProtonateAction();
void createMutateBaseSubMenu();
void createSugarToggleAction();
void createAddComplementaryStrandAction();
void createDeleteAction();

QMenu* m_mutate_residue_menu = nullptr;
QAction* m_set_d_form_action = nullptr;
QAction* m_protonate_action = nullptr;
QMenu* m_mutate_base_menu = nullptr;
QAction* m_sugar_toggle_action = nullptr;
QAction* m_add_complement_action = nullptr;

// Bases from the current selection that have a Watson-Crick complement.
// Computed once per updateActions() and reused both to set the action's
// enabled state and as the payload when the action is triggered.
std::unordered_set<const RDKit::Atom*> m_complement_bases;
};

} // namespace sketcher
Expand Down
Loading