Files
rdkit/Code/GraphMol/WedgeBonds.cpp
Chris Von Bargen 020b755ad4 Fix WedgeMolBonds stealing wiggly bonds from adjacent chiral atoms (#9267)
The standard re-wedging pattern is:

    clearSingleBondDirFlags(mol);  // saves _UnknownStereo=1, clears BondDir to NONE
    WedgeMolBonds(mol, &conf);     // re-derives wedges from chiral tags
    // ... caller restores BondDir::UNKNOWN on bonds with _UnknownStereo=1 ...

Wiggly bonds at chiral centers should survive this round-trip but did
not: countChiralNbrs only checked BondDir to decide whether a chiral
atom's stereo was already expressed, missing the _UnknownStereo=1
marker that clearSingleBondDirFlags saved when it cleared BondDir to
NONE. With the marker invisible to countChiralNbrs, pickBondToWedge
would pick the wiggly bond itself (terminal neighbors are scored
lower), and the caller's subsequent restore of BondDir::UNKNOWN would
erase the wedge, leaving the chiral atom with no visible stereo.

Fix: extend countChiralNbrs to recognize bonds with _UnknownStereo=1
as equivalent to BondDir::UNKNOWN. The chiral atom is then pre-skipped
in pickBondsToWedge, the same way it already is for bonds that still
have BondDir::UNKNOWN set.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 19:26:58 +02:00

694 lines
23 KiB
C++

//
// Copyright (C) 2023 Greg Landrum and other RDKit contributors
//
// @@ All Rights Reserved @@
// This file is part of the RDKit.
// The contents are covered by the terms of the BSD license
// which is included in the file license.txt, found at the root
// of the RDKit source tree.
//
#include <GraphMol/RDKitBase.h>
#include <GraphMol/Atropisomers.h>
#include <RDGeneral/types.h>
#include <sstream>
#include <set>
#include <algorithm>
#include <RDGeneral/utils.h>
#include <RDGeneral/Invariant.h>
#include <RDGeneral/RDLog.h>
#include <boost/dynamic_bitset.hpp>
#include <Geometry/point.h>
#include "Chirality.h"
#include <cstdlib>
namespace RDKit {
namespace Chirality {
const BondWedgingParameters defaultWedgingParams;
namespace {
std::tuple<unsigned int, unsigned int, unsigned int> getDoubleBondPresence(
const ROMol &mol, const Atom &atom) {
unsigned int hasDouble = 0;
unsigned int hasKnownDouble = 0;
unsigned int hasAnyDouble = 0;
for (const auto bond : mol.atomBonds(&atom)) {
if (bond->getBondType() == Bond::BondType::DOUBLE) {
++hasDouble;
if (bond->getStereo() == Bond::BondStereo::STEREOANY) {
++hasAnyDouble;
} else if (bond->getStereo() > Bond::BondStereo::STEREOANY) {
++hasKnownDouble;
}
}
}
return std::make_tuple(hasDouble, hasKnownDouble, hasAnyDouble);
}
} // namespace
namespace detail {
std::pair<bool, INT_VECT> countChiralNbrs(const ROMol &mol, int noNbrs) {
INT_VECT nChiralNbrs(mol.getNumAtoms(), noNbrs);
// start by looking for bonds that are already wedged. A wiggly bond with
// BondDir::NONE plus _UnknownStereo=1 (the state left by
// clearSingleBondDirFlags) is treated the same as one that still has
// BondDir::UNKNOWN: an intentional stereo annotation we should not overwrite.
for (const auto bond : mol.bonds()) {
bool isDirected = bond->getBondDir() == Bond::BEGINWEDGE ||
bond->getBondDir() == Bond::BEGINDASH ||
bond->getBondDir() == Bond::UNKNOWN;
if (!isDirected) {
int unknownStereo = 0;
if (bond->getPropIfPresent(common_properties::_UnknownStereo,
unknownStereo) &&
unknownStereo) {
isDirected = true;
}
}
if (isDirected) {
if (bond->getBeginAtom()->getChiralTag() == Atom::CHI_TETRAHEDRAL_CW ||
bond->getBeginAtom()->getChiralTag() == Atom::CHI_TETRAHEDRAL_CCW) {
nChiralNbrs[bond->getBeginAtomIdx()] = noNbrs + 1;
} else if (bond->getEndAtom()->getChiralTag() ==
Atom::CHI_TETRAHEDRAL_CW ||
bond->getEndAtom()->getChiralTag() ==
Atom::CHI_TETRAHEDRAL_CCW) {
nChiralNbrs[bond->getEndAtomIdx()] = noNbrs + 1;
}
}
}
// now rank atoms by the number of chiral neighbors or Hs they have:
bool chiNbrs = false;
for (const auto at : mol.atoms()) {
if (nChiralNbrs[at->getIdx()] > noNbrs) {
// std::cerr << " SKIPPING1: " << at->getIdx() << std::endl;
continue;
}
auto type = at->getChiralTag();
if (type != Atom::CHI_TETRAHEDRAL_CW && type != Atom::CHI_TETRAHEDRAL_CCW) {
continue;
}
nChiralNbrs[at->getIdx()] = 0;
chiNbrs = true;
for (const auto nat : mol.atomNeighbors(at)) {
if (nat->getAtomicNum() == 1) {
// special case: it's an H... we weight these especially high:
nChiralNbrs[at->getIdx()] -= 10;
continue;
}
type = nat->getChiralTag();
if (type != Atom::CHI_TETRAHEDRAL_CW &&
type != Atom::CHI_TETRAHEDRAL_CCW) {
continue;
}
nChiralNbrs[at->getIdx()] -= 1;
}
}
return std::make_pair(chiNbrs, nChiralNbrs);
}
//
// Determine bond wedge state
///
Bond::BondDir determineBondWedgeState(const Bond *bond,
unsigned int fromAtomIdx,
const Conformer *conf) {
PRECONDITION(bond, "no bond");
PRECONDITION(bond->getBondType() == Bond::SINGLE,
"bad bond order for wedging");
const auto mol = &(bond->getOwningMol());
PRECONDITION(mol, "no mol");
auto res = bond->getBondDir();
if (!conf) {
return res;
}
Atom *atom;
Atom *bondAtom;
if (bond->getBeginAtom()->getIdx() == fromAtomIdx) {
atom = bond->getBeginAtom();
bondAtom = bond->getEndAtom();
} else {
atom = bond->getEndAtom();
bondAtom = bond->getBeginAtom();
}
auto chiralType = atom->getChiralTag();
TEST_ASSERT(chiralType == Atom::CHI_TETRAHEDRAL_CW ||
chiralType == Atom::CHI_TETRAHEDRAL_CCW);
// if we got this far, we really need to think about it:
std::list<int> neighborBondIndices;
std::list<double> neighborBondAngles;
auto centerLoc = conf->getAtomPos(atom->getIdx());
auto tmpPt = conf->getAtomPos(bondAtom->getIdx());
centerLoc.z = 0.0;
tmpPt.z = 0.0;
RDGeom::Point3D refVect;
try {
refVect = centerLoc.directionVector(tmpPt);
} catch (const std::runtime_error &) {
// we have a problem with the reference bond;
// it's probably that the center and the tmp atom overlap
return res;
}
neighborBondIndices.push_back(bond->getIdx());
neighborBondAngles.push_back(0.0);
for (const auto nbrBond : mol->atomBonds(atom)) {
const auto otherAtom = nbrBond->getOtherAtom(atom);
if (nbrBond != bond) {
tmpPt = conf->getAtomPos(otherAtom->getIdx());
tmpPt.z = 0.0;
RDGeom::Point3D tmpVect;
try {
tmpVect = centerLoc.directionVector(tmpPt);
} catch (const std::runtime_error &) {
// we have a problem with the tmp bond;
// it's probably that the atoms overlap
return res;
}
auto angle = refVect.signedAngleTo(tmpVect);
if (angle < 0.0) {
angle += 2. * M_PI;
}
auto nbrIt = neighborBondIndices.begin();
auto angleIt = neighborBondAngles.begin();
// find the location of this neighbor in our angle-sorted list
// of neighbors:
while (angleIt != neighborBondAngles.end() && angle > (*angleIt)) {
++angleIt;
++nbrIt;
}
neighborBondAngles.insert(angleIt, angle);
neighborBondIndices.insert(nbrIt, nbrBond->getIdx());
}
}
// at this point, neighborBondIndices contains a list of bond
// indices from the central atom. They are arranged starting
// at the reference bond in CCW order (based on the current
// depiction).
// if we already have one bond with direction set, then we can use it to
// decide what the direction of this one is
// we're starting from scratch... do the work!
int nSwaps = atom->getPerturbationOrder(neighborBondIndices);
// in the case of three-coordinated atoms we may have to worry about
// the location of the implicit hydrogen - Issue 209
// Check if we have one of these situation
//
// 0 1 0 2
// * \*/
// 1 - C - 2 C
//
// here the hydrogen will be between 1 and 2 and we need to add an
// additional swap
if (neighborBondAngles.size() == 3) {
// three coordinated
auto angleIt = neighborBondAngles.begin();
++angleIt; // the first is the 0 (or reference bond - we will ignore
// that
double angle1 = (*angleIt);
++angleIt;
double angle2 = (*angleIt);
constexpr double angleTol =
M_PI * 1.9 / 180.; // just under 2 degrees tolerance, which is what we
// use when perceiving T-shaped geometries
if (angle2 - angle1 >= (M_PI - angleTol)) {
// we have the above situation
nSwaps++;
}
}
#ifdef VERBOSE_STEREOCHEM
BOOST_LOG(rdDebugLog) << "--------- " << nSwaps << std::endl;
std::copy(neighborBondIndices.begin(), neighborBondIndices.end(),
std::ostream_iterator<int>(BOOST_LOG(rdDebugLog), " "));
BOOST_LOG(rdDebugLog) << std::endl;
std::copy(neighborBondAngles.begin(), neighborBondAngles.end(),
std::ostream_iterator<double>(BOOST_LOG(rdDebugLog), " "));
BOOST_LOG(rdDebugLog) << std::endl;
#endif
if (chiralType == Atom::CHI_TETRAHEDRAL_CCW) {
if (nSwaps % 2 == 1) {
res = Bond::BEGINDASH;
} else {
res = Bond::BEGINWEDGE;
}
} else {
if (nSwaps % 2 == 1) {
res = Bond::BEGINWEDGE;
} else {
res = Bond::BEGINDASH;
}
}
return res;
}
Bond::BondDir determineBondWedgeState(
const Bond *bond,
const std::map<int, std::unique_ptr<RDKit::Chirality::WedgeInfoBase>>
&wedgeBonds,
const Conformer *conf) {
PRECONDITION(bond, "no bond");
int bid = bond->getIdx();
auto wbi = wedgeBonds.find(bid);
if (wbi == wedgeBonds.end()) {
return bond->getBondDir();
}
if (wbi->second->getType() ==
Chirality::WedgeInfoType::WedgeInfoTypeAtropisomer) {
return wbi->second->getDir();
} else {
return determineBondWedgeState(bond, wbi->second->getIdx(), conf);
}
}
// Logic for two wedges at one atom (based on IUPAC stuff)
// - at least four neighbors
// - neighboring bonds get wedged
// - same rules for picking which one for first
// - not ring bonds (?)
// picks a bond for atom that we will wedge when we write the mol file
// returns idx of that bond.
int pickBondToWedge(
const Atom *atom, const ROMol &mol, const INT_VECT &nChiralNbrs,
const std::map<int, std::unique_ptr<Chirality::WedgeInfoBase>> &wedgeBonds,
int noNbrs) {
// here is what we are going to do
// - at each chiral center look for a bond that is begins at the atom and
// is not yet picked to be wedged for a different chiral center, preferring
// bonds to Hs
// - if we do not find a bond that begins at the chiral center - we will take
// the first bond that is not yet picked by any other chiral centers
// we use the orders calculated above to determine which order to do the
// wedging
// we need ring information; make sure findSSSR has been called before
// if not call now
if (!mol.getRingInfo()->isSssrOrBetter()) {
MolOps::findSSSR(mol);
}
std::vector<std::pair<int, int>> nbrScores;
for (const auto bond : mol.atomBonds(atom)) {
// can only wedge single bonds:
if (bond->getBondType() != Bond::SINGLE) {
continue;
}
int bid = bond->getIdx();
if (wedgeBonds.find(bid) == wedgeBonds.end()) {
// very strong preference for Hs:
auto *oatom = bond->getOtherAtom(atom);
if (oatom->getAtomicNum() == 1) {
nbrScores.emplace_back(-1000000,
bid); // lower than anything else can be
continue;
}
// prefer lower atomic numbers with lower degrees and no specified
// chirality:
int nbrScore = oatom->getAtomicNum() + 100 * oatom->getDegree() +
1000 * ((oatom->getChiralTag() != Atom::CHI_UNSPECIFIED));
// prefer neighbors that are nonchiral or have as few chiral neighbors
// as possible:
int oIdx = oatom->getIdx();
if (nChiralNbrs[oIdx] < noNbrs) {
// the counts are negative, so we have to subtract them off
nbrScore -= 100000 * nChiralNbrs[oIdx];
}
// prefer bonds to non-ring atoms:
nbrScore += 10000 * mol.getRingInfo()->numAtomRings(oIdx);
// prefer non-ring bonds;
nbrScore += 20000 * mol.getRingInfo()->numBondRings(bid);
// prefer bonds to atoms which don't have a double bond from them
auto [hasDoubleBond, hasKnownDoubleBond, hasAnyDoubleBond] =
getDoubleBondPresence(mol, *oatom);
nbrScore += 11000 * hasDoubleBond;
nbrScore += 12000 * hasKnownDoubleBond;
nbrScore += 23000 * hasAnyDoubleBond;
// if at all possible, do not go to marked attachment points
// since they may well be removed when we write a mol block
if (oatom->hasProp(common_properties::_fromAttachPoint)) {
nbrScore += 500000;
}
// std::cerr << " nrbScore: " << idx << " - " << oIdx << " : "
// << nbrScore << " nChiralNbrs: " << nChiralNbrs[oIdx]
// << std::endl;
nbrScores.emplace_back(nbrScore, bid);
}
}
// There's still one situation where this whole thing can fail: an unlucky
// situation where all neighbors of all neighbors of an atom are chiral
// and that atom ends up being the last one picked for stereochem
// assignment. This also happens in cases where the chiral atom doesn't
// have all of its neighbors (like when working with partially sanitized
// fragments)
//
// We'll bail here by returning -1
if (nbrScores.empty()) {
return -1;
}
auto minPr = std::min_element(nbrScores.begin(), nbrScores.end());
return minPr->second;
}
} // namespace detail
// returns map of bondIdx -> bond begin atom for those bonds that
// need wedging.
std::map<int, std::unique_ptr<Chirality::WedgeInfoBase>> pickBondsToWedge(
const ROMol &mol, const BondWedgingParameters *params) {
const Conformer *conf = nullptr;
if (mol.getNumConformers()) {
conf = &mol.getConformer();
}
return pickBondsToWedge(mol, params, conf);
}
std::map<int, std::unique_ptr<Chirality::WedgeInfoBase>> pickBondsToWedge(
const ROMol &mol, const BondWedgingParameters *params,
const Conformer *conf) {
if (!params) {
params = &defaultWedgingParams;
}
std::vector<unsigned int> indices(mol.getNumAtoms());
std::iota(indices.begin(), indices.end(), 0);
static int noNbrs = 100;
auto [chiNbrs, nChiralNbrs] = detail::countChiralNbrs(mol, noNbrs);
if (chiNbrs) {
std::sort(indices.begin(), indices.end(),
[&nChiralNbrs = nChiralNbrs](auto i1, auto i2) {
return nChiralNbrs[i1] < nChiralNbrs[i2];
});
}
std::map<int, std::unique_ptr<Chirality::WedgeInfoBase>> wedgeInfo;
for (auto idx : indices) {
if (nChiralNbrs[idx] > noNbrs) {
// std::cerr << " SKIPPING2: " << idx << std::endl;
continue; // already have a wedged bond here
}
auto atom = mol.getAtomWithIdx(idx);
auto type = atom->getChiralTag();
// the indices are ordered such that all chiral atoms come first. If
// this has no chiral flag, we can stop the whole loop:
if (type != Atom::CHI_TETRAHEDRAL_CW && type != Atom::CHI_TETRAHEDRAL_CCW) {
break;
}
auto bnd1 =
detail::pickBondToWedge(atom, mol, nChiralNbrs, wedgeInfo, noNbrs);
if (bnd1 >= 0) {
auto wi = std::unique_ptr<RDKit::Chirality::WedgeInfoChiral>(
new RDKit::Chirality::WedgeInfoChiral(idx));
wedgeInfo[bnd1] = std::move(wi);
}
}
RDKit::Atropisomers::wedgeBondsFromAtropisomers(mol, conf, wedgeInfo);
return wedgeInfo;
}
namespace {
// conditions here:
// 1. only degree four atoms (IUPAC)
// 2. no ring bonds (IUPAC)
// 3. not to chiral atoms (general IUPAC wedging rule)
void addSecondWedgeAroundAtom(ROMol &mol, Bond *refBond,
const Conformer *conf) {
PRECONDITION(refBond, "no reference bond provided");
PRECONDITION(conf, "no conformer provided");
auto atom = refBond->getBeginAtom();
// we only do degree four atoms (per IUPAC recommendation)
if (atom->getDegree() < 4) {
return;
}
auto aloc = conf->getAtomPos(atom->getIdx());
aloc.z = 0.0;
auto refVect = conf->getAtomPos(refBond->getEndAtomIdx());
refVect.z = 0.0;
refVect = aloc.directionVector(refVect);
double minAngle = 10000.0;
unsigned int bestDegree = 100;
Bond *bondToWedge = nullptr;
for (auto bond : mol.atomBonds(atom)) {
if (bond == refBond || bond->getBondType() != Bond::BondType::SINGLE ||
bond->getBondDir() != Bond::BondDir::NONE ||
bond->getOtherAtom(atom)->getChiralTag() !=
Atom::ChiralType::CHI_UNSPECIFIED ||
mol.getRingInfo()->numBondRings(bond->getIdx())) {
continue;
}
// FIX: There's more checking required here
auto bVect = conf->getAtomPos(bond->getOtherAtomIdx(atom->getIdx()));
bVect.z = 0.0;
bVect = aloc.directionVector(bVect);
auto angle = refVect.angleTo(bVect);
if ((angle - minAngle) < 5 * M_PI / 180 &&
bond->getOtherAtom(atom)->getDegree() <= bestDegree) {
bondToWedge = bond;
minAngle = angle;
bestDegree = bond->getOtherAtom(atom)->getDegree();
}
}
// if we got a bond and the angle is < 120 degrees (quasi-arbitrary)
if (bondToWedge && minAngle < 2 * M_PI / 3) {
bondToWedge->setBondDir(refBond->getBondDir() == Bond::BondDir::BEGINDASH
? Bond::BondDir::BEGINWEDGE
: Bond::BondDir::BEGINDASH);
if (bondToWedge->getBeginAtomIdx() != atom->getIdx()) {
bondToWedge->setEndAtomIdx(bondToWedge->getBeginAtomIdx());
bondToWedge->setBeginAtomIdx(atom->getIdx());
}
}
}
} // namespace
void wedgeMolBonds(ROMol &mol, const Conformer *conf,
const BondWedgingParameters *params) {
PRECONDITION(conf || mol.getNumConformers(), "no conformer available");
if (!conf) {
conf = &mol.getConformer();
}
if (!params) {
params = &defaultWedgingParams;
}
// we need ring info
if (!mol.getRingInfo() || !mol.getRingInfo()->isSssrOrBetter()) {
MolOps::findSSSR(mol);
}
auto wedgeBonds = Chirality::pickBondsToWedge(mol, params, conf);
// loop over the bonds we need to wedge:
for (const auto &[wbi, wedgeInfo] : wedgeBonds) {
if (wedgeInfo->getType() ==
Chirality::WedgeInfoType::WedgeInfoTypeAtropisomer) {
mol.getBondWithIdx(wbi)->setBondDir(wedgeInfo->getDir());
} else { // chiral atom needs wedging
auto bond = mol.getBondWithIdx(wbi);
auto dir =
detail::determineBondWedgeState(bond, wedgeInfo->getIdx(), conf);
if (dir == Bond::BEGINWEDGE || dir == Bond::BEGINDASH) {
bond->setBondDir(dir);
// it is possible that this
// wedging was determined by a chiral atom at the end of the
// bond (instead of at the beginning). In this case we need to
// reverse the begin and end atoms for the bond
if (static_cast<unsigned int>(wedgeInfo->getIdx()) !=
bond->getBeginAtomIdx()) {
auto tmp = bond->getBeginAtomIdx();
bond->setBeginAtomIdx(bond->getEndAtomIdx());
bond->setEndAtomIdx(tmp);
}
}
}
}
if (params->wedgeTwoBondsIfPossible) {
// This should probably check whether the existing wedge
// is in agreement with the chiral tag on the atom.
for (const auto atom : mol.atoms()) {
if (atom->getChiralTag() != Atom::CHI_TETRAHEDRAL_CW &&
atom->getChiralTag() != Atom::CHI_TETRAHEDRAL_CCW) {
continue;
}
unsigned numWedged = 0;
Bond *wedgedBond = nullptr;
for (const auto bond : mol.atomBonds(atom)) {
if (bond->getBeginAtom() == atom &&
bond->getBondType() == Bond::SINGLE &&
(bond->getBondDir() == Bond::BEGINWEDGE ||
bond->getBondDir() == Bond::BEGINDASH)) {
++numWedged;
wedgedBond = bond;
}
}
if (numWedged == 1) {
addSecondWedgeAroundAtom(mol, wedgedBond, conf);
}
}
}
}
void wedgeBond(Bond *bond, unsigned int fromAtomIdx, const Conformer *conf) {
PRECONDITION(bond, "no bond");
PRECONDITION(conf, "no conformer");
PRECONDITION(&conf->getOwningMol() == &bond->getOwningMol(),
"bond and conformer do not belong to same molecule");
if (bond->getBondType() != Bond::SINGLE) {
return;
}
Bond::BondDir dir = detail::determineBondWedgeState(bond, fromAtomIdx, conf);
if (dir == Bond::BEGINWEDGE || dir == Bond::BEGINDASH) {
bond->setBondDir(dir);
}
}
bool wedgingHasChirality(const ROMol &mol, const Bond *b) {
// see if this wedge should have wedging. It can if the begin atom
// has chirality or the begin atom is the part of an atropisomer bond
Atom *atom = b->getBeginAtom();
if (atom->getChiralTag() != Atom::CHI_UNSPECIFIED) {
return true;
}
// see if this is part of an atropisomer bond
for (const auto bond2 : mol.atomBonds(atom)) {
if (bond2->getBondType() == Bond::BondType::SINGLE) {
if (bond2 == b) {
continue; // a bond is NOT its own neighbor
}
if (bond2->getStereo() == Bond::STEREOATROPCCW ||
bond2->getStereo() == Bond::STEREOATROPCW) {
return true;
}
}
}
return false;
}
void reapplyMolBlockWedging(ROMol &mol, bool allBondTypes,
bool verify) {
MolOps::clearDirFlags(mol, true);
for (auto b : mol.bonds()) {
int explicit_unknown_stereo = -1;
if (b->getPropIfPresent<int>(common_properties::_UnknownStereo,
explicit_unknown_stereo) &&
explicit_unknown_stereo) {
b->setBondDir(Bond::UNKNOWN);
}
// if the bond is not a double bond, and it is not connected to an
// atropisomer bond AND if the start atom is not chiral, we will skip it -
// it should not have a wedge or dash
if (verify &&
(!canHaveDirection(*b) || !wedgingHasChirality(mol, b))) {
continue;
}
int bond_dir = -1;
if (b->getPropIfPresent<int>(common_properties::_MolFileBondStereo,
bond_dir)) {
if (allBondTypes || canHaveDirection(*b)) {
if (bond_dir == 1) {
b->setBondDir(Bond::BEGINWEDGE);
} else if (bond_dir == 6) {
b->setBondDir(Bond::BEGINDASH);
}
}
if (b->getBondType() == Bond::DOUBLE) {
if (bond_dir == 0 && b->getStereo() == Bond::STEREOANY) {
b->setBondDir(Bond::NONE);
b->setStereo(Bond::STEREONONE);
} else if (bond_dir == 3) {
b->setBondDir(Bond::EITHERDOUBLE);
b->setStereo(Bond::STEREOANY);
}
}
}
int cfg = -1;
b->getPropIfPresent<int>(common_properties::_MolFileBondCfg, cfg);
switch (cfg) {
case 1:
if (allBondTypes || canHaveDirection(*b)) {
b->setBondDir(Bond::BEGINWEDGE);
}
break;
case 2:
if (canHaveDirection(*b)) {
b->setBondDir(Bond::UNKNOWN);
} else if (b->getBondType() == Bond::DOUBLE) {
b->setBondDir(Bond::EITHERDOUBLE);
b->setStereo(Bond::STEREOANY);
}
break;
case 3:
if (allBondTypes || canHaveDirection(*b)) {
b->setBondDir(Bond::BEGINDASH);
}
break;
case 0:
case -1:
if (bond_dir == -1 && b->getBondType() == Bond::DOUBLE &&
b->getStereo() == Bond::STEREOANY) {
b->setBondDir(Bond::NONE);
b->setStereo(Bond::STEREONONE);
}
}
}
}
void clearMolBlockWedgingInfo(ROMol &mol) {
for (auto b : mol.bonds()) {
b->clearProp(common_properties::_MolFileBondStereo);
b->clearProp(common_properties::_MolFileBondCfg);
}
}
void invertMolBlockWedgingInfo(ROMol &mol) {
for (auto b : mol.bonds()) {
int bond_dir = -1;
if (b->getPropIfPresent<int>(common_properties::_MolFileBondStereo,
bond_dir)) {
if (bond_dir == 1) {
b->setProp<int>(common_properties::_MolFileBondStereo, 6);
} else if (bond_dir == 6) {
b->setProp<int>(common_properties::_MolFileBondStereo, 1);
}
}
int cfg = -1;
if (b->getPropIfPresent<int>(common_properties::_MolFileBondCfg, cfg)) {
if (cfg == 1) {
b->setProp<int>(common_properties::_MolFileBondCfg, 3);
} else if (cfg == 3) {
b->setProp<int>(common_properties::_MolFileBondCfg, 1);
}
}
}
}
} // namespace Chirality
} // namespace RDKit