Repository: rdkit/rdkit — pull request #5445 (cpp)
Title: Molzip: save bond dir when zipping

Fixes #5450 

Simple fix to allow mol zip to save the bond direction when zipping molecules.

This is necessary for the CDXML parser.

Resolved issues:
title: MolZip doesn't preserve bond directions when zipping
body: This is more esoteric, but required for cdxml parsing.

Hints:

Fix patch:
diff --git a/Code/GraphMol/ChemTransforms/MolFragmenter.cpp b/Code/GraphMol/ChemTransforms/MolFragmenter.cpp
index 80b5d7ee6d7..762b8f05aa4 100644
--- a/Code/GraphMol/ChemTransforms/MolFragmenter.cpp
+++ b/Code/GraphMol/ChemTransforms/MolFragmenter.cpp
@@ -746,18 +746,72 @@ struct ZipBond {
                       "molzip: begin atom and specified dummy atom connection "
                       "are not bonded.")
       auto bond_type_a = bnd->getBondType();
+      auto bond_dir_a = bnd->getBondDir();
+      auto a_is_start = bnd->getBeginAtom() == a;
+    
       bnd = newmol.getBondBetweenAtoms(b->getIdx(), b_dummy->getIdx());
       CHECK_INVARIANT(bnd != nullptr,
                       "molzip: end atom and specified dummy connection atom "
                       "are not bonded.")
       auto bond_type_b = bnd->getBondType();
+
+      auto bond_dir_b = bnd->getBondDir();
+      auto b_is_start = bnd->getBeginAtom() == b;
+      
+      unsigned int bnd_idx = 0;
+      // Fusion bond-dir logic table
+      // a-* b-* => a-b
+      //  < = wedge
+
+      //  a<* b-* => a<b
+      //  a>* b-* => a>b
+      //  a-* b>* => a<b
+      //  a-* b<* => a>b
+      Bond::BondDir bond_dir;
+      auto start = a;
+      auto end = b;
+      if(bond_dir_a != Bond::BondDir::NONE && bond_dir_b != Bond::BondDir::NONE) {
+          // are we consistent between the two bond orders
+          // check for the case of fragment on bonds where a<* and b>* or a>* and b<*
+          //  when < is either a hash or wedge bond but not both.
+          bool consistent_directions = false;
+          if(bond_dir_a == bond_dir_b) {
+              if((a_is_start != b_is_start)){
+                  consistent_directions = true;
+              }
+          }
+          if(!consistent_directions) {
+              BOOST_LOG(rdWarningLog) << "inconsistent bond directions when merging fragments, ignoring..." << std::endl;
+              bond_dir_a = bond_dir_b = Bond::BondDir::NONE;
+          } else {
+              bond_dir_b = Bond::BondDir::NONE;
+          }
+      }
+        
+      if (bond_dir_a != Bond::BondDir::NONE) {
+          if(!a_is_start) {
+              start = b;
+              end = a;
+          }
+          bond_dir = bond_dir_a;
+      } else if (bond_dir_b != Bond::BondDir::NONE) {
+          if(b_is_start) {
+            start = b;
+            end = a;
+          }
+          bond_dir = bond_dir_b;
+      }
+    
       if (bond_type_a != Bond::BondType::SINGLE) {
-        newmol.addBond(a, b, bond_type_a);
+        bnd_idx = newmol.addBond(start, end, bond_type_a);
       } else if (bond_type_b != Bond::BondType::SINGLE) {
-        newmol.addBond(a, b, bond_type_b);
+        bnd_idx = newmol.addBond(start, end, bond_type_b);
       } else {
-        newmol.addBond(a, b, Bond::BondType::SINGLE);
+        bnd_idx = newmol.addBond(start, end, Bond::BondType::SINGLE);
       }
+        
+      newmol.getBondWithIdx(bnd_idx-1)->setBondDir(bond_dir);
+
     }
     a_dummy->setProp("__molzip_used", true);
     b_dummy->setProp("__molzip_used", true);


Test patch:
diff --git a/Code/GraphMol/ChemTransforms/catch_tests.cpp b/Code/GraphMol/ChemTransforms/catch_tests.cpp
index d06c126d3d6..1d020a5436c 100644
--- a/Code/GraphMol/ChemTransforms/catch_tests.cpp
+++ b/Code/GraphMol/ChemTransforms/catch_tests.cpp
@@ -398,6 +398,54 @@ TEST_CASE("molzip", "[]") {
     auto b = "[*:1]-N=C"_smiles;
     auto mol = molzip(*a, *b, p);
   }
+
+  SECTION("MolZip saves bonddir") {
+      { // a-* b<*
+         auto a = "CC[*:1]"_smiles;
+         auto b = "N[*:1]"_smiles;
+         b->getBondWithIdx(0)->setBondDir(Bond::BondDir::BEGINWEDGE);
+         auto mol = molzip(*a, *b);
+         CHECK(MolToSmiles(*mol) == "CCN");
+         CHECK(mol->getBondWithIdx(1)->getBondDir() == Bond::BondDir::BEGINWEDGE);
+         CHECK(mol->getBondWithIdx(1)->getBeginAtom()->getIdx() == 2);
+       }
+       { // a>* b-*
+         auto a = "[*:1]CC"_smiles;
+         auto b = "N[*:1]"_smiles;
+         a->getBondWithIdx(0)->setBondDir(Bond::BondDir::BEGINWEDGE);
+         auto mol = molzip(*a, *b);
+         CHECK(MolToSmiles(*mol) == "CCN");
+         CHECK(mol->getBondWithIdx(1)->getBondDir() == Bond::BondDir::BEGINWEDGE);
+         CHECK(mol->getBondWithIdx(1)->getBeginAtom()->getIdx() == 2);
+       }
+       { // a<* b-*
+         auto a = "CC[*:1]"_smiles;
+         auto b = "N[*:1]"_smiles;
+         a->getBondWithIdx(1)->setBondDir(Bond::BondDir::BEGINWEDGE);
+         auto mol = molzip(*a, *b);
+         CHECK(MolToSmiles(*mol) == "CCN");
+         CHECK(mol->getBondWithIdx(1)->getBondDir() == Bond::BondDir::BEGINWEDGE);
+         CHECK(mol->getBondWithIdx(1)->getBeginAtom()->getIdx() == 1);
+       }
+       { // a>* b-*
+         auto a = "[*:1]CC"_smiles;
+         auto b = "N[*:1]"_smiles;
+         a->getBondWithIdx(0)->setBondDir(Bond::BondDir::BEGINWEDGE);
+         auto mol = molzip(*a, *b);
+         CHECK(MolToSmiles(*mol) == "CCN");
+         CHECK(mol->getBondWithIdx(1)->getBondDir() == Bond::BondDir::BEGINWEDGE);
+         CHECK(mol->getBondWithIdx(1)->getBeginAtom()->getIdx() == 2);
+       }
+       { // a-* b<*
+         auto a = "CC[*:1]"_smiles;
+         auto b = "[*:1]N"_smiles;
+         b->getBondWithIdx(0)->setBondDir(Bond::BondDir::BEGINWEDGE);
+         auto mol = molzip(*a, *b);
+         CHECK(MolToSmiles(*mol) == "CCN");
+         CHECK(mol->getBondWithIdx(1)->getBondDir() == Bond::BondDir::BEGINWEDGE);
+         CHECK(mol->getBondWithIdx(1)->getBeginAtom()->getIdx() == 1);
+       }
+   }
 }
 
 TEST_CASE(