mirror of
https://github.com/rdkit/rdkit.git
synced 2026-06-04 21:54:27 +08:00
Add option to draw all CIP codes in DrawMol.cpp (#8609)
* enable showAllCIPCodes in DrawMol code * Made consistent with old behavior + add test * Added python test * Fixed FREETYPE=OFF tests * Fixed FREETYPE=OFF tests * Adjusted pytest * Enabled JSON drawer option setting + new JSON test * update JS docs
This commit is contained in:
@@ -33,6 +33,7 @@
|
||||
#include <GraphMol/MolEnumerator/LinkNode.h>
|
||||
#include <GraphMol/MolTransforms/MolTransforms.h>
|
||||
#include <GraphMol/Depictor/RDDepictor.h>
|
||||
#include <GraphMol/StereoGroup.h>
|
||||
#include <GraphMol/Atropisomers.h>
|
||||
|
||||
namespace RDKit {
|
||||
@@ -194,9 +195,6 @@ void DrawMol::initDrawMolecule(const ROMol &mol) {
|
||||
bool removeAffectedStereoGroups = true;
|
||||
Chirality::simplifyEnhancedStereo(*drawMol_, removeAffectedStereoGroups);
|
||||
}
|
||||
if (drawOptions_.addStereoAnnotation) {
|
||||
Chirality::addStereoAnnotations(*drawMol_);
|
||||
}
|
||||
if (drawOptions_.addAtomIndices) {
|
||||
addAtomIndices(*drawMol_);
|
||||
}
|
||||
@@ -220,9 +218,10 @@ void DrawMol::extractAll(double scale) {
|
||||
extractHighlights(scale);
|
||||
extractAttachments();
|
||||
extractAtomNotes();
|
||||
if (!drawOptions_.addStereoAnnotation) {
|
||||
extractStereoGroups();
|
||||
if (drawOptions_.addStereoAnnotation) {
|
||||
extractCIPCodes(drawOptions_.showAllCIPCodes);
|
||||
}
|
||||
extractStereoGroups(); // always show StereoGroups
|
||||
extractBondNotes();
|
||||
extractRadicals();
|
||||
extractSGroupData();
|
||||
@@ -465,6 +464,69 @@ void DrawMol::extractAtomNotes() {
|
||||
}
|
||||
}
|
||||
|
||||
// ****************************************************************************
|
||||
void DrawMol::extractCIPCodes(bool showAllCIPCodes) {
|
||||
boost::dynamic_bitset<> maskedAtoms(drawMol_->getNumAtoms());
|
||||
boost::dynamic_bitset<> maskedBonds(drawMol_->getNumBonds());
|
||||
|
||||
if(!showAllCIPCodes) { // record atoms and bonds whose codes should be hidden
|
||||
for (const StereoGroup &group : drawMol_->getStereoGroups()) {
|
||||
StereoGroupType stereoGroupType;
|
||||
|
||||
stereoGroupType = group.getGroupType();
|
||||
if(stereoGroupType == RDKit::StereoGroupType::STEREO_OR || \
|
||||
stereoGroupType == RDKit::StereoGroupType::STEREO_AND ) {
|
||||
for (const auto atom : group.getAtoms()) {
|
||||
maskedAtoms.set(atom->getIdx());
|
||||
}
|
||||
for (const auto bond : group.getBonds()) {
|
||||
maskedBonds.set(bond->getIdx());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto atom : drawMol_->atoms()) {
|
||||
std::string cip;
|
||||
if (!maskedAtoms[atom->getIdx()] &&
|
||||
atom->getPropIfPresent(common_properties::_CIPCode, cip)) {
|
||||
cip = "(" + cip + ")";
|
||||
DrawAnnotation *annot = new DrawAnnotation(
|
||||
cip, TextAlignType::MIDDLE, "CIP_Code",
|
||||
drawOptions_.annotationFontScale, Point2D(0.0, 0.0),
|
||||
drawOptions_.atomNoteColour, textDrawer_);
|
||||
calcAnnotationPosition(atom, *annot);
|
||||
annotations_.emplace_back(annot);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto bond : drawMol_->bonds()) {
|
||||
std::string cip;
|
||||
// Add E or Z CIP codes if missing to be compatible with previous
|
||||
// implemtnation. In future, user should be responsible for calling
|
||||
// AssignCIPLabels() before drawing to harmonize behavior with
|
||||
// how R,S,M,P CIP codes are handled
|
||||
if (!maskedBonds[bond->getIdx()]) {
|
||||
if (!bond->getPropIfPresent(common_properties::_CIPCode, cip)) {
|
||||
if (bond->getStereo() == Bond::STEREOE) {
|
||||
cip = "E";
|
||||
} else if (bond->getStereo() == Bond::STEREOZ) {
|
||||
cip = "Z";
|
||||
}
|
||||
}
|
||||
if (!cip.empty()) {
|
||||
cip = "(" + cip + ")";
|
||||
DrawAnnotation *annot = new DrawAnnotation(
|
||||
cip, TextAlignType::MIDDLE, "CIP_Code",
|
||||
drawOptions_.annotationFontScale, Point2D(0.0, 0.0),
|
||||
drawOptions_.bondNoteColour, textDrawer_);
|
||||
calcAnnotationPosition(bond, *annot);
|
||||
annotations_.emplace_back(annot);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ****************************************************************************
|
||||
void DrawMol::extractStereoGroups() {
|
||||
int orCount(0), andCount(0);
|
||||
|
||||
@@ -112,6 +112,7 @@ class DrawMol {
|
||||
void extractRegions();
|
||||
void extractAttachments();
|
||||
void extractMolNotes();
|
||||
void extractCIPCodes(bool showAllCIPCodes);
|
||||
void extractStereoGroups();
|
||||
void extractAtomNotes();
|
||||
void extractBondNotes();
|
||||
|
||||
@@ -259,6 +259,8 @@ struct RDKIT_MOLDRAW2D_EXPORT MolDrawOptions {
|
||||
bool dummyIsotopeLabels = true; // adds isotope labels to dummy atoms.
|
||||
|
||||
bool addStereoAnnotation = false; // adds E/Z and R/S to drawings.
|
||||
bool showAllCIPCodes = false; // show CIP codes for 'or' & 'and'
|
||||
// StereoGroups that are normally hidden
|
||||
bool atomHighlightsAreCircles = false; // forces atom highlights always to be
|
||||
// circles. Default (false) is to put
|
||||
// ellipses round longer labels.
|
||||
|
||||
@@ -241,6 +241,7 @@ void updateMolDrawOptionsFromJSON(MolDrawOptions &opts,
|
||||
PT_OPT_GET(isotopeLabels);
|
||||
PT_OPT_GET(dummyIsotopeLabels);
|
||||
PT_OPT_GET(addStereoAnnotation);
|
||||
PT_OPT_GET(showAllCIPCodes);
|
||||
PT_OPT_GET(atomHighlightsAreCircles);
|
||||
PT_OPT_GET(centreMoleculesBeforeDrawing);
|
||||
PT_OPT_GET(explicitMethyl);
|
||||
|
||||
@@ -922,6 +922,9 @@ BOOST_PYTHON_MODULE(rdMolDraw2D) {
|
||||
.def_readwrite("addStereoAnnotation",
|
||||
&RDKit::MolDrawOptions::addStereoAnnotation,
|
||||
"adds R/S and E/Z to drawings. Default False.")
|
||||
.def_readwrite("showAllCIPCodes",
|
||||
&RDKit::MolDrawOptions::showAllCIPCodes,
|
||||
"show all defined CIP codes (no hiding!). Default False.")
|
||||
.def_readwrite("addAtomIndices", &RDKit::MolDrawOptions::addAtomIndices,
|
||||
"adds atom indices to drawings. Default False.")
|
||||
.def_readwrite("addBondIndices", &RDKit::MolDrawOptions::addBondIndices,
|
||||
|
||||
@@ -843,7 +843,19 @@ M END''')
|
||||
do = rdMolDraw2D.MolDrawOptions()
|
||||
with self.assertRaises(AttributeError):
|
||||
do.rhubarb = True
|
||||
|
||||
|
||||
def testShowAllCIPCodes(self):
|
||||
m = Chem.MolFromSmiles("Cc1cccc(F)c1-c1c(C)cc([C@H](C)O)cc1Cl |wU:7.7,o1:7,&1:13|")
|
||||
self.assertIsNotNone(m)
|
||||
d2d = rdMolDraw2D.MolDraw2DSVG(-1, -1)
|
||||
opts = d2d.drawOptions()
|
||||
opts.addStereoAnnotation = True
|
||||
opts.showAllCIPCodes = True
|
||||
d2d.DrawMolecule(m)
|
||||
d2d.FinishDrawing()
|
||||
text = d2d.GetDrawingText()
|
||||
#confirm that at least one CIP code is present
|
||||
self.assertTrue("class='CIP_Code'" in text)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -10751,4 +10751,99 @@ TEST_CASE("Solid arrowhead in wrong place (Github 8500)") {
|
||||
Catch::Matchers::WithinAbs(expVals[i].second, 1.0e-4));
|
||||
}
|
||||
check_file_hash("testArrowheads.svg");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Show all CIP codes (Github 8561)") {
|
||||
auto m = R"CTAB(
|
||||
RDKit 2D
|
||||
|
||||
0 0 0 0 0 0 0 0 0 0999 V3000
|
||||
M V30 BEGIN CTAB
|
||||
M V30 COUNTS 19 20 0 0 0
|
||||
M V30 BEGIN ATOM
|
||||
M V30 1 C 3.000000 0.000000 0.000000 0
|
||||
M V30 2 C 1.500000 0.000000 0.000000 0
|
||||
M V30 3 C 0.750000 -1.299038 0.000000 0
|
||||
M V30 4 C -0.750000 -1.299038 0.000000 0
|
||||
M V30 5 C -1.500000 0.000000 0.000000 0
|
||||
M V30 6 C -0.750000 1.299038 0.000000 0
|
||||
M V30 7 F -1.500000 2.598076 0.000000 0
|
||||
M V30 8 C 0.750000 1.299038 0.000000 0
|
||||
M V30 9 C 1.500000 2.598076 0.000000 0
|
||||
M V30 10 C 3.000000 2.598076 0.000000 0
|
||||
M V30 11 C 3.750000 1.299038 0.000000 0
|
||||
M V30 12 C 3.750000 3.897114 0.000000 0
|
||||
M V30 13 C 3.000000 5.196152 0.000000 0
|
||||
M V30 14 C 3.750000 6.495191 0.000000 0
|
||||
M V30 15 C 5.250000 6.495191 0.000000 0
|
||||
M V30 16 O 3.000000 7.794229 0.000000 0
|
||||
M V30 17 C 1.500000 5.196152 0.000000 0
|
||||
M V30 18 C 0.750000 3.897114 0.000000 0
|
||||
M V30 19 Cl -0.750000 3.897114 0.000000 0
|
||||
M V30 END ATOM
|
||||
M V30 BEGIN BOND
|
||||
M V30 1 1 1 2
|
||||
M V30 2 2 2 3
|
||||
M V30 3 1 3 4
|
||||
M V30 4 2 4 5
|
||||
M V30 5 1 5 6
|
||||
M V30 6 1 6 7
|
||||
M V30 7 2 6 8
|
||||
M V30 8 1 8 9
|
||||
M V30 9 2 9 10
|
||||
M V30 10 1 10 11
|
||||
M V30 11 1 10 12
|
||||
M V30 12 2 12 13
|
||||
M V30 13 1 13 14
|
||||
M V30 14 1 14 15 CFG=1
|
||||
M V30 15 1 14 16
|
||||
M V30 16 1 13 17
|
||||
M V30 17 2 17 18
|
||||
M V30 18 1 18 19
|
||||
M V30 19 1 8 2 CFG=3
|
||||
M V30 20 1 18 9
|
||||
M V30 END BOND
|
||||
M V30 BEGIN COLLECTION
|
||||
M V30 MDLV30/STEREL1 ATOMS=(1 8)
|
||||
M V30 MDLV30/STERAC1 ATOMS=(1 14)
|
||||
M V30 END COLLECTION
|
||||
M V30 END CTAB
|
||||
M END
|
||||
)CTAB"_ctab;
|
||||
REQUIRE(m);
|
||||
CIPLabeler::assignCIPLabels(*m);
|
||||
MolDraw2DSVG drawer(350, 300);
|
||||
drawer.drawOptions().addStereoAnnotation = true;
|
||||
drawer.drawOptions().showAllCIPCodes = true;
|
||||
drawer.drawMolecule(*m);
|
||||
drawer.finishDrawing();
|
||||
auto text = drawer.getDrawingText();
|
||||
std::ofstream outs("testShowAllCIPLabels.svg");
|
||||
outs << text;
|
||||
outs.close();
|
||||
|
||||
// Check for Blue (M) CIP label on Bond
|
||||
TEST_ASSERT(
|
||||
text.find("class='CIP_Code'") !=
|
||||
std::string::npos);
|
||||
|
||||
//try again, this tiem using JSON to set options
|
||||
const char *json =
|
||||
"{\"addStereoAnnotation\":true, \"showAllCIPCodes\":true}";
|
||||
MolDraw2DSVG drawer2(350, 300);
|
||||
MolDrawOptions opts;
|
||||
MolDraw2DUtils::updateMolDrawOptionsFromJSON(opts, json);
|
||||
drawer2.drawOptions() = opts;
|
||||
drawer2.drawMolecule(*m);
|
||||
drawer2.finishDrawing();
|
||||
auto text2 = drawer2.getDrawingText();
|
||||
std::ofstream outs2("testShowAllCIPLabels2.svg");
|
||||
outs2 << text2;
|
||||
outs2.close();
|
||||
|
||||
// Check for Blue (M) CIP label on Bond
|
||||
TEST_ASSERT(
|
||||
text2.find("class='CIP_Code'") !=
|
||||
std::string::npos);
|
||||
}
|
||||
|
||||
|
||||
@@ -4036,12 +4036,12 @@ void test20Annotate() {
|
||||
#ifdef RDK_BUILD_FREETYPE_SUPPORT
|
||||
#if DO_TEST_ASSERT
|
||||
// last note
|
||||
TEST_ASSERT(text.find("<path class='note' d='M 273.9 236.3") !=
|
||||
TEST_ASSERT(text.find("<path class='CIP_Code' d='M 273.9 236.3") !=
|
||||
std::string::npos);
|
||||
#endif
|
||||
#else
|
||||
// this is the (E)
|
||||
TEST_ASSERT(text.find("<text x='260.3' y='232.0' class='note' "
|
||||
TEST_ASSERT(text.find("<text x='260.3' y='232.0' class='CIP_Code' "
|
||||
"style='font-size:20px;font-style:normal;font-weight:"
|
||||
"normal;fill-opacity:1;stroke:none;font-family:sans-"
|
||||
"serif;text-anchor:start;fill:#7F7FFF' >E</text>") !=
|
||||
|
||||
@@ -185,6 +185,7 @@ addBondIndices,
|
||||
isotopeLabels,
|
||||
dummyIsotopeLabels,
|
||||
addStereoAnnotation,
|
||||
showAllCIPLabels,
|
||||
atomHighlightsAreCircles,
|
||||
centreMoleculesBeforeDrawing,
|
||||
explicitMethyl,
|
||||
@@ -321,4 +322,4 @@ mol1.draw_to_canvas_with_highlights(canvas_mol1, JSON.stringify({ legend: `mol1
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user