// // Copyright (c) 2024, Glysade Inc // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials provided // with the distribution. // * Neither the name of Novartis Institutes for BioMedical Research Inc. // nor the names of its contributors may be used to endorse or promote // products derived from this software without specific prior written // permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // #include "chemdraw.h" #include #include #include #include "ChemDrawStartInclude.h" #include "chemdraw/CDXStdObjects.h" #include "ChemDrawEndInclude.h" namespace RDKit { namespace v2 { const double DEFAULT_CDX_BOND_LENGTH = 14.4; namespace { // Do we need to set explicit hs in chemdraw, this uses basically the same // logic as SmilesWriter bool needsExplicitHs(const Atom *atom) { auto num = atom->getAtomicNum(); const INT_VECT &defaultVs = PeriodicTable::getTable()->getValenceList(num); int totalValence = atom->getTotalValence(); bool nonStandard = false; if (atom->getNumRadicalElectrons()) { nonStandard = true; } else if ((num == 7 || num == 15) && atom->getIsAromatic() && atom->getNumExplicitHs()) { // another type of "nonstandard" valence is an aromatic N or P with // explicit Hs indicated: nonStandard = true; } else { nonStandard = (totalValence != defaultVs.front() && atom->getTotalNumHs()); } return nonStandard; } } // namespace std::string MolToChemDrawBlock(const ROMol &mol, CDXFormat format) { RWMol trmol(mol); MolOps::Kekulize(trmol); if (!trmol.getNumConformers()) { RDDepict::compute2DCoords(trmol); } CDXObjectID object_id = 1; CDXDocument document(object_id++); auto *page = new CDXPage(object_id++); document.m_bondLength = DEFAULT_CDX_BOND_LENGTH; document.m_flags |= CDXDocument::CDXDocumentProperty1::has_bondLength; auto *fragment = new CDXFragment(object_id++); page->AddChild(fragment); std::vector nodes; nodes.reserve(trmol.getNumAtoms()); const Conformer *conf = nullptr; if (trmol.getNumConformers() == 0) { RDDepict::compute2DCoords(trmol); } conf = &trmol.getConformer(0); bool is3D = conf->is3D(); // I REALLY don't know why this is 2*DEFAULT_CDX_BOND_LENGTH but it looks // right // when loading the CDX into ChemDraw // We convert the average bond length into the target bond length here double target_bond_length = 2 * DEFAULT_CDX_BOND_LENGTH; double dist = 0.0; for (auto bond : trmol.bonds()) { auto pos1 = conf->getAtomPos(bond->getBeginAtomIdx()); auto pos2 = conf->getAtomPos(bond->getEndAtomIdx()); dist += (pos1 - pos2).length(); } dist /= trmol.getNumBonds(); double scale = is3D ? 1. : target_bond_length / dist; auto wedgeBonds = Chirality::pickBondsToWedge(trmol, nullptr, conf); for (auto &atom : trmol.atoms()) { auto *node = new CDXNode(object_id + atom->getIdx()); auto pos = conf->getAtomPos(atom->getIdx()); if (is3D) { node->Position3D(CDXPoint3D(CDXCoordinatefromPoints(pos.x), -CDXCoordinatefromPoints(pos.y), CDXCoordinatefromPoints(pos.z))); } else { node->Position(CDXPoint2D(CDXCoordinatefromPoints(scale * pos.x), CDXCoordinatefromPoints(-scale * pos.y))); } node->m_nodeType = kCDXNodeType_Element; node->m_isotope = atom->getIsotope(); node->m_elementNum = atom->getAtomicNum(); // Use the same logic from the smiles writer needs brackets // // node->m_numHydrogens = atom->getNumExplicitHs() ? // atom->getNumExplicitHs() // : kNumHydrogenUnspecified; node->m_numHydrogens = needsExplicitHs(atom) ? atom->getTotalNumHs() : kNumHydrogenUnspecified; node->m_charge = atom->getFormalCharge() * 0x1000000; if (atom->getFormalCharge() || atom->getNumRadicalElectrons() != 0) { node->m_numHydrogens = atom->getTotalNumHs(); // XXX is this right? We seem to need to set // it with charges } if (atom->getNumRadicalElectrons()) { switch (atom->getNumRadicalElectrons()) { case 0: break; case 1: node->m_radical = kCDXRadical_Singlet; break; case 2: break; case 3: break; } } // this might be a bit slow, perhaps make into a map... unsigned int sgnum = 0; for (auto &sg : trmol.getStereoGroups()) { sgnum++; for (auto &sgatom : sg.getAtoms()) { if (atom->getIdx() == sgatom->getIdx()) { switch (sg.getGroupType()) { case StereoGroupType::STEREO_ABSOLUTE: node->m_enhancedStereoType = kCDXEnhancedStereo_Absolute; break; case StereoGroupType::STEREO_OR: node->m_enhancedStereoType = kCDXEnhancedStereo_Or; break; case StereoGroupType::STEREO_AND: node->m_enhancedStereoType = kCDXEnhancedStereo_And; break; } node->m_enhancedStereoGroupNum = sgnum; } } } nodes.push_back(node); fragment->AddChild(node); } for (auto &bond : trmol.bonds()) { auto *cdxbond = new CDXBond(object_id + mol.getNumAtoms() + bond->getIdx()); int dirCode = 0; bool reverse = false; Chirality::GetMolFileBondStereoInfo(bond, wedgeBonds, conf, dirCode, reverse); switch (bond->getBondType()) { case Bond::BondType::SINGLE: cdxbond->m_bondOrder = kCDXBondOrder_Single; break; case Bond::DOUBLE: cdxbond->m_bondOrder = kCDXBondOrder_Double; break; case Bond::TRIPLE: cdxbond->m_bondOrder = kCDXBondOrder_Triple; break; case Bond::QUADRUPLE: cdxbond->m_bondOrder = kCDXBondOrder_Quadruple; break; case Bond::QUINTUPLE: cdxbond->m_bondOrder = kCDXBondOrder_Quintuple; break; case Bond::HEXTUPLE: cdxbond->m_bondOrder = kCDXBondOrder_Sextuple; break; case Bond::ONEANDAHALF: cdxbond->m_bondOrder = kCDXBondOrder_OneHalf; break; case Bond::TWOANDAHALF: cdxbond->m_bondOrder = kCDXBondOrder_TwoHalf; break; case Bond::THREEANDAHALF: cdxbond->m_bondOrder = kCDXBondOrder_ThreeHalf; break; case Bond::FOURANDAHALF: cdxbond->m_bondOrder = kCDXBondOrder_FourHalf; break; case Bond::FIVEANDAHALF: cdxbond->m_bondOrder = kCDXBondOrder_FiveHalf; break; case Bond::AROMATIC: cdxbond->m_bondOrder = kCDXBondOrder_OneHalf; break; case Bond::IONIC: cdxbond->m_bondOrder = kCDXBondOrder_Ionic; break; case Bond::HYDROGEN: cdxbond->m_bondOrder = kCDXBondOrder_Hydrogen; break; case Bond::THREECENTER: cdxbond->m_bondOrder = kCDXBondOrder_ThreeCenter; break; case Bond::DATIVE: cdxbond->m_bondOrder = kCDXBondOrder_Dative; break; case Bond::UNSPECIFIED: { auto query = describeQuery(bond); if (query == "DoubleOrAromaticBond 1 = val\n") { cdxbond->m_bondOrder = kCDXBondOrder_DoubleOrAromatic; } else if (query == "SingleOrAromaticBond 1 = val\n") { cdxbond->m_bondOrder = kCDXBondOrder_SingleOrAromatic; } else if (query == "SingleOrDoubleBond 1 = val\n") { cdxbond->m_bondOrder = kCDXBondOrder_SingleOrDouble; } else { cdxbond->m_bondOrder = kCDXBondOrder_Any; } } break; case Bond::DATIVEONE: case Bond::DATIVEL: case Bond::DATIVER: case Bond::OTHER: case Bond::ZERO: // unhandled break; } cdxbond->Connects(nodes[bond->getBeginAtomIdx()], nodes[bond->getEndAtomIdx()]); switch (dirCode) { case 6: // swap 1 and 6 due to swapped y cdxbond->m_display = reverse ? kCDXBondDisplay_WedgedHashEnd : kCDXBondDisplay_WedgedHashBegin; break; case 1: cdxbond->m_display = reverse ? kCDXBondDisplay_WedgeEnd : kCDXBondDisplay_WedgeBegin; break; case 3: cdxbond->m_display = kCDXBondDisplay_Wavy; break; default: break; } if (bond->getBondDir() == Bond::BondDir::EITHERDOUBLE || bond->getBondDir() == Bond::BondDir::UNKNOWN) { cdxbond->m_display = kCDXBondDisplay_Wavy; } fragment->AddChild(cdxbond); } document.AddChild(page); document.m_colorTable.m_colors .clear(); // if this isn't empty something fails. std::ostringstream os; if (format == CDXFormat::CDXML) { os << kCDXML_HeaderString; XMLDataSink ds(os); document.XMLWrite(ds); } else { CDXostream ds(os); CDXWriteDocToStorage(&document, ds); } return os.str(); } } // namespace v2 } // namespace RDKit