// // Copyright (C) 2019-2020 Greg Landrum // // @@ 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 "catch.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef RDK_BUILD_CAIRO_SUPPORT #include #include "MolDraw2DCairo.h" #endif // a lot of the tests check flags in the SVG. That doesn't // happen with the Freetype versions static const bool NO_FREETYPE = true; using namespace RDKit; TEST_CASE("prepareAndDrawMolecule", "[drawing]") { SECTION("basics") { auto m1 = "C1N[C@@H]2OCC12"_smiles; REQUIRE(m1); // we will be able to recognize that the prep worked because there // will be an H in the output: MolDraw2DSVG drawer(200, 200, -1, -1, NO_FREETYPE); MolDraw2DUtils::prepareAndDrawMolecule(drawer, *m1); drawer.finishDrawing(); std::string text = drawer.getDrawingText(); CHECK(text.find(">H") != std::string::npos); } } TEST_CASE("tag atoms in SVG", "[drawing][SVG]") { SECTION("basics") { auto m1 = "C1N[C@@H]2OCC12"_smiles; REQUIRE(m1); MolDraw2DSVG drawer(200, 200, -1, -1, NO_FREETYPE); MolDraw2DUtils::prepareMolForDrawing(*m1); drawer.drawMolecule(*m1); std::map actions; actions["onclick"] = "alert"; double radius = 0.2; drawer.tagAtoms(*m1, radius, actions); drawer.finishDrawing(); std::string text = drawer.getDrawingText(); std::ofstream outs("testAtomTags_1.svg"); outs << text; outs.flush(); CHECK(text.find(" xps(gridSz); std::vector yps(gridSz); double minX = 1000, minY = 1000, maxX = -1000, maxY = -1000; const auto conf = m1->getConformer(); for (size_t i = 0; i < conf.getNumAtoms(); ++i) { minX = std::min(minX, conf.getAtomPos(i).x); minY = std::min(minY, conf.getAtomPos(i).y); maxX = std::max(maxX, conf.getAtomPos(i).x); maxY = std::max(maxY, conf.getAtomPos(i).y); } double x1 = minX - 0.5, y1 = minY - 0.5, x2 = maxX + 0.5, y2 = maxY + 0.5; double dx = (x2 - x1) / gridSz, dy = (y2 - y1) / gridSz; double maxV = 0.0; for (size_t ix = 0; ix < gridSz; ++ix) { auto px = x1 + ix * dx; xps[ix] = px; for (size_t iy = 0; iy < gridSz; ++iy) { auto py = y1 + iy * dy; if (ix == 0) { yps[iy] = py; } RDGeom::Point2D loc(px, py); double val = 0.0; for (size_t ia = 0; ia < conf.getNumAtoms(); ++ia) { auto dv = loc - RDGeom::Point2D(conf.getAtomPos(ia).x, conf.getAtomPos(ia).y); auto r = dv.length(); if (r > 0.1) { val += 1 / r; } } maxV = std::max(val, maxV); grid[ix * gridSz + iy] = val; } } std::vector levels; drawer.clearDrawing(); MolDraw2DUtils::contourAndDrawGrid(drawer, grid, xps, yps, 10, levels, MolDraw2DUtils::ContourParams(), m1.get()); drawer.drawOptions().clearBackground = false; drawer.drawMolecule(*m1); drawer.finishDrawing(); std::string text = drawer.getDrawingText(); std::ofstream outs("contourMol_1.svg"); outs << text; outs.flush(); delete[] grid; } SECTION("gaussian basics") { MolDraw2DSVG drawer(250, 250, -1, -1, NO_FREETYPE); MolDraw2DUtils::prepareMolForDrawing(*m1); drawer.drawOptions().padding = 0.1; const auto conf = m1->getConformer(); std::vector cents(conf.getNumAtoms()); std::vector weights(conf.getNumAtoms()); std::vector widths(conf.getNumAtoms()); for (size_t i = 0; i < conf.getNumAtoms(); ++i) { cents[i] = Point2D(conf.getAtomPos(i).x, conf.getAtomPos(i).y); weights[i] = 1; widths[i] = 0.4 * PeriodicTable::getTable()->getRcovalent( m1->getAtomWithIdx(i)->getAtomicNum()); } std::vector levels; drawer.clearDrawing(); MolDraw2DUtils::contourAndDrawGaussians( drawer, cents, weights, widths, 10, levels, MolDraw2DUtils::ContourParams(), m1.get()); drawer.drawOptions().clearBackground = false; drawer.drawMolecule(*m1); drawer.finishDrawing(); std::string text = drawer.getDrawingText(); std::ofstream outs("contourMol_2.svg"); outs << text; outs.flush(); } SECTION("gaussian fill") { MolDraw2DSVG drawer(250, 250, -1, -1, NO_FREETYPE); MolDraw2DUtils::prepareMolForDrawing(*m1); drawer.drawOptions().padding = 0.1; const auto conf = m1->getConformer(); std::vector cents(conf.getNumAtoms()); std::vector weights(conf.getNumAtoms()); std::vector widths(conf.getNumAtoms()); for (size_t i = 0; i < conf.getNumAtoms(); ++i) { cents[i] = Point2D(conf.getAtomPos(i).x, conf.getAtomPos(i).y); weights[i] = i % 2 ? -0.5 : 1; widths[i] = 0.4 * PeriodicTable::getTable()->getRcovalent( m1->getAtomWithIdx(i)->getAtomicNum()); } std::vector levels; MolDraw2DUtils::ContourParams cps; cps.fillGrid = true; drawer.clearDrawing(); MolDraw2DUtils::contourAndDrawGaussians(drawer, cents, weights, widths, 10, levels, cps, m1.get()); drawer.drawOptions().clearBackground = false; drawer.drawMolecule(*m1); drawer.finishDrawing(); std::string text = drawer.getDrawingText(); std::ofstream outs("contourMol_3.svg"); outs << text; outs.flush(); } SECTION("gaussian fill 2") { auto m2 = "C1N[C@@H]2OCC12C=CC"_smiles; REQUIRE(m2); MolDraw2DSVG drawer(450, 250, -1, -1, NO_FREETYPE); MolDraw2DUtils::prepareMolForDrawing(*m2); drawer.drawOptions().padding = 0.1; const auto conf = m2->getConformer(); std::vector cents(conf.getNumAtoms()); std::vector weights(conf.getNumAtoms()); std::vector widths(conf.getNumAtoms()); for (size_t i = 0; i < conf.getNumAtoms(); ++i) { cents[i] = Point2D(conf.getAtomPos(i).x, conf.getAtomPos(i).y); weights[i] = i % 2 ? -0.5 : 1; widths[i] = 0.3 * PeriodicTable::getTable()->getRcovalent( m2->getAtomWithIdx(i)->getAtomicNum()); } std::vector levels; MolDraw2DUtils::ContourParams cps; cps.fillGrid = true; cps.gridResolution = 0.5; drawer.clearDrawing(); MolDraw2DUtils::contourAndDrawGaussians(drawer, cents, weights, widths, 10, levels, cps, m2.get()); drawer.drawOptions().clearBackground = false; drawer.drawMolecule(*m2); drawer.finishDrawing(); std::string text = drawer.getDrawingText(); std::ofstream outs("contourMol_4.svg"); outs << text; outs.flush(); } } TEST_CASE("dative bonds", "[drawing][organometallics]") { SECTION("basics") { auto m1 = "N->[Pt]"_smiles; REQUIRE(m1); MolDraw2DSVG drawer(200, 200, -1, -1, NO_FREETYPE); MolDraw2DUtils::prepareMolForDrawing(*m1); drawer.drawMolecule(*m1); drawer.finishDrawing(); std::string text = drawer.getDrawingText(); std::ofstream outs("testDativeBonds_1.svg"); outs << text; outs.flush(); CHECK(text.find("N") != std::string::npos); } { MolDraw2DSVG drawer(200, 200, -1, -1, NO_FREETYPE); assignBWPalette(drawer.drawOptions().atomColourPalette); MolDraw2DUtils::prepareAndDrawMolecule(drawer, *m1); drawer.finishDrawing(); std::string text = drawer.getDrawingText(); std::ofstream outs("testFoundations_2.svg"); outs << text; outs.flush(); CHECK(text.find("fill:#0000FF' >N") == std::string::npos); CHECK(text.find("fill:#000000' >N") != std::string::npos); } } SECTION("test") { { MolDraw2DSVG drawer(200, 200, -1, -1, NO_FREETYPE); MolDrawOptions options = drawer.drawOptions(); assignBWPalette(options.atomColourPalette); drawer.drawOptions() = options; MolDraw2DUtils::prepareAndDrawMolecule(drawer, *m1); drawer.finishDrawing(); std::string text = drawer.getDrawingText(); std::ofstream outs("testTest_1.svg"); outs << text; outs.flush(); CHECK(text.find("fill:#0000FF' >N") == std::string::npos); CHECK(text.find("fill:#000000' >N") != std::string::npos); } } } TEST_CASE("bad DrawMolecules() when molecules are not kekulized", "[drawing][bug]") { auto m1 = "CCN(CC)CCn1nc2c3ccccc3sc3c(CNS(C)(=O)=O)ccc1c32"_smiles; REQUIRE(m1); SECTION("foundations") { MolDraw2DSVG drawer(500, 200, 250, 200, NO_FREETYPE); drawer.drawOptions().prepareMolsBeforeDrawing = false; RWMol dm1(*m1); RWMol dm2(*m1); bool kekulize = false; MolDraw2DUtils::prepareMolForDrawing(dm1, kekulize); kekulize = true; MolDraw2DUtils::prepareMolForDrawing(dm2, kekulize); MOL_PTR_VECT ms{&dm1, &dm2}; drawer.drawMolecule(dm1); drawer.finishDrawing(); std::string text = drawer.getDrawingText(); std::ofstream outs("testKekulizationProblems_1.svg"); outs << text; outs.flush(); // this is a very crude test - really we just need to look at the SVG - but // it's better than nothing. CHECK(text.find( "") == std::string::npos); } } TEST_CASE("draw atom/bond indices", "[drawing]") { auto m1 = "C[C@H](F)N"_smiles; REQUIRE(m1); SECTION("foundations") { { MolDraw2DSVG drawer(250, 200, -1, -1, NO_FREETYPE); drawer.drawMolecule(*m1); drawer.finishDrawing(); std::string text = drawer.getDrawingText(); std::ofstream outs("testAtomBondIndices_1.svg"); outs << text; outs.flush(); CHECK(text.find(">1") == std::string::npos); CHECK(text.find(">(") == std::string::npos); CHECK(text.find(">S") == std::string::npos); CHECK(text.find(">)") == std::string::npos); } { MolDraw2DSVG drawer(250, 200, -1, -1, NO_FREETYPE); drawer.drawOptions().addAtomIndices = true; drawer.drawMolecule(*m1); drawer.finishDrawing(); std::string text = drawer.getDrawingText(); std::ofstream outs("testAtomBondIndices_2.svg"); outs << text; outs.flush(); CHECK(text.find(">1") != std::string::npos); // it only appears once though: CHECK(text.find(">1", text.find(">1") + 1) == std::string::npos); CHECK(text.find("1,(S)") == std::string::npos); } { MolDraw2DSVG drawer(250, 200, -1, -1, NO_FREETYPE); drawer.drawOptions().addBondIndices = true; drawer.drawMolecule(*m1); drawer.finishDrawing(); std::string text = drawer.getDrawingText(); std::ofstream outs("testAtomBondIndices_3.svg"); outs << text; outs.flush(); CHECK(text.find(">1") != std::string::npos); // it only appears once though: CHECK(text.find(">1", text.find(">1") + 1) == std::string::npos); } { MolDraw2DSVG drawer(250, 200, -1, -1, NO_FREETYPE); drawer.drawOptions().addAtomIndices = true; drawer.drawOptions().addBondIndices = true; drawer.drawMolecule(*m1); drawer.finishDrawing(); std::string text = drawer.getDrawingText(); std::ofstream outs("testAtomBondIndices_4.svg"); outs << text; outs.flush(); CHECK(text.find(">1") != std::string::npos); // it appears twice: CHECK(text.find(">1", text.find(">1") + 1) != std::string::npos); } { MolDraw2DSVG drawer(250, 200, -1, -1, NO_FREETYPE); m1->getAtomWithIdx(2)->setProp(common_properties::atomNote, "foo"); drawer.drawOptions().addAtomIndices = true; drawer.drawOptions().addStereoAnnotation = true; drawer.drawMolecule(*m1); m1->getAtomWithIdx(2)->clearProp(common_properties::atomNote); drawer.finishDrawing(); std::string text = drawer.getDrawingText(); std::ofstream outs("testAtomBondIndices_5.svg"); outs << text; outs.flush(); CHECK(text.find(">1") != std::string::npos); CHECK(text.find(">,") != std::string::npos); CHECK(text.find(">(") != std::string::npos); CHECK(text.find(">S") != std::string::npos); CHECK(text.find(")") != std::string::npos); CHECK(text.find(">2") != std::string::npos); CHECK(text.find(">f") != std::string::npos); CHECK(text.find(">o") != std::string::npos); } } } TEST_CASE("Github #3226: Lines in wedge bonds being drawn too closely together", "[drawing]") { auto m1 = "C[C@H](C1=C(C=CC(=C1Cl)F)Cl)OC2=C(N=CC(=C2)C3=CN(N=C3)C4CCNCC4)N"_smiles; REQUIRE(m1); SECTION("larger SVG") { { MolDraw2DSVG drawer(450, 400); drawer.drawMolecule(*m1); drawer.finishDrawing(); std::string text = drawer.getDrawingText(); std::ofstream outs("testGithub3226_1.svg"); outs << text; outs.flush(); std::vector tkns; boost::algorithm::find_all(tkns, text, "bond-0"); CHECK(tkns.size() == 6); } } #ifdef RDK_BUILD_CAIRO_SUPPORT SECTION("larger PNG") { { MolDraw2DCairo drawer(450, 400); drawer.drawMolecule(*m1); drawer.finishDrawing(); drawer.writeDrawingText("testGithub3226_1.png"); } } #endif SECTION("smaller SVG") { { MolDraw2DSVG drawer(200, 150); drawer.drawMolecule(*m1); drawer.finishDrawing(); std::string text = drawer.getDrawingText(); std::ofstream outs("testGithub3226_2.svg"); outs << text; outs.flush(); std::vector tkns; boost::algorithm::find_all(tkns, text, "bond-0"); CHECK(tkns.size() == 4); } } #ifdef RDK_BUILD_CAIRO_SUPPORT SECTION("smaller PNG") { { MolDraw2DCairo drawer(200, 150); drawer.drawMolecule(*m1); drawer.finishDrawing(); drawer.writeDrawingText("testGithub3226_2.png"); } } #endif SECTION("middle SVG") { { MolDraw2DSVG drawer(250, 200); drawer.drawMolecule(*m1); drawer.finishDrawing(); std::string text = drawer.getDrawingText(); std::ofstream outs("testGithub3226_3.svg"); outs << text; outs.flush(); std::vector tkns; boost::algorithm::find_all(tkns, text, "bond-0"); CHECK(tkns.size() == 4); } } #ifdef RDK_BUILD_CAIRO_SUPPORT SECTION("middle PNG") { { MolDraw2DCairo drawer(250, 200); drawer.drawMolecule(*m1); drawer.finishDrawing(); drawer.writeDrawingText("testGithub3226_3.png"); } } #endif } TEST_CASE("github #3258: ", "[drawing][bug]") { auto m1 = "CCN"_smiles; REQUIRE(m1); SECTION("foundations") { MolDraw2DSVG drawer(500, 200, 250, 200, NO_FREETYPE); drawer.drawOptions().addAtomIndices = true; drawer.drawOptions().addBondIndices = true; RWMol dm1(*m1); RWMol dm2(*m1); MOL_PTR_VECT ms{&dm1, &dm2}; drawer.drawMolecules(ms); drawer.finishDrawing(); std::string text = drawer.getDrawingText(); CHECK(text.find(">,") == std::string::npos); CHECK(!dm1.hasProp("_atomIndicesAdded")); CHECK(!dm1.hasProp("_bondIndicesAdded")); } } #ifdef RDK_BUILD_CAIRO_SUPPORT TEST_CASE("adding png metadata", "[drawing][png]") { SECTION("molecule") { auto m1 = R"CTAB( Mrv2014 08172015242D 0 0 0 0 0 999 V3000 M V30 BEGIN CTAB M V30 COUNTS 3 2 0 0 0 M V30 BEGIN ATOM M V30 1 C 2.31 -1.3337 0 0 M V30 2 C 3.6437 -2.1037 0 0 M V30 3 O 4.9774 -1.3337 0 0 M V30 END ATOM M V30 BEGIN BOND M V30 1 1 1 2 M V30 2 1 2 3 M V30 END BOND M V30 END CTAB M END )CTAB"_ctab; REQUIRE(m1); { MolDraw2DCairo drawer(250, 200); drawer.drawMolecule(*m1); drawer.finishDrawing(); auto png = drawer.getDrawingText(); drawer.writeDrawingText("testPNGMetadata_1.png"); CHECK(png.find(PNGData::smilesTag) != std::string::npos); CHECK(png.find(PNGData::molTag) != std::string::npos); CHECK(png.find(PNGData::pklTag) != std::string::npos); std::unique_ptr newmol(PNGStringToMol(png)); REQUIRE(newmol); CHECK(MolToCXSmiles(*m1) == MolToCXSmiles(*newmol)); } { // disable metadata output MolDraw2DCairo drawer(250, 200); drawer.drawOptions().includeMetadata = false; drawer.drawMolecule(*m1); drawer.finishDrawing(); auto png = drawer.getDrawingText(); CHECK(png.find(PNGData::smilesTag) == std::string::npos); CHECK(png.find(PNGData::molTag) == std::string::npos); CHECK(png.find(PNGData::pklTag) == std::string::npos); } { // draw multiple molecules MolDraw2DCairo drawer(250, 200); drawer.drawMolecule(*m1); drawer.drawMolecule(*m1); drawer.finishDrawing(); auto png = drawer.getDrawingText(); CHECK(png.find(PNGData::smilesTag) != std::string::npos); CHECK(png.find(PNGData::molTag) != std::string::npos); CHECK(png.find(PNGData::pklTag) != std::string::npos); CHECK(png.find(PNGData::smilesTag + "1") != std::string::npos); CHECK(png.find(PNGData::molTag + "1") != std::string::npos); CHECK(png.find(PNGData::pklTag + "1") != std::string::npos); } } SECTION("reaction") { std::unique_ptr rxn(RxnSmartsToChemicalReaction( "[N:1][C:2][C:3](=[O:4])[O:5].[N:6][C:7][C:8](=[O:9])[O:10]>>[N:1]1[C:" "2][C:3](=[O:4])[N:6][C:7][C:8]1=[O:9].[O:5][O:10]")); REQUIRE(rxn); { MolDraw2DCairo drawer(600, 200); drawer.drawReaction(*rxn); drawer.finishDrawing(); auto png = drawer.getDrawingText(); drawer.writeDrawingText("testPNGMetadata_2.png"); CHECK(png.find(PNGData::smilesTag) == std::string::npos); CHECK(png.find(PNGData::molTag) == std::string::npos); CHECK(png.find(PNGData::pklTag) == std::string::npos); CHECK(png.find(PNGData::rxnPklTag) != std::string::npos); CHECK(png.find(PNGData::rxnSmartsTag) != std::string::npos); std::unique_ptr rxn2(PNGStringToChemicalReaction(png)); REQUIRE(rxn2); CHECK(ChemicalReactionToRxnSmarts(*rxn) == ChemicalReactionToRxnSmarts(*rxn2)); } { // disable metadata MolDraw2DCairo drawer(600, 200); drawer.drawOptions().includeMetadata = false; drawer.drawReaction(*rxn); drawer.finishDrawing(); auto png = drawer.getDrawingText(); CHECK(png.find(PNGData::smilesTag) == std::string::npos); CHECK(png.find(PNGData::molTag) == std::string::npos); CHECK(png.find(PNGData::pklTag) == std::string::npos); CHECK(png.find(PNGData::rxnPklTag) == std::string::npos); CHECK(png.find(PNGData::rxnSmartsTag) == std::string::npos); } } } #endif TEST_CASE( "github #3392: prepareMolForDrawing() incorrectly adds chiral Hs if no " "ring info is present", "[bug]") { SECTION("foundations") { SmilesParserParams ps; ps.sanitize = false; ps.removeHs = false; std::unique_ptr m1(SmilesToMol("C[C@H](F)Cl", ps)); REQUIRE(m1); m1->updatePropertyCache(); CHECK(m1->getNumAtoms() == 4); const bool kekulize = false; const bool addChiralHs = true; MolDraw2DUtils::prepareMolForDrawing(*m1, kekulize, addChiralHs); CHECK(m1->getNumAtoms() == 4); } } TEST_CASE( "github #3369: support new CIP code and StereoGroups in " "addStereoAnnotation()", "[chirality]") { auto m1 = "C[C@@H]1N[C@H](C)[C@@H]([C@H](C)[C@@H]1C)C1[C@@H](C)O[C@@H](C)[C@@H](C)[C@H]1C/C=C/C |a:5,o1:1,8,o2:14,16,&1:11,18,&2:3,6,r|"_smiles; REQUIRE(m1); SECTION("defaults") { ROMol m2(*m1); MolDraw2D_detail::addStereoAnnotation(m2); std::string txt; CHECK(m2.getAtomWithIdx(5)->getPropIfPresent(common_properties::atomNote, txt)); CHECK(txt == "abs (S)"); CHECK(m2.getAtomWithIdx(3)->getPropIfPresent(common_properties::atomNote, txt)); CHECK(txt == "and4"); } SECTION("including CIP with relative stereo") { ROMol m2(*m1); bool includeRelativeCIP = true; MolDraw2D_detail::addStereoAnnotation(m2, includeRelativeCIP); std::string txt; CHECK(m2.getAtomWithIdx(5)->getPropIfPresent(common_properties::atomNote, txt)); CHECK(txt == "abs (S)"); CHECK(m2.getAtomWithIdx(3)->getPropIfPresent(common_properties::atomNote, txt)); CHECK(txt == "and4 (R)"); } SECTION("new CIP labels") { ROMol m2(*m1); REQUIRE(m2.getBondBetweenAtoms(20, 21)); m2.getBondBetweenAtoms(20, 21)->setStereo(Bond::BondStereo::STEREOTRANS); // initially no label is assigned since we have TRANS MolDraw2D_detail::addStereoAnnotation(m2); CHECK( !m2.getBondBetweenAtoms(20, 21)->hasProp(common_properties::bondNote)); CIPLabeler::assignCIPLabels(m2); std::string txt; CHECK(m2.getBondBetweenAtoms(20, 21)->getPropIfPresent( common_properties::_CIPCode, txt)); CHECK(txt == "E"); MolDraw2D_detail::addStereoAnnotation(m2); CHECK(m2.getBondBetweenAtoms(20, 21)->getPropIfPresent( common_properties::bondNote, txt)); CHECK(txt == "(E)"); } SECTION("works with the drawing code") { MolDraw2DSVG drawer(300, 250); RWMol dm1(*m1); bool includeRelativeCIP = true; MolDraw2D_detail::addStereoAnnotation(dm1, includeRelativeCIP); drawer.drawMolecule(dm1); drawer.finishDrawing(); std::string text = drawer.getDrawingText(); std::ofstream outs("testGithub3369_1.svg"); outs << text; outs.flush(); } } TEST_CASE("includeRadicals", "[options]") { SECTION("basics") { auto m = "[O][C]"_smiles; REQUIRE(m); int panelHeight = -1; int panelWidth = -1; bool noFreeType = true; { MolDraw2DSVG drawer(250, 200, panelWidth, panelHeight, noFreeType); drawer.drawMolecule(*m); drawer.finishDrawing(); auto text = drawer.getDrawingText(); std::ofstream outs("testIncludeRadicals_1a.svg"); outs << text; outs.flush(); CHECK(text.find("getConformer(); std::vector polyg; for (const auto &pt : conf.getPositions()) { polyg.emplace_back(pt); } MolDraw2DSVG drawer(350, 300); drawer.drawMolecule(*m, "molecule legend"); drawer.setFillPolys(true); drawer.setColour(DrawColour(1.0, 0.3, 1.0)); drawer.drawPolygon(polyg); drawer.finishDrawing(); auto text = drawer.getDrawingText(); std::ofstream outs("testLegendsAndDrawing-1.svg"); outs << text; outs.flush(); // make sure the polygon starts at a bond CHECK(text.find("getAtomWithIdx(1)->setProp("atomNote", "CCC"); m->getAtomWithIdx(2)->setProp("atomNote", "ccc"); m->getBondWithIdx(0)->setProp("bondNote", "CCC"); MolDraw2DSVG drawer(350, 300); drawer.drawMolecule(*m); drawer.finishDrawing(); auto text = drawer.getDrawingText(); std::ofstream outs("testGithub3577-1.svg"); outs << text; outs.flush(); } } TEST_CASE("hand drawn", "[play]") { SECTION("basics") { auto m = "CC[CH](C)[CH]1NC(=O)[CH](Cc2ccc(O)cc2)NC(=O)[CH](N)CSSC[CH](C(=O)N2CCC[CH]2C(=O)N[CH](CC(C)C)C(=O)NCC(N)=O)NC(=O)[CH](CC(N)=O)NC(=O)[CH](CCC(N)=O)NC1=O"_smiles; REQUIRE(m); RDDepict::preferCoordGen = true; MolDraw2DUtils::prepareMolForDrawing(*m); std::string fName = getenv("RDBASE"); fName += "/Data/Fonts/ComicNeue-Regular.ttf"; { MolDraw2DSVG drawer(450, 400); drawer.drawOptions().fontFile = fName; drawer.drawOptions().comicMode = true; drawer.drawMolecule(*m, "Oxytocin (flat)"); drawer.finishDrawing(); auto text = drawer.getDrawingText(); std::ofstream outs("testHandDrawn-1.svg"); outs << text; outs.flush(); } #ifdef RDK_BUILD_CAIRO_SUPPORT { MolDraw2DCairo drawer(450, 400); drawer.drawOptions().fontFile = fName; drawer.drawOptions().comicMode = true; drawer.drawMolecule(*m, "Oxytocin (flat)"); drawer.finishDrawing(); drawer.writeDrawingText("testHandDrawn-1.png"); } #endif } SECTION("with chirality") { auto m = "CC[C@H](C)[C@@H]1NC(=O)[C@H](Cc2ccc(O)cc2)NC(=O)[C@@H](N)CSSC[C@@H](C(=O)N2CCC[C@H]2C(=O)N[C@@H](CC(C)C)C(=O)NCC(N)=O)NC(=O)[C@H](CC(N)=O)NC(=O)[C@H](CCC(N)=O)NC1=O"_smiles; REQUIRE(m); RDDepict::preferCoordGen = true; MolDraw2DUtils::prepareMolForDrawing(*m); std::string fName = getenv("RDBASE"); fName += "/Data/Fonts/ComicNeue-Regular.ttf"; { MolDraw2DSVG drawer(450, 400); drawer.drawOptions().fontFile = fName; drawer.drawOptions().comicMode = true; drawer.drawMolecule(*m, "Oxytocin"); drawer.finishDrawing(); auto text = drawer.getDrawingText(); std::ofstream outs("testHandDrawn-2.svg"); outs << text; outs.flush(); } #ifdef RDK_BUILD_CAIRO_SUPPORT { MolDraw2DCairo drawer(450, 400); drawer.drawOptions().fontFile = fName; drawer.drawOptions().comicMode = true; drawer.drawMolecule(*m, "Oxytocin"); drawer.finishDrawing(); drawer.writeDrawingText("testHandDrawn-2.png"); } #endif } SECTION("smaller") { auto m = "N=c1nc([C@H]2NCCCC2)cc(N)n1O"_smiles; REQUIRE(m); RDDepict::preferCoordGen = true; MolDraw2DUtils::prepareMolForDrawing(*m); std::string fName = getenv("RDBASE"); fName += "/Data/Fonts/ComicNeue-Regular.ttf"; { MolDraw2DSVG drawer(350, 300); drawer.drawOptions().fontFile = fName; drawer.drawOptions().comicMode = true; drawer.drawMolecule(*m); drawer.finishDrawing(); auto text = drawer.getDrawingText(); std::ofstream outs("testHandDrawn-3.svg"); outs << text; outs.flush(); } #ifdef RDK_BUILD_CAIRO_SUPPORT { MolDraw2DCairo drawer(350, 300); drawer.drawOptions().fontFile = fName; drawer.drawOptions().comicMode = true; drawer.drawMolecule(*m); drawer.finishDrawing(); drawer.writeDrawingText("testHandDrawn-3.png"); } #endif } SECTION("another one") { auto m = "CCCc1nn(C)c2c(=O)nc(-c3cc(S(=O)(=O)N4CCN(C)CC4)ccc3OCC)[nH]c12"_smiles; REQUIRE(m); RDDepict::preferCoordGen = true; MolDraw2DUtils::prepareMolForDrawing(*m); std::string fName = getenv("RDBASE"); fName += "/Data/Fonts/ComicNeue-Regular.ttf"; { MolDraw2DSVG drawer(350, 300); drawer.drawOptions().fontFile = fName; drawer.drawOptions().comicMode = true; drawer.drawMolecule(*m); drawer.finishDrawing(); auto text = drawer.getDrawingText(); std::ofstream outs("testHandDrawn-4.svg"); outs << text; outs.flush(); } #ifdef RDK_BUILD_CAIRO_SUPPORT { MolDraw2DCairo drawer(350, 300); drawer.drawOptions().fontFile = fName; drawer.drawOptions().comicMode = true; drawer.drawMolecule(*m); drawer.finishDrawing(); drawer.writeDrawingText("testHandDrawn-4.png"); } #endif } SECTION("large") { auto m = "CC[C@H](C)[C@@H](C(=O)N[C@@H]([C@@H](C)CC)C(=O)N[C@@H](CCCCN)C(=O)N[C@@H](CC(=O)N)C(=O)N[C@@H](C)C(=O)N[C@@H](Cc1ccc(cc1)O)C(=O)N[C@@H](CCCCN)C(=O)N[C@@H](CCCCN)C(=O)NCC(=O)N[C@@H](CCC(=O)N)C(=O)O)NC(=O)[C@H](C)NC(=O)[C@H](CC(=O)N)NC(=O)[C@H](CCCCN)NC(=O)[C@H](Cc2ccccc2)NC(=O)[C@H](CC(C)C)NC(=O)[C@H]([C@@H](C)O)NC(=O)[C@H](C(C)C)NC(=O)[C@H](CC(C)C)NC(=O)[C@@H]3CCCN3C(=O)[C@H]([C@@H](C)O)NC(=O)[C@H](CCC(=O)N)NC(=O)[C@H](CO)NC(=O)[C@H](CCCCN)NC(=O)[C@H](CCC(=O)N)NC(=O)[C@H](CO)NC(=O)[C@H]([C@@H](C)O)NC(=O)[C@H](CCSC)NC(=O)[C@H](Cc4ccccc4)NC(=O)CNC(=O)CNC(=O)[C@H](Cc5ccc(cc5)O)N"_smiles; REQUIRE(m); RDDepict::preferCoordGen = true; MolDraw2DUtils::prepareMolForDrawing(*m); std::string fName = getenv("RDBASE"); fName += "/Data/Fonts/ComicNeue-Regular.ttf"; { MolDraw2DSVG drawer(900, 450); drawer.drawMolecule(*m); drawer.finishDrawing(); auto text = drawer.getDrawingText(); std::ofstream outs("testHandDrawn-5a.svg"); outs << text; outs.flush(); } { MolDraw2DSVG drawer(900, 450); drawer.drawOptions().fontFile = fName; drawer.drawOptions().comicMode = true; drawer.drawMolecule(*m); drawer.finishDrawing(); auto text = drawer.getDrawingText(); std::ofstream outs("testHandDrawn-5b.svg"); outs << text; outs.flush(); } #ifdef RDK_BUILD_CAIRO_SUPPORT { MolDraw2DCairo drawer(900, 450); drawer.drawOptions().fontFile = fName; drawer.drawOptions().comicMode = true; drawer.drawMolecule(*m); drawer.finishDrawing(); drawer.writeDrawingText("testHandDrawn-5.png"); } #endif } } TEST_CASE("drawMoleculeBrackets", "[extras]") { SECTION("basics") { auto m = R"CTAB( ACCLDraw11042015112D 0 0 0 0 0 999 V3000 M V30 BEGIN CTAB M V30 COUNTS 5 4 1 0 0 M V30 BEGIN ATOM M V30 1 C 7 -6.7813 0 0 M V30 2 C 8.0229 -6.1907 0 0 CFG=3 M V30 3 C 8.0229 -5.0092 0 0 M V30 4 C 9.046 -6.7814 0 0 M V30 5 C 10.0692 -6.1907 0 0 M V30 END ATOM M V30 BEGIN BOND M V30 1 1 1 2 M V30 2 1 2 3 M V30 3 1 2 4 M V30 4 1 4 5 M V30 END BOND M V30 BEGIN SGROUP M V30 1 SRU 1 ATOMS=(3 3 2 4) XBONDS=(2 1 4) BRKXYZ=(9 7.51 -7.08 0 7.51 - M V30 -5.9 0 0 0 0) BRKXYZ=(9 9.56 -5.9 0 9.56 -7.08 0 0 0 0) - M V30 CONNECT=HT LABEL=n M V30 END SGROUP M V30 END CTAB M END )CTAB"_ctab; REQUIRE(m); { MolDraw2DSVG drawer(350, 300); drawer.drawMolecule(*m); drawer.finishDrawing(); auto text = drawer.getDrawingText(); std::ofstream outs("testBrackets-1a.svg"); outs << text; outs.flush(); } { // rotation MolDraw2DSVG drawer(350, 300); drawer.drawOptions().rotate = 90; drawer.drawMolecule(*m); drawer.finishDrawing(); auto text = drawer.getDrawingText(); std::ofstream outs("testBrackets-1b.svg"); outs << text; outs.flush(); } { // centering MolDraw2DSVG drawer(350, 300); drawer.drawOptions().centreMoleculesBeforeDrawing = true; drawer.drawMolecule(*m); drawer.finishDrawing(); auto text = drawer.getDrawingText(); std::ofstream outs("testBrackets-1c.svg"); outs << text; outs.flush(); } { // rotation + centering MolDraw2DSVG drawer(350, 300); drawer.drawOptions().centreMoleculesBeforeDrawing = true; drawer.drawOptions().rotate = 90; drawer.drawMolecule(*m); drawer.finishDrawing(); auto text = drawer.getDrawingText(); std::ofstream outs("testBrackets-1d.svg"); outs << text; outs.flush(); } { // rotation MolDraw2DSVG drawer(350, 300); drawer.drawOptions().rotate = 180; drawer.drawMolecule(*m); drawer.finishDrawing(); auto text = drawer.getDrawingText(); std::ofstream outs("testBrackets-1e.svg"); outs << text; outs.flush(); } } SECTION("three brackets") { auto m = R"CTAB(three brackets Mrv2014 11052006542D 0 0 0 0 0 999 V3000 M V30 BEGIN CTAB M V30 COUNTS 6 5 1 0 0 M V30 BEGIN ATOM M V30 1 * -1.375 3.1667 0 0 M V30 2 C -0.0413 3.9367 0 0 M V30 3 C 1.2924 3.1667 0 0 M V30 4 * 2.626 3.9367 0 0 M V30 5 C 0.0003 5.6017 0 0 M V30 6 * 1.334 6.3717 0 0 M V30 END ATOM M V30 BEGIN BOND M V30 1 1 1 2 M V30 2 1 2 3 M V30 3 1 3 4 M V30 4 1 2 5 M V30 5 1 5 6 M V30 END BOND M V30 BEGIN SGROUP M V30 1 SRU 0 ATOMS=(3 2 3 5) XBONDS=(3 1 3 5) BRKXYZ=(9 0.0875 6.7189 0 - M V30 1.0115 5.1185 0 0 0 0) BRKXYZ=(9 1.3795 4.2839 0 2.3035 2.6835 0 0 0 - M V30 0) BRKXYZ=(9 -0.1285 2.8194 0 -1.0525 4.4198 0 0 0 0) CONNECT=HT - M V30 LABEL=n M V30 END SGROUP M V30 END CTAB M END)CTAB"_ctab; REQUIRE(m); { MolDraw2DSVG drawer(350, 300); drawer.drawMolecule(*m); drawer.finishDrawing(); auto text = drawer.getDrawingText(); std::ofstream outs("testBrackets-2a.svg"); outs << text; outs.flush(); } { // rotation MolDraw2DSVG drawer(350, 300); drawer.drawOptions().rotate = 90; drawer.drawMolecule(*m); drawer.finishDrawing(); auto text = drawer.getDrawingText(); std::ofstream outs("testBrackets-2b.svg"); outs << text; outs.flush(); } { // centering MolDraw2DSVG drawer(350, 300); drawer.drawOptions().centreMoleculesBeforeDrawing = true; drawer.drawMolecule(*m); drawer.finishDrawing(); auto text = drawer.getDrawingText(); std::ofstream outs("testBrackets-2c.svg"); outs << text; outs.flush(); } { // rotation + centering MolDraw2DSVG drawer(350, 300); drawer.drawOptions().centreMoleculesBeforeDrawing = true; drawer.drawOptions().rotate = 90; drawer.drawMolecule(*m); drawer.finishDrawing(); auto text = drawer.getDrawingText(); std::ofstream outs("testBrackets-2d.svg"); outs << text; outs.flush(); } } SECTION("ChEBI 59342") { // thanks to John Mayfield for pointing out the example auto m = R"CTAB(ChEBI59342 Marvin 05041012302D 29 30 0 0 1 0 999 V2000 10.1615 -7.7974 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 8.7305 -6.9763 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 8.7309 -7.8004 0.0000 C 0 0 2 0 0 0 0 0 0 0 0 0 9.4464 -8.2109 0.0000 C 0 0 2 0 0 0 0 0 0 0 0 0 8.0153 -8.2225 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 9.4464 -9.0437 0.0000 C 0 0 2 0 0 0 0 0 0 0 0 0 8.0138 -9.0500 0.0000 C 0 0 1 0 0 0 0 0 0 0 0 0 8.7293 -9.4606 0.0000 C 0 0 1 0 0 0 0 0 0 0 0 0 10.1669 -9.4529 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 7.3058 -9.4590 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 8.7368 -10.2801 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0 8.0263 -10.6992 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 8.0339 -11.5241 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 7.3081 -10.2933 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 8.7305 -5.3264 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 8.0159 -5.7369 0.0000 C 0 0 2 0 0 0 0 0 0 0 0 0 8.0159 -6.5618 0.0000 C 0 0 2 0 0 0 0 0 0 0 0 0 7.2936 -5.3263 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 7.2936 -6.9762 0.0000 C 0 0 2 0 0 0 0 0 0 0 0 0 6.5751 -5.7368 0.0000 C 0 0 1 0 0 0 0 0 0 0 0 0 6.5751 -6.5618 0.0000 C 0 0 1 0 0 0 0 0 0 0 0 0 7.2973 -7.8049 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 5.8681 -5.3263 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 5.8680 -6.9762 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0 5.1510 -6.5684 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 4.4392 -6.9856 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 5.1455 -5.7435 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 10.4142 -5.3560 0.0000 * 0 0 0 0 0 0 0 0 0 0 0 0 11.5590 -7.8297 0.0000 * 0 0 0 0 0 0 0 0 0 0 0 0 3 2 1 6 0 0 0 3 4 1 0 0 0 0 3 5 1 0 0 0 0 4 6 1 0 0 0 0 4 1 1 1 0 0 0 5 7 1 0 0 0 0 6 8 1 0 0 0 0 6 9 1 1 0 0 0 7 10 1 1 0 0 0 8 11 1 6 0 0 0 7 8 1 0 0 0 0 13 12 1 0 0 0 0 14 12 2 0 0 0 0 11 12 1 0 0 0 0 16 15 1 6 0 0 0 16 17 1 0 0 0 0 16 18 1 0 0 0 0 17 19 1 0 0 0 0 17 2 1 1 0 0 0 18 20 1 0 0 0 0 19 21 1 0 0 0 0 19 22 1 1 0 0 0 20 23 1 1 0 0 0 21 24 1 6 0 0 0 20 21 1 0 0 0 0 26 25 1 0 0 0 0 27 25 2 0 0 0 0 24 25 1 0 0 0 0 15 28 1 0 0 0 0 1 29 1 0 0 0 0 M STY 1 1 SRU M SCN 1 1 HT M SAL 1 15 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 M SAL 1 12 16 17 18 19 20 21 22 23 24 25 26 27 M SDI 1 4 9.4310 -4.9261 9.4165 -5.7510 M SDI 1 4 10.7464 -7.3983 10.7274 -8.2231 M SBL 1 2 30 29 M SMT 1 n M END)CTAB"_ctab; REQUIRE(m); { MolDraw2DSVG drawer(350, 300); drawer.drawMolecule(*m); drawer.finishDrawing(); auto text = drawer.getDrawingText(); std::ofstream outs("testBrackets-3a.svg"); outs << text; outs.flush(); } } SECTION("pathological bracket orientation") { { // including the bonds auto m = R"CTAB(bogus Mrv2014 11202009512D 0 0 0 0 0 999 V3000 M V30 BEGIN CTAB M V30 COUNTS 9 8 1 0 1 M V30 BEGIN ATOM M V30 1 C 23.5462 -14.464 0 0 M V30 2 C 20.8231 -13.0254 0 0 M V30 3 C 20.8776 -14.5628 0 0 M V30 4 C 22.2391 -15.2819 0 0 M V30 5 C 16.2969 -9.9426 0 0 M V30 6 C 14.963 -10.7089 0 0 M V30 7 C 19.463 -12.2987 0 0 M V30 8 * 19.4398 -9.9979 0 0 M V30 9 * 26.1554 -14.4332 0 0 M V30 END ATOM M V30 BEGIN BOND M V30 1 1 3 4 M V30 2 1 6 7 M V30 3 1 5 8 M V30 4 1 1 9 M V30 5 1 7 2 M V30 6 1 6 5 M V30 7 1 4 1 M V30 8 1 3 2 M V30 END BOND M V30 BEGIN SGROUP M V30 1 SRU 0 ATOMS=(7 4 3 7 6 5 2 1) XBONDS=(2 3 4) BRKXYZ=(9 17.6045 - M V30 -9.1954 0 17.5775 -10.7352 0 0 0 0) BRKXYZ=(9 24.6113 -13.6813 0 - M V30 24.6296 -15.2213 0 0 0 0) CONNECT=HT LABEL=n M V30 END SGROUP M V30 END CTAB M END )CTAB"_ctab; REQUIRE(m); MolDraw2DSVG drawer(350, 300); drawer.drawMolecule(*m); drawer.finishDrawing(); auto text = drawer.getDrawingText(); std::ofstream outs("testBrackets-4a.svg"); outs << text; outs.flush(); } { // no bonds in the sgroup, the bracket should point the other way // (towards the majority of the atoms in the sgroup) auto m = R"CTAB(bogus Mrv2014 11202009512D 0 0 0 0 0 999 V3000 M V30 BEGIN CTAB M V30 COUNTS 9 8 1 0 1 M V30 BEGIN ATOM M V30 1 C 23.5462 -14.464 0 0 M V30 2 C 20.8231 -13.0254 0 0 M V30 3 C 20.8776 -14.5628 0 0 M V30 4 C 22.2391 -15.2819 0 0 M V30 5 C 16.2969 -9.9426 0 0 M V30 6 C 14.963 -10.7089 0 0 M V30 7 C 19.463 -12.2987 0 0 M V30 8 * 19.4398 -9.9979 0 0 M V30 9 * 26.1554 -14.4332 0 0 M V30 END ATOM M V30 BEGIN BOND M V30 1 1 3 4 M V30 2 1 6 7 M V30 3 1 5 8 M V30 4 1 1 9 M V30 5 1 7 2 M V30 6 1 6 5 M V30 7 1 4 1 M V30 8 1 3 2 M V30 END BOND M V30 BEGIN SGROUP M V30 1 SRU 0 ATOMS=(7 4 3 7 6 5 2 1) BRKXYZ=(9 17.6045 - M V30 -9.1954 0 17.5775 -10.7352 0 0 0 0) BRKXYZ=(9 24.6113 -13.6813 0 - M V30 24.6296 -15.2213 0 0 0 0) CONNECT=HT LABEL=n M V30 END SGROUP M V30 END CTAB M END )CTAB"_ctab; REQUIRE(m); MolDraw2DSVG drawer(350, 300); drawer.drawMolecule(*m); drawer.finishDrawing(); auto text = drawer.getDrawingText(); std::ofstream outs("testBrackets-4b.svg"); outs << text; outs.flush(); } } SECTION("comic brackets (no font though)") { auto m = R"CTAB( ACCLDraw11042015112D 0 0 0 0 0 999 V3000 M V30 BEGIN CTAB M V30 COUNTS 5 4 1 0 0 M V30 BEGIN ATOM M V30 1 C 7 -6.7813 0 0 M V30 2 C 8.0229 -6.1907 0 0 CFG=3 M V30 3 C 8.0229 -5.0092 0 0 M V30 4 C 9.046 -6.7814 0 0 M V30 5 C 10.0692 -6.1907 0 0 M V30 END ATOM M V30 BEGIN BOND M V30 1 1 1 2 M V30 2 1 2 3 M V30 3 1 2 4 M V30 4 1 4 5 M V30 END BOND M V30 BEGIN SGROUP M V30 1 SRU 1 ATOMS=(3 3 2 4) XBONDS=(2 1 4) BRKXYZ=(9 7.51 -7.08 0 7.51 - M V30 -5.9 0 0 0 0) BRKXYZ=(9 9.56 -5.9 0 9.56 -7.08 0 0 0 0) - M V30 CONNECT=HT LABEL=n M V30 END SGROUP M V30 END CTAB M END )CTAB"_ctab; REQUIRE(m); { MolDraw2DSVG drawer(350, 300); drawer.drawOptions().comicMode = true; drawer.drawMolecule(*m); drawer.finishDrawing(); auto text = drawer.getDrawingText(); std::ofstream outs("testBrackets-5a.svg"); outs << text; outs.flush(); } } } #ifdef RDK_BUILD_CAIRO_SUPPORT TEST_CASE("github #3543: Error adding PNG metadata when kekulize=False", "[bug][metadata][png]") { SECTION("basics") { auto m = "n1cccc1"_smarts; m->updatePropertyCache(false); MolDraw2DCairo drawer(350, 300); bool kekulize = false; MolDraw2DUtils::prepareMolForDrawing(*m, kekulize); drawer.drawOptions().prepareMolsBeforeDrawing = false; drawer.drawMolecule(*m); drawer.finishDrawing(); auto png = drawer.getDrawingText(); } SECTION("as reported") { auto m = "n1cnc2c(n)ncnc12"_smarts; m->updatePropertyCache(false); MolDraw2DCairo drawer(350, 300); bool kekulize = false; MolDraw2DUtils::prepareMolForDrawing(*m, kekulize); drawer.drawOptions().prepareMolsBeforeDrawing = false; drawer.drawMolecule(*m); drawer.finishDrawing(); auto png = drawer.getDrawingText(); } } #endif