api/wasm/indigo-ketcher/indigo-ketcher.cpp (931 lines of code) (raw):

#include <iomanip> #include <map> #include <set> #include <sstream> #include <string> #include <unordered_map> #include <utility> #include <vector> #include <emscripten.h> #include <emscripten/bind.h> #define RAPIDJSON_HAS_STDSTRING 1 #include <cppcodec/base64_rfc4648.hpp> #include <rapidjson/document.h> #include <rapidjson/writer.h> #include "indigo-inchi.h" #include "indigo.h" #ifndef INDIGO_NO_RENDER #include "indigo-renderer.h" #endif namespace indigo { using cstring = const char*; EM_JS(void, jsThrow, (cstring str), { throw UTF8ToString(str); }); EM_JS(void, print_jsn, (cstring str, int n), { console.log(UTF8ToString(str) + n); }); #ifdef _DEBUG EM_JS(void, print_js, (cstring str), { console.log(UTF8ToString(str)); }); #else void print_js(const cstring) { } #endif int _checkResult(int result) { if (result < 0) { jsThrow(indigoGetLastError()); } return result; } double _checkResultFloat(double result) { if (result < 0.5) { jsThrow(indigoGetLastError()); } return result; } cstring _checkResultString(cstring result) { if (result == nullptr) { jsThrow(indigoGetLastError()); } return result; } struct IndigoObject { const int id; IndigoObject(const IndigoObject&) = delete; IndigoObject(IndigoObject&&) = default; IndigoObject operator=(const IndigoObject&) = delete; IndigoObject& operator=(IndigoObject&&) = delete; explicit IndigoObject(const int id) : id(id) { } ~IndigoObject() { indigoFree(id); } }; struct IndigoKetcherObject { enum KOType { EKETMolecule, EKETMoleculeQuery, EKETReaction, EKETReactionQuery, EKETDocument, }; KOType objtype; private: std::shared_ptr<const IndigoObject> indigo_object; std::shared_ptr<const IndigoObject> parent = nullptr; public: explicit IndigoKetcherObject(const int id, const KOType type, std::shared_ptr<const IndigoObject> parent = nullptr) : indigo_object(std::make_shared<IndigoObject>(id)), objtype(type), parent(std::move(parent)) { } void set(const int id, const KOType type, std::shared_ptr<const IndigoObject> parent = nullptr) { indigo_object = std::make_shared<IndigoObject>(id); objtype = type; } std::string toString(const std::map<std::string, std::string>& options, const std::string& outputFormat, int library = -1) const { print_js("toString:"); std::string result; if (outputFormat == "molfile" || outputFormat == "rxnfile" || outputFormat == "chemical/x-mdl-molfile" || outputFormat == "chemical/x-mdl-rxnfile") { if (is_reaction()) result = _checkResultString(indigoRxnfile(id())); else result = _checkResultString(indigoMolfile(id())); } else if (outputFormat == "smiles" || outputFormat == "chemical/x-daylight-smiles" || outputFormat == "chemical/x-chemaxon-cxsmiles") { if (options.count("smiles") > 0 && options.at("smiles") == "canonical") { result = _checkResultString(indigoCanonicalSmiles(id())); } else { if (outputFormat == "chemical/x-chemaxon-cxsmiles") indigoSetOption("smiles-saving-format", "chemaxon"); else if (outputFormat == "chemical/x-daylight-smiles") indigoSetOption("smiles-saving-format", "daylight"); result = _checkResultString(indigoSmiles(id())); } } else if (outputFormat == "sequence" || outputFormat == "chemical/x-sequence") { result = _checkResultString(indigoSequence(id(), library)); } else if (outputFormat == "peptide-sequence-3-letter" || outputFormat == "chemical/x-peptide-sequence-3-letter") { result = _checkResultString(indigoSequence3Letter(id(), library)); } else if (outputFormat == "fasta" || outputFormat == "chemical/x-fasta") { result = _checkResultString(indigoFasta(id(), library)); } else if (outputFormat == "idt" || outputFormat == "chemical/x-idt") { result = _checkResultString(indigoIdt(id(), library)); } else if (outputFormat == "helm" || outputFormat == "chemical/x-helm") { result = _checkResultString(indigoHelm(id(), library)); } else if (outputFormat == "smarts" || outputFormat == "chemical/x-daylight-smarts") { if (options.count("smarts") > 0 && options.at("smarts") == "canonical") result = _checkResultString(indigoCanonicalSmarts(id())); else result = _checkResultString(indigoSmarts(id())); } else if (outputFormat == "cml" || outputFormat == "chemical/x-cml") { result = _checkResultString(indigoCml(id())); } else if (outputFormat == "cdxml" || outputFormat == "chemical/x-cdxml") { result = _checkResultString(indigoCdxml(id())); } else if (outputFormat == "cdx" || outputFormat == "chemical/x-cdx") { result = _checkResultString(indigoCdxBase64(id())); } else if (outputFormat == "inchi" || outputFormat == "chemical/x-inchi") { result = _checkResultString(indigoInchiGetInchi(id())); } else if (outputFormat == "inchi-key" || outputFormat == "chemical/x-inchi-key") { std::string inchi_str = _checkResultString(indigoInchiGetInchi(id())); result = _checkResultString(indigoInchiGetInchiKey(inchi_str.c_str())); } else if (outputFormat == "inchi-aux" || outputFormat == "chemical/x-inchi-aux") { std::stringstream ss; ss << _checkResultString(indigoInchiGetInchi(id())) << '\n' << _checkResultString(indigoInchiGetAuxInfo()); result = ss.str(); } else if (outputFormat == "ket" || outputFormat == "chemical/x-indigo-ket") { print_js(outputFormat.c_str()); result = _checkResultString(indigoJson(id())); } else if (outputFormat == "sdf" || outputFormat == "chemical/x-sdf") { auto buffer = IndigoObject(_checkResult(indigoWriteBuffer())); auto comp_it = IndigoObject(_checkResult(indigoIterateComponents(id()))); while (indigoHasNext(comp_it.id)) { const auto frag = IndigoObject(_checkResult(indigoNext(comp_it.id))); const auto mol = IndigoObject(_checkResult(indigoClone(frag.id))); indigoSdfAppend(buffer.id, mol.id); } print_js(outputFormat.c_str()); result = _checkResultString(indigoToString(buffer.id)); } else if (outputFormat == "rdf" || outputFormat == "chemical/x-rdf") { auto buffer = IndigoObject(_checkResult(indigoWriteBuffer())); auto reac_it = IndigoObject(_checkResult(indigoIterateReactions(id()))); indigoRdfHeader(buffer.id); while (indigoHasNext(reac_it.id)) { const auto reac_obj = IndigoObject(_checkResult(indigoNext(reac_it.id))); const auto reac = IndigoObject(_checkResult(indigoClone(reac_obj.id))); indigoRdfAppend(buffer.id, reac.id); } print_js(outputFormat.c_str()); result = _checkResultString(indigoToString(buffer.id)); } else { std::stringstream ss; ss << "Unknown output format: " << outputFormat; jsThrow(ss.str().c_str()); return ""; // suppress warning } auto out = options.find("output-content-type"); if (out != options.end() && out->second == "application/json") { std::string originalFormat = _checkResultString(indigoGetOriginalFormat(id())); rapidjson::Document resultJson; auto& allocator = resultJson.GetAllocator(); resultJson.SetObject(); resultJson.AddMember("struct", result, allocator); resultJson.AddMember("format", outputFormat, allocator); resultJson.AddMember("original_format", originalFormat, allocator); rapidjson::StringBuffer buffer; rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); resultJson.Accept(writer); return buffer.GetString(); } else { return result; } } IndigoKetcherObject substructure(const std::vector<int>& selected_atoms) const { std::vector<int> mutable_selected_atoms(selected_atoms); int* selected_atoms_array = &mutable_selected_atoms[0]; return IndigoKetcherObject(_checkResult(indigoGetSubmolecule(id(), static_cast<int>(selected_atoms.size()), selected_atoms_array)), objtype, indigo_object); } int id() const { return indigo_object->id; } bool is_reaction() const { return objtype == EKETReaction || objtype == EKETReactionQuery; } }; void indigoSetOptions(const std::map<std::string, std::string>& options) { std::set<std::string> to_skip{"smiles", "smarts", "input-format", "output-content-type", "monomerLibrary"}; for (const auto& option : options) { if (to_skip.count(option.first) < 1) { _checkResult(indigoSetOption(option.first.c_str(), option.second.c_str())); } } } class IndigoSession { private: const qword id; public: IndigoSession() : id(indigoAllocSessionId()) { indigoSetSessionId(id); _checkResult(indigoInchiInit(id)); } ~IndigoSession() { indigoInchiDispose(id); indigoReleaseSessionId(id); } IndigoSession(const IndigoSession&) = delete; IndigoSession& operator=(const IndigoSession&) = delete; IndigoSession(IndigoSession&&) = delete; IndigoSession& operator=(IndigoSession&&) = delete; qword getSessionId() const { return id; } }; class IndigoRendererSession { private: const qword _sessionId; public: explicit IndigoRendererSession(const qword sessionId) : _sessionId(sessionId) { #ifndef INDIGO_NO_RENDER indigoRendererInit(_sessionId); #else jsThrow("No renderer included in this wasm bundle!"); #endif } ~IndigoRendererSession() { #ifndef INDIGO_NO_RENDER indigoRendererDispose(_sessionId); #else jsThrow("No renderer included in this wasm bundle!"); #endif } IndigoRendererSession(const IndigoRendererSession&) = delete; IndigoRendererSession& operator=(const IndigoRendererSession&) = delete; IndigoRendererSession(IndigoRendererSession&&) = delete; IndigoRendererSession& operator=(IndigoRendererSession&&) = delete; }; IndigoKetcherObject loadMoleculeOrReaction(const std::string& data, const std::map<std::string, std::string>& options, int library = -1, bool use_document = false) { static std::unordered_map<std::string, std::string> seq_formats = {{"chemical/x-peptide-sequence", "PEPTIDE"}, {"chemical/x-peptide-sequence-3-letter", "PEPTIDE-3-LETTER"}, {"chemical/x-rna-sequence", "RNA"}, {"chemical/x-dna-sequence", "DNA"}}; static std::unordered_map<std::string, std::string> fasta_formats = { {"chemical/x-peptide-fasta", "PEPTIDE"}, {"chemical/x-rna-fasta", "RNA"}, {"chemical/x-dna-fasta", "DNA"}}; print_js("loadMoleculeOrReaction:"); std::vector<std::string> exceptionMessages; exceptionMessages.reserve(4); int objectId = -1; auto input_format = options.find("input-format"); if (input_format != options.end() && (input_format->second == "smarts" || input_format->second == "chemical/x-daylight-smarts")) { print_js("load as smarts"); objectId = indigoLoadSmartsFromBuffer(data.c_str(), data.size()); if (objectId >= 0) { return IndigoKetcherObject(objectId, IndigoKetcherObject::EKETMoleculeQuery); } exceptionMessages.emplace_back(indigoGetLastError()); // Let's try reaction print_js("try as reaction"); objectId = indigoLoadReactionSmartsFromBuffer(data.c_str(), data.size()); if (objectId >= 0) { return IndigoKetcherObject(objectId, IndigoKetcherObject::EKETReaction); } exceptionMessages.emplace_back(indigoGetLastError()); } else if (input_format != options.end() && seq_formats.count(input_format->second)) { auto seq_it = seq_formats.find(input_format->second); objectId = indigoLoadSequenceFromString(data.c_str(), seq_it->second.c_str(), library); if (objectId >= 0) return IndigoKetcherObject(objectId, IndigoKetcherObject::EKETMolecule); exceptionMessages.emplace_back(indigoGetLastError()); } else if (input_format != options.end() && fasta_formats.count(input_format->second)) { auto fasta_it = fasta_formats.find(input_format->second); objectId = indigoLoadFastaFromString(data.c_str(), fasta_it->second.c_str(), library); if (objectId >= 0) return IndigoKetcherObject(objectId, IndigoKetcherObject::EKETMolecule); exceptionMessages.emplace_back(indigoGetLastError()); } else if (input_format != options.end() && input_format->second == "chemical/x-idt") { objectId = indigoLoadIdtFromString(data.c_str(), library); if (objectId >= 0) return IndigoKetcherObject(objectId, IndigoKetcherObject::EKETMolecule); exceptionMessages.emplace_back(indigoGetLastError()); } else if (input_format != options.end() && input_format->second == "chemical/x-helm") { objectId = indigoLoadHelmFromString(data.c_str(), library); if (objectId >= 0) return IndigoKetcherObject(objectId, IndigoKetcherObject::EKETMolecule); exceptionMessages.emplace_back(indigoGetLastError()); } else { if (data.find("InChI") == 0) { objectId = indigoInchiLoadMolecule(data.c_str()); if (objectId >= 0) return IndigoKetcherObject(objectId, IndigoKetcherObject::EKETMolecule); } if (use_document) { print_js("try as document"); objectId = indigoLoadKetDocumentFromBuffer(data.c_str(), data.size()); if (objectId >= 0) { return IndigoKetcherObject(objectId, IndigoKetcherObject::EKETDocument); } } bool query = false; auto i = options.find("query"); if (i != options.end() and i->second == "true") { query = true; } if (!query) { // Let's try a simple molecule print_js("try as molecule"); objectId = indigoLoadMoleculeFromBuffer(data.c_str(), data.size()); if (objectId >= 0) { return IndigoKetcherObject(objectId, IndigoKetcherObject::EKETMolecule); } exceptionMessages.emplace_back(indigoGetLastError()); // Let's try reaction print_js("try as reaction"); objectId = indigoLoadReactionFromBuffer(data.c_str(), data.size()); if (objectId >= 0) { return IndigoKetcherObject(objectId, IndigoKetcherObject::EKETReaction); } exceptionMessages.emplace_back(indigoGetLastError()); if (library >= 0) { print_js("try as IDT"); objectId = indigoLoadIdtFromString(data.c_str(), library); if (objectId >= 0) { return IndigoKetcherObject(objectId, IndigoKetcherObject::EKETDocument); } print_js("try as HELM"); objectId = indigoLoadHelmFromString(data.c_str(), library); if (objectId >= 0) { return IndigoKetcherObject(objectId, IndigoKetcherObject::EKETDocument); } } } exceptionMessages.emplace_back(indigoGetLastError()); // Let's try query molecule print_js("try as query molecule"); objectId = indigoLoadQueryMoleculeFromBuffer(data.c_str(), data.size()); if (objectId >= 0) { return IndigoKetcherObject(objectId, IndigoKetcherObject::EKETMoleculeQuery); } exceptionMessages.emplace_back(indigoGetLastError()); // Let's try query reaction print_js("try as query reaction"); objectId = indigoLoadQueryReactionFromBuffer(data.c_str(), data.size()); if (objectId >= 0) { return IndigoKetcherObject(objectId, IndigoKetcherObject::EKETReactionQuery); } } // It's not anything we can load, let's throw an exception std::stringstream ss; ss << "Given string could not be loaded as (query or plain) molecule or reaction, see the error messages: "; for (const auto& exceptionMessage : exceptionMessages) { ss << "'" << exceptionMessage << "', "; } std::string exceptionText = ss.str(); exceptionText.pop_back(); exceptionText.pop_back(); jsThrow(exceptionText.c_str()); } std::string version() { return _checkResultString(indigoVersion()); } std::string versionInfo() { return _checkResultString(indigoVersionInfo()); } std::string convert(const std::string& data, const std::string& outputFormat, const std::map<std::string, std::string>& options) { const IndigoSession session; std::map<std::string, std::string> options_copy; for (const auto& option : options) { if (option.first != "monomerLibrary") { options_copy[option.first] = option.second; } } int library = -1; auto monomerLibrary = options.find("monomerLibrary"); if (monomerLibrary != options.end() && monomerLibrary->second.size()) { library = indigoLoadMonomerLibraryFromString(monomerLibrary->second.c_str()); } else { library = indigoLoadMonomerLibraryFromString("{\"root\":{}}"); } if (outputFormat.find("smarts") != std::string::npos) { options_copy["query"] = "true"; } indigoSetOptions(options); std::string input_format = "ket"; if (const auto& it = options.find("input-format"); it != options.end()) input_format = it->second; bool use_document = false; static const std::set<std::string> document_formats{"sequence", "chemical/x-sequence", "peptide-sequence-3-letter", "chemical/x-peptide-sequence-3-letter", "fasta", "chemical/x-fasta", "idt", "chemical/x-idt", "helm", "chemical/x-helm"}; if ((input_format == "ket" || input_format == "application/json") && outputFormat.size() > 0 && document_formats.count(outputFormat) > 0) use_document = true; IndigoKetcherObject iko = loadMoleculeOrReaction(data, options_copy, library, use_document); return iko.toString(options, outputFormat.size() ? outputFormat : "ket", library); } std::string convert_explicit_hydrogens(const std::string& data, const std::string& mode, const std::string& outputFormat, const std::map<std::string, std::string>& options) { const IndigoSession session; indigoSetOptions(options); std::map<std::string, std::string> options_copy = options; if (outputFormat.find("smarts") != std::string::npos) { options_copy["query"] = "true"; } IndigoKetcherObject iko = loadMoleculeOrReaction(data, options_copy); if (mode == "fold") { _checkResult(indigoFoldHydrogens(iko.id())); } else if (mode == "unfold") { _checkResult(indigoUnfoldHydrogens(iko.id())); } else if (mode == "auto") { _checkResult(indigoFoldUnfoldHydrogens(iko.id())); } return iko.toString(options, outputFormat.size() ? outputFormat : "ket"); } std::string aromatize(const std::string& data, const std::string& outputFormat, const std::map<std::string, std::string>& options) { const IndigoSession session; indigoSetOptions(options); const auto iko = loadMoleculeOrReaction(data.c_str(), options); _checkResult(indigoAromatize(iko.id())); return iko.toString(options, outputFormat.size() ? outputFormat : "ket"); } std::string dearomatize(const std::string& data, const std::string& outputFormat, const std::map<std::string, std::string>& options) { const IndigoSession session; indigoSetOptions(options); const auto iko = loadMoleculeOrReaction(data.c_str(), options); _checkResult(indigoDearomatize(iko.id())); return iko.toString(options, outputFormat.size() ? outputFormat : "ket"); } std::string layout(const std::string& data, const std::string& outputFormat, const std::map<std::string, std::string>& options) { const IndigoSession session; indigoSetOptions(options); std::map<std::string, std::string> options_copy = options; if (outputFormat.find("smarts") != std::string::npos) { options_copy["query"] = "true"; } const auto iko = loadMoleculeOrReaction(data.c_str(), options_copy); _checkResult(indigoLayout(iko.id())); return iko.toString(options, outputFormat.size() ? outputFormat : "ket"); } std::string clean2d(const std::string& data, const std::string& outputFormat, const std::map<std::string, std::string>& options, const std::vector<int>& selected_atoms) { const IndigoSession session; indigoSetOptions(options); auto iko = loadMoleculeOrReaction(data.c_str(), options); const auto& subiko = (selected_atoms.empty()) ? iko : iko.substructure(selected_atoms); _checkResult(indigoClean2d(subiko.id())); return iko.toString(options, outputFormat.size() ? outputFormat : "ket"); } std::string automap(const std::string& data, const std::string& mode, const std::string& outputFormat, const std::map<std::string, std::string>& options) { const IndigoSession session; indigoSetOptions(options); const auto iko = loadMoleculeOrReaction(data.c_str(), options); _checkResult(indigoAutomap(iko.id(), mode.c_str())); return iko.toString(options, outputFormat.size() ? outputFormat : "ket"); } std::string check(const std::string& data, const std::string& properties, const std::map<std::string, std::string>& options) { const IndigoSession session; indigoSetOptions(options); const auto iko = loadMoleculeOrReaction(data.c_str(), options); return _checkResultString(indigoCheckObj(iko.id(), properties.c_str())); } std::string calculateCip(const std::string& data, const std::string& outputFormat, const std::map<std::string, std::string>& options) { const IndigoSession session; indigoSetOptions(options); indigoSetOption("json-saving-add-stereo-desc", "true"); indigoSetOption("molfile-saving-add-stereo-desc", "true"); const auto iko = loadMoleculeOrReaction(data.c_str(), options); return iko.toString(options, outputFormat.size() ? outputFormat : "ket"); } void qmol2mol(IndigoKetcherObject& iko, const std::set<int>& selected_set) { IndigoObject qc(_checkResult(indigoClone(iko.id()))); // create query copy const auto atoms_iterator = IndigoObject(_checkResult(indigoIterateAtoms(qc.id))); while (const auto atom_id = _checkResult(indigoNext(atoms_iterator.id))) { int aix = _checkResult(indigoIndex(atom_id)); if (selected_set.size() && selected_set.find(aix) == selected_set.end()) _checkResult(indigoResetAtom(atom_id, "C")); // replace not selected atoms with C } const auto bonds_iterator = IndigoObject(_checkResult(indigoIterateBonds(qc.id))); while (const auto bond_id = _checkResult(indigoNext(bonds_iterator.id))) { int bix = _checkResult(indigoIndex(bond_id)); const auto beg = _checkResult(indigoIndex(_checkResult(indigoSource(bond_id)))); const auto end = _checkResult(indigoIndex(_checkResult(indigoDestination(bond_id)))); if (selected_set.size() && selected_set.find(beg) == selected_set.end() && selected_set.find(end) == selected_set.end()) _checkResult(indigoRemoveBonds(qc.id, 1, &bix)); } auto mid = indigoLoadMoleculeFromString(indigoMolfile(qc.id)); if (mid < 0) jsThrow("Cannot calculate properties for structures with query features!"); iko.set(mid, IndigoKetcherObject::EKETMolecule); } class VectorStringSemicoloned { const std::vector<std::string>& vec; public: explicit VectorStringSemicoloned(const std::vector<std::string>& v) : vec(v) { } friend std::stringstream& operator<<(std::stringstream& ss, const VectorStringSemicoloned& vs); }; std::stringstream& operator<<(std::stringstream& ss, const VectorStringSemicoloned& vs) { bool isFirst = true; for (const auto& str : vs.vec) { if (isFirst) isFirst = false; else ss << "; "; ss << str; } return ss; } class VectorStringBracketed { const std::vector<std::string>& vec1; const std::vector<std::string>& vec2; public: explicit VectorStringBracketed(const std::vector<std::string>& v1, const std::vector<std::string>& v2) : vec1(v1), vec2(v2) { } static void dumpVector(const std::vector<std::string>& v, std::stringstream& ss, bool both) { bool isFirst = true; for (const auto& str : v) { if (v.size() == 1 && !both) { ss << str; break; } if (isFirst) isFirst = false; else ss << "+"; ss << "[" << str << "]"; } } friend std::stringstream& operator<<(std::stringstream& ss, const VectorStringBracketed& vs); }; std::stringstream& operator<<(std::stringstream& ss, const VectorStringBracketed& vs) { bool both = vs.vec1.size() && vs.vec2.size(); VectorStringBracketed::dumpVector(vs.vec1, ss, both); if (both) ss << " > "; VectorStringBracketed::dumpVector(vs.vec2, ss, both); return ss; } void pushDoubleAsStr(double arg, std::vector<std::string>& vec) { std::stringstream ss; if (arg < 0.5) ss << indigoGetLastError(); else ss << std::fixed << std::setprecision(7) << arg; std::string str = ss.str(); if (str.size()) vec.push_back(str); else vec.push_back("Unknown error"); } void calculate_molecule(IndigoKetcherObject iko, std::stringstream& molecularWeightStream, std::stringstream& mostAbundantMassStream, std::stringstream& monoisotopicMassStream, std::stringstream& massCompositionStream, std::stringstream& grossFormulaStream, const std::vector<int>& selected_atoms) { const std::set<int> selected_set(selected_atoms.begin(), selected_atoms.end()); if (iko.objtype == IndigoKetcherObject::EKETMoleculeQuery) qmol2mol(iko, selected_set); const auto componentsCount = _checkResult(indigoCountComponents(iko.id())); if (indigoCountRGroups(iko.id()) || indigoCountAttachmentPoints(iko.id())) jsThrow("Cannot calculate properties for RGroups"); std::vector<std::string> molWeights, mamMasses, misoMasses, massCompositions, grossFormulas; for (auto i = 0; i < componentsCount; i++) { const auto component = IndigoObject(_checkResult(indigoComponent(iko.id(), i))); std::vector<int> component_atoms; const auto component_atoms_iterator = IndigoObject(_checkResult(indigoIterateAtoms(component.id))); indigoUnselect(iko.id()); while (const auto atom_id = _checkResult(indigoNext(component_atoms_iterator.id))) { const auto atom = IndigoObject(atom_id); int aix = _checkResult(indigoIndex(atom.id)); if (selected_set.size() && selected_set.find(aix) == selected_set.end()) continue; component_atoms.push_back(aix); indigoSelect(atom_id); } if (component_atoms.size()) { pushDoubleAsStr(indigoMolecularWeight(iko.id()), molWeights); pushDoubleAsStr(indigoMostAbundantMass(iko.id()), mamMasses); pushDoubleAsStr(indigoMonoisotopicMass(iko.id()), misoMasses); const auto* massComposition = indigoMassComposition(iko.id()); if (massComposition == nullptr) massCompositions.push_back(indigoGetLastError()); else massCompositions.push_back(std::string(massComposition)); const auto grossFormulaObject = IndigoObject(_checkResult(indigoGrossFormula(iko.id()))); const auto* grossFormula = indigoToString(grossFormulaObject.id); if (grossFormula == nullptr) grossFormulas.push_back(indigoGetLastError()); else grossFormulas.push_back(std::string(grossFormula)); } } molecularWeightStream << VectorStringSemicoloned(molWeights); mostAbundantMassStream << VectorStringSemicoloned(mamMasses); monoisotopicMassStream << VectorStringSemicoloned(misoMasses); massCompositionStream << VectorStringSemicoloned(massCompositions); grossFormulaStream << VectorStringSemicoloned(grossFormulas); } void calculate_iteration_object(const IndigoObject& iterator, std::vector<std::string>& molWeights, std::vector<std::string>& mamMasses, std::vector<std::string>& misoMasses, std::vector<std::string>& massCompositions, std::vector<std::string>& grossFormulas, const std::vector<int>& selected_atoms, int& base) { while (const auto id = _checkResult(indigoNext(iterator.id))) { auto mol = IndigoKetcherObject(id, _checkResult(indigoCheckQuery(id)) ? IndigoKetcherObject::EKETMoleculeQuery : IndigoKetcherObject::EKETMolecule); std::vector<int> subselect; if (selected_atoms.size()) { for (int i = 0; i < selected_atoms.size(); ++i) { int atom_id = selected_atoms[i] - base; if (atom_id >= 0) subselect.push_back(atom_id); } } if (!selected_atoms.size() || subselect.size()) { std::stringstream mws, mams, misos, mcs, gfs; calculate_molecule(mol, mws, mams, misos, mcs, gfs, subselect); if (gfs.str().size()) // If we have a gross formula, we also have everything else { molWeights.push_back(mws.str()); mamMasses.push_back(mams.str()); misoMasses.push_back(misos.str()); massCompositions.push_back(mcs.str()); grossFormulas.push_back(gfs.str()); } } if (selected_atoms.size()) base += indigoCountAtoms(id); } } void calculate_reaction(const IndigoKetcherObject& iko, std::stringstream& molecularWeightStream, std::stringstream& mostAbundantMassStream, std::stringstream& monoisotopicMassStream, std::stringstream& massCompositionStream, std::stringstream& grossFormulaStream, const std::vector<int>& selected_atoms) { enum { IDX_REACTANTS = 0, IDX_PRODUCTS = 1 }; std::vector<std::string> molWeights[2], mamMasses[2], misoMasses[2], massCompositions[2], grossFormulas[2]; const auto mol_iterator = IndigoObject(_checkResult(indigoIterateMolecules(iko.id()))); while (const auto mol_id = _checkResult(indigoNext(mol_iterator.id))) { if (_checkResult(indigoCountRGroups(mol_id)) || _checkResult(indigoCountAttachmentPoints(mol_id))) jsThrow("Cannot calculate properties for RGroups"); } int base = 0; calculate_iteration_object(IndigoObject(_checkResult(indigoIterateReactants(iko.id()))), molWeights[IDX_REACTANTS], mamMasses[IDX_REACTANTS], misoMasses[IDX_REACTANTS], massCompositions[IDX_REACTANTS], grossFormulas[IDX_REACTANTS], selected_atoms, base); calculate_iteration_object(IndigoObject(_checkResult(indigoIterateProducts(iko.id()))), molWeights[IDX_PRODUCTS], mamMasses[IDX_PRODUCTS], misoMasses[IDX_PRODUCTS], massCompositions[IDX_PRODUCTS], grossFormulas[IDX_PRODUCTS], selected_atoms, base); molecularWeightStream << VectorStringBracketed(molWeights[IDX_REACTANTS], molWeights[IDX_PRODUCTS]); mostAbundantMassStream << VectorStringBracketed(mamMasses[IDX_REACTANTS], mamMasses[IDX_PRODUCTS]); monoisotopicMassStream << VectorStringBracketed(misoMasses[IDX_REACTANTS], misoMasses[IDX_PRODUCTS]); massCompositionStream << VectorStringBracketed(massCompositions[IDX_REACTANTS], massCompositions[IDX_PRODUCTS]); grossFormulaStream << VectorStringBracketed(grossFormulas[IDX_REACTANTS], grossFormulas[IDX_PRODUCTS]); } std::string calculate(const std::string& data, const std::map<std::string, std::string>& options, const std::vector<int>& selected_atoms) { const IndigoSession session; indigoSetOptions(options); auto iko = loadMoleculeOrReaction(data.c_str(), options); rapidjson::Document result; auto& allocator = result.GetAllocator(); result.SetObject(); std::stringstream molecularWeightStream; std::stringstream mostAbundantMassStream; std::stringstream monoisotopicMassStream; std::stringstream massCompositionStream; std::stringstream grossFormulaStream; switch (iko.objtype) { case IndigoKetcherObject::EKETMoleculeQuery: case IndigoKetcherObject::EKETMolecule: calculate_molecule(iko, molecularWeightStream, mostAbundantMassStream, monoisotopicMassStream, massCompositionStream, grossFormulaStream, selected_atoms); break; case IndigoKetcherObject::EKETReactionQuery: case IndigoKetcherObject::EKETReaction: calculate_reaction(iko, molecularWeightStream, mostAbundantMassStream, monoisotopicMassStream, massCompositionStream, grossFormulaStream, selected_atoms); break; } result.AddMember("molecular-weight", molecularWeightStream.str(), allocator); result.AddMember("most-abundant-mass", mostAbundantMassStream.str(), allocator); result.AddMember("monoisotopic-mass", monoisotopicMassStream.str(), allocator); result.AddMember("mass-composition", massCompositionStream.str(), allocator); result.AddMember("gross-formula", grossFormulaStream.str(), allocator); rapidjson::StringBuffer buffer; rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); result.Accept(writer); return buffer.GetString(); } std::string render(const std::string& data, const std::map<std::string, std::string>& options) { #ifndef INDIGO_NO_RENDER const IndigoSession session; const IndigoRendererSession indigoRendererSession(session.getSessionId()); indigoSetOptions(options); const auto iko = loadMoleculeOrReaction(data.c_str(), options); auto buffer_object = IndigoObject(_checkResult(indigoWriteBuffer())); char* raw_ptr = nullptr; int size = 0; _checkResult(indigoRender(iko.id(), buffer_object.id)); _checkResult(indigoToBuffer(buffer_object.id, &raw_ptr, &size)); return cppcodec::base64_rfc4648::encode(raw_ptr, size); #else jsThrow("No renderer included in this wasm bundle!"); return ""; #endif } std::string reactionComponents(const std::string& data, const std::map<std::string, std::string>& options) { using namespace rapidjson; Document result; result.SetObject(); { const IndigoSession session; indigoSetOptions(options); const auto reactionId = _checkResult(indigoLoadQueryReactionFromString(data.c_str())); // reactants { Value reactants(kArrayType); reactants.SetArray(); const auto reactantsIteratorId = _checkResult(indigoIterateReactants(reactionId)); while (const auto reactantId = _checkResult(indigoNext(reactantsIteratorId))) { Value reactant(kStringType); reactant.SetString(_checkResultString(indigoSmiles(reactantId)), result.GetAllocator()); reactants.PushBack(reactant, result.GetAllocator()); _checkResult(indigoFree(reactantId)); } result.AddMember("reactants", reactants, result.GetAllocator()); _checkResult(indigoFree(reactantsIteratorId)); } // catalysts { Value catalysts(kArrayType); catalysts.SetArray(); const auto catalystsIteratorId = _checkResult(indigoIterateCatalysts(reactionId)); while (const auto catalystId = _checkResult(indigoNext(catalystsIteratorId))) { Value catalyst(kStringType); catalyst.SetString(_checkResultString(indigoSmiles(catalystId)), result.GetAllocator()); catalysts.PushBack(catalyst, result.GetAllocator()); _checkResult(indigoFree(catalystId)); } result.AddMember("catalysts", catalysts, result.GetAllocator()); _checkResult(indigoFree(catalystsIteratorId)); } // products { Value products(kArrayType); products.SetArray(); const auto productsIteratorId = _checkResult(indigoIterateProducts(reactionId)); while (const auto productId = _checkResult(indigoNext(productsIteratorId))) { Value product(kStringType); product.SetString(_checkResultString(indigoSmiles(productId)), result.GetAllocator()); products.PushBack(product, result.GetAllocator()); _checkResult(indigoFree(productId)); } result.AddMember("products", products, result.GetAllocator()); _checkResult(indigoFree(productsIteratorId)); } _checkResult(indigoFree(reactionId)); } // Serialize results StringBuffer buffer; Writer<rapidjson::StringBuffer> writer(buffer); result.Accept(writer); return buffer.GetString(); } EMSCRIPTEN_BINDINGS(module) { emscripten::function("version", &version); emscripten::function("versionInfo", &versionInfo); emscripten::function("convert", &convert); emscripten::function("convert_explicit_hydrogens", &convert_explicit_hydrogens); emscripten::function("aromatize", &aromatize); emscripten::function("dearomatize", &dearomatize); emscripten::function("layout", &layout); emscripten::function("clean2d", &clean2d); emscripten::function("automap", &automap); emscripten::function("check", &check); emscripten::function("calculateCip", &calculateCip); emscripten::function("calculate", &calculate); emscripten::function("render", &render); emscripten::function("reactionComponents", &reactionComponents); emscripten::register_vector<int>("VectorInt"); emscripten::register_map<std::string, std::string>("MapStringString"); }; }