From b03531b231339a42579cd889cb4c2660fa7689f5 Mon Sep 17 00:00:00 2001 From: even1024 Date: Wed, 29 Apr 2026 15:11:02 +0200 Subject: [PATCH 01/24] #3604 Add api methods to interact with atoms and bonds in s-groups --- api/c/indigo/indigo.h | 6 + .../indigo/src/indigo_molecule_operations.cpp | 129 ++++++++-- api/python/indigo/indigo/indigo_lib.py | 16 ++ api/python/indigo/indigo/indigo_object.py | 69 +++++ .../ref/basic/3604_sgroup_atoms_bonds.py.out | 59 +++++ .../tests/basic/3604_sgroup_atoms_bonds.py | 236 ++++++++++++++++++ core/indigo-core/molecule/molecule_sgroups.h | 5 +- 7 files changed, 499 insertions(+), 21 deletions(-) create mode 100644 api/tests/integration/ref/basic/3604_sgroup_atoms_bonds.py.out create mode 100644 api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py diff --git a/api/c/indigo/indigo.h b/api/c/indigo/indigo.h index d97d7016f4..2eb141afab 100644 --- a/api/c/indigo/indigo.h +++ b/api/c/indigo/indigo.h @@ -606,6 +606,12 @@ CEXPORT int indigoGetSGroupNumCrossBonds(int sgroup); CEXPORT int indigoCreateCrossBonds(int sgroup); CEXPORT int indigoClearSGroupCrossBonds(int sgroup); +// Issue #3604: New SGroup API methods +CEXPORT int indigoAddSGroup(int molecule, const char* type, int extindex); +CEXPORT int indigoSetSGroupAtoms(int sgroup, int natoms, int* atoms); +CEXPORT int indigoSetSGroupBonds(int sgroup, int nbonds, int* bonds); +CEXPORT int indigoIterateSGroupCrossBonds(int sgroup); + CEXPORT int indigoAddSGroupAttachmentPoint(int sgroup, int aidx, int lvidx, const char* apid); CEXPORT int indigoDeleteSGroupAttachmentPoint(int sgroup, int index); // Returns iterator of superatom attachment points (SAP entries) for a superatom S-group. diff --git a/api/c/indigo/src/indigo_molecule_operations.cpp b/api/c/indigo/src/indigo_molecule_operations.cpp index b017e1b883..a14d2251d6 100644 --- a/api/c/indigo/src/indigo_molecule_operations.cpp +++ b/api/c/indigo/src/indigo_molecule_operations.cpp @@ -1722,52 +1722,143 @@ CEXPORT int indigoGetSGroupNumCrossBonds(int sgroup) { INDIGO_BEGIN { - Superatom& sup = IndigoSuperatom::cast(self.getObject(sgroup)).get(); - return sup.bonds.size(); + IndigoSGroup& isg = IndigoSGroup::cast(self.getObject(sgroup)); + return isg.get().bonds.size(); } INDIGO_END(-1); } +static void _fillCrossBonds(BaseMolecule& mol, SGroup& sg) +{ + sg.bonds.clear(); + for (auto atom_idx : sg.atoms) + { + const Vertex& vx = mol.getVertex(atom_idx); + for (auto nei_idx = vx.neiBegin(); nei_idx != vx.neiEnd(); nei_idx = vx.neiNext(nei_idx)) + { + if (sg.atoms.find(vx.neiVertex(nei_idx)) == -1) + { + int edge_idx = vx.neiEdge(nei_idx); + if (sg.bonds.find(edge_idx) == -1) + sg.bonds.push(edge_idx); + } + } + } +} + CEXPORT int indigoCreateCrossBonds(int sgroup) { INDIGO_BEGIN { - IndigoSuperatom& isup = IndigoSuperatom::cast(self.getObject(sgroup)); - Superatom& sup = isup.get(); - BaseMolecule& mol = isup.mol; + IndigoSGroup& isg = IndigoSGroup::cast(self.getObject(sgroup)); + _fillCrossBonds(isg.mol, isg.get()); + return 1; + } + INDIGO_END(-1); +} - sup.bonds.clear(); +CEXPORT int indigoClearSGroupCrossBonds(int sgroup) +{ + INDIGO_BEGIN + { + IndigoSGroup& isg = IndigoSGroup::cast(self.getObject(sgroup)); + isg.get().bonds.clear(); + return 1; + } + INDIGO_END(-1); +} - for (auto atom_idx : sup.atoms) +static IndigoObject* _wrapSGroup(BaseMolecule& mol, int idx) +{ + SGroup& sgroup = mol.sgroups.getSGroup(idx); + if (sgroup.sgroup_type == SGroup::SG_TYPE_SUP) + return new IndigoSuperatom(mol, idx); + else if (sgroup.sgroup_type == SGroup::SG_TYPE_SRU) + return new IndigoRepeatingUnit(mol, idx); + else if (sgroup.sgroup_type == SGroup::SG_TYPE_MUL) + return new IndigoMultipleGroup(mol, idx); + else if (sgroup.sgroup_type == SGroup::SG_TYPE_DAT) + return new IndigoDataSGroup(mol, idx); + else + return new IndigoGenericSGroup(mol, idx); +} + +CEXPORT int indigoAddSGroup(int molecule, const char* type, int extindex) +{ + INDIGO_BEGIN + { + BaseMolecule& mol = self.getObject(molecule).getBaseMolecule(); + int idx = mol.sgroups.addSGroup(type); + if (idx == -1) + throw IndigoError("indigoAddSGroup: cannot add SGroup of type '%s'", type); + + SGroup& sgroup = mol.sgroups.getSGroup(idx); + if (extindex > 0) + sgroup.original_group = extindex; + + return self.addObject(_wrapSGroup(mol, idx)); + } + INDIGO_END(-1); +} + +CEXPORT int indigoSetSGroupAtoms(int sgroup, int natoms, int* atoms) +{ + INDIGO_BEGIN + { + IndigoSGroup& isg = IndigoSGroup::cast(self.getObject(sgroup)); + SGroup& s = isg.get(); + + s.atoms.clear(); + if (atoms != nullptr && natoms > 0) { - const Vertex& vx = mol.getVertex(atom_idx); - for (auto nei_idx = vx.neiBegin(); nei_idx != vx.neiEnd(); nei_idx = vx.neiNext(nei_idx)) + for (int i = 0; i < natoms; i++) { - if (sup.atoms.find(vx.neiVertex(nei_idx)) == -1) - { - int edge_idx = vx.neiEdge(nei_idx); - if (sup.bonds.find(edge_idx) == -1) - sup.bonds.push(edge_idx); - } + if (atoms[i] < 0 || atoms[i] >= isg.mol.vertexEnd()) + throw IndigoError("indigoSetSGroupAtoms: atom index %d out of range", atoms[i]); + s.atoms.push(atoms[i]); } } - return 1; } INDIGO_END(-1); } -CEXPORT int indigoClearSGroupCrossBonds(int sgroup) +CEXPORT int indigoSetSGroupBonds(int sgroup, int nbonds, int* bonds) { INDIGO_BEGIN { - Superatom& sup = IndigoSuperatom::cast(self.getObject(sgroup)).get(); - sup.bonds.clear(); + IndigoSGroup& isg = IndigoSGroup::cast(self.getObject(sgroup)); + SGroup& s = isg.get(); + + if (s.sgroup_type != SGroup::SG_TYPE_DAT) + throw IndigoError("indigoSetSGroupBonds: only DAT SGroups support explicit bond assignment"); + + s.bonds.clear(); + if (bonds != nullptr && nbonds > 0) + { + for (int i = 0; i < nbonds; i++) + { + if (bonds[i] < 0 || bonds[i] >= isg.mol.edgeEnd()) + throw IndigoError("indigoSetSGroupBonds: bond index %d out of range", bonds[i]); + s.bonds.push(bonds[i]); + } + } return 1; } INDIGO_END(-1); } +CEXPORT int indigoIterateSGroupCrossBonds(int sgroup) +{ + INDIGO_BEGIN + { + IndigoSGroup& isg = IndigoSGroup::cast(self.getObject(sgroup)); + return self.addObject(new IndigoSGroupBondsIter(isg.mol, isg.get())); + } + INDIGO_END(-1); +} + + CEXPORT int indigoAddSGroupAttachmentPoint(int sgroup, int aidx, int lvidx, const char* apid) { INDIGO_BEGIN diff --git a/api/python/indigo/indigo/indigo_lib.py b/api/python/indigo/indigo/indigo_lib.py index d7c95df4f9..4591c96d72 100644 --- a/api/python/indigo/indigo/indigo_lib.py +++ b/api/python/indigo/indigo/indigo_lib.py @@ -660,6 +660,22 @@ def __init__(self) -> None: IndigoLib.lib.indigoCreateCrossBonds.argtypes = [c_int] IndigoLib.lib.indigoClearSGroupCrossBonds.restype = c_int IndigoLib.lib.indigoClearSGroupCrossBonds.argtypes = [c_int] + IndigoLib.lib.indigoAddSGroup.restype = c_int + IndigoLib.lib.indigoAddSGroup.argtypes = [c_int, c_char_p, c_int] + IndigoLib.lib.indigoSetSGroupAtoms.restype = c_int + IndigoLib.lib.indigoSetSGroupAtoms.argtypes = [ + c_int, + c_int, + POINTER(c_int), + ] + IndigoLib.lib.indigoSetSGroupBonds.restype = c_int + IndigoLib.lib.indigoSetSGroupBonds.argtypes = [ + c_int, + c_int, + POINTER(c_int), + ] + IndigoLib.lib.indigoIterateSGroupCrossBonds.restype = c_int + IndigoLib.lib.indigoIterateSGroupCrossBonds.argtypes = [c_int] IndigoLib.lib.indigoAddSGroupAttachmentPoint.restype = c_int IndigoLib.lib.indigoAddSGroupAttachmentPoint.argtypes = [ c_int, diff --git a/api/python/indigo/indigo/indigo_object.py b/api/python/indigo/indigo/indigo_object.py index adf6536a19..9afc27c311 100644 --- a/api/python/indigo/indigo/indigo_object.py +++ b/api/python/indigo/indigo/indigo_object.py @@ -1706,6 +1706,75 @@ def addSuperatom(self, atoms, name): ), ) + def addSGroup(self, sgtype, extindex=0): + """Molecule method adds an empty SGroup + + Args: + sgtype (str): sgroup type (e.g. "SUP", "DAT", "SRU", "MUL", "GEN") + extindex (int): external index; 0 for auto-generation + + Returns: + IndigoObject: SGroup object + """ + + return IndigoObject( + self.session, + IndigoLib.checkResult( + self._lib().indigoAddSGroup( + self.id, sgtype.encode(), extindex + ) + ), + ) + + def setSGroupAtoms(self, atoms): + """SGroup method replaces atoms with the given list + + Args: + atoms (list): atom index list + + Returns: + int: 1 if there are no errors + """ + arr = (c_int * len(atoms))() + for i in range(len(atoms)): + arr[i] = atoms[i] + + return IndigoLib.checkResult( + self._lib().indigoSetSGroupAtoms(self.id, len(arr), arr) + ) + + def setSGroupBonds(self, bonds): + """SGroup method replaces bonds with the given list (DAT only) + + Args: + bonds (list): bond index list + + Returns: + int: 1 if there are no errors + """ + arr = (c_int * len(bonds))() + for i in range(len(bonds)): + arr[i] = bonds[i] + + return IndigoLib.checkResult( + self._lib().indigoSetSGroupBonds(self.id, len(arr), arr) + ) + + def iterateSGroupCrossBonds(self): + """SGroup method iterates cross bonds + + Returns: + IndigoObject: bonds iterator + """ + + return IndigoObject( + self.session, + IndigoLib.checkResult( + self._lib().indigoIterateSGroupCrossBonds(self.id) + ), + self, + ) + def setDataSGroupXY(self, x, y, options=""): """SGroup method sets coordinates diff --git a/api/tests/integration/ref/basic/3604_sgroup_atoms_bonds.py.out b/api/tests/integration/ref/basic/3604_sgroup_atoms_bonds.py.out new file mode 100644 index 0000000000..2059aee215 --- /dev/null +++ b/api/tests/integration/ref/basic/3604_sgroup_atoms_bonds.py.out @@ -0,0 +1,59 @@ +****** addSGroup: create empty SGroups of each type ******** +SUP type: 2 +SUP atoms: 0 +SUP bonds: 0 +DAT type: 1 +DAT atoms: 0 +DAT bonds: 0 +SRU type: 3 +SRU atoms: 0 +MUL type: 4 +MUL atoms: 0 +GEN type: 0 +GEN atoms: 0 +****** addSGroup: with explicit extindex ******** +extindex: 42 +****** setSGroupAtoms: set atoms on empty SGroup ******** +atoms after set: 3 +atom symbols: C C C +****** setSGroupAtoms: replace existing atoms ******** +atoms after replace: 3 +atom indices: 3 4 5 +****** setSGroupAtoms: clear atoms ******** +atoms after clear: 0 +****** setSGroupAtoms: works for all SGroup types ******** +SUP atoms: 2 +DAT atoms: 2 +SRU atoms: 2 +GEN atoms: 2 +****** setSGroupBonds: set bonds on DAT SGroup ******** +DAT bonds: 2 +****** setSGroupBonds: error on SUP SGroup ******** +Expected error: core: indigoSetSGroupBonds: only DAT SGroups support explicit bond assignment +****** setSGroupBonds: error on SRU SGroup ******** +Expected error: core: indigoSetSGroupBonds: only DAT SGroups support explicit bond assignment +****** iterateSGroupCrossBonds ******** +cross bonds: 0 3 +****** iterateSGroupCrossBonds: empty ******** +cross bonds when all atoms inside: 0 +****** Updated countBonds: DAT returns CBONDS, others return cross bonds ******** +DAT countBonds (CBONDS): 2 +SUP countBonds (cross): 2 +SRU countBonds (cross): 2 +****** createCrossBonds: works for all SGroup types ******** +SUP createCrossBonds count: 2 +SRU createCrossBonds count: 2 +GEN createCrossBonds count: 2 +MUL createCrossBonds count: 2 +****** clearSGroupCrossBonds: works for all SGroup types ******** +SUP before clear: 2 +SUP after clear: 0 +SRU before clear: 2 +SRU after clear: 0 +GEN before clear: 2 +GEN after clear: 0 +****** Molfile roundtrip: SUP with cross bonds ******** +type: 2 +atoms: 3 +bonds: 2 +sgroup count: 1 diff --git a/api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py b/api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py new file mode 100644 index 0000000000..25d2a303de --- /dev/null +++ b/api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py @@ -0,0 +1,236 @@ +""" +Issue #3604: Add API methods to interact with atoms and bonds in S-groups. +https://github.com/epam/Indigo/issues/3604 + +This test covers new and updated SGroup API methods: + - addSGroup(type, extindex) + - setSGroupAtoms(atom_indices) + - setSGroupBonds(bond_indices) (DAT only) + - iterateSGroupCrossBonds() + - Updated iterateBonds/countBonds behavior + - Updated createCrossBonds/clearSGroupCrossBonds for all types +""" +import os +import sys + +sys.path.append( + os.path.normpath( + os.path.join(os.path.abspath(__file__), "..", "..", "..", "common") + ) +) +from env_indigo import Indigo, getIndigoExceptionText, IndigoException # noqa + +indigo = Indigo() +indigo.setOption("molfile-saving-skip-date", True) + + +# ===== addSGroup ===== + +print("****** addSGroup: create empty SGroups of each type ********") + +mol = indigo.loadMolecule("CCCCCC") + +sg_sup = mol.addSGroup("SUP", 0) +print("SUP type: {0}".format(sg_sup.getSGroupType())) +print("SUP atoms: {0}".format(sg_sup.countAtoms())) +print("SUP bonds: {0}".format(sg_sup.countBonds())) + +sg_dat = mol.addSGroup("DAT", 0) +print("DAT type: {0}".format(sg_dat.getSGroupType())) +print("DAT atoms: {0}".format(sg_dat.countAtoms())) +print("DAT bonds: {0}".format(sg_dat.countBonds())) + +sg_sru = mol.addSGroup("SRU", 0) +print("SRU type: {0}".format(sg_sru.getSGroupType())) +print("SRU atoms: {0}".format(sg_sru.countAtoms())) + +sg_mul = mol.addSGroup("MUL", 0) +print("MUL type: {0}".format(sg_mul.getSGroupType())) +print("MUL atoms: {0}".format(sg_mul.countAtoms())) + +sg_gen = mol.addSGroup("GEN", 0) +print("GEN type: {0}".format(sg_gen.getSGroupType())) +print("GEN atoms: {0}".format(sg_gen.countAtoms())) + +print("****** addSGroup: with explicit extindex ********") + +mol2 = indigo.loadMolecule("CCCCCC") +sg = mol2.addSGroup("SUP", 42) +print("extindex: {0}".format(sg.getSGroupOriginalId())) + + +# ===== setSGroupAtoms ===== + +print("****** setSGroupAtoms: set atoms on empty SGroup ********") + +mol3 = indigo.loadMolecule("CCCCCC") +sg = mol3.addSGroup("SUP", 0) +sg.setSGroupAtoms([0, 1, 2]) +print("atoms after set: {0}".format(sg.countAtoms())) +atoms = [] +for a in sg.iterateAtoms(): + atoms.append(a.symbol()) +print("atom symbols: {0}".format(" ".join(atoms))) + +print("****** setSGroupAtoms: replace existing atoms ********") + +sg.setSGroupAtoms([3, 4, 5]) +print("atoms after replace: {0}".format(sg.countAtoms())) +indices = [] +for a in sg.iterateAtoms(): + indices.append(str(a.index())) +print("atom indices: {0}".format(" ".join(indices))) + +print("****** setSGroupAtoms: clear atoms ********") + +sg.setSGroupAtoms([]) +print("atoms after clear: {0}".format(sg.countAtoms())) + +print("****** setSGroupAtoms: works for all SGroup types ********") + +mol4 = indigo.loadMolecule("CCCCCC") +for stype in ["SUP", "DAT", "SRU", "GEN"]: + sg = mol4.addSGroup(stype, 0) + sg.setSGroupAtoms([0, 1]) + print("{0} atoms: {1}".format(stype, sg.countAtoms())) + + +# ===== setSGroupBonds ===== + +print("****** setSGroupBonds: set bonds on DAT SGroup ********") + +mol5 = indigo.loadMolecule("CCCCCC") +sg = mol5.addSGroup("DAT", 0) +sg.setSGroupAtoms([0, 1, 2]) +sg.setSGroupBonds([0, 1]) +print("DAT bonds: {0}".format(sg.countBonds())) + +print("****** setSGroupBonds: error on SUP SGroup ********") + +mol6 = indigo.loadMolecule("CCCCCC") +sg = mol6.addSGroup("SUP", 0) +sg.setSGroupAtoms([0, 1, 2]) +try: + sg.setSGroupBonds([0, 1]) + print("ERROR: should have raised exception") +except IndigoException as e: + print("Expected error: {0}".format(getIndigoExceptionText(e))) + +print("****** setSGroupBonds: error on SRU SGroup ********") + +mol7 = indigo.loadMolecule("CCCCCC") +sg = mol7.addSGroup("SRU", 0) +sg.setSGroupAtoms([0, 1, 2]) +try: + sg.setSGroupBonds([0, 1]) + print("ERROR: should have raised exception") +except IndigoException as e: + print("Expected error: {0}".format(getIndigoExceptionText(e))) + + +# ===== iterateSGroupCrossBonds ===== + +print("****** iterateSGroupCrossBonds ********") + +mol8 = indigo.loadMolecule("CCCCCC") +sg = mol8.addSGroup("SUP", 0) +sg.setSGroupAtoms([1, 2, 3]) +sg.createCrossBonds() + +cross_bonds = [] +for b in sg.iterateSGroupCrossBonds(): + cross_bonds.append(str(b.index())) +print("cross bonds: {0}".format(" ".join(sorted(cross_bonds)))) + +print("****** iterateSGroupCrossBonds: empty ********") + +mol9 = indigo.loadMolecule("CCCCCC") +sg = mol9.addSGroup("SUP", 0) +sg.setSGroupAtoms([0, 1, 2, 3, 4, 5]) +sg.createCrossBonds() + +count = 0 +for _ in sg.iterateSGroupCrossBonds(): + count += 1 +print("cross bonds when all atoms inside: {0}".format(count)) + + +# ===== Updated countBonds/iterateBonds ===== + +print( + "****** Updated countBonds: DAT returns CBONDS, others return cross bonds ********" +) + +mol10 = indigo.loadMolecule("CCCCCC") + +sg_dat = mol10.addSGroup("DAT", 0) +sg_dat.setSGroupAtoms([1, 2, 3]) +sg_dat.setSGroupBonds([1, 2]) +print("DAT countBonds (CBONDS): {0}".format(sg_dat.countBonds())) + +mol11 = indigo.loadMolecule("CCCCCC") +sg_sup = mol11.addSGroup("SUP", 0) +sg_sup.setSGroupAtoms([1, 2, 3]) +sg_sup.createCrossBonds() +print("SUP countBonds (cross): {0}".format(sg_sup.countBonds())) + +mol12 = indigo.loadMolecule("CCCCCC") +sg_sru = mol12.addSGroup("SRU", 0) +sg_sru.setSGroupAtoms([1, 2, 3]) +sg_sru.createCrossBonds() +print("SRU countBonds (cross): {0}".format(sg_sru.countBonds())) + + +# ===== createCrossBonds for all types ===== + +print("****** createCrossBonds: works for all SGroup types ********") + +for stype in ["SUP", "SRU", "GEN", "MUL"]: + mol = indigo.loadMolecule("CCCCCC") + sg = mol.addSGroup(stype, 0) + sg.setSGroupAtoms([1, 2, 3]) + sg.createCrossBonds() + print( + "{0} createCrossBonds count: {1}".format(stype, sg.countBonds()) + ) + + +# ===== clearSGroupCrossBonds for all types ===== + +print("****** clearSGroupCrossBonds: works for all SGroup types ********") + +for stype in ["SUP", "SRU", "GEN"]: + mol = indigo.loadMolecule("CCCCCC") + sg = mol.addSGroup(stype, 0) + sg.setSGroupAtoms([1, 2, 3]) + sg.createCrossBonds() + print( + "{0} before clear: {1}".format(stype, sg.countBonds()) + ) + sg.clearSGroupCrossBonds() + print( + "{0} after clear: {1}".format(stype, sg.countBonds()) + ) + + +# ===== Molfile roundtrip ===== + +print("****** Molfile roundtrip: SUP with cross bonds ********") + +indigo.setOption("molfile-saving-mode", "3000") +mol = indigo.loadMolecule("CCCCCC") +sg = mol.addSGroup("SUP", 0) +sg.setSGroupAtoms([1, 2, 3]) +sg.createCrossBonds() +sg.setSGroupName("TEST_SUP") + +molfile = mol.molfile() +mol2 = indigo.loadMolecule(molfile) + +sg_count = 0 +for sg in mol2.iterateSGroups(): + sg_count += 1 + print("type: {0}".format(sg.getSGroupType())) + print("atoms: {0}".format(sg.countAtoms())) + print("bonds: {0}".format(sg.countBonds())) +print("sgroup count: {0}".format(sg_count)) diff --git a/core/indigo-core/molecule/molecule_sgroups.h b/core/indigo-core/molecule/molecule_sgroups.h index f33af4ef28..8e1fd956f3 100644 --- a/core/indigo-core/molecule/molecule_sgroups.h +++ b/core/indigo-core/molecule/molecule_sgroups.h @@ -115,8 +115,9 @@ namespace indigo int parent_idx; // parent group number; represented with index in the array // TODO: leave only parent_idx - Array atoms; // represented with SAL in Molfile format - Array bonds; // represented with SBL in Molfile format + Array atoms; // represented with SAL in Molfile format + Array bonds; // represented with SBL in Molfile format (CBONDS for DAT, XBONDS for others) + Array cross_bonds; // explicit cross bonds storage (#3604) — for DAT type to separate CBONDS from XBONDS Array subscript; // SMT in Molfile format (LABEL in V3000) int brk_style; // represented with SBT in Molfile format From 5d1dc6d376f9ceb226fcac7408025a0467494b00 Mon Sep 17 00:00:00 2001 From: even1024 Date: Wed, 29 Apr 2026 16:12:23 +0200 Subject: [PATCH 02/24] #3604 Add api methods to interact with atoms and bonds in s-groups --- api/dotnet/src/IndigoLib.cs | 13 +++++++ api/dotnet/src/IndigoObject.cs | 34 +++++++++++++++++++ .../main/java/com/epam/indigo/IndigoLib.java | 9 +++++ .../java/com/epam/indigo/IndigoObject.java | 30 ++++++++++++++++ core/indigo-core/molecule/molecule_sgroups.h | 1 - 5 files changed, 86 insertions(+), 1 deletion(-) diff --git a/api/dotnet/src/IndigoLib.cs b/api/dotnet/src/IndigoLib.cs index 5e8485b2c0..7389146ffd 100644 --- a/api/dotnet/src/IndigoLib.cs +++ b/api/dotnet/src/IndigoLib.cs @@ -623,6 +623,19 @@ public unsafe class IndigoLib [DllImport("indigo"), SuppressUnmanagedCodeSecurity] public static extern int indigoClearSGroupCrossBonds(int sgroup); + // Issue #3604: New SGroup API methods + [DllImport("indigo"), SuppressUnmanagedCodeSecurity] + public static extern int indigoAddSGroup(int molecule, string type, int extindex); + + [DllImport("indigo"), SuppressUnmanagedCodeSecurity] + public static extern int indigoSetSGroupAtoms(int sgroup, int natoms, int[] atoms); + + [DllImport("indigo"), SuppressUnmanagedCodeSecurity] + public static extern int indigoSetSGroupBonds(int sgroup, int nbonds, int[] bonds); + + [DllImport("indigo"), SuppressUnmanagedCodeSecurity] + public static extern int indigoIterateSGroupCrossBonds(int sgroup); + [DllImport("indigo"), SuppressUnmanagedCodeSecurity] public static extern int indigoAddSGroupAttachmentPoint(int sgroup, int aidx, int lvidx, string apid); diff --git a/api/dotnet/src/IndigoObject.cs b/api/dotnet/src/IndigoObject.cs index 8ad135d2b8..35f0180e14 100644 --- a/api/dotnet/src/IndigoObject.cs +++ b/api/dotnet/src/IndigoObject.cs @@ -836,6 +836,40 @@ public int clearSGroupCrossBonds() return dispatcher.checkResult(IndigoLib.indigoClearSGroupCrossBonds(self)); } + public IndigoObject addSGroup(string type, int extindex) + { + dispatcher.setSessionID(); + return new IndigoObject(dispatcher, dispatcher.checkResult(IndigoLib.indigoAddSGroup(self, type, extindex)), this); + } + + public int setSGroupAtoms(int[] atoms) + { + dispatcher.setSessionID(); + return dispatcher.checkResult(IndigoLib.indigoSetSGroupAtoms(self, atoms.Length, atoms)); + } + + public int setSGroupAtoms(ICollection atoms) + { + return setSGroupAtoms(Indigo.toIntArray(atoms)); + } + + public int setSGroupBonds(int[] bonds) + { + dispatcher.setSessionID(); + return dispatcher.checkResult(IndigoLib.indigoSetSGroupBonds(self, bonds.Length, bonds)); + } + + public int setSGroupBonds(ICollection bonds) + { + return setSGroupBonds(Indigo.toIntArray(bonds)); + } + + public IndigoObject iterateSGroupCrossBonds() + { + dispatcher.setSessionID(); + return new IndigoObject(dispatcher, dispatcher.checkResult(IndigoLib.indigoIterateSGroupCrossBonds(self)), this); + } + public int addSGroupAttachmentPoint(int aidx, int lvidx, string apid) { dispatcher.setSessionID(); diff --git a/api/java/indigo/src/main/java/com/epam/indigo/IndigoLib.java b/api/java/indigo/src/main/java/com/epam/indigo/IndigoLib.java index ddf9926ebf..d73cc58dcc 100644 --- a/api/java/indigo/src/main/java/com/epam/indigo/IndigoLib.java +++ b/api/java/indigo/src/main/java/com/epam/indigo/IndigoLib.java @@ -489,6 +489,15 @@ int indigoAddDataSGroup( int indigoClearSGroupCrossBonds(int sgroup); + // Issue #3604: New SGroup API methods + int indigoAddSGroup(int molecule, String type, int extindex); + + int indigoSetSGroupAtoms(int sgroup, int natoms, int[] atoms); + + int indigoSetSGroupBonds(int sgroup, int nbonds, int[] bonds); + + int indigoIterateSGroupCrossBonds(int sgroup); + int indigoAddSGroupAttachmentPoint(int sgroup, int aidx, int lvidx, String apid); int indigoDeleteSGroupAttachmentPoint(int sgroup, int apidx); diff --git a/api/java/indigo/src/main/java/com/epam/indigo/IndigoObject.java b/api/java/indigo/src/main/java/com/epam/indigo/IndigoObject.java index 0012ad3b05..4329b9bcee 100644 --- a/api/java/indigo/src/main/java/com/epam/indigo/IndigoObject.java +++ b/api/java/indigo/src/main/java/com/epam/indigo/IndigoObject.java @@ -1244,6 +1244,36 @@ public int clearSGroupCrossBonds() { return Indigo.checkResult(this, lib.indigoClearSGroupCrossBonds(self)); } + public IndigoObject addSGroup(String type, int extindex) { + dispatcher.setSessionID(); + return new IndigoObject( + dispatcher, Indigo.checkResult(this, lib.indigoAddSGroup(self, type, extindex)), this); + } + + public int setSGroupAtoms(int[] atoms) { + dispatcher.setSessionID(); + return Indigo.checkResult(this, lib.indigoSetSGroupAtoms(self, atoms.length, atoms)); + } + + public int setSGroupAtoms(Collection atoms) { + return setSGroupAtoms(Indigo.toIntArray(atoms)); + } + + public int setSGroupBonds(int[] bonds) { + dispatcher.setSessionID(); + return Indigo.checkResult(this, lib.indigoSetSGroupBonds(self, bonds.length, bonds)); + } + + public int setSGroupBonds(Collection bonds) { + return setSGroupBonds(Indigo.toIntArray(bonds)); + } + + public IndigoObject iterateSGroupCrossBonds() { + dispatcher.setSessionID(); + return new IndigoObject( + dispatcher, Indigo.checkResult(this, lib.indigoIterateSGroupCrossBonds(self)), this); + } + public int addSGroupAttachmentPoint(int aidx, int lvidx, String apid) { dispatcher.setSessionID(); return Indigo.checkResult( diff --git a/core/indigo-core/molecule/molecule_sgroups.h b/core/indigo-core/molecule/molecule_sgroups.h index 8e1fd956f3..5271db0332 100644 --- a/core/indigo-core/molecule/molecule_sgroups.h +++ b/core/indigo-core/molecule/molecule_sgroups.h @@ -117,7 +117,6 @@ namespace indigo Array atoms; // represented with SAL in Molfile format Array bonds; // represented with SBL in Molfile format (CBONDS for DAT, XBONDS for others) - Array cross_bonds; // explicit cross bonds storage (#3604) — for DAT type to separate CBONDS from XBONDS Array subscript; // SMT in Molfile format (LABEL in V3000) int brk_style; // represented with SBT in Molfile format From a4df23ee0259d7216880c732597cac1861b461d4 Mon Sep 17 00:00:00 2001 From: even1024 Date: Wed, 29 Apr 2026 17:22:45 +0200 Subject: [PATCH 03/24] misc fixes --- api/python/indigo/indigo/indigo_object.py | 4 +--- .../tests/basic/3604_sgroup_atoms_bonds.py | 13 ++++--------- core/indigo-core/molecule/molecule_sgroups.h | 4 ++-- 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/api/python/indigo/indigo/indigo_object.py b/api/python/indigo/indigo/indigo_object.py index 9afc27c311..e5161b6589 100644 --- a/api/python/indigo/indigo/indigo_object.py +++ b/api/python/indigo/indigo/indigo_object.py @@ -1720,9 +1720,7 @@ def addSGroup(self, sgtype, extindex=0): return IndigoObject( self.session, IndigoLib.checkResult( - self._lib().indigoAddSGroup( - self.id, sgtype.encode(), extindex - ) + self._lib().indigoAddSGroup(self.id, sgtype.encode(), extindex) ), ) diff --git a/api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py b/api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py index 25d2a303de..8997348c73 100644 --- a/api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py +++ b/api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py @@ -10,6 +10,7 @@ - Updated iterateBonds/countBonds behavior - Updated createCrossBonds/clearSGroupCrossBonds for all types """ + import os import sys @@ -190,9 +191,7 @@ sg = mol.addSGroup(stype, 0) sg.setSGroupAtoms([1, 2, 3]) sg.createCrossBonds() - print( - "{0} createCrossBonds count: {1}".format(stype, sg.countBonds()) - ) + print("{0} createCrossBonds count: {1}".format(stype, sg.countBonds())) # ===== clearSGroupCrossBonds for all types ===== @@ -204,13 +203,9 @@ sg = mol.addSGroup(stype, 0) sg.setSGroupAtoms([1, 2, 3]) sg.createCrossBonds() - print( - "{0} before clear: {1}".format(stype, sg.countBonds()) - ) + print("{0} before clear: {1}".format(stype, sg.countBonds())) sg.clearSGroupCrossBonds() - print( - "{0} after clear: {1}".format(stype, sg.countBonds()) - ) + print("{0} after clear: {1}".format(stype, sg.countBonds())) # ===== Molfile roundtrip ===== diff --git a/core/indigo-core/molecule/molecule_sgroups.h b/core/indigo-core/molecule/molecule_sgroups.h index 5271db0332..02df3a3012 100644 --- a/core/indigo-core/molecule/molecule_sgroups.h +++ b/core/indigo-core/molecule/molecule_sgroups.h @@ -115,8 +115,8 @@ namespace indigo int parent_idx; // parent group number; represented with index in the array // TODO: leave only parent_idx - Array atoms; // represented with SAL in Molfile format - Array bonds; // represented with SBL in Molfile format (CBONDS for DAT, XBONDS for others) + Array atoms; // represented with SAL in Molfile format + Array bonds; // represented with SBL in Molfile format (CBONDS for DAT, XBONDS for others) Array subscript; // SMT in Molfile format (LABEL in V3000) int brk_style; // represented with SBT in Molfile format From fb8fcd09cf10314f66ecc4706b0a30b3dbc7c4d6 Mon Sep 17 00:00:00 2001 From: even1024 Date: Wed, 29 Apr 2026 17:31:54 +0200 Subject: [PATCH 04/24] misc fixes --- api/c/indigo/src/indigo_molecule_operations.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/api/c/indigo/src/indigo_molecule_operations.cpp b/api/c/indigo/src/indigo_molecule_operations.cpp index a14d2251d6..07e2b1e545 100644 --- a/api/c/indigo/src/indigo_molecule_operations.cpp +++ b/api/c/indigo/src/indigo_molecule_operations.cpp @@ -1770,17 +1770,19 @@ CEXPORT int indigoClearSGroupCrossBonds(int sgroup) static IndigoObject* _wrapSGroup(BaseMolecule& mol, int idx) { - SGroup& sgroup = mol.sgroups.getSGroup(idx); - if (sgroup.sgroup_type == SGroup::SG_TYPE_SUP) + switch (mol.sgroups.getSGroup(idx).sgroup_type) + { + case SGroup::SG_TYPE_SUP: return new IndigoSuperatom(mol, idx); - else if (sgroup.sgroup_type == SGroup::SG_TYPE_SRU) + case SGroup::SG_TYPE_SRU: return new IndigoRepeatingUnit(mol, idx); - else if (sgroup.sgroup_type == SGroup::SG_TYPE_MUL) + case SGroup::SG_TYPE_MUL: return new IndigoMultipleGroup(mol, idx); - else if (sgroup.sgroup_type == SGroup::SG_TYPE_DAT) + case SGroup::SG_TYPE_DAT: return new IndigoDataSGroup(mol, idx); - else + default: return new IndigoGenericSGroup(mol, idx); + } } CEXPORT int indigoAddSGroup(int molecule, const char* type, int extindex) @@ -1858,7 +1860,6 @@ CEXPORT int indigoIterateSGroupCrossBonds(int sgroup) INDIGO_END(-1); } - CEXPORT int indigoAddSGroupAttachmentPoint(int sgroup, int aidx, int lvidx, const char* apid) { INDIGO_BEGIN From b315befa2bff56b6327b9114467e804ab7ef196d Mon Sep 17 00:00:00 2001 From: even1024 Date: Wed, 29 Apr 2026 17:41:12 +0200 Subject: [PATCH 05/24] misc fixes --- api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py b/api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py index 8997348c73..d6029f2f81 100644 --- a/api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py +++ b/api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py @@ -19,7 +19,7 @@ os.path.join(os.path.abspath(__file__), "..", "..", "..", "common") ) ) -from env_indigo import Indigo, getIndigoExceptionText, IndigoException # noqa +from env_indigo import Indigo, IndigoException, getIndigoExceptionText # noqa indigo = Indigo() indigo.setOption("molfile-saving-skip-date", True) From c4606b9d1009577d68be28457c52a5a6b704f936 Mon Sep 17 00:00:00 2001 From: even1024 Date: Wed, 29 Apr 2026 19:06:42 +0200 Subject: [PATCH 06/24] test fix --- .../rendering/sgroups_instrumentation.py.out | 4 ++-- .../rendering/sgroups_instrumentation.py | 19 +++++-------------- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/api/tests/integration/ref/rendering/sgroups_instrumentation.py.out b/api/tests/integration/ref/rendering/sgroups_instrumentation.py.out index cd62bd95e8..59f2c6c7a6 100644 --- a/api/tests/integration/ref/rendering/sgroups_instrumentation.py.out +++ b/api/tests/integration/ref/rendering/sgroups_instrumentation.py.out @@ -965,8 +965,8 @@ Cross bonds after clear: 0 Cross bond indices after clear: [] Cross bonds after create: 2 Cross bond indices after create: [0, 3] -Expected error for clearSGroupCrossBonds on data SGroup: core: is not a superatom -Expected error for createCrossBonds on data SGroup: core: is not a superatom +clearSGroupCrossBonds on data SGroup: OK +createCrossBonds on data SGroup: OK ****** Get/Set Multiplier ******** -INDIGO-01000000002D diff --git a/api/tests/integration/tests/rendering/sgroups_instrumentation.py b/api/tests/integration/tests/rendering/sgroups_instrumentation.py index 016ac3d3ea..7c84b3ed92 100644 --- a/api/tests/integration/tests/rendering/sgroups_instrumentation.py +++ b/api/tests/integration/tests/rendering/sgroups_instrumentation.py @@ -214,21 +214,12 @@ def _cross_bond_indices(sg): print("Cross bond indices after create: %s" % after_create_indices) break # test with first match only + # #3604: clearSGroupCrossBonds and createCrossBonds now work for all SGroup types data_sg = m.addDataSGroup([0, 1], [], "ID", "test") - try: - data_sg.clearSGroupCrossBonds() - except IndigoException as e: - print( - "Expected error for clearSGroupCrossBonds on data SGroup: %s" - % getIndigoExceptionText(e) - ) - try: - data_sg.createCrossBonds() - except IndigoException as e: - print( - "Expected error for createCrossBonds on data SGroup: %s" - % getIndigoExceptionText(e) - ) + data_sg.clearSGroupCrossBonds() + print("clearSGroupCrossBonds on data SGroup: OK") + data_sg.createCrossBonds() + print("createCrossBonds on data SGroup: OK") print("****** SGroup Cross Bonds ********") From 38166f422de454187630d73e1ff3822acea42c6d Mon Sep 17 00:00:00 2001 From: even1024 Date: Thu, 30 Apr 2026 15:45:48 +0200 Subject: [PATCH 07/24] refactored --- api/c/indigo/src/indigo_molecule.cpp | 6 ++-- .../indigo/src/indigo_molecule_operations.cpp | 19 ++++++----- core/indigo-core/molecule/molecule_sgroups.h | 12 +++++-- .../molecule/src/base_molecule.cpp | 4 +-- .../molecule/src/base_molecule_misc.cpp | 4 +-- .../molecule/src/base_molecule_templates.cpp | 34 +++++++++---------- core/indigo-core/molecule/src/cmf_loader.cpp | 2 +- core/indigo-core/molecule/src/cmf_saver.cpp | 2 +- .../molecule/src/molecule_json_loader.cpp | 4 +-- .../molecule/src/molecule_json_saver.cpp | 6 ++-- .../molecule/src/molecule_sgroups.cpp | 2 +- .../molecule/src/molfile_loader_v2000.cpp | 2 +- .../molecule/src/molfile_loader_v3000.cpp | 2 +- .../molecule/src/molfile_saver.cpp | 18 +++++----- .../molecule/src/smiles_loader_parsers.cpp | 4 +-- 15 files changed, 65 insertions(+), 56 deletions(-) diff --git a/api/c/indigo/src/indigo_molecule.cpp b/api/c/indigo/src/indigo_molecule.cpp index 06b7e3b1bd..e14bc45b91 100644 --- a/api/c/indigo/src/indigo_molecule.cpp +++ b/api/c/indigo/src/indigo_molecule.cpp @@ -1159,7 +1159,7 @@ IndigoSGroupBondsIter::~IndigoSGroupBondsIter() bool IndigoSGroupBondsIter::hasNext() { - return _idx + 1 < _sgroup.bonds.size(); + return _idx + 1 < _sgroup.getBonds().size(); } IndigoObject* IndigoSGroupBondsIter::next() @@ -1168,7 +1168,7 @@ IndigoObject* IndigoSGroupBondsIter::next() return 0; _idx++; - return new IndigoBond(_mol, _sgroup.bonds[_idx]); + return new IndigoBond(_mol, _sgroup.getBonds()[_idx]); } int _indigoIterateAtoms(Indigo& self, int molecule, int type) @@ -1312,7 +1312,7 @@ CEXPORT int indigoCountBonds(int molecule) auto sg = _getSGroupFromObject(obj); if (sg) - return sg.get().bonds.size(); + return sg.get().getBonds().size(); BaseMolecule& mol = obj.getBaseMolecule(); diff --git a/api/c/indigo/src/indigo_molecule_operations.cpp b/api/c/indigo/src/indigo_molecule_operations.cpp index 07e2b1e545..f43107f841 100644 --- a/api/c/indigo/src/indigo_molecule_operations.cpp +++ b/api/c/indigo/src/indigo_molecule_operations.cpp @@ -1356,7 +1356,7 @@ CEXPORT int indigoAddDataSGroup(int molecule, int natoms, int* atoms, int nbonds dsg.atoms.concat(atoms, natoms); if (bonds != nullptr) - dsg.bonds.concat(bonds, nbonds); + dsg.cbonds.concat(bonds, nbonds); if (data != nullptr) dsg.data.readString(data, true); @@ -1637,7 +1637,7 @@ CEXPORT int indigoCreateSGroup(const char* type, int mapping, const char* name) if (((sgroup.atoms.find(edge.beg) != -1) && (sgroup.atoms.find(edge.end) == -1)) || ((sgroup.atoms.find(edge.end) != -1) && (sgroup.atoms.find(edge.beg) == -1))) { - sgroup.bonds.push(i); + sgroup.xbonds.push(i); } } @@ -1723,14 +1723,14 @@ CEXPORT int indigoGetSGroupNumCrossBonds(int sgroup) INDIGO_BEGIN { IndigoSGroup& isg = IndigoSGroup::cast(self.getObject(sgroup)); - return isg.get().bonds.size(); + return isg.get().xbonds.size(); } INDIGO_END(-1); } static void _fillCrossBonds(BaseMolecule& mol, SGroup& sg) { - sg.bonds.clear(); + sg.xbonds.clear(); for (auto atom_idx : sg.atoms) { const Vertex& vx = mol.getVertex(atom_idx); @@ -1739,8 +1739,8 @@ static void _fillCrossBonds(BaseMolecule& mol, SGroup& sg) if (sg.atoms.find(vx.neiVertex(nei_idx)) == -1) { int edge_idx = vx.neiEdge(nei_idx); - if (sg.bonds.find(edge_idx) == -1) - sg.bonds.push(edge_idx); + if (sg.xbonds.find(edge_idx) == -1) + sg.xbonds.push(edge_idx); } } } @@ -1762,7 +1762,7 @@ CEXPORT int indigoClearSGroupCrossBonds(int sgroup) INDIGO_BEGIN { IndigoSGroup& isg = IndigoSGroup::cast(self.getObject(sgroup)); - isg.get().bonds.clear(); + isg.get().xbonds.clear(); return 1; } INDIGO_END(-1); @@ -1835,14 +1835,15 @@ CEXPORT int indigoSetSGroupBonds(int sgroup, int nbonds, int* bonds) if (s.sgroup_type != SGroup::SG_TYPE_DAT) throw IndigoError("indigoSetSGroupBonds: only DAT SGroups support explicit bond assignment"); - s.bonds.clear(); + DataSGroup& dsg = (DataSGroup&)s; + dsg.cbonds.clear(); if (bonds != nullptr && nbonds > 0) { for (int i = 0; i < nbonds; i++) { if (bonds[i] < 0 || bonds[i] >= isg.mol.edgeEnd()) throw IndigoError("indigoSetSGroupBonds: bond index %d out of range", bonds[i]); - s.bonds.push(bonds[i]); + dsg.cbonds.push(bonds[i]); } } return 1; diff --git a/core/indigo-core/molecule/molecule_sgroups.h b/core/indigo-core/molecule/molecule_sgroups.h index 02df3a3012..8dcfeec0fc 100644 --- a/core/indigo-core/molecule/molecule_sgroups.h +++ b/core/indigo-core/molecule/molecule_sgroups.h @@ -115,8 +115,11 @@ namespace indigo int parent_idx; // parent group number; represented with index in the array // TODO: leave only parent_idx - Array atoms; // represented with SAL in Molfile format - Array bonds; // represented with SBL in Molfile format (CBONDS for DAT, XBONDS for others) + Array atoms; // represented with SAL in Molfile format + Array xbonds; // crossing bonds, represented with XBONDS/SBL in Molfile format + + virtual const Array& getBonds() const { return xbonds; } + virtual Array& getBonds() { return xbonds; } Array subscript; // SMT in Molfile format (LABEL in V3000) int brk_style; // represented with SBT in Molfile format @@ -136,6 +139,11 @@ namespace indigo DataSGroup(); ~DataSGroup() override; + Array cbonds; // chemical bonds, represented with CBONDS/SBL in Molfile format + + const Array& getBonds() const override { return cbonds; } + Array& getBonds() override { return cbonds; } + Array description; // SDT in Molfile format (filed units or format) Array name; // SDT in Molfile format (field name) Array type; // SDT in Molfile format (field type) diff --git a/core/indigo-core/molecule/src/base_molecule.cpp b/core/indigo-core/molecule/src/base_molecule.cpp index 48a8d7e8e7..b0b7a2c847 100644 --- a/core/indigo-core/molecule/src/base_molecule.cpp +++ b/core/indigo-core/molecule/src/base_molecule.cpp @@ -490,9 +490,9 @@ void BaseMolecule::_mergeWithSubmolecule_Sub(BaseMolecule& mol, const Array void BaseMolecule::_flipSGroupBond(SGroup& sgroup, int src_bond_idx, int new_bond_idx) { - int idx = sgroup.bonds.find(src_bond_idx); + int idx = sgroup.getBonds().find(src_bond_idx); if (idx != -1) - sgroup.bonds[idx] = new_bond_idx; + sgroup.getBonds()[idx] = new_bond_idx; } void BaseMolecule::_flipSuperatomBond(Superatom& sa, int src_bond_idx, int new_bond_idx) diff --git a/core/indigo-core/molecule/src/base_molecule_misc.cpp b/core/indigo-core/molecule/src/base_molecule_misc.cpp index 458494e67e..440e7bdc1f 100644 --- a/core/indigo-core/molecule/src/base_molecule_misc.cpp +++ b/core/indigo-core/molecule/src/base_molecule_misc.cpp @@ -677,8 +677,8 @@ int BaseMolecule::transformHELMtoSGroups(Array& helm_class, Array& h { ap_idx = v.neiVertex(k); int b_idx = findEdgeIndex(v.neiVertex(k), i); - sg.bonds.push(b_idx); - lvsg.bonds.push(b_idx); + sg.xbonds.push(b_idx); + lvsg.xbonds.push(b_idx); } } diff --git a/core/indigo-core/molecule/src/base_molecule_templates.cpp b/core/indigo-core/molecule/src/base_molecule_templates.cpp index 3f31e3e042..936e83575a 100644 --- a/core/indigo-core/molecule/src/base_molecule_templates.cpp +++ b/core/indigo-core/molecule/src/base_molecule_templates.cpp @@ -414,8 +414,8 @@ int BaseMolecule::_transformTGroupToSGroup(int idx, int t_idx) int bond_idx = findEdgeIndex(att_atoms[i], mapping[tg_atoms[i]]); if (bond_idx > -1) { - if (su.bonds.find(bond_idx) == -1) - su.bonds.push(bond_idx); + if (su.xbonds.find(bond_idx) == -1) + su.xbonds.push(bond_idx); } } } @@ -452,9 +452,9 @@ void BaseMolecule::_collectSuparatomAttachmentPoints(Superatom& sa, std::unorder } else // Try to create attachment points from crossing bond information { - for (int k = 0; k < sa.bonds.size(); k++) + for (int k = 0; k < sa.xbonds.size(); k++) { - const Edge& edge = getEdge(sa.bonds[k]); + const Edge& edge = getEdge(sa.xbonds[k]); int ap_aidx = -1; int ap_lvidx = -1; if (sa.atoms.find(edge.beg) != -1) @@ -498,7 +498,7 @@ void BaseMolecule::_connectTemplateAtom(Superatom& sa, int t_idx, Array& or if (ap.lvidx < 0) { int edge_idx = -1; - for (auto xbond_idx : sa.bonds) + for (auto xbond_idx : sa.xbonds) { const Edge& e = getEdge(xbond_idx); int dest_atom = e.beg == ap.aidx ? e.end : (e.end == ap.aidx ? e.beg : -1); @@ -834,7 +834,7 @@ int BaseMolecule::_transformSGroupToTGroup(int sg_idx, int& tg_id) ap_points_ids.clear(); ap_ids.clear(); - if (su.attachment_points.size() == 0 && su.bonds.size() == 0) + if (su.attachment_points.size() == 0 && su.xbonds.size() == 0) return -1; if (su.attachment_points.size() > 0) @@ -1183,7 +1183,7 @@ int BaseMolecule::_createSGroupFromFragment(Array& sg_atoms, const TGroup& int t_xbond_idx = findEdgeIndex(att_point_idx, v.neiVertex(k)); if (fragment.getBondOrder(q_xbond_idx) == getBondOrder(t_xbond_idx)) { - su_new.bonds.push(t_xbond_idx); + su_new.xbonds.push(t_xbond_idx); int idap = su_new.attachment_points.add(); Superatom::_AttachmentPoint& ap = su_new.attachment_points.at(idap); ap.aidx = att_point_idx; @@ -1208,11 +1208,11 @@ void BaseMolecule::_removeAtomsFromSGroup(SGroup& sgroup, Array& mapping) if (mapping[sgroup.atoms[i]] == -1) sgroup.atoms.remove(i); } - for (i = sgroup.bonds.size() - 1; i >= 0; i--) + for (i = sgroup.xbonds.size() - 1; i >= 0; i--) { - const Edge& edge = getEdge(sgroup.bonds[i]); + const Edge& edge = getEdge(sgroup.xbonds[i]); if (mapping[edge.beg] == -1 || mapping[edge.end] == -1) - sgroup.bonds.remove(i); + sgroup.xbonds.remove(i); } updateEditRevision(); } @@ -1260,10 +1260,10 @@ void BaseMolecule::_removeBondsFromSGroup(SGroup& sgroup, Array& mapping) { int i; - for (i = sgroup.bonds.size() - 1; i >= 0; i--) + for (i = sgroup.xbonds.size() - 1; i >= 0; i--) { - if (mapping[sgroup.bonds[i]] == -1) - sgroup.bonds.remove(i); + if (mapping[sgroup.xbonds[i]] == -1) + sgroup.xbonds.remove(i); } updateEditRevision(); } @@ -1312,17 +1312,17 @@ bool BaseMolecule::_mergeSGroupWithSubmolecule(SGroup& sgroup, SGroup& super, Ba merged = true; } } - for (i = 0; i < super.bonds.size(); i++) + for (i = 0; i < super.xbonds.size(); i++) { - const Edge& edge = supermol.getEdge(super.bonds[i]); + const Edge& edge = supermol.getEdge(super.xbonds[i]); - if (edge_mapping[super.bonds[i]] < 0) + if (edge_mapping[super.xbonds[i]] < 0) continue; if (mapping[edge.beg] < 0 || mapping[edge.end] < 0) throw Error("internal: edge is not mapped"); - sgroup.bonds.push(edge_mapping[super.bonds[i]]); + sgroup.xbonds.push(edge_mapping[super.xbonds[i]]); merged = true; } diff --git a/core/indigo-core/molecule/src/cmf_loader.cpp b/core/indigo-core/molecule/src/cmf_loader.cpp index af6f9ae970..470ce0c051 100644 --- a/core/indigo-core/molecule/src/cmf_loader.cpp +++ b/core/indigo-core/molecule/src/cmf_loader.cpp @@ -884,7 +884,7 @@ void CmfLoader::_readUIntArray(Array& dest) void CmfLoader::_readGeneralSGroup(SGroup& sgroup) { _readUIntArray(sgroup.atoms); - _readUIntArray(sgroup.bonds); + _readUIntArray(sgroup.getBonds()); } void CmfLoader::_readExtSection(Molecule& mol) diff --git a/core/indigo-core/molecule/src/cmf_saver.cpp b/core/indigo-core/molecule/src/cmf_saver.cpp index 7534c77b9c..51116c61f7 100644 --- a/core/indigo-core/molecule/src/cmf_saver.cpp +++ b/core/indigo-core/molecule/src/cmf_saver.cpp @@ -271,7 +271,7 @@ void CmfSaver::_encodeUIntArraySkipNegative(const Array& data) void CmfSaver::_encodeBaseSGroup(Molecule& /* mol */, SGroup& sgroup, const Mapping& mapping) { _encodeUIntArray(sgroup.atoms, *mapping.atom_mapping); - _encodeUIntArray(sgroup.bonds, *mapping.bond_mapping); + _encodeUIntArray(sgroup.getBonds(), *mapping.bond_mapping); } void CmfSaver::_encodeExtSection(Molecule& mol, const Mapping& mapping) diff --git a/core/indigo-core/molecule/src/molecule_json_loader.cpp b/core/indigo-core/molecule/src/molecule_json_loader.cpp index a94c8f05bb..eaaf9b079d 100644 --- a/core/indigo-core/molecule/src/molecule_json_loader.cpp +++ b/core/indigo-core/molecule/src/molecule_json_loader.cpp @@ -1190,7 +1190,7 @@ void MoleculeJsonLoader::parseSGroups(const rapidjson::Value& sgroups, BaseMolec const Value& bonds = s["bonds"]; for (rapidjson::SizeType j = 0; j < bonds.Size(); ++j) { - sgroup.bonds.push(bonds[j].GetInt()); + sgroup.getBonds().push(bonds[j].GetInt()); } } } @@ -1226,7 +1226,7 @@ void MoleculeJsonLoader::fillXBondsAndBrackets(Superatom& sa, BaseMolecule& mol) if (atoms.find(target_atom) == atoms.end()) { const auto& target_pos = mol.getAtomXyz(target_atom); - sa.bonds.push(vx.neiEdge(i)); + sa.xbonds.push(vx.neiEdge(i)); brackets.emplace_back((target_pos.x - src_pos.x) / 2, (target_pos.y - src_pos.y) / 2); } } diff --git a/core/indigo-core/molecule/src/molecule_json_saver.cpp b/core/indigo-core/molecule/src/molecule_json_saver.cpp index c24eadfbeb..d83beb9cef 100644 --- a/core/indigo-core/molecule/src/molecule_json_saver.cpp +++ b/core/indigo-core/molecule/src/molecule_json_saver.cpp @@ -501,12 +501,12 @@ void MoleculeJsonSaver::saveSGroup(SGroup& sgroup, JsonWriter& writer) break; } - if (sgroup.bonds.size()) + if (sgroup.getBonds().size()) { writer.Key("bonds"); writer.StartArray(); - for (int i = 0; i < sgroup.bonds.size(); ++i) - writer.Int(sgroup.bonds[i]); + for (int i = 0; i < sgroup.getBonds().size(); ++i) + writer.Int(sgroup.getBonds()[i]); writer.EndArray(); } diff --git a/core/indigo-core/molecule/src/molecule_sgroups.cpp b/core/indigo-core/molecule/src/molecule_sgroups.cpp index 73dea28b79..a2846495f2 100644 --- a/core/indigo-core/molecule/src/molecule_sgroups.cpp +++ b/core/indigo-core/molecule/src/molecule_sgroups.cpp @@ -681,7 +681,7 @@ void MoleculeSGroups::findSGroups(int property, Array& indices, Array& for (i = _sgroups.begin(); i != _sgroups.end(); i = _sgroups.next(i)) { SGroup& sg = *_sgroups.at(i); - if (_cmpIndices(sg.bonds, indices)) + if (_cmpIndices(sg.getBonds(), indices)) { sgs.push(i); } diff --git a/core/indigo-core/molecule/src/molfile_loader_v2000.cpp b/core/indigo-core/molecule/src/molfile_loader_v2000.cpp index 740a5c179e..983840a67a 100644 --- a/core/indigo-core/molecule/src/molfile_loader_v2000.cpp +++ b/core/indigo-core/molecule/src/molfile_loader_v2000.cpp @@ -920,7 +920,7 @@ void MolfileLoader::_readCtab2000() if (strncmp(chars, "SAL", 3) == 0) sgroup->atoms.push(_scanner.readIntFix(3) - 1); else // SBL - sgroup->bonds.push(_scanner.readIntFix(3) - 1); + sgroup->getBonds().push(_scanner.readIntFix(3) - 1); } } _scanner.skipLine(); diff --git a/core/indigo-core/molecule/src/molfile_loader_v3000.cpp b/core/indigo-core/molecule/src/molfile_loader_v3000.cpp index ac45b09274..7125602ce7 100644 --- a/core/indigo-core/molecule/src/molfile_loader_v3000.cpp +++ b/core/indigo-core/molecule/src/molfile_loader_v3000.cpp @@ -1194,7 +1194,7 @@ void MolfileLoader::_readSGroup3000(const char* str) n = scanner.readInt1(); while (n-- > 0) { - sgroup->bonds.push(scanner.readInt() - 1); + sgroup->getBonds().push(scanner.readInt() - 1); scanner.skipSpace(); } scanner.skip(1); // ) diff --git a/core/indigo-core/molecule/src/molfile_saver.cpp b/core/indigo-core/molecule/src/molfile_saver.cpp index d168103264..c0705b22a4 100644 --- a/core/indigo-core/molecule/src/molfile_saver.cpp +++ b/core/indigo-core/molecule/src/molfile_saver.cpp @@ -1118,14 +1118,14 @@ void MolfileSaver::_writeGenericSGroup3000(SGroup& sgroup, int idx, Output& outp output.printf(" %d", _atom_mapping[sgroup.atoms[i]]); output.printf(")"); } - if (sgroup.bonds.size() > 0) + if (sgroup.getBonds().size() > 0) { if (sgroup.sgroup_type == SGroup::SG_TYPE_DAT) - output.printf(" CBONDS=(%d", sgroup.bonds.size()); + output.printf(" CBONDS=(%d", sgroup.getBonds().size()); else - output.printf(" XBONDS=(%d", sgroup.bonds.size()); - for (i = 0; i < sgroup.bonds.size(); i++) - output.printf(" %d", _bond_mapping[sgroup.bonds[i]]); + output.printf(" XBONDS=(%d", sgroup.getBonds().size()); + for (i = 0; i < sgroup.getBonds().size(); i++) + output.printf(" %d", _bond_mapping[sgroup.getBonds()[i]]); output.printf(")"); } if (sgroup.sgroup_subtype > 0) @@ -1759,12 +1759,12 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) output.printf(" %3d", _atom_mapping[sgroup.atoms[k]]); output.writeCR(); } - for (j = 0; j < sgroup.bonds.size(); j += 8) + for (j = 0; j < sgroup.getBonds().size(); j += 8) { int k; - output.printf("M SBL %3d%3d", sgroup.original_group, std::min(sgroup.bonds.size(), j + 8) - j); - for (k = j; k < std::min(sgroup.bonds.size(), j + 8); k++) - output.printf(" %3d", _bond_mapping[sgroup.bonds[k]]); + output.printf("M SBL %3d%3d", sgroup.original_group, std::min(sgroup.getBonds().size(), j + 8) - j); + for (k = j; k < std::min(sgroup.getBonds().size(), j + 8); k++) + output.printf(" %3d", _bond_mapping[sgroup.getBonds()[k]]); output.writeCR(); } diff --git a/core/indigo-core/molecule/src/smiles_loader_parsers.cpp b/core/indigo-core/molecule/src/smiles_loader_parsers.cpp index 74b88cec06..da9f0c3d78 100644 --- a/core/indigo-core/molecule/src/smiles_loader_parsers.cpp +++ b/core/indigo-core/molecule/src/smiles_loader_parsers.cpp @@ -1698,7 +1698,7 @@ void SmilesLoader::_handlePolymerRepetition(int i) if (_atoms[edge.beg].polymer_index != i && _atoms[edge.end].polymer_index != i) continue; if (_atoms[edge.beg].polymer_index == i && _atoms[edge.end].polymer_index == i) - sgroup->bonds.push(j); + sgroup->xbonds.push(j); else { // bond going out of the sgroup @@ -1748,7 +1748,7 @@ void SmilesLoader::_handlePolymerRepetition(int i) for (k = rep->edgeBegin(); k != rep->edgeEnd(); k = rep->edgeNext(k)) { const Edge& edge = rep->getEdge(k); - sgroup->bonds.push(_bmol->findEdgeIndex(mapping[edge.beg], mapping[edge.end])); + sgroup->xbonds.push(_bmol->findEdgeIndex(mapping[edge.beg], mapping[edge.end])); } if (rep_end >= 0 && end_bond >= 0) From 6fd23c1885012f89e5c4e41a05904492668df4c5 Mon Sep 17 00:00:00 2001 From: even1024 Date: Mon, 4 May 2026 07:59:28 +0200 Subject: [PATCH 08/24] refactor --- .../src/indigo_abbreviations_expand.cpp | 2 +- .../indigo/src/indigo_molecule_operations.cpp | 14 +++---- core/indigo-core/molecule/ket_objects.h | 2 +- core/indigo-core/molecule/molecule_sgroups.h | 2 +- .../molecule/src/base_molecule.cpp | 2 +- .../molecule/src/base_molecule_misc.cpp | 6 +-- .../molecule/src/base_molecule_sgroups.cpp | 4 +- .../molecule/src/base_molecule_templates.cpp | 40 +++++++++---------- core/indigo-core/molecule/src/cmf_loader.cpp | 6 +-- core/indigo-core/molecule/src/cmf_saver.cpp | 4 +- core/indigo-core/molecule/src/cml_loader.cpp | 4 +- core/indigo-core/molecule/src/cml_saver.cpp | 4 +- core/indigo-core/molecule/src/ket_objects.cpp | 2 +- .../molecule/src/molecule_cdxml_loader.cpp | 4 +- .../molecule/src/molecule_cdxml_saver.cpp | 6 +-- .../molecule/src/molecule_gross_formula.cpp | 2 +- .../molecule/src/molecule_json_loader.cpp | 8 ++-- .../molecule/src/molecule_json_saver.cpp | 6 +-- .../molecule/src/molecule_sgroups.cpp | 2 +- .../molecule/src/molfile_loader_v2000.cpp | 2 +- .../molecule/src/molfile_loader_v3000.cpp | 4 +- .../molecule/src/molfile_saver.cpp | 24 +++++------ .../molecule/src/sequence_saver.cpp | 2 +- .../molecule/src/smiles_loader_parsers.cpp | 2 +- .../indigo-core/molecule/src/smiles_saver.cpp | 2 +- core/render2d/src/render_internal.cpp | 10 ++--- 26 files changed, 83 insertions(+), 83 deletions(-) diff --git a/api/c/indigo/src/indigo_abbreviations_expand.cpp b/api/c/indigo/src/indigo_abbreviations_expand.cpp index 08b535b6e3..6c7203b924 100644 --- a/api/c/indigo/src/indigo_abbreviations_expand.cpp +++ b/api/c/indigo/src/indigo_abbreviations_expand.cpp @@ -760,7 +760,7 @@ namespace indigo int sid = mol.sgroups.addSGroup(SGroup::SG_TYPE_SUP); Superatom& super = (Superatom&)mol.sgroups.getSGroup(sid); - super.subscript.readString(mol.getPseudoAtom(v), true); + super.label.readString(mol.getPseudoAtom(v), true); for (int ve = expanded.vertexBegin(); ve != expanded.vertexEnd(); ve = expanded.vertexNext(ve)) super.atoms.push(mapping[ve]); diff --git a/api/c/indigo/src/indigo_molecule_operations.cpp b/api/c/indigo/src/indigo_molecule_operations.cpp index f43107f841..ccf381cc93 100644 --- a/api/c/indigo/src/indigo_molecule_operations.cpp +++ b/api/c/indigo/src/indigo_molecule_operations.cpp @@ -778,7 +778,7 @@ void IndigoSuperatom::remove() const char* IndigoSuperatom::getName() { - return ((Superatom&)mol.sgroups.getSGroup(idx)).subscript.ptr(); + return ((Superatom&)mol.sgroups.getSGroup(idx)).label.ptr(); } IndigoSuperatom& IndigoSuperatom::cast(IndigoObject& obj) @@ -1375,7 +1375,7 @@ CEXPORT int indigoAddSuperatom(int molecule, int natoms, int* atoms, const char* BaseMolecule& mol = self.getObject(molecule).getBaseMolecule(); int idx = mol.sgroups.addSGroup(SGroup::SG_TYPE_SUP); Superatom& satom = (Superatom&)mol.sgroups.getSGroup(idx); - satom.subscript.appendString(name, true); + satom.label.appendString(name, true); if (atoms == nullptr) throw IndigoError("indigoAddSuperatom(): atoms were not specified"); @@ -1641,7 +1641,7 @@ CEXPORT int indigoCreateSGroup(const char* type, int mapping, const char* name) } } - sgroup.subscript.appendString(name, true); + sgroup.label.appendString(name, true); if (sgroup.sgroup_type == SGroup::SG_TYPE_SUP) { @@ -1698,7 +1698,7 @@ CEXPORT int indigoSetSGroupName(int sgroup, const char* sgname) { IndigoObject& obj = self.getObject(sgroup); SGroup& sg = IndigoSGroup::cast(obj).get(); - sg.subscript.readString(sgname, true); + sg.label.readString(sgname, true); return 1; } @@ -1711,9 +1711,9 @@ CEXPORT const char* indigoGetSGroupName(int sgroup) { IndigoObject& obj = self.getObject(sgroup); SGroup& sg = IndigoSGroup::cast(obj).get(); - if (sg.subscript.size() < 1) + if (sg.label.size() < 1) return ""; - return sg.subscript.ptr(); + return sg.label.ptr(); } INDIGO_END(0); } @@ -2073,7 +2073,7 @@ CEXPORT const char* indigoGetRepeatingUnitSubscript(int sgroup) INDIGO_BEGIN { RepeatingUnit& ru = IndigoRepeatingUnit::cast(self.getObject(sgroup)).get(); - return ru.subscript.ptr(); + return ru.label.ptr(); } INDIGO_END(0); } diff --git a/core/indigo-core/molecule/ket_objects.h b/core/indigo-core/molecule/ket_objects.h index 5e68d11da3..4f9cb80d40 100644 --- a/core/indigo-core/molecule/ket_objects.h +++ b/core/indigo-core/molecule/ket_objects.h @@ -361,7 +361,7 @@ namespace indigo enum class StringProps { - subscript + label }; private: diff --git a/core/indigo-core/molecule/molecule_sgroups.h b/core/indigo-core/molecule/molecule_sgroups.h index 8dcfeec0fc..6244597c43 100644 --- a/core/indigo-core/molecule/molecule_sgroups.h +++ b/core/indigo-core/molecule/molecule_sgroups.h @@ -121,7 +121,7 @@ namespace indigo virtual const Array& getBonds() const { return xbonds; } virtual Array& getBonds() { return xbonds; } - Array subscript; // SMT in Molfile format (LABEL in V3000) + Array label; // SMT in Molfile format (LABEL in V3000) int brk_style; // represented with SBT in Molfile format Array brackets; // represented with SDI in Molfile format DisplayOption contracted; // display option (-1 if undefined, 0 - expanded, 1 - contracted) diff --git a/core/indigo-core/molecule/src/base_molecule.cpp b/core/indigo-core/molecule/src/base_molecule.cpp index b0b7a2c847..b38bb49502 100644 --- a/core/indigo-core/molecule/src/base_molecule.cpp +++ b/core/indigo-core/molecule/src/base_molecule.cpp @@ -178,7 +178,7 @@ void BaseMolecule::mergeSGroupsWithSubmolecule(BaseMolecule& mol, Array& ma sg.parent_idx = supersg.parent_idx; sg.original_group = supersg.original_group; sg.parent_group = supersg.parent_group; - sg.subscript.copy(supersg.subscript); + sg.label.copy(supersg.label); if (_mergeSGroupWithSubmolecule(sg, supersg, mol, mapping, edge_mapping)) { diff --git a/core/indigo-core/molecule/src/base_molecule_misc.cpp b/core/indigo-core/molecule/src/base_molecule_misc.cpp index 440e7bdc1f..66b038ffd7 100644 --- a/core/indigo-core/molecule/src/base_molecule_misc.cpp +++ b/core/indigo-core/molecule/src/base_molecule_misc.cpp @@ -644,7 +644,7 @@ int BaseMolecule::transformHELMtoSGroups(Array& helm_class, Array& h int idx = sgroups.addSGroup("SUP"); Superatom& sg = (Superatom&)sgroups.getSGroup(idx); sg.atoms.copy(sg_atoms); - sg.subscript.copy(helm_name); + sg.label.copy(helm_name); if (helm_class.size() > 6 && strncmp(helm_class.ptr(), "PEPTIDE", 7) == 0) sg.sa_class.readString("AA", true); else @@ -663,9 +663,9 @@ int BaseMolecule::transformHELMtoSGroups(Array& helm_class, Array& h Superatom& lvsg = (Superatom&)sgroups.getSGroup(lvidx); lvsg.atoms.push(i); if (strncmp(r_names.at(r_num), "O", 1) == 0 && strlen(r_names.at(r_num)) == 1) - lvsg.subscript.readString("OH", true); + lvsg.label.readString("OH", true); else - lvsg.subscript.readString(r_names.at(r_num), true); + lvsg.label.readString(r_names.at(r_num), true); lvsg.sa_class.readString("LGRP", true); asMolecule().resetAtom(i, Element::fromString(r_names.at(r_num))); diff --git a/core/indigo-core/molecule/src/base_molecule_sgroups.cpp b/core/indigo-core/molecule/src/base_molecule_sgroups.cpp index bf89e78a97..c5d6bab74a 100644 --- a/core/indigo-core/molecule/src/base_molecule_sgroups.cpp +++ b/core/indigo-core/molecule/src/base_molecule_sgroups.cpp @@ -770,8 +770,8 @@ int BaseMolecule::transformFullCTABtoSCSR(ObjArray& templates) if (use_scsr_name) { - if ( (tg.tgroup_name.memcmp(su.subscript) == -1) && - (tg.tgroup_alias.memcmp(su.subscript) == -1) ) + if ( (tg.tgroup_name.memcmp(su.label) == -1) && + (tg.tgroup_alias.memcmp(su.label) == -1) ) { continue; } diff --git a/core/indigo-core/molecule/src/base_molecule_templates.cpp b/core/indigo-core/molecule/src/base_molecule_templates.cpp index 936e83575a..b7d6abb885 100644 --- a/core/indigo-core/molecule/src/base_molecule_templates.cpp +++ b/core/indigo-core/molecule/src/base_molecule_templates.cpp @@ -709,7 +709,7 @@ bool BaseMolecule::_replaceExpandedMonomerWithTemplate(int sg_idx, int& tg_id, M std::unordered_map& added_templates, Array& remove_atoms) { auto& sa = static_cast(sgroups.getSGroup(sg_idx)); - if (!sgroups.hasSGroup(sg_idx) || sa.subscript.size() == 0 || sa.sa_class.size() == 0) + if (!sgroups.hasSGroup(sg_idx) || sa.label.size() == 0 || sa.sa_class.size() == 0) return false; // skip LGRP @@ -734,7 +734,7 @@ bool BaseMolecule::_replaceExpandedMonomerWithTemplate(int sg_idx, int& tg_id, M } // find or create template group for residue - auto template_inchi_id = monomerNameByAlias(sa.sa_class.ptr(), sa.subscript.ptr()) + "/" + std::string(sa.sa_class.ptr()) + "/" + residue_inchi_str; + auto template_inchi_id = monomerNameByAlias(sa.sa_class.ptr(), sa.label.ptr()) + "/" + std::string(sa.sa_class.ptr()) + "/" + residue_inchi_str; auto it_added = added_templates.find(template_inchi_id); bool is_added = it_added == added_templates.end(); int tg_index = is_added ? tgroups.addTGroup() : it_added->second; @@ -744,8 +744,8 @@ bool BaseMolecule::_replaceExpandedMonomerWithTemplate(int sg_idx, int& tg_id, M { tg.tgroup_id = ++tg_id; tg.tgroup_class.copy(sa.sa_class); - if (sa.subscript.size()) - tg.tgroup_name.copy(sa.subscript); + if (sa.label.size()) + tg.tgroup_name.copy(sa.label); if (sa.sa_natreplace.size() > 0) tg.tgroup_natreplace.copy(sa.sa_natreplace); res = _restoreTemplateFromLibrary(tg, mtl, residue_inchi_str); @@ -765,7 +765,7 @@ bool BaseMolecule::_replaceExpandedMonomerWithTemplate(int sg_idx, int& tg_id, M Transformation tform; if (affine && tform.fromAffineMatrix(transform)) { - int ta_idx = addTemplateAtom(sa.subscript.ptr()); + int ta_idx = addTemplateAtom(sa.label.ptr()); setAtomXyz(ta_idx, tform.shift); tform.shift.clear(); if (tform.hasTransformation()) @@ -811,7 +811,7 @@ int BaseMolecule::_transformSGroupToTGroup(int sg_idx, int& tg_id) Superatom& su = (Superatom&)sgroups.getSGroup(sg_idx); - if (su.subscript.size() == 0) + if (su.label.size() == 0) return -1; if (su.sa_class.size() && std::string(su.sa_class.ptr()) == "LGRP") @@ -918,8 +918,8 @@ int BaseMolecule::_transformSGroupToTGroup(int sg_idx, int& tg_id) tg.tgroup_class.copy(su.sa_class); - if (su.subscript.size() > 0) - tg.tgroup_name.copy(su.subscript); + if (su.label.size() > 0) + tg.tgroup_name.copy(su.label); tg.tgroup_alias.clear(); tg.tgroup_comment.clear(); tg.tgroup_full_name.clear(); @@ -956,7 +956,7 @@ int BaseMolecule::_transformSGroupToTGroup(int sg_idx, int& tg_id) else { Superatom& sup_new = (Superatom&)sg; - if ((strcmp(su.subscript.ptr(), sup_new.subscript.ptr()) == 0) && (su.attachment_points.size() == sup_new.attachment_points.size())) + if ((strcmp(su.label.ptr(), sup_new.label.ptr()) == 0) && (su.attachment_points.size() == sup_new.attachment_points.size())) { residue_sg_idx = fragment_sgroups[j]; } @@ -1163,7 +1163,7 @@ int BaseMolecule::_createSGroupFromFragment(Array& sg_atoms, const TGroup& Superatom& su_new = (Superatom&)sgroups.getSGroup(new_sg_idx); su_new.atoms.copy(sg_atoms); - su_new.subscript.copy(tg.tgroup_name); + su_new.label.copy(tg.tgroup_name); su_new.sa_class.copy(tg.tgroup_class); su_new.sa_natreplace.copy(tg.tgroup_natreplace); @@ -1208,11 +1208,11 @@ void BaseMolecule::_removeAtomsFromSGroup(SGroup& sgroup, Array& mapping) if (mapping[sgroup.atoms[i]] == -1) sgroup.atoms.remove(i); } - for (i = sgroup.xbonds.size() - 1; i >= 0; i--) + for (i = sgroup.getBonds().size() - 1; i >= 0; i--) { - const Edge& edge = getEdge(sgroup.xbonds[i]); + const Edge& edge = getEdge(sgroup.getBonds()[i]); if (mapping[edge.beg] == -1 || mapping[edge.end] == -1) - sgroup.xbonds.remove(i); + sgroup.getBonds().remove(i); } updateEditRevision(); } @@ -1260,10 +1260,10 @@ void BaseMolecule::_removeBondsFromSGroup(SGroup& sgroup, Array& mapping) { int i; - for (i = sgroup.xbonds.size() - 1; i >= 0; i--) + for (i = sgroup.getBonds().size() - 1; i >= 0; i--) { - if (mapping[sgroup.xbonds[i]] == -1) - sgroup.xbonds.remove(i); + if (mapping[sgroup.getBonds()[i]] == -1) + sgroup.getBonds().remove(i); } updateEditRevision(); } @@ -1312,17 +1312,17 @@ bool BaseMolecule::_mergeSGroupWithSubmolecule(SGroup& sgroup, SGroup& super, Ba merged = true; } } - for (i = 0; i < super.xbonds.size(); i++) + for (i = 0; i < super.getBonds().size(); i++) { - const Edge& edge = supermol.getEdge(super.xbonds[i]); + const Edge& edge = supermol.getEdge(super.getBonds()[i]); - if (edge_mapping[super.xbonds[i]] < 0) + if (edge_mapping[super.getBonds()[i]] < 0) continue; if (mapping[edge.beg] < 0 || mapping[edge.end] < 0) throw Error("internal: edge is not mapped"); - sgroup.xbonds.push(edge_mapping[super.xbonds[i]]); + sgroup.getBonds().push(edge_mapping[super.getBonds()[i]]); merged = true; } diff --git a/core/indigo-core/molecule/src/cmf_loader.cpp b/core/indigo-core/molecule/src/cmf_loader.cpp index 470ce0c051..c6ebce0ed6 100644 --- a/core/indigo-core/molecule/src/cmf_loader.cpp +++ b/core/indigo-core/molecule/src/cmf_loader.cpp @@ -743,7 +743,7 @@ void CmfLoader::_readSGroup(int code, Molecule& mol) Superatom& s = (Superatom&)mol.sgroups.getSGroup(idx); _readGeneralSGroup(s); - _readString(s.subscript); + _readString(s.label); _readString(s.sa_class); byte bits = _scanner->readByte(); if (bits & 0x01) // -1 and 1 are the same from here @@ -768,9 +768,9 @@ void CmfLoader::_readSGroup(int code, Molecule& mol) _readGeneralSGroup(s); if (version >= 2) - _readString(s.subscript); + _readString(s.label); else - s.subscript.readString("n", true); + s.label.readString("n", true); s.connectivity = _scanner->readPackedUInt(); } diff --git a/core/indigo-core/molecule/src/cmf_saver.cpp b/core/indigo-core/molecule/src/cmf_saver.cpp index 51116c61f7..d48f417ca0 100644 --- a/core/indigo-core/molecule/src/cmf_saver.cpp +++ b/core/indigo-core/molecule/src/cmf_saver.cpp @@ -351,7 +351,7 @@ void CmfSaver::_encodeExtSection(Molecule& mol, const Mapping& mapping) Superatom& sa = (Superatom&)sg; _encode(CMF_SUPERATOM); _encodeBaseSGroup(mol, sa, mapping); - _encodeString(sa.subscript); + _encodeString(sa.label); _encodeString(sa.sa_class); byte packed = static_cast(((int)sa.contracted & 0x01) | (sa.bond_connections.size() << 1)); _output->writeByte(packed); @@ -368,7 +368,7 @@ void CmfSaver::_encodeExtSection(Molecule& mol, const Mapping& mapping) RepeatingUnit& su = (RepeatingUnit&)sg; _encode(CMF_REPEATINGUNIT); _encodeBaseSGroup(mol, su, mapping); - _encodeString(su.subscript); + _encodeString(su.label); _output->writePackedUInt(su.connectivity); } else if (sg.sgroup_type == SGroup::SG_TYPE_MUL) diff --git a/core/indigo-core/molecule/src/cml_loader.cpp b/core/indigo-core/molecule/src/cml_loader.cpp index d39585d207..727d182c58 100644 --- a/core/indigo-core/molecule/src/cml_loader.cpp +++ b/core/indigo-core/molecule/src/cml_loader.cpp @@ -1569,7 +1569,7 @@ void CmlLoader::_loadSGroupElement(XMLElement* elem, std::unordered_mapAttribute("title"); if (title != 0) - sru->subscript.readString(title, true); + sru->label.readString(title, true); const char* connect = elem->Attribute("connect"); if (connect != 0) @@ -1793,7 +1793,7 @@ void CmlLoader::_loadSGroupElement(XMLElement* elem, std::unordered_mapAttribute("title"); if (title != 0) - sup->subscript.readString(title, true); + sup->label.readString(title, true); XMLNode* pChild; for (pChild = elem->FirstChild(); pChild != 0; pChild = pChild->NextSibling()) diff --git a/core/indigo-core/molecule/src/cml_saver.cpp b/core/indigo-core/molecule/src/cml_saver.cpp index 7d3bb288c2..89b0b5a69f 100644 --- a/core/indigo-core/molecule/src/cml_saver.cpp +++ b/core/indigo-core/molecule/src/cml_saver.cpp @@ -715,7 +715,7 @@ void CmlSaver::_addSgroupElement(XMLElement* molecule, BaseMolecule& mol, SGroup Superatom& sup = (Superatom&)sgroup; - const char* name = sup.subscript.ptr(); + const char* name = sup.label.ptr(); if (name != 0 && strlen(name) > 0) { sg->SetAttribute("title", name); @@ -737,7 +737,7 @@ void CmlSaver::_addSgroupElement(XMLElement* molecule, BaseMolecule& mol, SGroup RepeatingUnit& sru = (RepeatingUnit&)sgroup; - const char* name = sru.subscript.ptr(); + const char* name = sru.label.ptr(); if (name != 0 && strlen(name) > 0) { sg->SetAttribute("title", name); diff --git a/core/indigo-core/molecule/src/ket_objects.cpp b/core/indigo-core/molecule/src/ket_objects.cpp index 00b18a1a33..cc480c0af9 100644 --- a/core/indigo-core/molecule/src/ket_objects.cpp +++ b/core/indigo-core/molecule/src/ket_objects.cpp @@ -172,7 +172,7 @@ IMPL_ERROR(KetRUSGroup, "Ket RU SGroup") const std::map& KetRUSGroup::getStringPropStrToIdx() const { static std::map str_to_idx{ - {"subscript", toUType(StringProps::subscript)}, + {"subscript", toUType(StringProps::label)}, }; return str_to_idx; }; diff --git a/core/indigo-core/molecule/src/molecule_cdxml_loader.cpp b/core/indigo-core/molecule/src/molecule_cdxml_loader.cpp index c2fcde37e2..2d0029baf7 100644 --- a/core/indigo-core/molecule/src/molecule_cdxml_loader.cpp +++ b/core/indigo-core/molecule/src/molecule_cdxml_loader.cpp @@ -1185,7 +1185,7 @@ void MoleculeCdxmlLoader::_addBracket(BaseMolecule& mol, const CdxmlBracket& bra { Superatom& sa = (Superatom&)sgroup; sa.contracted = DisplayOption::Contracted; - sa.subscript.readString(bracket.label.c_str(), true); + sa.label.readString(bracket.label.c_str(), true); sa.display_position.copy(bracket.superatom_position); } else @@ -1194,7 +1194,7 @@ void MoleculeCdxmlLoader::_addBracket(BaseMolecule& mol, const CdxmlBracket& bra case kCDXBracketUsage_SRU: { RepeatingUnit& ru = (RepeatingUnit&)sgroup; ru.connectivity = bracket.repeat_pattern; - ru.subscript.readString(bracket.label.c_str(), true); + ru.label.readString(bracket.label.c_str(), true); } break; case kCDXBracketUsage_MultipleGroup: { diff --git a/core/indigo-core/molecule/src/molecule_cdxml_saver.cpp b/core/indigo-core/molecule/src/molecule_cdxml_saver.cpp index 45f13e117a..98d5c4b1d8 100644 --- a/core/indigo-core/molecule/src/molecule_cdxml_saver.cpp +++ b/core/indigo-core/molecule/src/molecule_cdxml_saver.cpp @@ -1130,7 +1130,7 @@ void MoleculeCdxmlSaver::addFragmentNodes(BaseMolecule& mol, tinyxml2::XMLElemen } auto& sa = (Superatom&)mol.sgroups.getSGroup(kvp.first); - if (sa.subscript.size()) + if (sa.label.size()) { XMLElement* t = _doc->NewElement("t"); node->LinkEndChild(t); @@ -1147,7 +1147,7 @@ void MoleculeCdxmlSaver::addFragmentNodes(BaseMolecule& mol, tinyxml2::XMLElemen t->SetAttribute("LabelAlignment", "Above"); XMLElement* s = _doc->NewElement("s"); t->LinkEndChild(s); - XMLText* txt = _doc->NewText(sa.subscript.ptr()); + XMLText* txt = _doc->NewText(sa.label.ptr()); s->LinkEndChild(txt); } } @@ -2172,7 +2172,7 @@ void MoleculeCdxmlSaver::deleteNamelessSGroups(BaseMolecule& bmol) if (sg.sgroup_type == SGroup::SG_TYPE_SUP) { auto& sa = static_cast(sg); - if (sa.subscript.size() == 0 || std::string(sa.subscript.ptr()).size() == 0) + if (sa.label.size() == 0 || std::string(sa.label.ptr()).size() == 0) bmol.sgroups.remove(j); } } diff --git a/core/indigo-core/molecule/src/molecule_gross_formula.cpp b/core/indigo-core/molecule/src/molecule_gross_formula.cpp index 1f9673c71d..3d7c791bae 100644 --- a/core/indigo-core/molecule/src/molecule_gross_formula.cpp +++ b/core/indigo-core/molecule/src/molecule_gross_formula.cpp @@ -155,7 +155,7 @@ std::unique_ptr MoleculeGrossFormula::collect(BaseMolecule& mol, bo { RepeatingUnit* ru = (RepeatingUnit*)&mol.sgroups.getSGroup(i - 1, SGroup::SG_TYPE_SRU); filters[i].copy(ru->atoms); - indices[i].copy(ru->subscript.ptr(), ru->subscript.size() - 1); // Remove '0' symbol at the end + indices[i].copy(ru->label.ptr(), ru->label.size() - 1); // Remove '0' symbol at the end // Filter polymer atoms for (int j = 0; j < filters[i].size(); j++) { diff --git a/core/indigo-core/molecule/src/molecule_json_loader.cpp b/core/indigo-core/molecule/src/molecule_json_loader.cpp index eaaf9b079d..c99a9c5a22 100644 --- a/core/indigo-core/molecule/src/molecule_json_loader.cpp +++ b/core/indigo-core/molecule/src/molecule_json_loader.cpp @@ -1055,7 +1055,7 @@ void MoleculeJsonLoader::parseSGroups(const rapidjson::Value& sgroups, BaseMolec RepeatingUnit& ru = (RepeatingUnit&)sgroup; if (s.HasMember("subscript")) { - sgroup.subscript.readString(s["subscript"].GetString(), true); + sgroup.label.readString(s["subscript"].GetString(), true); } if (s.HasMember("connectivity")) @@ -1073,7 +1073,7 @@ void MoleculeJsonLoader::parseSGroups(const rapidjson::Value& sgroups, BaseMolec case SGroup::SG_TYPE_SUP: { Superatom& sg = (Superatom&)sgroup; if (s.HasMember("name")) - sgroup.subscript.readString(s["name"].GetString(), true); + sgroup.label.readString(s["name"].GetString(), true); if (s.HasMember("expanded")) sg.contracted = s["expanded"].GetBool() ? DisplayOption::Expanded : DisplayOption::Contracted; if (s.HasMember("class")) @@ -1518,7 +1518,7 @@ int MoleculeJsonLoader::parseMonomerTemplate(const rapidjson::Value& monomer_tem } } sa.sa_class.appendString("LGRP", true); - sa.subscript.appendString(group_name.c_str(), true); + sa.label.appendString(group_name.c_str(), true); att_desc.leaving_group = grp_idx; fillXBondsAndBrackets(sa, monomer_mol); @@ -1559,7 +1559,7 @@ int MoleculeJsonLoader::parseMonomerTemplate(const rapidjson::Value& monomer_tem sa.sa_class.appendString(mclass.c_str(), true); if (alias.size()) - sa.subscript.appendString(alias.c_str(), true); + sa.label.appendString(alias.c_str(), true); if (natreplace.size()) sa.sa_natreplace.appendString(natreplace.c_str(), true); diff --git a/core/indigo-core/molecule/src/molecule_json_saver.cpp b/core/indigo-core/molecule/src/molecule_json_saver.cpp index d83beb9cef..4fdb3c86e1 100644 --- a/core/indigo-core/molecule/src/molecule_json_saver.cpp +++ b/core/indigo-core/molecule/src/molecule_json_saver.cpp @@ -360,7 +360,7 @@ void MoleculeJsonSaver::saveSGroup(SGroup& sgroup, JsonWriter& writer) case SGroup::SG_TYPE_SUP: { Superatom& sa = (Superatom&)sgroup; writer.Key("name"); - writer.String(sgroup.subscript.size() ? sgroup.subscript.ptr() : ""); + writer.String(sgroup.label.size() ? sgroup.label.ptr() : ""); if (sa.contracted == DisplayOption::Expanded) { writer.Key("expanded"); @@ -402,10 +402,10 @@ void MoleculeJsonSaver::saveSGroup(SGroup& sgroup, JsonWriter& writer) break; case SGroup::SG_TYPE_SRU: { RepeatingUnit& ru = (RepeatingUnit&)sgroup; - if (sgroup.subscript.size()) + if (sgroup.label.size()) { writer.Key("subscript"); - writer.String(sgroup.subscript.ptr()); + writer.String(sgroup.label.ptr()); } writer.Key("connectivity"); diff --git a/core/indigo-core/molecule/src/molecule_sgroups.cpp b/core/indigo-core/molecule/src/molecule_sgroups.cpp index a2846495f2..79f441715c 100644 --- a/core/indigo-core/molecule/src/molecule_sgroups.cpp +++ b/core/indigo-core/molecule/src/molecule_sgroups.cpp @@ -510,7 +510,7 @@ void MoleculeSGroups::findSGroups(int property, const char* str, Array& sgs for (i = _sgroups.begin(); i != _sgroups.end(); i = _sgroups.next(i)) { SGroup& sg = *_sgroups.at(i); - BufferScanner sc(sg.subscript); + BufferScanner sc(sg.label); if (sc.findWordIgnoreCase(str)) { sgs.push(i); diff --git a/core/indigo-core/molecule/src/molfile_loader_v2000.cpp b/core/indigo-core/molecule/src/molfile_loader_v2000.cpp index 983840a67a..1e1137c926 100644 --- a/core/indigo-core/molecule/src/molfile_loader_v2000.cpp +++ b/core/indigo-core/molecule/src/molfile_loader_v2000.cpp @@ -1081,7 +1081,7 @@ void MolfileLoader::_readCtab2000() { SGroup& sgroup = _bmol->sgroups.getSGroup(_sgroup_mapping[sgroup_idx]); _scanner.skip(1); - _scanner.readQuotedLine(sgroup.subscript, true); + _scanner.readQuotedLine(sgroup.label, true); } } else if (strncmp(chars, "SCL", 3) == 0) diff --git a/core/indigo-core/molecule/src/molfile_loader_v3000.cpp b/core/indigo-core/molecule/src/molfile_loader_v3000.cpp index 7125602ce7..8401185245 100644 --- a/core/indigo-core/molecule/src/molfile_loader_v3000.cpp +++ b/core/indigo-core/molecule/src/molfile_loader_v3000.cpp @@ -1341,9 +1341,9 @@ void MolfileLoader::_readSGroup3000(const char* str) } if (c == ' ' && !has_quote) break; - sgroup->subscript.push(c); + sgroup->label.push(c); } - sgroup->subscript.push(0); + sgroup->label.push(0); } else if (strcmp(entity.ptr(), "CLASS") == 0) { diff --git a/core/indigo-core/molecule/src/molfile_saver.cpp b/core/indigo-core/molecule/src/molfile_saver.cpp index c0705b22a4..4816a9d4b9 100644 --- a/core/indigo-core/molecule/src/molfile_saver.cpp +++ b/core/indigo-core/molecule/src/molfile_saver.cpp @@ -905,12 +905,12 @@ void MolfileSaver::_writeCtab(Output& output, BaseMolecule& mol, bool query) sup.bond_connections[j].bond_dir.y, 0.f); } } - if (sgroup.subscript.size() > 1) + if (sgroup.label.size() > 1) { - if (sgroup.subscript.find(' ') > -1) - out.printf(" LABEL=\"%s\"", sgroup.subscript.ptr()); + if (sgroup.label.find(' ') > -1) + out.printf(" LABEL=\"%s\"", sgroup.label.ptr()); else - out.printf(" LABEL=%s", sgroup.subscript.ptr()); + out.printf(" LABEL=%s", sgroup.label.ptr()); } // convert CHEM to LINKER for BIOVIA if (sup.sa_class.size() > 1) @@ -1022,12 +1022,12 @@ void MolfileSaver::_writeCtab(Output& output, BaseMolecule& mol, bool query) out.printf(" CONNECT=HT"); else out.printf(" CONNECT=EU"); - if (sgroup.subscript.size() > 1) + if (sgroup.label.size() > 1) { - if (sgroup.subscript.find(' ') > -1) - out.printf(" LABEL=\"%s\"", sgroup.subscript.ptr()); + if (sgroup.label.find(' ') > -1) + out.printf(" LABEL=\"%s\"", sgroup.label.ptr()); else - out.printf(" LABEL=%s", sgroup.subscript.ptr()); + out.printf(" LABEL=%s", sgroup.label.ptr()); } _writeMultiString(output, buf.ptr(), buf.size()); } @@ -1769,12 +1769,12 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) } // Write SMT (subscript/label) for any SGroup type that has it - if (sgroup.sgroup_type != SGroup::SG_TYPE_MUL && sgroup.subscript.size() > 1) + if (sgroup.sgroup_type != SGroup::SG_TYPE_MUL && sgroup.label.size() > 1) { - if (sgroup.subscript.find(' ') > -1) - output.printfCR("M SMT %3d \"%s\"", sgroup.original_group, sgroup.subscript.ptr()); + if (sgroup.label.find(' ') > -1) + output.printfCR("M SMT %3d \"%s\"", sgroup.original_group, sgroup.label.ptr()); else - output.printfCR("M SMT %3d %s", sgroup.original_group, sgroup.subscript.ptr()); + output.printfCR("M SMT %3d %s", sgroup.original_group, sgroup.label.ptr()); } if (sgroup.sgroup_type == SGroup::SG_TYPE_SUP) diff --git a/core/indigo-core/molecule/src/sequence_saver.cpp b/core/indigo-core/molecule/src/sequence_saver.cpp index 4cbf6310a5..5122fad109 100644 --- a/core/indigo-core/molecule/src/sequence_saver.cpp +++ b/core/indigo-core/molecule/src/sequence_saver.cpp @@ -1045,7 +1045,7 @@ std::string SequenceSaver::saveHELM(KetDocument& document, std::vector(sgroup); - if (sa.subscript.size() != 0 && sa.subscript.ptr()[0] != 0) + if (sa.label.size() != 0 && sa.label.ptr()[0] != 0) continue; // convert leaving atom H to rg-ref auto res = mol_atom_to_ap.try_emplace(mol_id); diff --git a/core/indigo-core/molecule/src/smiles_loader_parsers.cpp b/core/indigo-core/molecule/src/smiles_loader_parsers.cpp index da9f0c3d78..ef8179dc3b 100644 --- a/core/indigo-core/molecule/src/smiles_loader_parsers.cpp +++ b/core/indigo-core/molecule/src/smiles_loader_parsers.cpp @@ -848,7 +848,7 @@ void SmilesLoader::_readOtherStuff() { RepeatingUnit& ru = static_cast(sgroup); if (subscript.size()) - ru.subscript.readString(subscript.ptr(), true); + ru.label.readString(subscript.ptr(), true); if (connectivity == "ht") ru.connectivity = RepeatingUnit::HEAD_TO_TAIL; else if (connectivity == "hh") diff --git a/core/indigo-core/molecule/src/smiles_saver.cpp b/core/indigo-core/molecule/src/smiles_saver.cpp index 8cbb93824c..80ebb28991 100644 --- a/core/indigo-core/molecule/src/smiles_saver.cpp +++ b/core/indigo-core/molecule/src/smiles_saver.cpp @@ -1874,7 +1874,7 @@ void SmilesSaver::_writeSGroups() RepeatingUnit& ru = static_cast(sg); _output.writeString("n:"); _writeSGroupAtoms(sg); - _output.printf(":%s:", ru.subscript.ptr() ? ru.subscript.ptr() : ""); + _output.printf(":%s:", ru.label.ptr() ? ru.label.ptr() : ""); switch (ru.connectivity) { case SGroup::HEAD_TO_TAIL: diff --git a/core/render2d/src/render_internal.cpp b/core/render2d/src/render_internal.cpp index fecaee297a..e8828b012d 100644 --- a/core/render2d/src/render_internal.cpp +++ b/core/render2d/src/render_internal.cpp @@ -620,7 +620,7 @@ void MoleculeRenderInternal::_initSGroups(Tree& sgroups, Rect2f parent) int tiIndex = _pushTextItem(sg, RenderItem::RIT_SGROUP); TextItem& index = _data.textitems[tiIndex]; index.fontsize = FONT_SIZE_ATTR; - bprintf(index.text, group.subscript.size() > 0 ? group.subscript.ptr() : "n"); + bprintf(index.text, group.label.size() > 0 ? group.label.ptr() : "n"); _positionIndex(sg, tiIndex, true); if (group.connectivity != RepeatingUnit::HEAD_TO_TAIL) { @@ -668,12 +668,12 @@ void MoleculeRenderInternal::_initSGroups(Tree& sgroups, Rect2f parent) _placeBrackets(sg, group.atoms, brackets); _loadBrackets(sg, brackets); - if (group.subscript.size() == 0 || std::string(group.subscript.ptr()).empty()) + if (group.label.size() == 0 || std::string(group.label.ptr()).empty()) sg.hide_brackets = true; int tiIndex = _pushTextItem(sg, RenderItem::RIT_SGROUP); TextItem& index = _data.textitems[tiIndex]; index.fontsize = FONT_SIZE_ATTR; - bprintf(index.text, "%s", group.subscript.ptr()); + bprintf(index.text, "%s", group.label.ptr()); _positionIndex(sg, tiIndex, true); parent = ILLEGAL_RECT(); @@ -850,13 +850,13 @@ void MoleculeRenderInternal::_prepareSGroups(bool collapseAtLeastOneSuperatom) if (mol.isQueryMolecule()) { - superAtomID = mol.asQueryMolecule().addAtom(new QueryMolecule::Atom(QueryMolecule::ATOM_PSEUDO, group.subscript.ptr())); + superAtomID = mol.asQueryMolecule().addAtom(new QueryMolecule::Atom(QueryMolecule::ATOM_PSEUDO, group.label.ptr())); } else { Molecule& amol = mol.asMolecule(); superAtomID = amol.addAtom(ELEM_PSEUDO); - amol.setPseudoAtom(superAtomID, group.subscript.ptr()); + amol.setPseudoAtom(superAtomID, group.label.ptr()); } QS_DEF(RedBlackSet, groupAtoms); groupAtoms.clear(); From 2b356edc05ab061b3e32466c1013889798515fb7 Mon Sep 17 00:00:00 2001 From: even1024 Date: Mon, 4 May 2026 08:40:12 +0200 Subject: [PATCH 09/24] refactor --- core/indigo-core/molecule/molecule_sgroups.h | 22 +++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/core/indigo-core/molecule/molecule_sgroups.h b/core/indigo-core/molecule/molecule_sgroups.h index 6244597c43..0bb6aa5fe8 100644 --- a/core/indigo-core/molecule/molecule_sgroups.h +++ b/core/indigo-core/molecule/molecule_sgroups.h @@ -118,10 +118,16 @@ namespace indigo Array atoms; // represented with SAL in Molfile format Array xbonds; // crossing bonds, represented with XBONDS/SBL in Molfile format - virtual const Array& getBonds() const { return xbonds; } - virtual Array& getBonds() { return xbonds; } + virtual const Array& getBonds() const + { + return xbonds; + } + virtual Array& getBonds() + { + return xbonds; + } - Array label; // SMT in Molfile format (LABEL in V3000) + Array label; // SMT in Molfile format (LABEL in V3000) int brk_style; // represented with SBT in Molfile format Array brackets; // represented with SDI in Molfile format DisplayOption contracted; // display option (-1 if undefined, 0 - expanded, 1 - contracted) @@ -141,8 +147,14 @@ namespace indigo Array cbonds; // chemical bonds, represented with CBONDS/SBL in Molfile format - const Array& getBonds() const override { return cbonds; } - Array& getBonds() override { return cbonds; } + const Array& getBonds() const override + { + return cbonds; + } + Array& getBonds() override + { + return cbonds; + } Array description; // SDT in Molfile format (filed units or format) Array name; // SDT in Molfile format (field name) From a11f391a9c55e3f7f5a0dc3ddb1a0d61a0673b99 Mon Sep 17 00:00:00 2001 From: even1024 Date: Thu, 7 May 2026 12:44:20 +0200 Subject: [PATCH 10/24] nullable refactoring --- .../indigo/src/indigo_molecule_operations.cpp | 18 ++--- core/indigo-core/common/base_cpp/nullable.h | 45 +++++++++++-- core/indigo-core/layout/src/metalayout.cpp | 4 +- .../layout/src/molecule_layout.cpp | 2 +- core/indigo-core/molecule/molecule_sgroups.h | 55 +++++++-------- .../molecule/src/base_molecule.cpp | 2 +- .../molecule/src/base_molecule_sgroups.cpp | 2 +- core/indigo-core/molecule/src/cmf_saver.cpp | 9 +-- core/indigo-core/molecule/src/cml_loader.cpp | 6 +- core/indigo-core/molecule/src/cml_saver.cpp | 6 +- .../indigo-core/molecule/src/ket_document.cpp | 4 +- .../molecule/src/molecule_cdxml_loader.cpp | 2 +- .../molecule/src/molecule_cdxml_saver.cpp | 4 +- .../molecule/src/molecule_cip_calculator.cpp | 8 +-- .../molecule/src/molecule_json_loader.cpp | 4 +- .../molecule/src/molecule_json_saver.cpp | 4 +- .../molecule/src/molecule_sgroups.cpp | 2 +- .../molecule/src/molfile_loader_v3000.cpp | 4 +- .../molecule/src/molfile_saver.cpp | 67 ++++++++++--------- .../molecule/src/smiles_loader_parsers.cpp | 4 +- core/indigo-core/tests/tests/formats.cpp | 12 ++-- core/render2d/src/render_internal.cpp | 10 +-- 22 files changed, 160 insertions(+), 114 deletions(-) diff --git a/api/c/indigo/src/indigo_molecule_operations.cpp b/api/c/indigo/src/indigo_molecule_operations.cpp index ccf381cc93..4f7d7138fa 100644 --- a/api/c/indigo/src/indigo_molecule_operations.cpp +++ b/api/c/indigo/src/indigo_molecule_operations.cpp @@ -1393,8 +1393,8 @@ CEXPORT int indigoSetDataSGroupXY(int sgroup, float x, float y, const char* opti { DataSGroup& dsg = IndigoDataSGroup::cast(self.getObject(sgroup)).get(); - dsg.display_pos.x = x; - dsg.display_pos.y = y; + dsg.display_pos->x = x; + dsg.display_pos->y = y; dsg.detached = true; if (options != 0 && options[0] != 0) @@ -1432,8 +1432,8 @@ CEXPORT int indigoSetSGroupCoords(int sgroup, float x, float y) { DataSGroup& dsg = IndigoDataSGroup::cast(self.getObject(sgroup)).get(); - dsg.display_pos.x = x; - dsg.display_pos.y = y; + dsg.display_pos->x = x; + dsg.display_pos->y = y; return 1; } @@ -1592,7 +1592,7 @@ CEXPORT int indigoSetSGroupXCoord(int sgroup, float x) { DataSGroup& dsg = IndigoDataSGroup::cast(self.getObject(sgroup)).get(); - dsg.display_pos.x = x; + dsg.display_pos->x = x; return 1; } @@ -1605,7 +1605,7 @@ CEXPORT int indigoSetSGroupYCoord(int sgroup, float y) { DataSGroup& dsg = IndigoDataSGroup::cast(self.getObject(sgroup)).get(); - dsg.display_pos.y = y; + dsg.display_pos->y = y; return 1; } @@ -2011,7 +2011,7 @@ CEXPORT int indigoGetSGroupDisplayOption(int sgroup) { Superatom& sup = IndigoSuperatom::cast(self.getObject(sgroup)).get(); if (sup.contracted > DisplayOption::Undefined) - return (int)sup.contracted; + return (int)(sup.contracted.hasValue() ? sup.contracted.get() : DisplayOption::Undefined); return 0; } @@ -2050,8 +2050,8 @@ CEXPORT float* indigoGetSGroupCoords(int sgroup) auto& tmp = self.getThreadTmpData(); auto& xy = ds.get().display_pos; - tmp.xyz[0] = xy.x; - tmp.xyz[1] = xy.y; + tmp.xyz[0] = xy->x; + tmp.xyz[1] = xy->y; tmp.xyz[2] = 0.f; return tmp.xyz; } diff --git a/core/indigo-core/common/base_cpp/nullable.h b/core/indigo-core/common/base_cpp/nullable.h index d1b9085770..c06576288e 100644 --- a/core/indigo-core/common/base_cpp/nullable.h +++ b/core/indigo-core/common/base_cpp/nullable.h @@ -31,23 +31,60 @@ namespace indigo class Nullable { public: - Nullable() : _has_value(false) + Nullable() : _has_value(false), _value{} { variable_name.readString("", true); } + Nullable(const Nullable& other) : _has_value(other._has_value), _value(other._value) + { + variable_name.copy(other.variable_name); + } + + Nullable& operator=(const Nullable& other) + { + _has_value = other._has_value; + _value = other._value; + variable_name.copy(other.variable_name); + return *this; + } + const T& get() const { - if (!_has_value) - throw Error("\"%s\" variable was not set", variable_name.ptr()); return _value; } - operator const T&() const + T& get() + { + return _value; + } + + bool operator==(const T& other) const + { + return _value == other; + } + + bool operator!=(const T& other) const + { + return _value != other; + } + + operator T&() + { return get(); } + const T* operator->() const + { + return &get(); + } + + T* operator->() + { + return &get(); + } + Nullable& operator=(const T& value) { set(value); diff --git a/core/indigo-core/layout/src/metalayout.cpp b/core/indigo-core/layout/src/metalayout.cpp index 0c2390887b..1e250541e2 100644 --- a/core/indigo-core/layout/src/metalayout.cpp +++ b/core/indigo-core/layout/src/metalayout.cpp @@ -325,8 +325,8 @@ void Metalayout::adjustMol(BaseMolecule& mol, const Vec2f& min, const Vec2f& pos { Vec2f new_center; mol.getSGroupAtomsCenterPoint(group, new_center); - group.display_pos.add(new_center); - group.display_pos.sub(data_centers[i]); + group.display_pos->add(new_center); + group.display_pos->sub(data_centers[i]); } } } diff --git a/core/indigo-core/layout/src/molecule_layout.cpp b/core/indigo-core/layout/src/molecule_layout.cpp index c5f469844f..6a8db6ec3c 100644 --- a/core/indigo-core/layout/src/molecule_layout.cpp +++ b/core/indigo-core/layout/src/molecule_layout.cpp @@ -359,7 +359,7 @@ void MoleculeLayout::_updateDataSGroups() Vec2f delta; delta.diff(after, before); - group.display_pos.add(delta); + group.display_pos->add(delta); } } } diff --git a/core/indigo-core/molecule/molecule_sgroups.h b/core/indigo-core/molecule/molecule_sgroups.h index 0bb6aa5fe8..0984fa2c8b 100644 --- a/core/indigo-core/molecule/molecule_sgroups.h +++ b/core/indigo-core/molecule/molecule_sgroups.h @@ -20,6 +20,7 @@ #define __molecule_sgroups__ #include "base_cpp/array.h" +#include "base_cpp/nullable.h" #include "base_cpp/obj_pool.h" #include "base_cpp/ptr_pool.h" #include "math/algebra.h" @@ -108,11 +109,11 @@ namespace indigo SGroup(); virtual ~SGroup(); - int sgroup_type; // group type, represnted with STY in Molfile format - int sgroup_subtype; // group subtype, represnted with SST in Molfile format - int original_group; // original group number - int parent_group; // parent group number; represented with SPL in Molfile format - int parent_idx; // parent group number; represented with index in the array + int sgroup_type; // group type, represnted with STY in Molfile format + Nullable sgroup_subtype; // group subtype, represnted with SST in Molfile format + Nullable original_group; // original group number + Nullable parent_group; // parent group number; represented with SPL in Molfile format + Nullable parent_idx; // parent group number; represented with index in the array // TODO: leave only parent_idx Array atoms; // represented with SAL in Molfile format @@ -127,10 +128,10 @@ namespace indigo return xbonds; } - Array label; // SMT in Molfile format (LABEL in V3000) - int brk_style; // represented with SBT in Molfile format - Array brackets; // represented with SDI in Molfile format - DisplayOption contracted; // display option (-1 if undefined, 0 - expanded, 1 - contracted) + Array label; // SMT in Molfile format (LABEL in V3000) + Nullable brk_style; // represented with SBT in Molfile format + Array brackets; // represented with SDI in Molfile format + Nullable contracted; // display option (-1 if undefined, 0 - expanded, 1 - contracted) static const char* typeToString(int sg_type); static int getType(const char* sg_type); @@ -156,20 +157,20 @@ namespace indigo return cbonds; } - Array description; // SDT in Molfile format (filed units or format) - Array name; // SDT in Molfile format (field name) - Array type; // SDT in Molfile format (field type) - Array querycode; // SDT in Molfile format (query code) - Array queryoper; // SDT in Molfile format (query operator) - Array data; // SCD/SED in Molfile format (field data) - Array sa_natreplace; // NATREPLACE (V3000 - 2017) - Vec2f display_pos; // SDD in Molfile format - bool detached; // or attached - bool relative; // or absolute + Array description; // SDT in Molfile format (filed units or format) + Array name; // SDT in Molfile format (field name) + Array type; // SDT in Molfile format (field type) + Array querycode; // SDT in Molfile format (query code) + Array queryoper; // SDT in Molfile format (query operator) + Array data; // SCD/SED in Molfile format (field data) + Array sa_natreplace; // NATREPLACE (V3000 - 2017) + Nullable display_pos; // SDD in Molfile format + bool detached; // or attached + bool relative; // or absolute bool display_units; - int num_chars; // number of characters - int dasp_pos; - char tag; // tag + Nullable num_chars; // number of characters + Nullable dasp_pos; + Nullable tag; // tag static constexpr char mrv_implicit_h[] = "MRV_IMPLICIT_H"; static constexpr char impl_prefix[] = "IMPL_H"; static constexpr size_t impl_prefix_len = sizeof(impl_prefix) - 1; @@ -188,7 +189,7 @@ namespace indigo Array sa_class; // SCL in Molfile format // SDS in Molfile format - int seqid; // SEQID (V3000 - 2017) + Nullable seqid; // SEQID (V3000 - 2017) Array sa_natreplace; // NATREPLACE (V3000 - 2017) bool unresolved; @@ -215,7 +216,7 @@ namespace indigo }; Array<_BondConnection> bond_connections; // SBV in Molfile format - Vec3f display_position; + Nullable display_position; private: Superatom(const Superatom&); @@ -227,7 +228,7 @@ namespace indigo RepeatingUnit(); ~RepeatingUnit() override; - int connectivity; + Nullable connectivity; private: RepeatingUnit(const RepeatingUnit&); @@ -239,7 +240,7 @@ namespace indigo CopolymerGroup(); ~CopolymerGroup() override; - int connectivity; + Nullable connectivity; private: CopolymerGroup(const CopolymerGroup&); @@ -252,7 +253,7 @@ namespace indigo ~MultipleGroup() override; Array parent_atoms; - int multiplier; + Nullable multiplier; private: MultipleGroup(const MultipleGroup&); diff --git a/core/indigo-core/molecule/src/base_molecule.cpp b/core/indigo-core/molecule/src/base_molecule.cpp index b38bb49502..d1cfc0ba2b 100644 --- a/core/indigo-core/molecule/src/base_molecule.cpp +++ b/core/indigo-core/molecule/src/base_molecule.cpp @@ -245,7 +245,7 @@ void BaseMolecule::mergeSGroupsWithSubmolecule(BaseMolecule& mol, Array& ma ap.apid.copy(supersa.attachment_points[j].apid); } } - sa.display_position.copy(supersa.display_position); + sa.display_position->copy(supersa.display_position); } else if (sg.sgroup_type == SGroup::SG_TYPE_SRU) { diff --git a/core/indigo-core/molecule/src/base_molecule_sgroups.cpp b/core/indigo-core/molecule/src/base_molecule_sgroups.cpp index c5d6bab74a..0f47f2df93 100644 --- a/core/indigo-core/molecule/src/base_molecule_sgroups.cpp +++ b/core/indigo-core/molecule/src/base_molecule_sgroups.cpp @@ -99,7 +99,7 @@ void BaseMolecule::collapse(BaseMolecule& bm, int id, Mapping& mapAtom, Mapping& const MultipleGroup& group = (MultipleGroup&)sg; - if (group.atoms.size() != group.multiplier * group.parent_atoms.size()) + if (group.atoms.size() != group.multiplier.get() * group.parent_atoms.size()) throw Error("The group is already collapsed or invalid"); QS_DEF(Array, toRemove); diff --git a/core/indigo-core/molecule/src/cmf_saver.cpp b/core/indigo-core/molecule/src/cmf_saver.cpp index d48f417ca0..0c7ce6316b 100644 --- a/core/indigo-core/molecule/src/cmf_saver.cpp +++ b/core/indigo-core/molecule/src/cmf_saver.cpp @@ -340,7 +340,7 @@ void CmfSaver::_encodeExtSection(Molecule& mol, const Mapping& mapping) _encodeString(sd.data); // Pack detached, relative, display_units, and sd.dasp_pos into one byte if (sd.dasp_pos < 0 || sd.dasp_pos > 9) - throw Error("DataSGroup dasp_pos field should be less than 10: %d", sd.dasp_pos); + throw Error("DataSGroup dasp_pos field should be less than 10: %d", sd.dasp_pos.get()); byte packed = (sd.dasp_pos & 0x0F) | (sd.detached << 4) | (sd.relative << 5) | (sd.display_units << 6); _output->writeByte(packed); _output->writePackedUInt(sd.num_chars); @@ -353,7 +353,8 @@ void CmfSaver::_encodeExtSection(Molecule& mol, const Mapping& mapping) _encodeBaseSGroup(mol, sa, mapping); _encodeString(sa.label); _encodeString(sa.sa_class); - byte packed = static_cast(((int)sa.contracted & 0x01) | (sa.bond_connections.size() << 1)); + byte packed = static_cast(((int)(sa.contracted.hasValue() ? sa.contracted.get() : DisplayOption::Undefined) & 0x01) | + (sa.bond_connections.size() << 1)); _output->writeByte(packed); if (sa.bond_connections.size() > 0) { @@ -378,7 +379,7 @@ void CmfSaver::_encodeExtSection(Molecule& mol, const Mapping& mapping) _encodeBaseSGroup(mol, sm, mapping); _encodeUIntArray(sm.parent_atoms, *mapping.atom_mapping); if (sm.multiplier < 0) - throw Error("internal error: SGroup multiplier is negative: %d", sm.multiplier); + throw Error("internal error: SGroup multiplier is negative: %d", sm.multiplier.get()); _output->writePackedUInt(sm.multiplier); } } @@ -824,7 +825,7 @@ void CmfSaver::_updateSGroupsXyzMinMax(Molecule& mol, Vec3f& min, Vec3f& max) DataSGroup& s = (DataSGroup&)sg; _updateBaseSGroupXyzMinMax(s, min, max); - Vec3f display_pos(s.display_pos.x, s.display_pos.y, 0); + Vec3f display_pos(s.display_pos->x, s.display_pos->y, 0); min.min(display_pos); max.max(display_pos); diff --git a/core/indigo-core/molecule/src/cml_loader.cpp b/core/indigo-core/molecule/src/cml_loader.cpp index 727d182c58..7dd0629acf 100644 --- a/core/indigo-core/molecule/src/cml_loader.cpp +++ b/core/indigo-core/molecule/src/cml_loader.cpp @@ -135,7 +135,7 @@ struct Atom // This methods splits a space-separated string and writes each values into an arbitrary string // property of Atom structure for each atom in the specified list -static void splitStringIntoProperties(const char* s, std::vector& atoms, std::string Atom::*property) +static void splitStringIntoProperties(const char* s, std::vector& atoms, std::string Atom::* property) { if (s == 0) return; @@ -1342,14 +1342,14 @@ void CmlLoader::_loadSGroupElement(XMLElement* elem, std::unordered_mapdisplay_pos.x = strscan.readFloat(); + dsg->display_pos->x = strscan.readFloat(); } const char* disp_y = elem->Attribute("y"); if (disp_y != 0) { BufferScanner strscan(disp_y); - dsg->display_pos.y = strscan.readFloat(); + dsg->display_pos->y = strscan.readFloat(); } const char* detached = elem->Attribute("dataDetached"); diff --git a/core/indigo-core/molecule/src/cml_saver.cpp b/core/indigo-core/molecule/src/cml_saver.cpp index 89b0b5a69f..2044351f25 100644 --- a/core/indigo-core/molecule/src/cml_saver.cpp +++ b/core/indigo-core/molecule/src/cml_saver.cpp @@ -582,7 +582,7 @@ void CmlSaver::_addSgroupElement(XMLElement* molecule, BaseMolecule& mol, SGroup QS_DEF(Array, buf); ArrayOutput out(buf); - out.printf("sg%d", sgroup.original_group); + out.printf("sg%d", sgroup.original_group.hasValue() ? sgroup.original_group.get() : 0); buf.push(0); sg->SetAttribute("id", buf.ptr()); @@ -651,8 +651,8 @@ void CmlSaver::_addSgroupElement(XMLElement* molecule, BaseMolecule& mol, SGroup sg->SetAttribute("queryOp", queryoper); } - sg->SetAttribute("x", dsg.display_pos.x); - sg->SetAttribute("y", dsg.display_pos.y); + sg->SetAttribute("x", dsg.display_pos->x); + sg->SetAttribute("y", dsg.display_pos->y); if (!dsg.detached) { diff --git a/core/indigo-core/molecule/src/ket_document.cpp b/core/indigo-core/molecule/src/ket_document.cpp index b982a3bfdb..90ef7090ad 100644 --- a/core/indigo-core/molecule/src/ket_document.cpp +++ b/core/indigo-core/molecule/src/ket_document.cpp @@ -565,7 +565,7 @@ KetDocument::KetDocument() { } -KetDocument::KetDocument(BaseMolecule& bmol) +KetDocument::KetDocument(BaseMolecule& bmol) : KetDocument() { // save molecule to ket std::string json; @@ -580,7 +580,7 @@ KetDocument::KetDocument(BaseMolecule& bmol) loader.parseJson(json, *this); } -KetDocument::KetDocument(BaseReaction& breact) +KetDocument::KetDocument(BaseReaction& breact) : KetDocument() { // save reaction to ket std::string json; diff --git a/core/indigo-core/molecule/src/molecule_cdxml_loader.cpp b/core/indigo-core/molecule/src/molecule_cdxml_loader.cpp index 2d0029baf7..e84e932223 100644 --- a/core/indigo-core/molecule/src/molecule_cdxml_loader.cpp +++ b/core/indigo-core/molecule/src/molecule_cdxml_loader.cpp @@ -1186,7 +1186,7 @@ void MoleculeCdxmlLoader::_addBracket(BaseMolecule& mol, const CdxmlBracket& bra Superatom& sa = (Superatom&)sgroup; sa.contracted = DisplayOption::Contracted; sa.label.readString(bracket.label.c_str(), true); - sa.display_position.copy(bracket.superatom_position); + sa.display_position->copy(bracket.superatom_position); } else switch (bracket.usage) diff --git a/core/indigo-core/molecule/src/molecule_cdxml_saver.cpp b/core/indigo-core/molecule/src/molecule_cdxml_saver.cpp index 98d5c4b1d8..7006818a22 100644 --- a/core/indigo-core/molecule/src/molecule_cdxml_saver.cpp +++ b/core/indigo-core/molecule/src/molecule_cdxml_saver.cpp @@ -1134,14 +1134,14 @@ void MoleculeCdxmlSaver::addFragmentNodes(BaseMolecule& mol, tinyxml2::XMLElemen { XMLElement* t = _doc->NewElement("t"); node->LinkEndChild(t); - Vec2f pos(sa.display_position.x + offset.x, -sa.display_position.y - offset.y); + Vec2f pos(sa.display_position->x + offset.x, -sa.display_position->y - offset.y); pos.scale(_bond_length); Vec2f v1(pos.x - _bond_length / 2, pos.y - _bond_length / 2); Vec2f v2(pos.x + _bond_length / 2, pos.y + _bond_length / 2); std::string pos_str = std::to_string(pos.x) + " " + std::to_string(pos.y); Rect2f bbox(v1, v2); std::string bbox_str = boundingBoxToString(bbox); - if (sa.display_position.x != 0.0f && sa.display_position.y != 0.0f) + if (sa.display_position->x != 0.0f && sa.display_position->y != 0.0f) node->SetAttribute("p", pos_str.c_str()); t->SetAttribute("LabelJustification", "Left"); t->SetAttribute("LabelAlignment", "Above"); diff --git a/core/indigo-core/molecule/src/molecule_cip_calculator.cpp b/core/indigo-core/molecule/src/molecule_cip_calculator.cpp index 0c05cd50c3..19dd9af79d 100644 --- a/core/indigo-core/molecule/src/molecule_cip_calculator.cpp +++ b/core/indigo-core/molecule/src/molecule_cip_calculator.cpp @@ -206,8 +206,8 @@ void MoleculeCIPCalculator::addCIPSgroups(BaseMolecule& mol) } sgroup.name.readString("INDIGO_CIP_DESC", true); - sgroup.display_pos.x = 0.0; - sgroup.display_pos.y = 0.0; + sgroup.display_pos->x = 0.0; + sgroup.display_pos->y = 0.0; sgroup.detached = true; sgroup.relative = true; } @@ -229,8 +229,8 @@ void MoleculeCIPCalculator::addCIPSgroups(BaseMolecule& mol) sgroup.data.readString("(Z)", true); sgroup.name.readString("INDIGO_CIP_DESC", true); - sgroup.display_pos.x = 0.0; - sgroup.display_pos.y = 0.0; + sgroup.display_pos->x = 0.0; + sgroup.display_pos->y = 0.0; sgroup.detached = true; sgroup.relative = true; } diff --git a/core/indigo-core/molecule/src/molecule_json_loader.cpp b/core/indigo-core/molecule/src/molecule_json_loader.cpp index c99a9c5a22..353086e6d2 100644 --- a/core/indigo-core/molecule/src/molecule_json_loader.cpp +++ b/core/indigo-core/molecule/src/molecule_json_loader.cpp @@ -1126,10 +1126,10 @@ void MoleculeJsonLoader::parseSGroups(const rapidjson::Value& sgroups, BaseMolec dsg.queryoper.readString(s["queryOp"].GetString(), true); if (s.HasMember("x")) - dsg.display_pos.x = s["x"].GetFloat(); + dsg.display_pos->x = s["x"].GetFloat(); if (s.HasMember("y")) - dsg.display_pos.y = s["y"].GetFloat(); + dsg.display_pos->y = s["y"].GetFloat(); if (s.HasMember("dataDetached")) dsg.detached = s["dataDetached"].GetBool(); diff --git a/core/indigo-core/molecule/src/molecule_json_saver.cpp b/core/indigo-core/molecule/src/molecule_json_saver.cpp index 4fdb3c86e1..ca394aeb04 100644 --- a/core/indigo-core/molecule/src/molecule_json_saver.cpp +++ b/core/indigo-core/molecule/src/molecule_json_saver.cpp @@ -320,9 +320,9 @@ void MoleculeJsonSaver::saveSGroup(SGroup& sgroup, JsonWriter& writer) } writer.Key("x"); - writeFloat(writer, dsg.display_pos.x); + writeFloat(writer, dsg.display_pos->x); writer.Key("y"); - writeFloat(writer, dsg.display_pos.y); + writeFloat(writer, dsg.display_pos->y); if (!dsg.detached) { diff --git a/core/indigo-core/molecule/src/molecule_sgroups.cpp b/core/indigo-core/molecule/src/molecule_sgroups.cpp index 79f441715c..b1fe7b04bd 100644 --- a/core/indigo-core/molecule/src/molecule_sgroups.cpp +++ b/core/indigo-core/molecule/src/molecule_sgroups.cpp @@ -105,7 +105,7 @@ Superatom::Superatom() : unresolved(false) seqid = -1; attachment_points.clear(); bond_connections.clear(); - display_position.clear(); + display_position->clear(); } Superatom::~Superatom() diff --git a/core/indigo-core/molecule/src/molfile_loader_v3000.cpp b/core/indigo-core/molecule/src/molfile_loader_v3000.cpp index 8401185245..5161894e8b 100644 --- a/core/indigo-core/molecule/src/molfile_loader_v3000.cpp +++ b/core/indigo-core/molecule/src/molfile_loader_v3000.cpp @@ -1657,8 +1657,8 @@ void MolfileLoader::_readSGroupDisplay(Scanner& scanner, DataSGroup& dsg) { int constexpr MIN_SDD_SIZE = 36; bool well_formatted = scanner.length() >= MIN_SDD_SIZE; - dsg.display_pos.x = scanner.readFloatFix(10); - dsg.display_pos.y = scanner.readFloatFix(10); + dsg.display_pos->x = scanner.readFloatFix(10); + dsg.display_pos->y = scanner.readFloatFix(10); int ch = ' '; if (well_formatted) { diff --git a/core/indigo-core/molecule/src/molfile_saver.cpp b/core/indigo-core/molecule/src/molfile_saver.cpp index 4816a9d4b9..1107c49e71 100644 --- a/core/indigo-core/molecule/src/molfile_saver.cpp +++ b/core/indigo-core/molecule/src/molfile_saver.cpp @@ -929,7 +929,7 @@ void MolfileSaver::_writeCtab(Output& output, BaseMolecule& mol, bool query) } } if (sup.seqid > 0) - out.printf(" SEQID=%d", sup.seqid); + out.printf(" SEQID=%d", (sup.seqid.hasValue() ? sup.seqid.get() : 0)); if (sup.sa_natreplace.size() > 1) out.printf(" NATREPLACE=%s", sup.sa_natreplace.ptr()); @@ -1062,7 +1062,7 @@ void MolfileSaver::_writeCtab(Output& output, BaseMolecule& mol, bool query) out.printf(" %d", _atom_mapping[mg.parent_atoms[j]]); out.printf(")"); } - out.printf(" MULT=%d", mg.multiplier); + out.printf(" MULT=%d", (mg.multiplier.hasValue() ? mg.multiplier.get() : 0)); _writeMultiString(output, buf.ptr(), buf.size()); } else @@ -1109,7 +1109,7 @@ void MolfileSaver::_writeGenericSGroup3000(SGroup& sgroup, int idx, Output& outp { int i; - output.printf("%d %s %d", sgroup.original_group, SGroup::typeToString(sgroup.sgroup_type), idx); + output.printf("%d %s %d", (sgroup.original_group.hasValue() ? sgroup.original_group.get() : 0), SGroup::typeToString(sgroup.sgroup_type), idx); if (sgroup.atoms.size() > 0) { @@ -1139,7 +1139,7 @@ void MolfileSaver::_writeGenericSGroup3000(SGroup& sgroup, int idx, Output& outp } if (sgroup.parent_group > 0) { - output.printf(" PARENT=%d", sgroup.parent_group); + output.printf(" PARENT=%d", (sgroup.parent_group.hasValue() ? sgroup.parent_group.get() : 0)); } for (i = 0; i < sgroup.brackets.size(); i++) { @@ -1695,7 +1695,7 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) for (i = j; i < std::min(sgroup_ids.size(), j + 8); i++) { SGroup* sgroup = &mol.sgroups.getSGroup(sgroup_ids[i]); - output.printf(" %3d %s", sgroup->original_group, SGroup::typeToString(sgroup->sgroup_type)); + output.printf(" %3d %s", (sgroup->original_group.hasValue() ? sgroup->original_group.get() : 0), SGroup::typeToString(sgroup->sgroup_type)); } output.writeCR(); } @@ -1723,7 +1723,8 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) for (i = j; i < std::min(sgroup_ids.size(), j + 8); i++) { SGroup* sgroup = &mol.sgroups.getSGroup(sgroup_ids[i]); - output.printf(" %3d %3d", sgroup->original_group, sgroup->original_group); + output.printf(" %3d %3d", (sgroup->original_group.hasValue() ? sgroup->original_group.get() : 0), + (sgroup->original_group.hasValue() ? sgroup->original_group.get() : 0)); } output.writeCR(); } @@ -1736,7 +1737,7 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) { RepeatingUnit* ru = (RepeatingUnit*)&mol.sgroups.getSGroup(i, SGroup::SG_TYPE_SRU); - output.printf(" %3d ", ru->original_group); + output.printf(" %3d ", (ru->original_group.hasValue() ? ru->original_group.get() : 0)); if (ru->connectivity == SGroup::HEAD_TO_HEAD) output.printf("HH "); @@ -1754,7 +1755,7 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) for (j = 0; j < sgroup.atoms.size(); j += 8) { int k; - output.printf("M SAL %3d%3d", sgroup.original_group, std::min(sgroup.atoms.size(), j + 8) - j); + output.printf("M SAL %3d%3d", (sgroup.original_group.hasValue() ? sgroup.original_group.get() : 0), std::min(sgroup.atoms.size(), j + 8) - j); for (k = j; k < std::min(sgroup.atoms.size(), j + 8); k++) output.printf(" %3d", _atom_mapping[sgroup.atoms[k]]); output.writeCR(); @@ -1762,7 +1763,8 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) for (j = 0; j < sgroup.getBonds().size(); j += 8) { int k; - output.printf("M SBL %3d%3d", sgroup.original_group, std::min(sgroup.getBonds().size(), j + 8) - j); + output.printf("M SBL %3d%3d", (sgroup.original_group.hasValue() ? sgroup.original_group.get() : 0), + std::min(sgroup.getBonds().size(), j + 8) - j); for (k = j; k < std::min(sgroup.getBonds().size(), j + 8); k++) output.printf(" %3d", _bond_mapping[sgroup.getBonds()[k]]); output.writeCR(); @@ -1772,9 +1774,9 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) if (sgroup.sgroup_type != SGroup::SG_TYPE_MUL && sgroup.label.size() > 1) { if (sgroup.label.find(' ') > -1) - output.printfCR("M SMT %3d \"%s\"", sgroup.original_group, sgroup.label.ptr()); + output.printfCR("M SMT %3d \"%s\"", (sgroup.original_group.hasValue() ? sgroup.original_group.get() : 0), sgroup.label.ptr()); else - output.printfCR("M SMT %3d %s", sgroup.original_group, sgroup.label.ptr()); + output.printfCR("M SMT %3d %s", (sgroup.original_group.hasValue() ? sgroup.original_group.get() : 0), sgroup.label.ptr()); } if (sgroup.sgroup_type == SGroup::SG_TYPE_SUP) @@ -1782,18 +1784,19 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) Superatom& superatom = (Superatom&)sgroup; if (superatom.sa_class.size() > 1) - output.printfCR("M SCL %3d %s", superatom.original_group, superatom.sa_class.ptr()); + output.printfCR("M SCL %3d %s", (superatom.original_group.hasValue() ? superatom.original_group.get() : 0), superatom.sa_class.ptr()); if (superatom.bond_connections.size() > 0) { for (j = 0; j < superatom.bond_connections.size(); j++) { - output.printfCR("M SBV %3d %3d %9.4f %9.4f", superatom.original_group, _bond_mapping[superatom.bond_connections[j].bond_idx], - superatom.bond_connections[j].bond_dir.x, superatom.bond_connections[j].bond_dir.y); + output.printfCR("M SBV %3d %3d %9.4f %9.4f", (superatom.original_group.hasValue() ? superatom.original_group.get() : 0), + _bond_mapping[superatom.bond_connections[j].bond_idx], superatom.bond_connections[j].bond_dir.x, + superatom.bond_connections[j].bond_dir.y); } } if (superatom.contracted == DisplayOption::Expanded) { - output.printfCR("M SDS EXP 1 %3d", superatom.original_group); + output.printfCR("M SDS EXP 1 %3d", (superatom.original_group.hasValue() ? superatom.original_group.get() : 0)); } if (superatom.attachment_points.size() > 0) { @@ -1804,7 +1807,7 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) { if (next_line) { - output.printf("M SAP %3d%3d", superatom.original_group, std::min(nrem, 6)); + output.printf("M SAP %3d%3d", (superatom.original_group.hasValue() ? superatom.original_group.get() : 0), std::min(nrem, 6)); next_line = false; } @@ -1834,7 +1837,7 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) { DataSGroup& datasgroup = (DataSGroup&)sgroup; - output.printf("M SDT %3d ", datasgroup.original_group); + output.printf("M SDT %3d ", (datasgroup.original_group.hasValue() ? datasgroup.original_group.get() : 0)); _writeFormattedString(output, datasgroup.name, 30); @@ -1848,7 +1851,7 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) output.writeCR(); - output.printf("M SDD %3d ", datasgroup.original_group); + output.printf("M SDD %3d ", (datasgroup.original_group.hasValue() ? datasgroup.original_group.get() : 0)); _writeDataSGroupDisplay(datasgroup, output); output.writeCR(); @@ -1869,7 +1872,7 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) output.writeString("SED "); else output.writeString("SCD "); - output.printf("%3d ", datasgroup.original_group); + output.printf("%3d ", (datasgroup.original_group.hasValue() ? datasgroup.original_group.get() : 0)); output.write(ptr, j); if (ptr[j] == '\n') @@ -1887,31 +1890,33 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) for (j = 0; j < mg.parent_atoms.size(); j += 8) { int k; - output.printf("M SPA %3d%3d", mg.original_group, std::min(mg.parent_atoms.size(), j + 8) - j); + output.printf("M SPA %3d%3d", (mg.original_group.hasValue() ? mg.original_group.get() : 0), std::min(mg.parent_atoms.size(), j + 8) - j); for (k = j; k < std::min(mg.parent_atoms.size(), j + 8); k++) output.printf(" %3d", _atom_mapping[mg.parent_atoms[k]]); output.writeCR(); } - output.printf("M SMT %3d %d\n", mg.original_group, mg.multiplier); + output.printf("M SMT %3d %d\n", (mg.original_group.hasValue() ? mg.original_group.get() : 0), + (mg.multiplier.hasValue() ? mg.multiplier.get() : 0)); } for (j = 0; j < sgroup.brackets.size(); j++) { - output.printf("M SDI %3d 4 %9.4f %9.4f %9.4f %9.4f\n", sgroup.original_group, sgroup.brackets[j][0].x, sgroup.brackets[j][0].y, - sgroup.brackets[j][1].x, sgroup.brackets[j][1].y); + output.printf("M SDI %3d 4 %9.4f %9.4f %9.4f %9.4f\n", (sgroup.original_group.hasValue() ? sgroup.original_group.get() : 0), + sgroup.brackets[j][0].x, sgroup.brackets[j][0].y, sgroup.brackets[j][1].x, sgroup.brackets[j][1].y); } if (sgroup.brackets.size() > 0 && sgroup.brk_style > 0) { - output.printf("M SBT 1 %3d %3d\n", sgroup.original_group, sgroup.brk_style); + output.printf("M SBT 1 %3d %3d\n", (sgroup.original_group.hasValue() ? sgroup.original_group.get() : 0), + (sgroup.brk_style.hasValue() ? sgroup.brk_style.get() : 0)); } if (sgroup.sgroup_subtype > 0) { if (sgroup.sgroup_subtype == SGroup::SG_SUBTYPE_ALT) - output.printf("M SST 1 %3d ALT\n", sgroup.original_group); + output.printf("M SST 1 %3d ALT\n", (sgroup.original_group.hasValue() ? sgroup.original_group.get() : 0)); else if (sgroup.sgroup_subtype == SGroup::SG_SUBTYPE_RAN) - output.printf("M SST 1 %3d RAN\n", sgroup.original_group); + output.printf("M SST 1 %3d RAN\n", (sgroup.original_group.hasValue() ? sgroup.original_group.get() : 0)); else if (sgroup.sgroup_subtype == SGroup::SG_SUBTYPE_BLO) - output.printf("M SST 1 %3d BLO\n", sgroup.original_group); + output.printf("M SST 1 %3d BLO\n", (sgroup.original_group.hasValue() ? sgroup.original_group.get() : 0)); } } } @@ -2202,12 +2207,14 @@ bool MolfileSaver::_checkAttPointOrder(BaseMolecule& mol, int rsite) void MolfileSaver::_writeDataSGroupDisplay(DataSGroup& datasgroup, Output& out) { - out.printf("%10.4f%10.4f %c%c%c", datasgroup.display_pos.x, datasgroup.display_pos.y, datasgroup.detached ? 'D' : 'A', datasgroup.relative ? 'R' : 'A', + out.printf("%10.4f%10.4f %c%c%c", datasgroup.display_pos->x, datasgroup.display_pos->y, datasgroup.detached ? 'D' : 'A', datasgroup.relative ? 'R' : 'A', datasgroup.display_units ? 'U' : ' '); if (datasgroup.num_chars == 0) - out.printf(" ALL 1 %c %1d ", datasgroup.tag, datasgroup.dasp_pos); + out.printf(" ALL 1 %c %1d ", (datasgroup.tag.hasValue() ? datasgroup.tag.get() : 0), + (datasgroup.dasp_pos.hasValue() ? datasgroup.dasp_pos.get() : 0)); else - out.printf(" %3d 1 %c %1d ", datasgroup.num_chars, datasgroup.tag, datasgroup.dasp_pos); + out.printf(" %3d 1 %c %1d ", (datasgroup.num_chars.hasValue() ? datasgroup.num_chars.get() : 0), + (datasgroup.tag.hasValue() ? datasgroup.tag.get() : 0), (datasgroup.dasp_pos.hasValue() ? datasgroup.dasp_pos.get() : 0)); } bool MolfileSaver::_hasNeighborEitherBond(BaseMolecule& mol, int edge_idx) diff --git a/core/indigo-core/molecule/src/smiles_loader_parsers.cpp b/core/indigo-core/molecule/src/smiles_loader_parsers.cpp index ef8179dc3b..b8beeb1198 100644 --- a/core/indigo-core/molecule/src/smiles_loader_parsers.cpp +++ b/core/indigo-core/molecule/src/smiles_loader_parsers.cpp @@ -802,11 +802,11 @@ void SmilesLoader::_readOtherStuff() _scanner.seek(pos, SEEK_SET); } _scanner.skip(1); // Skip ( - dsg.display_pos.x = _scanner.readFloat(); + dsg.display_pos->x = _scanner.readFloat(); c = _scanner.readChar(); if (c != ',') throw Error("Data S-group coord error"); - dsg.display_pos.y = _scanner.readFloat(); + dsg.display_pos->y = _scanner.readFloat(); c = _scanner.readChar(); if (c != ')') throw Error("Data S-group coord error"); diff --git a/core/indigo-core/tests/tests/formats.cpp b/core/indigo-core/tests/tests/formats.cpp index 61544db20b..eef8f15653 100644 --- a/core/indigo-core/tests/tests/formats.cpp +++ b/core/indigo-core/tests/tests/formats.cpp @@ -147,8 +147,8 @@ TEST_F(IndigoCoreFormatsTest, smiles_data_sgroups) ASSERT_STREQ(dsg.queryoper.ptr(), "like"); ASSERT_STREQ(dsg.description.ptr(), "unit"); ASSERT_EQ(dsg.tag, 't'); - ASSERT_EQ(dsg.display_pos.x, 0.0f); - ASSERT_EQ(dsg.display_pos.y, 0.0f); + ASSERT_EQ(dsg.display_pos->x, 0.0f); + ASSERT_EQ(dsg.display_pos->y, 0.0f); ASSERT_EQ(dsg.atoms.size(), 4); ASSERT_EQ(dsg.atoms.at(0), 3); ASSERT_EQ(dsg.atoms.at(1), 2); @@ -177,8 +177,8 @@ TEST_F(IndigoCoreFormatsTest, smiles_data_sgroups_coords) ASSERT_STREQ(dsg.queryoper.ptr(), ""); ASSERT_STREQ(dsg.description.ptr(), ""); ASSERT_EQ(dsg.tag, 's'); - ASSERT_EQ(dsg.display_pos.x, -1.5f); - ASSERT_EQ(dsg.display_pos.y, 7.8f); + ASSERT_EQ(dsg.display_pos->x, -1.5f); + ASSERT_EQ(dsg.display_pos->y, 7.8f); ASSERT_EQ(dsg.atoms.size(), 3); ASSERT_EQ(dsg.atoms.at(0), 1); ASSERT_EQ(dsg.atoms.at(1), 2); @@ -206,8 +206,8 @@ TEST_F(IndigoCoreFormatsTest, smiles_data_sgroups_short) ASSERT_EQ(dsg.queryoper.size(), 0); ASSERT_EQ(dsg.description.size(), 0); ASSERT_EQ(dsg.tag, ' '); - ASSERT_EQ(dsg.display_pos.x, 0.0f); - ASSERT_EQ(dsg.display_pos.y, 0.0f); + ASSERT_EQ(dsg.display_pos->x, 0.0f); + ASSERT_EQ(dsg.display_pos->y, 0.0f); ASSERT_EQ(dsg.atoms.size(), 3); ASSERT_EQ(dsg.atoms.at(0), 1); ASSERT_EQ(dsg.atoms.at(1), 2); diff --git a/core/render2d/src/render_internal.cpp b/core/render2d/src/render_internal.cpp index e8828b012d..93c757fec5 100644 --- a/core/render2d/src/render_internal.cpp +++ b/core/render2d/src/render_internal.cpp @@ -565,7 +565,7 @@ void MoleculeRenderInternal::_initSGroups(Tree& sgroups, Rect2f parent) TextItem& ti = _data.textitems[tii]; if (group.tag != ' ') { - ti.text.push(group.tag); + ti.text.push(group.tag.get()); ti.text.appendString(" = ", false); } @@ -594,7 +594,7 @@ void MoleculeRenderInternal::_initSGroups(Tree& sgroups, Rect2f parent) } else if (group.relative) { - _objDistTransform(ti.bbp, group.display_pos); + _objDistTransform(ti.bbp, group.display_pos.get()); if (group.atoms.size() > 0) { ti.bbp.add(_ad(group.atoms[0]).pos); @@ -606,7 +606,7 @@ void MoleculeRenderInternal::_initSGroups(Tree& sgroups, Rect2f parent) } else { - _objCoordTransform(ti.bbp, group.display_pos); + _objCoordTransform(ti.bbp, group.display_pos.get()); } parent = ILLEGAL_RECT(); @@ -655,7 +655,7 @@ void MoleculeRenderInternal::_initSGroups(Tree& sgroups, Rect2f parent) int tiIndex = _pushTextItem(sg, RenderItem::RIT_SGROUP); TextItem& index = _data.textitems[tiIndex]; index.fontsize = FONT_SIZE_ATTR; - bprintf(index.text, "%d", group.multiplier); + bprintf(index.text, "%d", group.multiplier.get()); _positionIndex(sg, tiIndex, true); parent = ILLEGAL_RECT(); } @@ -829,7 +829,7 @@ void MoleculeRenderInternal::_prepareSGroups(bool collapseAtLeastOneSuperatom) if (sgroup.sgroup_type == SGroup::SG_TYPE_SUP) { const Superatom& group = (Superatom&)sgroup; - Vec3f displayPosition = group.display_position; + Vec3f displayPosition = group.display_position.get(); bool useDisplayPosition = false; if (fabs(displayPosition.x) > EPSILON || fabs(displayPosition.y) > EPSILON || fabs(displayPosition.z) > EPSILON) { From 0ed55a98d7ce0b4a2f920bb0a747493b18924ebc Mon Sep 17 00:00:00 2001 From: even1024 Date: Thu, 7 May 2026 17:58:20 +0200 Subject: [PATCH 11/24] nullable refactoring --- core/indigo-core/molecule/src/cml_loader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/indigo-core/molecule/src/cml_loader.cpp b/core/indigo-core/molecule/src/cml_loader.cpp index 7dd0629acf..0d6b81e6d6 100644 --- a/core/indigo-core/molecule/src/cml_loader.cpp +++ b/core/indigo-core/molecule/src/cml_loader.cpp @@ -135,7 +135,7 @@ struct Atom // This methods splits a space-separated string and writes each values into an arbitrary string // property of Atom structure for each atom in the specified list -static void splitStringIntoProperties(const char* s, std::vector& atoms, std::string Atom::* property) +static void splitStringIntoProperties(const char* s, std::vector& atoms, std::string Atom::*property) { if (s == 0) return; From dd7297897241f70ea05769f6836ae436a30d46e1 Mon Sep 17 00:00:00 2001 From: even1024 Date: Sat, 9 May 2026 16:48:45 +0200 Subject: [PATCH 12/24] nullable refactoring --- .../indigo/src/indigo_molecule_operations.cpp | 14 ++++++----- core/indigo-core/common/base_cpp/nullable.h | 5 ++-- core/indigo-core/layout/src/metalayout.cpp | 8 ++++--- .../layout/src/molecule_layout.cpp | 6 +++-- .../molecule/src/base_molecule.cpp | 2 +- core/indigo-core/molecule/src/cmf_loader.cpp | 4 +++- core/indigo-core/molecule/src/cml_loader.cpp | 24 +++++++++++-------- .../molecule/src/molecule_cdxml_loader.cpp | 2 +- .../molecule/src/molecule_cip_calculator.cpp | 6 ++--- .../molecule/src/molecule_json_loader.cpp | 12 ++++++---- .../molecule/src/molecule_sgroups.cpp | 2 +- .../molecule/src/molfile_loader_v3000.cpp | 6 +++-- .../molecule/src/smiles_loader_parsers.cpp | 6 +++-- 13 files changed, 57 insertions(+), 40 deletions(-) diff --git a/api/c/indigo/src/indigo_molecule_operations.cpp b/api/c/indigo/src/indigo_molecule_operations.cpp index 4f7d7138fa..df1922be1f 100644 --- a/api/c/indigo/src/indigo_molecule_operations.cpp +++ b/api/c/indigo/src/indigo_molecule_operations.cpp @@ -1393,8 +1393,7 @@ CEXPORT int indigoSetDataSGroupXY(int sgroup, float x, float y, const char* opti { DataSGroup& dsg = IndigoDataSGroup::cast(self.getObject(sgroup)).get(); - dsg.display_pos->x = x; - dsg.display_pos->y = y; + dsg.display_pos.set(Vec2f(x, y)); dsg.detached = true; if (options != 0 && options[0] != 0) @@ -1432,8 +1431,7 @@ CEXPORT int indigoSetSGroupCoords(int sgroup, float x, float y) { DataSGroup& dsg = IndigoDataSGroup::cast(self.getObject(sgroup)).get(); - dsg.display_pos->x = x; - dsg.display_pos->y = y; + dsg.display_pos.set(Vec2f(x, y)); return 1; } @@ -1592,7 +1590,9 @@ CEXPORT int indigoSetSGroupXCoord(int sgroup, float x) { DataSGroup& dsg = IndigoDataSGroup::cast(self.getObject(sgroup)).get(); - dsg.display_pos->x = x; + Vec2f dp = dsg.display_pos.get(); + dp.x = x; + dsg.display_pos.set(dp); return 1; } @@ -1605,7 +1605,9 @@ CEXPORT int indigoSetSGroupYCoord(int sgroup, float y) { DataSGroup& dsg = IndigoDataSGroup::cast(self.getObject(sgroup)).get(); - dsg.display_pos->y = y; + Vec2f dp = dsg.display_pos.get(); + dp.y = y; + dsg.display_pos.set(dp); return 1; } diff --git a/core/indigo-core/common/base_cpp/nullable.h b/core/indigo-core/common/base_cpp/nullable.h index c06576288e..78caf17374 100644 --- a/core/indigo-core/common/base_cpp/nullable.h +++ b/core/indigo-core/common/base_cpp/nullable.h @@ -70,9 +70,8 @@ namespace indigo } operator T&() - { - return get(); + return _value; } const T* operator->() const @@ -82,7 +81,7 @@ namespace indigo T* operator->() { - return &get(); + return &_value; } Nullable& operator=(const T& value) diff --git a/core/indigo-core/layout/src/metalayout.cpp b/core/indigo-core/layout/src/metalayout.cpp index 1e250541e2..e9e3ec4798 100644 --- a/core/indigo-core/layout/src/metalayout.cpp +++ b/core/indigo-core/layout/src/metalayout.cpp @@ -321,12 +321,14 @@ void Metalayout::adjustMol(BaseMolecule& mol, const Vec2f& min, const Vec2f& pos if (sg.sgroup_type == SGroup::SG_TYPE_DAT) { DataSGroup& group = (DataSGroup&)sg; - if (!group.relative) + if (!group.relative && group.display_pos.hasValue()) { Vec2f new_center; mol.getSGroupAtomsCenterPoint(group, new_center); - group.display_pos->add(new_center); - group.display_pos->sub(data_centers[i]); + Vec2f dp = group.display_pos.get(); + dp.add(new_center); + dp.sub(data_centers[i]); + group.display_pos.set(dp); } } } diff --git a/core/indigo-core/layout/src/molecule_layout.cpp b/core/indigo-core/layout/src/molecule_layout.cpp index 6a8db6ec3c..e6df3f72a5 100644 --- a/core/indigo-core/layout/src/molecule_layout.cpp +++ b/core/indigo-core/layout/src/molecule_layout.cpp @@ -340,7 +340,7 @@ void MoleculeLayout::_updateDataSGroups() if (sg.sgroup_type == SGroup::SG_TYPE_DAT) { DataSGroup& group = (DataSGroup&)sg; - if (!group.relative) + if (!group.relative && group.display_pos.hasValue()) { Vec2f before; _molecule.getSGroupAtomsCenterPoint(group, before); @@ -359,7 +359,9 @@ void MoleculeLayout::_updateDataSGroups() Vec2f delta; delta.diff(after, before); - group.display_pos->add(delta); + Vec2f dp = group.display_pos.get(); + dp.add(delta); + group.display_pos.set(dp); } } } diff --git a/core/indigo-core/molecule/src/base_molecule.cpp b/core/indigo-core/molecule/src/base_molecule.cpp index d1cfc0ba2b..23d7c94237 100644 --- a/core/indigo-core/molecule/src/base_molecule.cpp +++ b/core/indigo-core/molecule/src/base_molecule.cpp @@ -245,7 +245,7 @@ void BaseMolecule::mergeSGroupsWithSubmolecule(BaseMolecule& mol, Array& ma ap.apid.copy(supersa.attachment_points[j].apid); } } - sa.display_position->copy(supersa.display_position); + sa.display_position = supersa.display_position; } else if (sg.sgroup_type == SGroup::SG_TYPE_SRU) { diff --git a/core/indigo-core/molecule/src/cmf_loader.cpp b/core/indigo-core/molecule/src/cmf_loader.cpp index c6ebce0ed6..0ea14e3d2e 100644 --- a/core/indigo-core/molecule/src/cmf_loader.cpp +++ b/core/indigo-core/molecule/src/cmf_loader.cpp @@ -843,7 +843,9 @@ void CmfLoader::_readSGroupXYZ(Scanner& scanner, int idx, Molecule& mol, const C { DataSGroup& s = (DataSGroup&)sg; _readBaseSGroupXyz(scanner, s, range); - _readVec2f(scanner, s.display_pos, range); + Vec2f dp; + _readVec2f(scanner, dp, range); + s.display_pos.set(dp); } else if (sg_type == SGroup::SG_TYPE_SUP) { diff --git a/core/indigo-core/molecule/src/cml_loader.cpp b/core/indigo-core/molecule/src/cml_loader.cpp index 0d6b81e6d6..adbf29ec82 100644 --- a/core/indigo-core/molecule/src/cml_loader.cpp +++ b/core/indigo-core/molecule/src/cml_loader.cpp @@ -135,7 +135,7 @@ struct Atom // This methods splits a space-separated string and writes each values into an arbitrary string // property of Atom structure for each atom in the specified list -static void splitStringIntoProperties(const char* s, std::vector& atoms, std::string Atom::*property) +static void splitStringIntoProperties(const char* s, std::vector& atoms, std::string Atom::* property) { if (s == 0) return; @@ -1339,17 +1339,21 @@ void CmlLoader::_loadSGroupElement(XMLElement* elem, std::unordered_mapdescription.readString(fieldtype, true); const char* disp_x = elem->Attribute("x"); - if (disp_x != 0) - { - BufferScanner strscan(disp_x); - dsg->display_pos->x = strscan.readFloat(); - } - const char* disp_y = elem->Attribute("y"); - if (disp_y != 0) + if (disp_x != 0 || disp_y != 0) { - BufferScanner strscan(disp_y); - dsg->display_pos->y = strscan.readFloat(); + Vec2f dp; + if (disp_x != 0) + { + BufferScanner strscan(disp_x); + dp.x = strscan.readFloat(); + } + if (disp_y != 0) + { + BufferScanner strscan(disp_y); + dp.y = strscan.readFloat(); + } + dsg->display_pos.set(dp); } const char* detached = elem->Attribute("dataDetached"); diff --git a/core/indigo-core/molecule/src/molecule_cdxml_loader.cpp b/core/indigo-core/molecule/src/molecule_cdxml_loader.cpp index e84e932223..dc17b63365 100644 --- a/core/indigo-core/molecule/src/molecule_cdxml_loader.cpp +++ b/core/indigo-core/molecule/src/molecule_cdxml_loader.cpp @@ -1186,7 +1186,7 @@ void MoleculeCdxmlLoader::_addBracket(BaseMolecule& mol, const CdxmlBracket& bra Superatom& sa = (Superatom&)sgroup; sa.contracted = DisplayOption::Contracted; sa.label.readString(bracket.label.c_str(), true); - sa.display_position->copy(bracket.superatom_position); + sa.display_position.set(Vec3f(bracket.superatom_position.x, bracket.superatom_position.y, bracket.superatom_position.z)); } else switch (bracket.usage) diff --git a/core/indigo-core/molecule/src/molecule_cip_calculator.cpp b/core/indigo-core/molecule/src/molecule_cip_calculator.cpp index 19dd9af79d..f285be9f0f 100644 --- a/core/indigo-core/molecule/src/molecule_cip_calculator.cpp +++ b/core/indigo-core/molecule/src/molecule_cip_calculator.cpp @@ -206,8 +206,7 @@ void MoleculeCIPCalculator::addCIPSgroups(BaseMolecule& mol) } sgroup.name.readString("INDIGO_CIP_DESC", true); - sgroup.display_pos->x = 0.0; - sgroup.display_pos->y = 0.0; + sgroup.display_pos.set(Vec2f(0.0f, 0.0f)); sgroup.detached = true; sgroup.relative = true; } @@ -229,8 +228,7 @@ void MoleculeCIPCalculator::addCIPSgroups(BaseMolecule& mol) sgroup.data.readString("(Z)", true); sgroup.name.readString("INDIGO_CIP_DESC", true); - sgroup.display_pos->x = 0.0; - sgroup.display_pos->y = 0.0; + sgroup.display_pos.set(Vec2f(0.0f, 0.0f)); sgroup.detached = true; sgroup.relative = true; } diff --git a/core/indigo-core/molecule/src/molecule_json_loader.cpp b/core/indigo-core/molecule/src/molecule_json_loader.cpp index 353086e6d2..572fadd3c1 100644 --- a/core/indigo-core/molecule/src/molecule_json_loader.cpp +++ b/core/indigo-core/molecule/src/molecule_json_loader.cpp @@ -1125,11 +1125,15 @@ void MoleculeJsonLoader::parseSGroups(const rapidjson::Value& sgroups, BaseMolec if (s.HasMember("queryOp")) dsg.queryoper.readString(s["queryOp"].GetString(), true); - if (s.HasMember("x")) - dsg.display_pos->x = s["x"].GetFloat(); + { + Vec2f dp; + if (s.HasMember("x")) + dp.x = s["x"].GetFloat(); - if (s.HasMember("y")) - dsg.display_pos->y = s["y"].GetFloat(); + if (s.HasMember("y")) + dp.y = s["y"].GetFloat(); + dsg.display_pos.set(dp); + } if (s.HasMember("dataDetached")) dsg.detached = s["dataDetached"].GetBool(); diff --git a/core/indigo-core/molecule/src/molecule_sgroups.cpp b/core/indigo-core/molecule/src/molecule_sgroups.cpp index b1fe7b04bd..ef2e660a45 100644 --- a/core/indigo-core/molecule/src/molecule_sgroups.cpp +++ b/core/indigo-core/molecule/src/molecule_sgroups.cpp @@ -105,7 +105,7 @@ Superatom::Superatom() : unresolved(false) seqid = -1; attachment_points.clear(); bond_connections.clear(); - display_position->clear(); + display_position.set(Vec3f(0, 0, 0)); } Superatom::~Superatom() diff --git a/core/indigo-core/molecule/src/molfile_loader_v3000.cpp b/core/indigo-core/molecule/src/molfile_loader_v3000.cpp index 5161894e8b..5431281505 100644 --- a/core/indigo-core/molecule/src/molfile_loader_v3000.cpp +++ b/core/indigo-core/molecule/src/molfile_loader_v3000.cpp @@ -1657,8 +1657,10 @@ void MolfileLoader::_readSGroupDisplay(Scanner& scanner, DataSGroup& dsg) { int constexpr MIN_SDD_SIZE = 36; bool well_formatted = scanner.length() >= MIN_SDD_SIZE; - dsg.display_pos->x = scanner.readFloatFix(10); - dsg.display_pos->y = scanner.readFloatFix(10); + Vec2f dp; + dp.x = scanner.readFloatFix(10); + dp.y = scanner.readFloatFix(10); + dsg.display_pos.set(dp); int ch = ' '; if (well_formatted) { diff --git a/core/indigo-core/molecule/src/smiles_loader_parsers.cpp b/core/indigo-core/molecule/src/smiles_loader_parsers.cpp index b8beeb1198..8268186896 100644 --- a/core/indigo-core/molecule/src/smiles_loader_parsers.cpp +++ b/core/indigo-core/molecule/src/smiles_loader_parsers.cpp @@ -802,14 +802,16 @@ void SmilesLoader::_readOtherStuff() _scanner.seek(pos, SEEK_SET); } _scanner.skip(1); // Skip ( - dsg.display_pos->x = _scanner.readFloat(); + Vec2f dp; + dp.x = _scanner.readFloat(); c = _scanner.readChar(); if (c != ',') throw Error("Data S-group coord error"); - dsg.display_pos->y = _scanner.readFloat(); + dp.y = _scanner.readFloat(); c = _scanner.readChar(); if (c != ')') throw Error("Data S-group coord error"); + dsg.display_pos.set(dp); } else { From cd2104619b264c72b2ac709bc96fd00da9044d3a Mon Sep 17 00:00:00 2001 From: even1024 Date: Sun, 10 May 2026 03:12:34 +0200 Subject: [PATCH 13/24] last commit --- .../indigo/src/indigo_molecule_operations.cpp | 12 +- api/dotnet/src/IndigoObject.cs | 2 +- .../java/com/epam/indigo/IndigoObject.java | 4 + .../ref/basic/3604_sgroup_atoms_bonds.py.out | 14 +- .../integration/ref/basic/basic_load.py.out | 22 +-- .../ref/basic/sgroups_basic.py.out | 32 ++--- .../ref/formats/mol_features.py.out | 52 +++---- .../rendering/sgroups_instrumentation.py.out | 18 +-- .../tests/basic/3604_sgroup_atoms_bonds.py | 75 ++++++++++ core/indigo-core/molecule/molecule_sgroups.h | 7 +- .../molecule/src/base_molecule.cpp | 6 +- core/indigo-core/molecule/src/cml_saver.cpp | 12 +- .../molecule/src/molecule_json_saver.cpp | 47 +++++-- .../molecule/src/molecule_sgroups.cpp | 4 +- .../molecule/src/molfile_loader_v2000.cpp | 20 ++- .../molecule/src/molfile_loader_v3000.cpp | 7 +- .../molecule/src/molfile_saver.cpp | 130 ++++++++++++------ 17 files changed, 326 insertions(+), 138 deletions(-) diff --git a/api/c/indigo/src/indigo_molecule_operations.cpp b/api/c/indigo/src/indigo_molecule_operations.cpp index df1922be1f..32571196d3 100644 --- a/api/c/indigo/src/indigo_molecule_operations.cpp +++ b/api/c/indigo/src/indigo_molecule_operations.cpp @@ -1798,7 +1798,7 @@ CEXPORT int indigoAddSGroup(int molecule, const char* type, int extindex) SGroup& sgroup = mol.sgroups.getSGroup(idx); if (extindex > 0) - sgroup.original_group = extindex; + sgroup.ext_index = extindex; return self.addObject(_wrapSGroup(mol, idx)); } @@ -2171,7 +2171,7 @@ CEXPORT int indigoGetSGroupOriginalId(int sgroup) INDIGO_BEGIN { IndigoSGroup& sg = IndigoSGroup::cast(self.getObject(sgroup)); - return sg.get().original_group; + return sg.get().index; } INDIGO_END(-1); } @@ -2185,11 +2185,11 @@ CEXPORT int indigoSetSGroupOriginalId(int sgroup, int new_original) for (auto i = sgr.mol.sgroups.begin(); i != sgr.mol.sgroups.end(); i = sgr.mol.sgroups.next(i)) { SGroup& sg = sgr.mol.sgroups.getSGroup(i); - if (sg.original_group == new_original && i != sgr.idx) + if (sg.index == new_original && i != sgr.idx) throw IndigoError("indigoSetSGroupOriginalId: duplicated sgroup id %d )", new_original); } - int old_original = sgr.get().original_group; + int old_original = sgr.get().index; if (old_original > 0) { for (auto i = sgr.mol.sgroups.begin(); i != sgr.mol.sgroups.end(); i = sgr.mol.sgroups.next(i)) @@ -2199,7 +2199,7 @@ CEXPORT int indigoSetSGroupOriginalId(int sgroup, int new_original) sg.parent_group = new_original; } } - sgr.get().original_group = new_original; + sgr.get().index = new_original; return 1; } @@ -2226,7 +2226,7 @@ CEXPORT int indigoSetSGroupParentId(int sgroup, int parent) for (auto i = sgr.mol.sgroups.begin(); i != sgr.mol.sgroups.end(); i = sgr.mol.sgroups.next(i)) { SGroup& sg = sgr.mol.sgroups.getSGroup(i); - if (sg.original_group == parent) + if (sg.index == parent) original_found = true; } if (!original_found) diff --git a/api/dotnet/src/IndigoObject.cs b/api/dotnet/src/IndigoObject.cs index 35f0180e14..d21e7075a5 100644 --- a/api/dotnet/src/IndigoObject.cs +++ b/api/dotnet/src/IndigoObject.cs @@ -836,7 +836,7 @@ public int clearSGroupCrossBonds() return dispatcher.checkResult(IndigoLib.indigoClearSGroupCrossBonds(self)); } - public IndigoObject addSGroup(string type, int extindex) + public IndigoObject addSGroup(string type, int extindex = 0) { dispatcher.setSessionID(); return new IndigoObject(dispatcher, dispatcher.checkResult(IndigoLib.indigoAddSGroup(self, type, extindex)), this); diff --git a/api/java/indigo/src/main/java/com/epam/indigo/IndigoObject.java b/api/java/indigo/src/main/java/com/epam/indigo/IndigoObject.java index 4329b9bcee..119367f428 100644 --- a/api/java/indigo/src/main/java/com/epam/indigo/IndigoObject.java +++ b/api/java/indigo/src/main/java/com/epam/indigo/IndigoObject.java @@ -1244,6 +1244,10 @@ public int clearSGroupCrossBonds() { return Indigo.checkResult(this, lib.indigoClearSGroupCrossBonds(self)); } + public IndigoObject addSGroup(String type) { + return addSGroup(type, 0); + } + public IndigoObject addSGroup(String type, int extindex) { dispatcher.setSessionID(); return new IndigoObject( diff --git a/api/tests/integration/ref/basic/3604_sgroup_atoms_bonds.py.out b/api/tests/integration/ref/basic/3604_sgroup_atoms_bonds.py.out index 2059aee215..174da25916 100644 --- a/api/tests/integration/ref/basic/3604_sgroup_atoms_bonds.py.out +++ b/api/tests/integration/ref/basic/3604_sgroup_atoms_bonds.py.out @@ -12,7 +12,7 @@ MUL atoms: 0 GEN type: 0 GEN atoms: 0 ****** addSGroup: with explicit extindex ******** -extindex: 42 +extindex: 0 ****** setSGroupAtoms: set atoms on empty SGroup ******** atoms after set: 3 atom symbols: C C C @@ -57,3 +57,15 @@ type: 2 atoms: 3 bonds: 2 sgroup count: 1 +****** ext_index: V3000 roundtrip with explicit extindex ******** +original id before save: 0 +roundtrip original id: 1 +V3000 index=1 extindex=42 +****** ext_index: V3000 roundtrip auto-assign (extindex=0) ******** +V3000 auto-assigned: index=1 extindex=1 +****** ext_index: V2000 roundtrip with explicit extindex ******** +V2000 SLB: M SLB 1 1 55 +V2000 roundtrip original id: 1 +****** ext_index: addSGroup without extindex (default=0) ******** +GEN type: 0 +GEN atoms: 2 diff --git a/api/tests/integration/ref/basic/basic_load.py.out b/api/tests/integration/ref/basic/basic_load.py.out index dd961f46c5..4918b7f75a 100644 --- a/api/tests/integration/ref/basic/basic_load.py.out +++ b/api/tests/integration/ref/basic/basic_load.py.out @@ -518,13 +518,13 @@ M V30 BEGIN COLLECTION M V30 MDLV30/STERAC1 ATOMS=(4 2 12 19 26) M V30 END COLLECTION M V30 BEGIN SGROUP -M V30 1 SUP 1 ATOMS=(8 1 2 3 4 5 6 7 8) XBONDS=(1 1) LABEL=Asx CLASS=AA SAP=- +M V30 1 SUP 0 ATOMS=(8 1 2 3 4 5 6 7 8) XBONDS=(1 1) LABEL=Asx CLASS=AA SAP=- M V30 (3 1 17 Al) SAP=(3 7 0 Br) -M V30 2 SUP 2 ATOMS=(8 9 12 13 14 15 16 17 18) XBONDS=(2 2 1) LABEL=Asx CLAS- +M V30 2 SUP 0 ATOMS=(8 9 12 13 14 15 16 17 18) XBONDS=(2 2 1) LABEL=Asx CLAS- M V30 S=AA SAP=(3 9 10 Al) SAP=(3 17 1 Br) -M V30 3 SUP 3 ATOMS=(8 10 19 20 21 22 23 24 25) XBONDS=(2 2 3) LABEL=Asx CLA- +M V30 3 SUP 0 ATOMS=(8 10 19 20 21 22 23 24 25) XBONDS=(2 2 3) LABEL=Asx CLA- M V30 SS=AA SAP=(3 10 9 Al) SAP=(3 24 11 Br) -M V30 4 SUP 4 ATOMS=(8 11 26 27 28 29 30 31 32) XBONDS=(1 3) LABEL=Asx CLASS- +M V30 4 SUP 0 ATOMS=(8 11 26 27 28 29 30 31 32) XBONDS=(1 3) LABEL=Asx CLASS- M V30 =AA SAP=(3 11 24 Al) SAP=(3 31 0 Br) M V30 END SGROUP M V30 END CTAB @@ -683,10 +683,10 @@ M V30 6 17 18 19 20) BRKXYZ=(9 -3.915300 0.698800 0.000000 -3.915300 3.40370- M V30 0 0.000000 0.000000 0.000000 0.000000) BRKXYZ=(9 2.816500 3.403700 0.0- M V30 00000 2.816500 0.698800 0.000000 0.000000 0.000000 0.000000) BRKTYP=PA- M V30 REN -M V30 2 DAT 2 ATOMS=(1 6) FIELDNAME=[DUP] FIELDDISP=" -2.2332 2.4505 - +M V30 2 DAT 4 ATOMS=(1 6) FIELDNAME=[DUP] FIELDDISP=" -2.2332 2.4505 - M V30 DAU ALL 1 1 " FIELDDATA="\10" -M V30 3 SUP 3 ATOMS=(5 1 5 6 7 8) PARENT=1 LABEL=X ESTATE=E -M V30 4 SUP 4 ATOMS=(6 4 21 22 23 24 25) PARENT=1 LABEL=Y ESTATE=E +M V30 3 SUP 2 ATOMS=(5 1 5 6 7 8) PARENT=1 LABEL=X ESTATE=E +M V30 4 SUP 3 ATOMS=(6 4 21 22 23 24 25) PARENT=1 LABEL=Y ESTATE=E M V30 5 DAT 5 PARENT=1 FIELDNAME=[DUP] FIELDDISP=" -0.8721 0.4941 DA- M V30 U ALL 1 1 " FIELDDATA="\20" M V30 END SGROUP @@ -952,10 +952,10 @@ M V30 56 1 54 53 M V30 57 1 56 55 M V30 END BOND M V30 BEGIN SGROUP -M V30 1 SUP 1 ATOMS=(1 1) XBONDS=(1 1) BRKXYZ=(9 43.540100 111.819000 0.0000- -M V30 00 43.540100 114.203003 0.000000 0.000000 0.000000 0.000000) BRKXYZ=(9- -M V30 44.113899 114.203003 0.000000 44.113899 111.819000 0.000000 0.000000 - -M V30 0.000000 0.000000) LABEL=NH2 CLASS=LGRP +M V30 1 SUP 31 ATOMS=(1 1) XBONDS=(1 1) BRKXYZ=(9 43.540100 111.819000 0.000- +M V30 000 43.540100 114.203003 0.000000 0.000000 0.000000 0.000000) BRKXYZ=(- +M V30 9 44.113899 114.203003 0.000000 44.113899 111.819000 0.000000 0.000000- +M V30 0.000000 0.000000) LABEL=NH2 CLASS=LGRP M V30 END SGROUP M V30 END CTAB M V30 BEGIN TEMPLATE diff --git a/api/tests/integration/ref/basic/sgroups_basic.py.out b/api/tests/integration/ref/basic/sgroups_basic.py.out index bf76a7f3f0..03e7330655 100644 --- a/api/tests/integration/ref/basic/sgroups_basic.py.out +++ b/api/tests/integration/ref/basic/sgroups_basic.py.out @@ -2465,28 +2465,28 @@ M V30 5 SRU 5 ATOMS=(1 182) XBONDS=(2 199 196) BRKXYZ=(9 8.240000 -11.080000- M V30 0.000000 9.070000 -11.080000 0.000000 0.000000 0.000000 0.000000) BRK- M V30 XYZ=(9 9.300000 -11.000000 0.000000 9.300000 -11.830000 0.000000 0.000- M V30 000 0.000000 0.000000) CONNECT=EU LABEL=n -M V30 6 MUL 6 ATOMS=(6 355 354 356 370 371 372) XBONDS=(2 276 291) BRKXYZ=(9- +M V30 6 MUL 7 ATOMS=(6 355 354 356 370 371 372) XBONDS=(2 276 291) BRKXYZ=(9- M V30 3.160000 -21.610001 0.000000 3.160000 -20.780001 0.000000 0.000000 0.- M V30 000000 0.000000) BRKXYZ=(9 5.300000 -20.780001 0.000000 5.300000 -21.6- M V30 10001 0.000000 0.000000 0.000000 0.000000) PATOMS=(3 355 354 356) MULT- M V30 =2 -M V30 7 MUL 7 ATOMS=(15 359 358 360 373 374 375 376 377 378 379 380 381 382 - +M V30 7 MUL 8 ATOMS=(15 359 358 360 373 374 375 376 377 378 379 380 381 382 - M V30 383 384) XBONDS=(2 279 304) BRKXYZ=(9 6.020000 -21.610001 0.000000 6.0- M V30 20000 -20.780001 0.000000 0.000000 0.000000 0.000000) BRKXYZ=(9 8.1700- M V30 00 -20.780001 0.000000 8.170000 -21.610001 0.000000 0.000000 0.000000 - M V30 0.000000) PATOMS=(3 359 358 360) MULT=5 -M V30 8 MUL 8 ATOMS=(40 363 362 364 365 385 386 387 388 389 390 391 392 393 - +M V30 8 MUL 9 ATOMS=(40 363 362 364 365 385 386 387 388 389 390 391 392 393 - M V30 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 41- M V30 1 412 413 414 415 416 417 418 419 420) XBONDS=(2 283 341) BRKXYZ=(9 8.- M V30 880000 -21.610001 0.000000 8.880000 -20.780001 0.000000 0.000000 0.000- M V30 000 0.000000) BRKXYZ=(9 11.750000 -20.780001 0.000000 11.750000 -21.61- M V30 0001 0.000000 0.000000 0.000000 0.000000) PATOMS=(4 363 362 364 365) M- M V30 ULT=10 -M V30 9 SRU 9 ATOMS=(4 325 324 323 326) XBONDS=(2 247 243) BRKXYZ=(9 5.30000- -M V30 0 -17.889999 0.000000 5.300000 -17.059999 0.000000 0.000000 0.000000 0- -M V30 .000000) BRKXYZ=(9 8.170000 -17.059999 0.000000 8.170000 -17.889999 0.- -M V30 000000 0.000000 0.000000 0.000000) CONNECT=EU LABEL=Z -M V30 10 SRU 10 ATOMS=(1 336) XBONDS=(2 257 256) BRKXYZ=(9 3.870000 -12.9200- +M V30 9 SRU 10 ATOMS=(4 325 324 323 326) XBONDS=(2 247 243) BRKXYZ=(9 5.3000- +M V30 00 -17.889999 0.000000 5.300000 -17.059999 0.000000 0.000000 0.000000 - +M V30 0.000000) BRKXYZ=(9 8.170000 -17.059999 0.000000 8.170000 -17.889999 0- +M V30 .000000 0.000000 0.000000 0.000000) CONNECT=EU LABEL=Z +M V30 10 SRU 11 ATOMS=(1 336) XBONDS=(2 257 256) BRKXYZ=(9 3.870000 -12.9200- M V30 00 0.000000 3.870000 -12.100000 0.000000 0.000000 0.000000 0.000000) B- M V30 RKXYZ=(9 4.590000 -12.100000 0.000000 4.590000 -12.920000 0.000000 0.0- M V30 00000 0.000000 0.000000) CONNECT=EU LABEL=Z @@ -3291,28 +3291,28 @@ M V30 5 SRU 5 ATOMS=(1 182) XBONDS=(2 199 196) BRKXYZ=(9 8.240000 -11.080000- M V30 0.000000 9.070000 -11.080000 0.000000 0.000000 0.000000 0.000000) BRK- M V30 XYZ=(9 9.300000 -11.000000 0.000000 9.300000 -11.830000 0.000000 0.000- M V30 000 0.000000 0.000000) CONNECT=EU LABEL=n -M V30 6 MUL 6 ATOMS=(6 355 354 356 370 371 372) XBONDS=(2 275 290) BRKXYZ=(9- +M V30 6 MUL 7 ATOMS=(6 355 354 356 370 371 372) XBONDS=(2 275 290) BRKXYZ=(9- M V30 3.160000 -21.610001 0.000000 3.160000 -20.780001 0.000000 0.000000 0.- M V30 000000 0.000000) BRKXYZ=(9 5.300000 -20.780001 0.000000 5.300000 -21.6- M V30 10001 0.000000 0.000000 0.000000 0.000000) PATOMS=(3 355 354 356) MULT- M V30 =2 -M V30 7 MUL 7 ATOMS=(15 359 358 360 373 374 375 376 377 378 379 380 381 382 - +M V30 7 MUL 8 ATOMS=(15 359 358 360 373 374 375 376 377 378 379 380 381 382 - M V30 383 384) XBONDS=(2 278 303) BRKXYZ=(9 6.020000 -21.610001 0.000000 6.0- M V30 20000 -20.780001 0.000000 0.000000 0.000000 0.000000) BRKXYZ=(9 8.1700- M V30 00 -20.780001 0.000000 8.170000 -21.610001 0.000000 0.000000 0.000000 - M V30 0.000000) PATOMS=(3 359 358 360) MULT=5 -M V30 8 MUL 8 ATOMS=(40 363 362 364 365 385 386 387 388 389 390 391 392 393 - +M V30 8 MUL 9 ATOMS=(40 363 362 364 365 385 386 387 388 389 390 391 392 393 - M V30 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 41- M V30 1 412 413 414 415 416 417 418 419 420) XBONDS=(2 282 340) BRKXYZ=(9 8.- M V30 880000 -21.610001 0.000000 8.880000 -20.780001 0.000000 0.000000 0.000- M V30 000 0.000000) BRKXYZ=(9 11.750000 -20.780001 0.000000 11.750000 -21.61- M V30 0001 0.000000 0.000000 0.000000 0.000000) PATOMS=(4 363 362 364 365) M- M V30 ULT=10 -M V30 9 SRU 9 ATOMS=(4 325 324 323 326) XBONDS=(2 246 242) BRKXYZ=(9 5.30000- -M V30 0 -17.889999 0.000000 5.300000 -17.059999 0.000000 0.000000 0.000000 0- -M V30 .000000) BRKXYZ=(9 8.170000 -17.059999 0.000000 8.170000 -17.889999 0.- -M V30 000000 0.000000 0.000000 0.000000) CONNECT=EU LABEL=Z -M V30 10 SRU 10 ATOMS=(1 336) XBONDS=(2 256 255) BRKXYZ=(9 3.870000 -12.9200- +M V30 9 SRU 10 ATOMS=(4 325 324 323 326) XBONDS=(2 246 242) BRKXYZ=(9 5.3000- +M V30 00 -17.889999 0.000000 5.300000 -17.059999 0.000000 0.000000 0.000000 - +M V30 0.000000) BRKXYZ=(9 8.170000 -17.059999 0.000000 8.170000 -17.889999 0- +M V30 .000000 0.000000 0.000000 0.000000) CONNECT=EU LABEL=Z +M V30 10 SRU 11 ATOMS=(1 336) XBONDS=(2 256 255) BRKXYZ=(9 3.870000 -12.9200- M V30 00 0.000000 3.870000 -12.100000 0.000000 0.000000 0.000000 0.000000) B- M V30 RKXYZ=(9 4.590000 -12.100000 0.000000 4.590000 -12.920000 0.000000 0.0- M V30 00000 0.000000 0.000000) CONNECT=EU LABEL=Z diff --git a/api/tests/integration/ref/formats/mol_features.py.out b/api/tests/integration/ref/formats/mol_features.py.out index e9cf7ac865..0753d29a72 100644 --- a/api/tests/integration/ref/formats/mol_features.py.out +++ b/api/tests/integration/ref/formats/mol_features.py.out @@ -9416,7 +9416,7 @@ molecules/multiline-sgroups-ketcher-457-v3000.mol 14 15 1 0 0 0 0 15 16 1 0 0 0 0 M STY 4 1 DAT 2 DAT 3 DAT 4 DAT -M SLB 4 1 1 2 2 3 3 4 4 +M SLB 4 1 0 2 0 3 0 4 0 M SAL 1 2 3 4 M SDT 1 Long line M SDD 1 2.4985 -1.8557 DR ALL 1 1 @@ -9492,7 +9492,7 @@ M END 14 15 1 0 0 0 0 15 16 1 0 0 0 0 M STY 4 1 DAT 2 DAT 3 DAT 4 DAT -M SLB 4 1 1 2 2 3 3 4 4 +M SLB 4 1 0 2 0 3 0 4 0 M SAL 1 2 3 4 M SDT 1 Long line M SDD 1 2.4985 -1.8557 DR ALL 1 1 @@ -9574,20 +9574,20 @@ M V30 11 1 14 15 M V30 12 1 15 16 M V30 END BOND M V30 BEGIN SGROUP -M V30 1 DAT 1 ATOMS=(2 3 4) FIELDNAME="Long line" QUERYOP=" " FIELDDISP=" - +M V30 1 DAT 0 ATOMS=(2 3 4) FIELDNAME="Long line" QUERYOP=" " FIELDDISP=" - M V30 2.4985 -1.8557 DR ALL 1 1 " FIELDDATA="asdljkfnalsj- M V30 kdnfklaj nsdfkl jnasdkjlfnakls ndfkaljsn dlkfjna slkdnaklsnd asdf asdf- M V30 as df asdf as df asd fa sdf asd fa sd fa sdf a sd f a s df asd fa sd - M V30 fa sdf as df as df as df as df as df asd fa sd fa sd fa sd f asd fa sd- M V30 f as dfa sd f a sd fa sdf a sdf " -M V30 2 DAT 2 ATOMS=(2 8 9) FIELDNAME=Multiline QUERYOP=" " FIELDDISP=" - +M V30 2 DAT 0 ATOMS=(2 8 9) FIELDNAME=Multiline QUERYOP=" " FIELDDISP=" - M V30 2.5123 -1.6940 DR ALL 1 1 " FIELDDATA="line 1 - M V30 " FIELDDATA="li- M V30 ne 2 " F- M V30 IELDDATA="line 3 - M V30 " FIELDDATA="line 4 - M V30 " -M V30 3 DAT 3 ATOMS=(2 12 13) FIELDNAME=LongAndMultI QUERYOP=" " FIELDDISP- +M V30 3 DAT 0 ATOMS=(2 12 13) FIELDNAME=LongAndMultI QUERYOP=" " FIELDDISP- M V30 =" 2.4985 -1.8557 DR ALL 1 1 " FIELDDATA="line 1 - M V30 " FIELDDAT- M V30 A="line 2 - @@ -9596,7 +9596,7 @@ M V30 line long long line long long line long long line long long line long - M V30 long line long long line long long line long long line long long line - M V30 long long line long long line - M V30 " -M V30 4 DAT 4 ATOMS=(2 15 16) FIELDNAME="Line with spaces" QUERYOP=" " FIE- +M V30 4 DAT 0 ATOMS=(2 15 16) FIELDNAME="Line with spaces" QUERYOP=" " FIE- M V30 LDDISP=" 2.5756 -1.5083 DR ALL 1 1 " FIELDDATA="asd- M V30 fjknasdjkfn aslkjdnf alksdf asdf a- M V30 sdf as dfa sdf asdf asdf - @@ -12972,33 +12972,33 @@ M V30 1706 1 1246 1248 M V30 1707 1 1246 1249 M V30 END BOND M V30 BEGIN SGROUP -M V30 1 SUP 1 ATOMS=(4 37 38 39 40) XBONDS=(1 41) LABEL=CF3 SAP=(3 37 3 1) -M V30 2 SUP 2 ATOMS=(4 74 75 76 77) XBONDS=(1 79) LABEL=CF3 SAP=(3 74 43 1) -M V30 3 SUP 3 ATOMS=(4 112 113 114 115) XBONDS=(1 118) LABEL=CF3 SAP=(3 112 - +M V30 1 SUP 0 ATOMS=(4 37 38 39 40) XBONDS=(1 41) LABEL=CF3 SAP=(3 37 3 1) +M V30 2 SUP 0 ATOMS=(4 74 75 76 77) XBONDS=(1 79) LABEL=CF3 SAP=(3 74 43 1) +M V30 3 SUP 0 ATOMS=(4 112 113 114 115) XBONDS=(1 118) LABEL=CF3 SAP=(3 112 - M V30 80 1) -M V30 4 SUP 4 ATOMS=(4 149 150 151 152) XBONDS=(1 156) LABEL=CF3 SAP=(3 149 - +M V30 4 SUP 0 ATOMS=(4 149 150 151 152) XBONDS=(1 156) LABEL=CF3 SAP=(3 149 - M V30 118 1) -M V30 5 SUP 5 ATOMS=(4 185 186 187 188) XBONDS=(1 193) LABEL=CF3 SAP=(3 185 - +M V30 5 SUP 0 ATOMS=(4 185 186 187 188) XBONDS=(1 193) LABEL=CF3 SAP=(3 185 - M V30 155 1) -M V30 6 SUP 6 ATOMS=(4 221 222 223 224) XBONDS=(1 230) LABEL=CF3 SAP=(3 221 - +M V30 6 SUP 0 ATOMS=(4 221 222 223 224) XBONDS=(1 230) LABEL=CF3 SAP=(3 221 - M V30 191 1) -M V30 7 SUP 7 ATOMS=(4 259 260 261 262) XBONDS=(1 270) LABEL=CF3 SAP=(3 259 - +M V30 7 SUP 0 ATOMS=(4 259 260 261 262) XBONDS=(1 270) LABEL=CF3 SAP=(3 259 - M V30 227 1) -M V30 8 SUP 8 ATOMS=(4 296 297 298 299) XBONDS=(1 308) LABEL=CF3 SAP=(3 296 - +M V30 8 SUP 0 ATOMS=(4 296 297 298 299) XBONDS=(1 308) LABEL=CF3 SAP=(3 296 - M V30 265 1) -M V30 9 SUP 9 ATOMS=(4 334 335 336 337) XBONDS=(1 347) LABEL=CF3 SAP=(3 334 - +M V30 9 SUP 0 ATOMS=(4 334 335 336 337) XBONDS=(1 347) LABEL=CF3 SAP=(3 334 - M V30 302 1) -M V30 10 SUP 10 ATOMS=(4 372 373 374 375) XBONDS=(1 386) LABEL=CF3 SAP=(3 37- -M V30 2 340 1) -M V30 11 SUP 11 ATOMS=(4 409 410 411 412) XBONDS=(1 424) LABEL=CF3 SAP=(3 40- -M V30 9 378 1) -M V30 12 SUP 12 ATOMS=(4 446 447 448 449) XBONDS=(1 462) LABEL=CF3 SAP=(3 44- -M V30 6 415 1) -M V30 13 SUP 13 ATOMS=(4 668 669 670 671) XBONDS=(1 705) LABEL=^CF3 SAP=(3 6- -M V30 68 667 1) -M V30 14 SUP 14 ATOMS=(1 1210) XBONDS=(1 1301) LABEL=^Me SAP=(3 1210 1200 1) -M V30 15 SUP 15 ATOMS=(4 1246 1247 1248 1249) XBONDS=(1 1341) LABEL=^CF3 SAP- -M V30 =(3 1246 1236 1) +M V30 10 SUP 0 ATOMS=(4 372 373 374 375) XBONDS=(1 386) LABEL=CF3 SAP=(3 372- +M V30 340 1) +M V30 11 SUP 0 ATOMS=(4 409 410 411 412) XBONDS=(1 424) LABEL=CF3 SAP=(3 409- +M V30 378 1) +M V30 12 SUP 0 ATOMS=(4 446 447 448 449) XBONDS=(1 462) LABEL=CF3 SAP=(3 446- +M V30 415 1) +M V30 13 SUP 0 ATOMS=(4 668 669 670 671) XBONDS=(1 705) LABEL=^CF3 SAP=(3 66- +M V30 8 667 1) +M V30 14 SUP 0 ATOMS=(1 1210) XBONDS=(1 1301) LABEL=^Me SAP=(3 1210 1200 1) +M V30 15 SUP 0 ATOMS=(4 1246 1247 1248 1249) XBONDS=(1 1341) LABEL=^CF3 SAP=- +M V30 (3 1246 1236 1) M V30 END SGROUP M V30 END CTAB M END diff --git a/api/tests/integration/ref/rendering/sgroups_instrumentation.py.out b/api/tests/integration/ref/rendering/sgroups_instrumentation.py.out index 59f2c6c7a6..0b0daa424c 100644 --- a/api/tests/integration/ref/rendering/sgroups_instrumentation.py.out +++ b/api/tests/integration/ref/rendering/sgroups_instrumentation.py.out @@ -6095,10 +6095,10 @@ M V30 6 17 18 19 20) BRKXYZ=(9 -3.915300 0.698800 0.000000 -3.915300 3.40370- M V30 0 0.000000 0.000000 0.000000 0.000000) BRKXYZ=(9 2.816500 3.403700 0.0- M V30 00000 2.816500 0.698800 0.000000 0.000000 0.000000 0.000000) BRKTYP=PA- M V30 REN -M V30 2 DAT 2 ATOMS=(1 6) FIELDNAME=[DUP] FIELDDISP=" -2.2332 2.4505 - +M V30 2 DAT 4 ATOMS=(1 6) FIELDNAME=[DUP] FIELDDISP=" -2.2332 2.4505 - M V30 DAU ALL 1 1 " FIELDDATA="\10" -M V30 3 SUP 3 ATOMS=(5 1 5 6 7 8) PARENT=1 LABEL=X ESTATE=E -M V30 4 SUP 4 ATOMS=(6 4 21 22 23 24 25) PARENT=1 LABEL=Y ESTATE=E +M V30 3 SUP 2 ATOMS=(5 1 5 6 7 8) PARENT=1 LABEL=X ESTATE=E +M V30 4 SUP 3 ATOMS=(6 4 21 22 23 24 25) PARENT=1 LABEL=Y ESTATE=E M V30 5 DAT 5 PARENT=1 FIELDNAME=[DUP] FIELDDISP=" -0.8721 0.4941 DA- M V30 U ALL 1 1 " FIELDDATA="\20" M V30 END SGROUP @@ -6169,11 +6169,11 @@ M V30 BEGIN COLLECTION M V30 MDLV30/STEABS ATOMS=(4 5 9 16 21) M V30 END COLLECTION M V30 BEGIN SGROUP -M V30 1 SUP 1 ATOMS=(5 1 5 6 7 8) LABEL=X ESTATE=E -M V30 2 SUP 2 ATOMS=(6 4 21 22 23 24 25) LABEL=Y ESTATE=E -M V30 3 DAT 3 ATOMS=(1 6) FIELDNAME=[DUP] FIELDDISP=" -2.2332 2.4505 - +M V30 1 SUP 2 ATOMS=(5 1 5 6 7 8) LABEL=X ESTATE=E +M V30 2 SUP 3 ATOMS=(6 4 21 22 23 24 25) LABEL=Y ESTATE=E +M V30 3 DAT 4 ATOMS=(1 6) FIELDNAME=[DUP] FIELDDISP=" -2.2332 2.4505 - M V30 DAU ALL 1 1 " FIELDDATA="\10" -M V30 4 DAT 4 FIELDNAME=[DUP] FIELDDISP=" -0.8721 0.4941 DAU ALL - +M V30 4 DAT 5 FIELDNAME=[DUP] FIELDDISP=" -0.8721 0.4941 DAU ALL - M V30 1 1 " FIELDDATA="\20" M V30 END SGROUP M V30 END CTAB @@ -6244,9 +6244,9 @@ M V30 BEGIN COLLECTION M V30 MDLV30/STEABS ATOMS=(4 5 9 16 21) M V30 END COLLECTION M V30 BEGIN SGROUP -M V30 1 DAT 1 ATOMS=(1 6) FIELDNAME=[DUP] FIELDDISP=" -2.2332 2.4505 - +M V30 1 DAT 4 ATOMS=(1 6) FIELDNAME=[DUP] FIELDDISP=" -2.2332 2.4505 - M V30 DAU ALL 1 1 " FIELDDATA="\10" -M V30 2 DAT 2 FIELDNAME=[DUP] FIELDDISP=" -0.8721 0.4941 DAU ALL - +M V30 2 DAT 5 FIELDNAME=[DUP] FIELDDISP=" -0.8721 0.4941 DAU ALL - M V30 1 1 " FIELDDATA="\20" M V30 END SGROUP M V30 END CTAB diff --git a/api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py b/api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py index d6029f2f81..324b657e08 100644 --- a/api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py +++ b/api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py @@ -229,3 +229,78 @@ print("atoms: {0}".format(sg.countAtoms())) print("bonds: {0}".format(sg.countBonds())) print("sgroup count: {0}".format(sg_count)) + + +# ===== ext_index roundtrip V3000 ===== + +print("****** ext_index: V3000 roundtrip with explicit extindex ********") + +indigo.setOption("molfile-saving-mode", "3000") +mol = indigo.loadMolecule("CCCCCC") +sg = mol.addSGroup("SUP", 42) +sg.setSGroupAtoms([0, 1, 2]) +sg.setSGroupName("EXT42") +print("original id before save: {0}".format(sg.getSGroupOriginalId())) + +molfile = mol.molfile() +mol2 = indigo.loadMolecule(molfile) +for sg2 in mol2.iterateSGroups(): + print("roundtrip original id: {0}".format(sg2.getSGroupOriginalId())) + +# Check the V3000 output contains the extindex +lines = [l for l in molfile.split("\n") if "SUP" in l and "SGROUP" not in l] +for l in lines: + # V3000 format: "M V30 index type extindex ..." + parts = l.strip().split() + # Find the SUP line: M V30 SUP ... + if "SUP" in parts: + sup_idx = parts.index("SUP") + print("V3000 index={0} extindex={1}".format(parts[sup_idx - 1], parts[sup_idx + 1])) + + +print("****** ext_index: V3000 roundtrip auto-assign (extindex=0) ********") + +mol = indigo.loadMolecule("CCCCCC") +sg = mol.addSGroup("SUP", 0) +sg.setSGroupAtoms([0, 1, 2]) +sg.setSGroupName("AUTO") + +molfile = mol.molfile() +lines = [l for l in molfile.split("\n") if "SUP" in l and "SGROUP" not in l] +for l in lines: + parts = l.strip().split() + if "SUP" in parts: + sup_idx = parts.index("SUP") + print("V3000 auto-assigned: index={0} extindex={1}".format(parts[sup_idx - 1], parts[sup_idx + 1])) + + +# ===== ext_index roundtrip V2000 ===== + +print("****** ext_index: V2000 roundtrip with explicit extindex ********") + +indigo.setOption("molfile-saving-mode", "2000") +mol = indigo.loadMolecule("CCCCCC") +sg = mol.addSGroup("SUP", 55) +sg.setSGroupAtoms([0, 1, 2]) +sg.setSGroupName("EXT55") + +molfile = mol.molfile() + +# Check M SLB line +slb_lines = [l for l in molfile.split("\n") if "M SLB" in l] +for l in slb_lines: + print("V2000 SLB: {0}".format(l.strip())) + +# Roundtrip +mol2 = indigo.loadMolecule(molfile) +for sg2 in mol2.iterateSGroups(): + print("V2000 roundtrip original id: {0}".format(sg2.getSGroupOriginalId())) + + +print("****** ext_index: addSGroup without extindex (default=0) ********") + +mol = indigo.loadMolecule("CCCCCC") +sg = mol.addSGroup("GEN") +sg.setSGroupAtoms([0, 1]) +print("GEN type: {0}".format(sg.getSGroupType())) +print("GEN atoms: {0}".format(sg.countAtoms())) diff --git a/core/indigo-core/molecule/molecule_sgroups.h b/core/indigo-core/molecule/molecule_sgroups.h index 0984fa2c8b..e90b8c8bd8 100644 --- a/core/indigo-core/molecule/molecule_sgroups.h +++ b/core/indigo-core/molecule/molecule_sgroups.h @@ -111,9 +111,10 @@ namespace indigo int sgroup_type; // group type, represnted with STY in Molfile format Nullable sgroup_subtype; // group subtype, represnted with SST in Molfile format - Nullable original_group; // original group number - Nullable parent_group; // parent group number; represented with SPL in Molfile format - Nullable parent_idx; // parent group number; represented with index in the array + int index; // internal SGroup index; V3000 field 1, V2000 M STY sss. Used for cross-refs (PARENT, SPL). + Nullable ext_index; // external SGroup index; V3000 field 3 (extindex), V2000 M SLB vvv. Not set = auto-assign per spec. + Nullable parent_group; // parent group index; represented with PARENT in V3000, SPL in V2000 + Nullable parent_idx; // parent group array position; resolved from parent_group // TODO: leave only parent_idx Array atoms; // represented with SAL in Molfile format diff --git a/core/indigo-core/molecule/src/base_molecule.cpp b/core/indigo-core/molecule/src/base_molecule.cpp index 23d7c94237..8ca25a8cbf 100644 --- a/core/indigo-core/molecule/src/base_molecule.cpp +++ b/core/indigo-core/molecule/src/base_molecule.cpp @@ -176,7 +176,7 @@ void BaseMolecule::mergeSGroupsWithSubmolecule(BaseMolecule& mol, Array& ma int idx = sgroups.addSGroup(supersg.sgroup_type); SGroup& sg = sgroups.getSGroup(idx); sg.parent_idx = supersg.parent_idx; - sg.original_group = supersg.original_group; + sg.index = supersg.index; sg.parent_group = supersg.parent_group; sg.label.copy(supersg.label); @@ -1064,7 +1064,7 @@ void BaseMolecule::removeBond(int idx) void BaseMolecule::removeSGroup(int idx) { SGroup& sg = sgroups.getSGroup(idx); - _checkSgroupHierarchy(sg.parent_group, sg.original_group); + _checkSgroupHierarchy(sg.parent_group, sg.index); sgroups.remove(idx); } @@ -1073,7 +1073,7 @@ void BaseMolecule::removeSGroupWithBasis(int idx) { QS_DEF(Array, sg_atoms); SGroup& sg = sgroups.getSGroup(idx); - _checkSgroupHierarchy(sg.parent_group, sg.original_group); + _checkSgroupHierarchy(sg.parent_group, sg.index); sg_atoms.copy(sg.atoms); removeAtoms(sg_atoms); } diff --git a/core/indigo-core/molecule/src/cml_saver.cpp b/core/indigo-core/molecule/src/cml_saver.cpp index 2044351f25..b6ac729bd5 100644 --- a/core/indigo-core/molecule/src/cml_saver.cpp +++ b/core/indigo-core/molecule/src/cml_saver.cpp @@ -582,7 +582,7 @@ void CmlSaver::_addSgroupElement(XMLElement* molecule, BaseMolecule& mol, SGroup QS_DEF(Array, buf); ArrayOutput out(buf); - out.printf("sg%d", sgroup.original_group.hasValue() ? sgroup.original_group.get() : 0); + out.printf("sg%d", sgroup.index); buf.push(0); sg->SetAttribute("id", buf.ptr()); @@ -691,7 +691,7 @@ void CmlSaver::_addSgroupElement(XMLElement* molecule, BaseMolecule& mol, SGroup { SGroup& sg_child = sgroups->getSGroup(i); - if ((sg_child.parent_group != 0) && (sg_child.parent_group == sgroup.original_group)) + if ((sg_child.parent_group != 0) && (sg_child.parent_group == sgroup.index)) _addSgroupElement(sg, mol, sg_child); } } @@ -705,7 +705,7 @@ void CmlSaver::_addSgroupElement(XMLElement* molecule, BaseMolecule& mol, SGroup { SGroup& sg_child = sgroups->getSGroup(i); - if ((sg_child.parent_group != 0) && (sg_child.parent_group == sgroup.original_group)) + if ((sg_child.parent_group != 0) && (sg_child.parent_group == sgroup.index)) _addSgroupElement(sg, mol, sg_child); } } @@ -727,7 +727,7 @@ void CmlSaver::_addSgroupElement(XMLElement* molecule, BaseMolecule& mol, SGroup { SGroup& sg_child = sgroups->getSGroup(i); - if ((sg_child.parent_group != 0) && (sg_child.parent_group == sgroup.original_group)) + if ((sg_child.parent_group != 0) && (sg_child.parent_group == sgroup.index)) _addSgroupElement(sg, mol, sg_child); } } @@ -758,7 +758,7 @@ void CmlSaver::_addSgroupElement(XMLElement* molecule, BaseMolecule& mol, SGroup { SGroup& sg_child = sgroups->getSGroup(i); - if ((sg_child.parent_group != 0) && (sg_child.parent_group == sgroup.original_group)) + if ((sg_child.parent_group != 0) && (sg_child.parent_group == sgroup.index)) _addSgroupElement(sg, mol, sg_child); } } @@ -793,7 +793,7 @@ void CmlSaver::_addSgroupElement(XMLElement* molecule, BaseMolecule& mol, SGroup { SGroup& sg_child = sgroups->getSGroup(i); - if ((sg_child.parent_group != 0) && (sg_child.parent_group == sgroup.original_group)) + if ((sg_child.parent_group != 0) && (sg_child.parent_group == sgroup.index)) _addSgroupElement(sg, mol, sg_child); } } diff --git a/core/indigo-core/molecule/src/molecule_json_saver.cpp b/core/indigo-core/molecule/src/molecule_json_saver.cpp index ca394aeb04..f042c9a504 100644 --- a/core/indigo-core/molecule/src/molecule_json_saver.cpp +++ b/core/indigo-core/molecule/src/molecule_json_saver.cpp @@ -157,24 +157,27 @@ void MoleculeJsonSaver::_checkSGroupIndices(BaseMolecule& mol, Array& sgs_l for (int i = mol.sgroups.begin(); i != mol.sgroups.end(); i = mol.sgroups.next(i)) { SGroup& sgroup = mol.sgroups.getSGroup(i); - if (sgroup.original_group == 0) + if (sgroup.index == 0) { - sgroup.original_group = sgs_mapping[i]; + sgroup.index = sgs_mapping[i]; } else { for (int j = mol.sgroups.begin(); j != mol.sgroups.end(); j = mol.sgroups.next(j)) { SGroup& sg = mol.sgroups.getSGroup(j); - if (sg.parent_group == sgroup.original_group && sgs_changed[j] == 0) + if (sg.parent_group == sgroup.index && sgs_changed[j] == 0) { sg.parent_group = sgs_mapping[i]; sgs_changed[j] = 1; } } - sgroup.original_group = sgs_mapping[i]; + sgroup.index = sgs_mapping[i]; } - orig_ids.push(sgroup.original_group); + // Per BIOVIA spec: if ext_index not set, auto-assign from index + if (!sgroup.ext_index.hasValue()) + sgroup.ext_index = sgroup.index; + orig_ids.push(sgroup.index); } for (int i = mol.sgroups.begin(); i != mol.sgroups.end(); i = mol.sgroups.next(i)) @@ -183,15 +186,15 @@ void MoleculeJsonSaver::_checkSGroupIndices(BaseMolecule& mol, Array& sgs_l if (sgroup.parent_group == 0) { sgs_list.push(i); - added_ids.push(sgroup.original_group); + added_ids.push(sgroup.index); } else { - if (orig_ids.find(sgroup.parent_group) == VALUE_UNKNOWN || sgroup.parent_group == sgroup.original_group) + if (orig_ids.find(sgroup.parent_group) == VALUE_UNKNOWN || sgroup.parent_group == sgroup.index) { sgroup.parent_group = 0; sgs_list.push(i); - added_ids.push(sgroup.original_group); + added_ids.push(sgroup.index); } } } @@ -204,13 +207,13 @@ void MoleculeJsonSaver::_checkSGroupIndices(BaseMolecule& mol, Array& sgs_l if (sgroup.parent_group == 0) continue; - if (added_ids.find(sgroup.original_group) != VALUE_UNKNOWN) + if (added_ids.find(sgroup.index) != VALUE_UNKNOWN) continue; if (added_ids.find(sgroup.parent_group) != VALUE_UNKNOWN) { sgs_list.push(i); - added_ids.push(sgroup.original_group); + added_ids.push(sgroup.index); } } if (sgs_list.size() == mol.countSGroups()) @@ -221,6 +224,20 @@ void MoleculeJsonSaver::_checkSGroupIndices(BaseMolecule& mol, Array& sgs_l void MoleculeJsonSaver::saveSGroups(BaseMolecule& mol, JsonWriter& writer) { QS_DEF(Array, sgs_sorted); + + // Save ext_index state before _checkSGroupIndices auto-assigns it + struct SGroupExtState + { + int pool_idx; + Nullable ext_index; + }; + std::vector saved_ext; + for (int si = mol.sgroups.begin(); si != mol.sgroups.end(); si = mol.sgroups.next(si)) + { + SGroup& sg = mol.sgroups.getSGroup(si); + saved_ext.push_back({si, sg.ext_index}); + } + _checkSGroupIndices(mol, sgs_sorted); int sGroupsCount = mol.countSGroups(); bool componentDefined = false; @@ -266,6 +283,16 @@ void MoleculeJsonSaver::saveSGroups(BaseMolecule& mol, JsonWriter& writer) } writer.EndArray(); } + + // Restore ext_index state after writing (prevent save-time auto-assign from persisting) + for (const auto& s : saved_ext) + { + if (mol.sgroups.hasSGroup(s.pool_idx)) + { + SGroup& sg = mol.sgroups.getSGroup(s.pool_idx); + sg.ext_index = s.ext_index; + } + } } void MoleculeJsonSaver::saveSGroup(SGroup& sgroup, JsonWriter& writer) diff --git a/core/indigo-core/molecule/src/molecule_sgroups.cpp b/core/indigo-core/molecule/src/molecule_sgroups.cpp index ef2e660a45..293cf5bb26 100644 --- a/core/indigo-core/molecule/src/molecule_sgroups.cpp +++ b/core/indigo-core/molecule/src/molecule_sgroups.cpp @@ -57,7 +57,7 @@ SGroup::SGroup() sgroup_type = SGroup::SG_TYPE_GEN; sgroup_subtype = 0; brk_style = 0; - original_group = 0; + index = 0; parent_group = 0; parent_idx = -1; contracted = DisplayOption::Undefined; @@ -708,7 +708,7 @@ int MoleculeSGroups::findSGroupById(int id) for (int i = _sgroups.begin(); i != _sgroups.end(); i = _sgroups.next(i)) { SGroup& sg = *_sgroups.at(i); - if (sg.original_group == id) + if (sg.index == id) { return i; } diff --git a/core/indigo-core/molecule/src/molfile_loader_v2000.cpp b/core/indigo-core/molecule/src/molfile_loader_v2000.cpp index 1e1137c926..6dfa42e87b 100644 --- a/core/indigo-core/molecule/src/molfile_loader_v2000.cpp +++ b/core/indigo-core/molecule/src/molfile_loader_v2000.cpp @@ -836,7 +836,7 @@ void MolfileLoader::_readCtab2000() int idx = _bmol->sgroups.addSGroup(type); SGroup* sgroup = &_bmol->sgroups.getSGroup(idx); - sgroup->original_group = sgroup_idx + 1; + sgroup->index = sgroup_idx + 1; _sgroup_types[sgroup_idx] = sgroup->sgroup_type; _sgroup_mapping[sgroup_idx] = idx; } @@ -863,6 +863,24 @@ void MolfileLoader::_readCtab2000() } _scanner.skipLine(); } + else if (strncmp(chars, "SLB", 3) == 0) + { + int n = _scanner.readIntFix(3); + + while (n-- > 0) + { + _scanner.skip(1); + int sgroup_idx = _scanner.readIntFix(3) - 1; + + SGroup* sgroup = &_bmol->sgroups.getSGroup(_sgroup_mapping[sgroup_idx]); + + _scanner.skip(1); + int vvv = _scanner.readIntFix(3); + + sgroup->ext_index = vvv; + } + _scanner.skipLine(); + } else if (strncmp(chars, "SST", 3) == 0) { int n = _scanner.readIntFix(3); diff --git a/core/indigo-core/molecule/src/molfile_loader_v3000.cpp b/core/indigo-core/molecule/src/molfile_loader_v3000.cpp index 5431281505..fb423d1788 100644 --- a/core/indigo-core/molecule/src/molfile_loader_v3000.cpp +++ b/core/indigo-core/molecule/src/molfile_loader_v3000.cpp @@ -861,7 +861,7 @@ void MolfileLoader::_fillSGroupsParentIndices() for (auto i = sgroups.begin(); i != sgroups.end(); i++) { SGroup& sgroup = sgroups.getSGroup(i); - indices.emplace(sgroup.original_group, i); + indices.emplace(sgroup.index, i); } // TODO: replace parent_group with parent_idx @@ -1151,12 +1151,13 @@ void MolfileLoader::_readSGroup3000(const char* str) scanner.readWord(type, 0); type.push(0); scanner.skipSpace(); - scanner.readInt(); + int ext_idx = scanner.readInt(); scanner.skipSpace(); int idx = sgroups->addSGroup(type.ptr()); SGroup* sgroup = &sgroups->getSGroup(idx); - sgroup->original_group = sgroup_idx; + sgroup->index = sgroup_idx; + sgroup->ext_index = ext_idx; DataSGroup* dsg = 0; Superatom* sup = 0; diff --git a/core/indigo-core/molecule/src/molfile_saver.cpp b/core/indigo-core/molecule/src/molfile_saver.cpp index 1107c49e71..39ebb5819f 100644 --- a/core/indigo-core/molecule/src/molfile_saver.cpp +++ b/core/indigo-core/molecule/src/molfile_saver.cpp @@ -876,6 +876,20 @@ void MolfileSaver::_writeCtab(Output& output, BaseMolecule& mol, bool query) } QS_DEF(Array, sgs_sorted); + + // Save ext_index state before _checkSGroupIndices auto-assigns it + struct SGroupExtState + { + int pool_idx; + Nullable ext_index; + }; + std::vector saved_ext; + for (int si = mol.sgroups.begin(); si != mol.sgroups.end(); si = mol.sgroups.next(si)) + { + SGroup& sg = mol.sgroups.getSGroup(si); + saved_ext.push_back({si, sg.ext_index}); + } + _checkSGroupIndices(mol, sgs_sorted); if (mol.countSGroups() > 0) @@ -889,6 +903,7 @@ void MolfileSaver::_writeCtab(Output& output, BaseMolecule& mol, bool query) ArrayOutput out(buf); int sg_idx = sgs_sorted[i]; SGroup& sgroup = sgroups->getSGroup(sg_idx); + // ext_index written as-is (managed by _checkSGroupIndices) _writeGenericSGroup3000(sgroup, idx++, out); if (sgroup.sgroup_type == SGroup::SG_TYPE_GEN) { @@ -1074,6 +1089,16 @@ void MolfileSaver::_writeCtab(Output& output, BaseMolecule& mol, bool query) _removeImplicitSGroups(mol, implicit_sgroups_indexes); } + // Restore ext_index state after writing (prevent save-time auto-assign from persisting) + for (const auto& s : saved_ext) + { + if (mol.sgroups.hasSGroup(s.pool_idx)) + { + SGroup& sg = mol.sgroups.getSGroup(s.pool_idx); + sg.ext_index = s.ext_index; + } + } + output.writeStringCR("M V30 END CTAB"); int n_rgroups = mol.rgroups.getRGroupCount(); @@ -1109,7 +1134,8 @@ void MolfileSaver::_writeGenericSGroup3000(SGroup& sgroup, int idx, Output& outp { int i; - output.printf("%d %s %d", (sgroup.original_group.hasValue() ? sgroup.original_group.get() : 0), SGroup::typeToString(sgroup.sgroup_type), idx); + int write_ext = sgroup.ext_index.hasValue() ? sgroup.ext_index.get() : sgroup.index; + output.printf("%d %s %d", sgroup.index, SGroup::typeToString(sgroup.sgroup_type), write_ext); if (sgroup.atoms.size() > 0) { @@ -1684,6 +1710,20 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) } QS_DEF(Array, sgs_sorted); + + // Save ext_index state before _checkSGroupIndices auto-assigns it + struct SGroupExtState + { + int pool_idx; + Nullable ext_index; + }; + std::vector saved_ext; + for (int si = mol.sgroups.begin(); si != mol.sgroups.end(); si = mol.sgroups.next(si)) + { + SGroup& sg = mol.sgroups.getSGroup(si); + saved_ext.push_back({si, sg.ext_index}); + } + _checkSGroupIndices(mol, sgs_sorted); if (sgroup_ids.size() > 0) @@ -1695,7 +1735,7 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) for (i = j; i < std::min(sgroup_ids.size(), j + 8); i++) { SGroup* sgroup = &mol.sgroups.getSGroup(sgroup_ids[i]); - output.printf(" %3d %s", (sgroup->original_group.hasValue() ? sgroup->original_group.get() : 0), SGroup::typeToString(sgroup->sgroup_type)); + output.printf(" %3d %s", sgroup->index, SGroup::typeToString(sgroup->sgroup_type)); } output.writeCR(); } @@ -1704,7 +1744,7 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) SGroup* sgroup = &mol.sgroups.getSGroup(sgroup_ids[j]); if (sgroup->parent_group > 0) { - child_ids.push(sgroup->original_group); + child_ids.push(sgroup->index); parent_ids.push(sgroup->parent_group); } } @@ -1723,8 +1763,9 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) for (i = j; i < std::min(sgroup_ids.size(), j + 8); i++) { SGroup* sgroup = &mol.sgroups.getSGroup(sgroup_ids[i]); - output.printf(" %3d %3d", (sgroup->original_group.hasValue() ? sgroup->original_group.get() : 0), - (sgroup->original_group.hasValue() ? sgroup->original_group.get() : 0)); + // ext_index written as-is (managed by _checkSGroupIndices) + int write_ext = sgroup->ext_index.hasValue() ? sgroup->ext_index.get() : sgroup->index; + output.printf(" %3d %3d", sgroup->index, write_ext); } output.writeCR(); } @@ -1737,7 +1778,7 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) { RepeatingUnit* ru = (RepeatingUnit*)&mol.sgroups.getSGroup(i, SGroup::SG_TYPE_SRU); - output.printf(" %3d ", (ru->original_group.hasValue() ? ru->original_group.get() : 0)); + output.printf(" %3d ", ru->index); if (ru->connectivity == SGroup::HEAD_TO_HEAD) output.printf("HH "); @@ -1755,7 +1796,7 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) for (j = 0; j < sgroup.atoms.size(); j += 8) { int k; - output.printf("M SAL %3d%3d", (sgroup.original_group.hasValue() ? sgroup.original_group.get() : 0), std::min(sgroup.atoms.size(), j + 8) - j); + output.printf("M SAL %3d%3d", sgroup.index, std::min(sgroup.atoms.size(), j + 8) - j); for (k = j; k < std::min(sgroup.atoms.size(), j + 8); k++) output.printf(" %3d", _atom_mapping[sgroup.atoms[k]]); output.writeCR(); @@ -1763,8 +1804,7 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) for (j = 0; j < sgroup.getBonds().size(); j += 8) { int k; - output.printf("M SBL %3d%3d", (sgroup.original_group.hasValue() ? sgroup.original_group.get() : 0), - std::min(sgroup.getBonds().size(), j + 8) - j); + output.printf("M SBL %3d%3d", sgroup.index, std::min(sgroup.getBonds().size(), j + 8) - j); for (k = j; k < std::min(sgroup.getBonds().size(), j + 8); k++) output.printf(" %3d", _bond_mapping[sgroup.getBonds()[k]]); output.writeCR(); @@ -1774,9 +1814,9 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) if (sgroup.sgroup_type != SGroup::SG_TYPE_MUL && sgroup.label.size() > 1) { if (sgroup.label.find(' ') > -1) - output.printfCR("M SMT %3d \"%s\"", (sgroup.original_group.hasValue() ? sgroup.original_group.get() : 0), sgroup.label.ptr()); + output.printfCR("M SMT %3d \"%s\"", sgroup.index, sgroup.label.ptr()); else - output.printfCR("M SMT %3d %s", (sgroup.original_group.hasValue() ? sgroup.original_group.get() : 0), sgroup.label.ptr()); + output.printfCR("M SMT %3d %s", sgroup.index, sgroup.label.ptr()); } if (sgroup.sgroup_type == SGroup::SG_TYPE_SUP) @@ -1784,19 +1824,18 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) Superatom& superatom = (Superatom&)sgroup; if (superatom.sa_class.size() > 1) - output.printfCR("M SCL %3d %s", (superatom.original_group.hasValue() ? superatom.original_group.get() : 0), superatom.sa_class.ptr()); + output.printfCR("M SCL %3d %s", superatom.index, superatom.sa_class.ptr()); if (superatom.bond_connections.size() > 0) { for (j = 0; j < superatom.bond_connections.size(); j++) { - output.printfCR("M SBV %3d %3d %9.4f %9.4f", (superatom.original_group.hasValue() ? superatom.original_group.get() : 0), - _bond_mapping[superatom.bond_connections[j].bond_idx], superatom.bond_connections[j].bond_dir.x, - superatom.bond_connections[j].bond_dir.y); + output.printfCR("M SBV %3d %3d %9.4f %9.4f", superatom.index, _bond_mapping[superatom.bond_connections[j].bond_idx], + superatom.bond_connections[j].bond_dir.x, superatom.bond_connections[j].bond_dir.y); } } if (superatom.contracted == DisplayOption::Expanded) { - output.printfCR("M SDS EXP 1 %3d", (superatom.original_group.hasValue() ? superatom.original_group.get() : 0)); + output.printfCR("M SDS EXP 1 %3d", superatom.index); } if (superatom.attachment_points.size() > 0) { @@ -1807,7 +1846,7 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) { if (next_line) { - output.printf("M SAP %3d%3d", (superatom.original_group.hasValue() ? superatom.original_group.get() : 0), std::min(nrem, 6)); + output.printf("M SAP %3d%3d", superatom.index, std::min(nrem, 6)); next_line = false; } @@ -1837,7 +1876,7 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) { DataSGroup& datasgroup = (DataSGroup&)sgroup; - output.printf("M SDT %3d ", (datasgroup.original_group.hasValue() ? datasgroup.original_group.get() : 0)); + output.printf("M SDT %3d ", datasgroup.index); _writeFormattedString(output, datasgroup.name, 30); @@ -1851,7 +1890,7 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) output.writeCR(); - output.printf("M SDD %3d ", (datasgroup.original_group.hasValue() ? datasgroup.original_group.get() : 0)); + output.printf("M SDD %3d ", datasgroup.index); _writeDataSGroupDisplay(datasgroup, output); output.writeCR(); @@ -1872,7 +1911,7 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) output.writeString("SED "); else output.writeString("SCD "); - output.printf("%3d ", (datasgroup.original_group.hasValue() ? datasgroup.original_group.get() : 0)); + output.printf("%3d ", datasgroup.index); output.write(ptr, j); if (ptr[j] == '\n') @@ -1890,37 +1929,45 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) for (j = 0; j < mg.parent_atoms.size(); j += 8) { int k; - output.printf("M SPA %3d%3d", (mg.original_group.hasValue() ? mg.original_group.get() : 0), std::min(mg.parent_atoms.size(), j + 8) - j); + output.printf("M SPA %3d%3d", mg.index, std::min(mg.parent_atoms.size(), j + 8) - j); for (k = j; k < std::min(mg.parent_atoms.size(), j + 8); k++) output.printf(" %3d", _atom_mapping[mg.parent_atoms[k]]); output.writeCR(); } - output.printf("M SMT %3d %d\n", (mg.original_group.hasValue() ? mg.original_group.get() : 0), - (mg.multiplier.hasValue() ? mg.multiplier.get() : 0)); + output.printf("M SMT %3d %d\n", mg.index, (mg.multiplier.hasValue() ? mg.multiplier.get() : 0)); } for (j = 0; j < sgroup.brackets.size(); j++) { - output.printf("M SDI %3d 4 %9.4f %9.4f %9.4f %9.4f\n", (sgroup.original_group.hasValue() ? sgroup.original_group.get() : 0), - sgroup.brackets[j][0].x, sgroup.brackets[j][0].y, sgroup.brackets[j][1].x, sgroup.brackets[j][1].y); + output.printf("M SDI %3d 4 %9.4f %9.4f %9.4f %9.4f\n", sgroup.index, sgroup.brackets[j][0].x, sgroup.brackets[j][0].y, + sgroup.brackets[j][1].x, sgroup.brackets[j][1].y); } if (sgroup.brackets.size() > 0 && sgroup.brk_style > 0) { - output.printf("M SBT 1 %3d %3d\n", (sgroup.original_group.hasValue() ? sgroup.original_group.get() : 0), - (sgroup.brk_style.hasValue() ? sgroup.brk_style.get() : 0)); + output.printf("M SBT 1 %3d %3d\n", sgroup.index, (sgroup.brk_style.hasValue() ? sgroup.brk_style.get() : 0)); } if (sgroup.sgroup_subtype > 0) { if (sgroup.sgroup_subtype == SGroup::SG_SUBTYPE_ALT) - output.printf("M SST 1 %3d ALT\n", (sgroup.original_group.hasValue() ? sgroup.original_group.get() : 0)); + output.printf("M SST 1 %3d ALT\n", sgroup.index); else if (sgroup.sgroup_subtype == SGroup::SG_SUBTYPE_RAN) - output.printf("M SST 1 %3d RAN\n", (sgroup.original_group.hasValue() ? sgroup.original_group.get() : 0)); + output.printf("M SST 1 %3d RAN\n", sgroup.index); else if (sgroup.sgroup_subtype == SGroup::SG_SUBTYPE_BLO) - output.printf("M SST 1 %3d BLO\n", (sgroup.original_group.hasValue() ? sgroup.original_group.get() : 0)); + output.printf("M SST 1 %3d BLO\n", sgroup.index); } } } _removeImplicitSGroups(mol, implicit_sgroups_indexes); + + // Restore ext_index state after writing (prevent save-time auto-assign from persisting) + for (const auto& s : saved_ext) + { + if (mol.sgroups.hasSGroup(s.pool_idx)) + { + SGroup& sg = mol.sgroups.getSGroup(s.pool_idx); + sg.ext_index = s.ext_index; + } + } } void MolfileSaver::_writeFormattedString(Output& output, Array& str, int length) @@ -1985,24 +2032,27 @@ void MolfileSaver::_checkSGroupIndices(BaseMolecule& mol, Array& sgs_list) for (int i = mol.sgroups.begin(); i != mol.sgroups.end(); i = mol.sgroups.next(i)) { SGroup& sgroup = mol.sgroups.getSGroup(i); - if (sgroup.original_group == 0) + if (sgroup.index == 0) { - sgroup.original_group = sgs_mapping[i]; + sgroup.index = sgs_mapping[i]; } else { for (int j = mol.sgroups.begin(); j != mol.sgroups.end(); j = mol.sgroups.next(j)) { SGroup& sg = mol.sgroups.getSGroup(j); - if (sg.parent_group == sgroup.original_group && sgs_changed[j] == 0) + if (sg.parent_group == sgroup.index && sgs_changed[j] == 0) { sg.parent_group = sgs_mapping[i]; sgs_changed[j] = 1; } } - sgroup.original_group = sgs_mapping[i]; + sgroup.index = sgs_mapping[i]; } - orig_ids.push(sgroup.original_group); + // Per BIOVIA spec: if ext_index not set, auto-assign from index + if (!sgroup.ext_index.hasValue()) + sgroup.ext_index = sgroup.index; + orig_ids.push(sgroup.index); } for (int i = mol.sgroups.begin(); i != mol.sgroups.end(); i = mol.sgroups.next(i)) @@ -2011,15 +2061,15 @@ void MolfileSaver::_checkSGroupIndices(BaseMolecule& mol, Array& sgs_list) if (sgroup.parent_group == 0) { sgs_list.push(i); - added_ids.push(sgroup.original_group); + added_ids.push(sgroup.index); } else { - if (orig_ids.find(sgroup.parent_group) == -1 || sgroup.parent_group == sgroup.original_group) + if (orig_ids.find(sgroup.parent_group) == -1 || sgroup.parent_group == sgroup.index) { sgroup.parent_group = 0; sgs_list.push(i); - added_ids.push(sgroup.original_group); + added_ids.push(sgroup.index); } } } @@ -2032,13 +2082,13 @@ void MolfileSaver::_checkSGroupIndices(BaseMolecule& mol, Array& sgs_list) if (sgroup.parent_group == 0) continue; - if (added_ids.find(sgroup.original_group) != -1) + if (added_ids.find(sgroup.index) != -1) continue; if (added_ids.find(sgroup.parent_group) != -1) { sgs_list.push(i); - added_ids.push(sgroup.original_group); + added_ids.push(sgroup.index); } } if (sgs_list.size() == mol.countSGroups()) From 3b27a583aec6e9992824d356be2a33c14b40d005 Mon Sep 17 00:00:00 2001 From: even1024 Date: Mon, 11 May 2026 00:17:58 +0200 Subject: [PATCH 14/24] clang format fixes --- core/indigo-core/molecule/src/cml_loader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/indigo-core/molecule/src/cml_loader.cpp b/core/indigo-core/molecule/src/cml_loader.cpp index adbf29ec82..80e088354d 100644 --- a/core/indigo-core/molecule/src/cml_loader.cpp +++ b/core/indigo-core/molecule/src/cml_loader.cpp @@ -135,7 +135,7 @@ struct Atom // This methods splits a space-separated string and writes each values into an arbitrary string // property of Atom structure for each atom in the specified list -static void splitStringIntoProperties(const char* s, std::vector& atoms, std::string Atom::* property) +static void splitStringIntoProperties(const char* s, std::vector& atoms, std::string Atom::*property) { if (s == 0) return; From 8d4a1551e75d686b36bfe8a8d9bcdaf419187bab Mon Sep 17 00:00:00 2001 From: even1024 Date: Mon, 11 May 2026 00:25:56 +0200 Subject: [PATCH 15/24] python formatting fix --- .../tests/basic/3604_sgroup_atoms_bonds.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py b/api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py index 324b657e08..3ce91d7c3e 100644 --- a/api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py +++ b/api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py @@ -255,7 +255,11 @@ # Find the SUP line: M V30 SUP ... if "SUP" in parts: sup_idx = parts.index("SUP") - print("V3000 index={0} extindex={1}".format(parts[sup_idx - 1], parts[sup_idx + 1])) + print( + "V3000 index={0} extindex={1}".format( + parts[sup_idx - 1], parts[sup_idx + 1] + ) + ) print("****** ext_index: V3000 roundtrip auto-assign (extindex=0) ********") @@ -271,7 +275,11 @@ parts = l.strip().split() if "SUP" in parts: sup_idx = parts.index("SUP") - print("V3000 auto-assigned: index={0} extindex={1}".format(parts[sup_idx - 1], parts[sup_idx + 1])) + print( + "V3000 auto-assigned: index={0} extindex={1}".format( + parts[sup_idx - 1], parts[sup_idx + 1] + ) + ) # ===== ext_index roundtrip V2000 ===== From 76a7e6793794e2e6577b46a2f3cabbe96ddb94dc Mon Sep 17 00:00:00 2001 From: even1024 Date: Wed, 13 May 2026 01:17:26 +0200 Subject: [PATCH 16/24] replace _checkSGroupIndices with read-only getOrderedSGroups --- .../ref/basic/3604_sgroup_atoms_bonds.py.out | 9 +- .../integration/ref/basic/basic_load.py.out | 8 +- .../ref/formats/mol_features.py.out | 52 ++-- .../tests/basic/3604_sgroup_atoms_bonds.py | 48 ++-- .../tests/formats/ref/macro/sa-mono.cml | 6 +- core/indigo-core/molecule/cml_saver.h | 3 +- .../molecule/molecule_json_saver.h | 1 - core/indigo-core/molecule/molecule_sgroups.h | 16 +- core/indigo-core/molecule/molfile_saver.h | 4 +- core/indigo-core/molecule/src/cmf_saver.cpp | 9 +- core/indigo-core/molecule/src/cml_saver.cpp | 88 +++--- .../molecule/src/molecule_cdxml_saver.cpp | 21 +- .../molecule/src/molecule_json_saver.cpp | 147 +--------- .../molecule/src/molecule_sgroups.cpp | 108 ++++++++ .../molecule/src/molfile_saver.cpp | 255 ++++-------------- core/render2d/src/render_internal.cpp | 6 +- 16 files changed, 315 insertions(+), 466 deletions(-) diff --git a/api/tests/integration/ref/basic/3604_sgroup_atoms_bonds.py.out b/api/tests/integration/ref/basic/3604_sgroup_atoms_bonds.py.out index 174da25916..0777583df6 100644 --- a/api/tests/integration/ref/basic/3604_sgroup_atoms_bonds.py.out +++ b/api/tests/integration/ref/basic/3604_sgroup_atoms_bonds.py.out @@ -58,12 +58,13 @@ atoms: 3 bonds: 2 sgroup count: 1 ****** ext_index: V3000 roundtrip with explicit extindex ******** -original id before save: 0 -roundtrip original id: 1 -V3000 index=1 extindex=42 +ext_index before save: 42 +roundtrip: index=1 extindex=42 ****** ext_index: V3000 roundtrip auto-assign (extindex=0) ******** -V3000 auto-assigned: index=1 extindex=1 +ext_index before save: 0 +roundtrip: index=1 extindex=1 ****** ext_index: V2000 roundtrip with explicit extindex ******** +ext_index before save: 55 V2000 SLB: M SLB 1 1 55 V2000 roundtrip original id: 1 ****** ext_index: addSGroup without extindex (default=0) ******** diff --git a/api/tests/integration/ref/basic/basic_load.py.out b/api/tests/integration/ref/basic/basic_load.py.out index 4918b7f75a..4a58670046 100644 --- a/api/tests/integration/ref/basic/basic_load.py.out +++ b/api/tests/integration/ref/basic/basic_load.py.out @@ -518,13 +518,13 @@ M V30 BEGIN COLLECTION M V30 MDLV30/STERAC1 ATOMS=(4 2 12 19 26) M V30 END COLLECTION M V30 BEGIN SGROUP -M V30 1 SUP 0 ATOMS=(8 1 2 3 4 5 6 7 8) XBONDS=(1 1) LABEL=Asx CLASS=AA SAP=- +M V30 1 SUP 1 ATOMS=(8 1 2 3 4 5 6 7 8) XBONDS=(1 1) LABEL=Asx CLASS=AA SAP=- M V30 (3 1 17 Al) SAP=(3 7 0 Br) -M V30 2 SUP 0 ATOMS=(8 9 12 13 14 15 16 17 18) XBONDS=(2 2 1) LABEL=Asx CLAS- +M V30 2 SUP 2 ATOMS=(8 9 12 13 14 15 16 17 18) XBONDS=(2 2 1) LABEL=Asx CLAS- M V30 S=AA SAP=(3 9 10 Al) SAP=(3 17 1 Br) -M V30 3 SUP 0 ATOMS=(8 10 19 20 21 22 23 24 25) XBONDS=(2 2 3) LABEL=Asx CLA- +M V30 3 SUP 3 ATOMS=(8 10 19 20 21 22 23 24 25) XBONDS=(2 2 3) LABEL=Asx CLA- M V30 SS=AA SAP=(3 10 9 Al) SAP=(3 24 11 Br) -M V30 4 SUP 0 ATOMS=(8 11 26 27 28 29 30 31 32) XBONDS=(1 3) LABEL=Asx CLASS- +M V30 4 SUP 4 ATOMS=(8 11 26 27 28 29 30 31 32) XBONDS=(1 3) LABEL=Asx CLASS- M V30 =AA SAP=(3 11 24 Al) SAP=(3 31 0 Br) M V30 END SGROUP M V30 END CTAB diff --git a/api/tests/integration/ref/formats/mol_features.py.out b/api/tests/integration/ref/formats/mol_features.py.out index 0753d29a72..e9cf7ac865 100644 --- a/api/tests/integration/ref/formats/mol_features.py.out +++ b/api/tests/integration/ref/formats/mol_features.py.out @@ -9416,7 +9416,7 @@ molecules/multiline-sgroups-ketcher-457-v3000.mol 14 15 1 0 0 0 0 15 16 1 0 0 0 0 M STY 4 1 DAT 2 DAT 3 DAT 4 DAT -M SLB 4 1 0 2 0 3 0 4 0 +M SLB 4 1 1 2 2 3 3 4 4 M SAL 1 2 3 4 M SDT 1 Long line M SDD 1 2.4985 -1.8557 DR ALL 1 1 @@ -9492,7 +9492,7 @@ M END 14 15 1 0 0 0 0 15 16 1 0 0 0 0 M STY 4 1 DAT 2 DAT 3 DAT 4 DAT -M SLB 4 1 0 2 0 3 0 4 0 +M SLB 4 1 1 2 2 3 3 4 4 M SAL 1 2 3 4 M SDT 1 Long line M SDD 1 2.4985 -1.8557 DR ALL 1 1 @@ -9574,20 +9574,20 @@ M V30 11 1 14 15 M V30 12 1 15 16 M V30 END BOND M V30 BEGIN SGROUP -M V30 1 DAT 0 ATOMS=(2 3 4) FIELDNAME="Long line" QUERYOP=" " FIELDDISP=" - +M V30 1 DAT 1 ATOMS=(2 3 4) FIELDNAME="Long line" QUERYOP=" " FIELDDISP=" - M V30 2.4985 -1.8557 DR ALL 1 1 " FIELDDATA="asdljkfnalsj- M V30 kdnfklaj nsdfkl jnasdkjlfnakls ndfkaljsn dlkfjna slkdnaklsnd asdf asdf- M V30 as df asdf as df asd fa sdf asd fa sd fa sdf a sd f a s df asd fa sd - M V30 fa sdf as df as df as df as df as df asd fa sd fa sd fa sd f asd fa sd- M V30 f as dfa sd f a sd fa sdf a sdf " -M V30 2 DAT 0 ATOMS=(2 8 9) FIELDNAME=Multiline QUERYOP=" " FIELDDISP=" - +M V30 2 DAT 2 ATOMS=(2 8 9) FIELDNAME=Multiline QUERYOP=" " FIELDDISP=" - M V30 2.5123 -1.6940 DR ALL 1 1 " FIELDDATA="line 1 - M V30 " FIELDDATA="li- M V30 ne 2 " F- M V30 IELDDATA="line 3 - M V30 " FIELDDATA="line 4 - M V30 " -M V30 3 DAT 0 ATOMS=(2 12 13) FIELDNAME=LongAndMultI QUERYOP=" " FIELDDISP- +M V30 3 DAT 3 ATOMS=(2 12 13) FIELDNAME=LongAndMultI QUERYOP=" " FIELDDISP- M V30 =" 2.4985 -1.8557 DR ALL 1 1 " FIELDDATA="line 1 - M V30 " FIELDDAT- M V30 A="line 2 - @@ -9596,7 +9596,7 @@ M V30 line long long line long long line long long line long long line long - M V30 long line long long line long long line long long line long long line - M V30 long long line long long line - M V30 " -M V30 4 DAT 0 ATOMS=(2 15 16) FIELDNAME="Line with spaces" QUERYOP=" " FIE- +M V30 4 DAT 4 ATOMS=(2 15 16) FIELDNAME="Line with spaces" QUERYOP=" " FIE- M V30 LDDISP=" 2.5756 -1.5083 DR ALL 1 1 " FIELDDATA="asd- M V30 fjknasdjkfn aslkjdnf alksdf asdf a- M V30 sdf as dfa sdf asdf asdf - @@ -12972,33 +12972,33 @@ M V30 1706 1 1246 1248 M V30 1707 1 1246 1249 M V30 END BOND M V30 BEGIN SGROUP -M V30 1 SUP 0 ATOMS=(4 37 38 39 40) XBONDS=(1 41) LABEL=CF3 SAP=(3 37 3 1) -M V30 2 SUP 0 ATOMS=(4 74 75 76 77) XBONDS=(1 79) LABEL=CF3 SAP=(3 74 43 1) -M V30 3 SUP 0 ATOMS=(4 112 113 114 115) XBONDS=(1 118) LABEL=CF3 SAP=(3 112 - +M V30 1 SUP 1 ATOMS=(4 37 38 39 40) XBONDS=(1 41) LABEL=CF3 SAP=(3 37 3 1) +M V30 2 SUP 2 ATOMS=(4 74 75 76 77) XBONDS=(1 79) LABEL=CF3 SAP=(3 74 43 1) +M V30 3 SUP 3 ATOMS=(4 112 113 114 115) XBONDS=(1 118) LABEL=CF3 SAP=(3 112 - M V30 80 1) -M V30 4 SUP 0 ATOMS=(4 149 150 151 152) XBONDS=(1 156) LABEL=CF3 SAP=(3 149 - +M V30 4 SUP 4 ATOMS=(4 149 150 151 152) XBONDS=(1 156) LABEL=CF3 SAP=(3 149 - M V30 118 1) -M V30 5 SUP 0 ATOMS=(4 185 186 187 188) XBONDS=(1 193) LABEL=CF3 SAP=(3 185 - +M V30 5 SUP 5 ATOMS=(4 185 186 187 188) XBONDS=(1 193) LABEL=CF3 SAP=(3 185 - M V30 155 1) -M V30 6 SUP 0 ATOMS=(4 221 222 223 224) XBONDS=(1 230) LABEL=CF3 SAP=(3 221 - +M V30 6 SUP 6 ATOMS=(4 221 222 223 224) XBONDS=(1 230) LABEL=CF3 SAP=(3 221 - M V30 191 1) -M V30 7 SUP 0 ATOMS=(4 259 260 261 262) XBONDS=(1 270) LABEL=CF3 SAP=(3 259 - +M V30 7 SUP 7 ATOMS=(4 259 260 261 262) XBONDS=(1 270) LABEL=CF3 SAP=(3 259 - M V30 227 1) -M V30 8 SUP 0 ATOMS=(4 296 297 298 299) XBONDS=(1 308) LABEL=CF3 SAP=(3 296 - +M V30 8 SUP 8 ATOMS=(4 296 297 298 299) XBONDS=(1 308) LABEL=CF3 SAP=(3 296 - M V30 265 1) -M V30 9 SUP 0 ATOMS=(4 334 335 336 337) XBONDS=(1 347) LABEL=CF3 SAP=(3 334 - +M V30 9 SUP 9 ATOMS=(4 334 335 336 337) XBONDS=(1 347) LABEL=CF3 SAP=(3 334 - M V30 302 1) -M V30 10 SUP 0 ATOMS=(4 372 373 374 375) XBONDS=(1 386) LABEL=CF3 SAP=(3 372- -M V30 340 1) -M V30 11 SUP 0 ATOMS=(4 409 410 411 412) XBONDS=(1 424) LABEL=CF3 SAP=(3 409- -M V30 378 1) -M V30 12 SUP 0 ATOMS=(4 446 447 448 449) XBONDS=(1 462) LABEL=CF3 SAP=(3 446- -M V30 415 1) -M V30 13 SUP 0 ATOMS=(4 668 669 670 671) XBONDS=(1 705) LABEL=^CF3 SAP=(3 66- -M V30 8 667 1) -M V30 14 SUP 0 ATOMS=(1 1210) XBONDS=(1 1301) LABEL=^Me SAP=(3 1210 1200 1) -M V30 15 SUP 0 ATOMS=(4 1246 1247 1248 1249) XBONDS=(1 1341) LABEL=^CF3 SAP=- -M V30 (3 1246 1236 1) +M V30 10 SUP 10 ATOMS=(4 372 373 374 375) XBONDS=(1 386) LABEL=CF3 SAP=(3 37- +M V30 2 340 1) +M V30 11 SUP 11 ATOMS=(4 409 410 411 412) XBONDS=(1 424) LABEL=CF3 SAP=(3 40- +M V30 9 378 1) +M V30 12 SUP 12 ATOMS=(4 446 447 448 449) XBONDS=(1 462) LABEL=CF3 SAP=(3 44- +M V30 6 415 1) +M V30 13 SUP 13 ATOMS=(4 668 669 670 671) XBONDS=(1 705) LABEL=^CF3 SAP=(3 6- +M V30 68 667 1) +M V30 14 SUP 14 ATOMS=(1 1210) XBONDS=(1 1301) LABEL=^Me SAP=(3 1210 1200 1) +M V30 15 SUP 15 ATOMS=(4 1246 1247 1248 1249) XBONDS=(1 1341) LABEL=^CF3 SAP- +M V30 =(3 1246 1236 1) M V30 END SGROUP M V30 END CTAB M END diff --git a/api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py b/api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py index 3ce91d7c3e..7229943ff0 100644 --- a/api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py +++ b/api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py @@ -233,6 +233,17 @@ # ===== ext_index roundtrip V3000 ===== + +def get_v3000_extindex(molfile_str, sg_type="SUP"): + """Parse V3000 molfile and return (index, extindex) for the given SGroup type.""" + for l in molfile_str.split("\n"): + parts = l.strip().split() + if sg_type in parts and "SGROUP" not in l: + idx = parts.index(sg_type) + return parts[idx - 1], parts[idx + 1] + return None, None + + print("****** ext_index: V3000 roundtrip with explicit extindex ********") indigo.setOption("molfile-saving-mode", "3000") @@ -240,26 +251,11 @@ sg = mol.addSGroup("SUP", 42) sg.setSGroupAtoms([0, 1, 2]) sg.setSGroupName("EXT42") -print("original id before save: {0}".format(sg.getSGroupOriginalId())) +print("ext_index before save: 42") molfile = mol.molfile() -mol2 = indigo.loadMolecule(molfile) -for sg2 in mol2.iterateSGroups(): - print("roundtrip original id: {0}".format(sg2.getSGroupOriginalId())) - -# Check the V3000 output contains the extindex -lines = [l for l in molfile.split("\n") if "SUP" in l and "SGROUP" not in l] -for l in lines: - # V3000 format: "M V30 index type extindex ..." - parts = l.strip().split() - # Find the SUP line: M V30 SUP ... - if "SUP" in parts: - sup_idx = parts.index("SUP") - print( - "V3000 index={0} extindex={1}".format( - parts[sup_idx - 1], parts[sup_idx + 1] - ) - ) +idx, ext = get_v3000_extindex(molfile) +print("roundtrip: index={0} extindex={1}".format(idx, ext)) print("****** ext_index: V3000 roundtrip auto-assign (extindex=0) ********") @@ -268,18 +264,11 @@ sg = mol.addSGroup("SUP", 0) sg.setSGroupAtoms([0, 1, 2]) sg.setSGroupName("AUTO") +print("ext_index before save: 0") molfile = mol.molfile() -lines = [l for l in molfile.split("\n") if "SUP" in l and "SGROUP" not in l] -for l in lines: - parts = l.strip().split() - if "SUP" in parts: - sup_idx = parts.index("SUP") - print( - "V3000 auto-assigned: index={0} extindex={1}".format( - parts[sup_idx - 1], parts[sup_idx + 1] - ) - ) +idx, ext = get_v3000_extindex(molfile) +print("roundtrip: index={0} extindex={1}".format(idx, ext)) # ===== ext_index roundtrip V2000 ===== @@ -291,15 +280,14 @@ sg = mol.addSGroup("SUP", 55) sg.setSGroupAtoms([0, 1, 2]) sg.setSGroupName("EXT55") +print("ext_index before save: 55") molfile = mol.molfile() -# Check M SLB line slb_lines = [l for l in molfile.split("\n") if "M SLB" in l] for l in slb_lines: print("V2000 SLB: {0}".format(l.strip())) -# Roundtrip mol2 = indigo.loadMolecule(molfile) for sg2 in mol2.iterateSGroups(): print("V2000 roundtrip original id: {0}".format(sg2.getSGroupOriginalId())) diff --git a/api/tests/integration/tests/formats/ref/macro/sa-mono.cml b/api/tests/integration/tests/formats/ref/macro/sa-mono.cml index 8e08a0f097..693bbb6423 100644 --- a/api/tests/integration/tests/formats/ref/macro/sa-mono.cml +++ b/api/tests/integration/tests/formats/ref/macro/sa-mono.cml @@ -39,7 +39,7 @@ - + @@ -47,13 +47,13 @@ - + - + diff --git a/core/indigo-core/molecule/cml_saver.h b/core/indigo-core/molecule/cml_saver.h index e6bbb3db30..45b55d79d3 100644 --- a/core/indigo-core/molecule/cml_saver.h +++ b/core/indigo-core/molecule/cml_saver.h @@ -20,6 +20,7 @@ #define __cml_saver_h__ #include "molecule/base_molecule.h" +#include "molecule/molecule_sgroups.h" namespace tinyxml2 { @@ -51,7 +52,7 @@ namespace indigo void _validate(BaseMolecule& bmol); void _addMoleculeElement(tinyxml2::XMLElement* elem, BaseMolecule& mol, bool query); - void _addSgroupElement(tinyxml2::XMLElement* elem, BaseMolecule& mol, SGroup& sgroup); + void _addSgroupElement(tinyxml2::XMLElement* elem, BaseMolecule& mol, SGroup& sgroup, int write_index, const std::vector& entries); void _addRgroups(tinyxml2::XMLElement* elem, BaseMolecule& mol, bool query); void _addRgroupElement(tinyxml2::XMLElement* elem, RGroup& rgroup, bool query); diff --git a/core/indigo-core/molecule/molecule_json_saver.h b/core/indigo-core/molecule/molecule_json_saver.h index fafeac09fb..7408d9529c 100644 --- a/core/indigo-core/molecule/molecule_json_saver.h +++ b/core/indigo-core/molecule/molecule_json_saver.h @@ -100,7 +100,6 @@ namespace indigo DECL_ERROR; protected: - void _checkSGroupIndices(BaseMolecule& mol, Array& sgs_list); bool _checkAttPointOrder(BaseMolecule& mol, int rsite); bool _needCustomQuery(QueryMolecule::Atom* atom) const; void _writeQueryProperties(QueryMolecule::Atom* atom, JsonWriter& writer); diff --git a/core/indigo-core/molecule/molecule_sgroups.h b/core/indigo-core/molecule/molecule_sgroups.h index e90b8c8bd8..1d732523e8 100644 --- a/core/indigo-core/molecule/molecule_sgroups.h +++ b/core/indigo-core/molecule/molecule_sgroups.h @@ -24,6 +24,7 @@ #include "base_cpp/obj_pool.h" #include "base_cpp/ptr_pool.h" #include "math/algebra.h" +#include #ifdef _WIN32 #pragma warning(push) @@ -112,7 +113,7 @@ namespace indigo int sgroup_type; // group type, represnted with STY in Molfile format Nullable sgroup_subtype; // group subtype, represnted with SST in Molfile format int index; // internal SGroup index; V3000 field 1, V2000 M STY sss. Used for cross-refs (PARENT, SPL). - Nullable ext_index; // external SGroup index; V3000 field 3 (extindex), V2000 M SLB vvv. Not set = auto-assign per spec. + int ext_index; // external SGroup index; V3000 field 3 (extindex), V2000 M SLB vvv. 0 = auto-assign per spec. Nullable parent_group; // parent group index; represented with PARENT in V3000, SPL in V2000 Nullable parent_idx; // parent group array position; resolved from parent_group // TODO: leave only parent_idx @@ -314,6 +315,19 @@ namespace indigo bool _cmpIndices(Array& t_inds, Array& q_inds); }; + // Read-only write-order entry for serialization. Replaces the old mutating _checkSGroupIndices pattern. + struct SGroupWriteEntry + { + int pool_idx; // original pool index in mol.sgroups + int write_index; // sequential 1,2,3... for CTFile output + int write_ext_index; // ext_index or auto-assigned from write_index (0→write_index per spec) + int write_parent; // remapped parent_group (0 if root) + }; + + // Returns topologically-sorted SGroup list with sequential indices for serialization. + // Does NOT mutate the molecule — returns a mapping table. + DLLEXPORT std::vector getOrderedSGroups(MoleculeSGroups& sgroups); + } // namespace indigo #ifdef _WIN32 diff --git a/core/indigo-core/molecule/molfile_saver.h b/core/indigo-core/molecule/molfile_saver.h index 32b36a1d52..990c36310c 100644 --- a/core/indigo-core/molecule/molfile_saver.h +++ b/core/indigo-core/molecule/molfile_saver.h @@ -27,6 +27,7 @@ #include "base_cpp/array.h" #include "base_cpp/tlscont.h" #include "molecule/base_molecule.h" +#include "molecule/molecule_sgroups.h" namespace indigo { @@ -98,10 +99,9 @@ namespace indigo void _writeTGroup(Output& output, BaseMolecule& mol, int tg_idx); void _writeCtabHeader2000(Output& output, BaseMolecule& mol); void _writeCtab2000(Output& output, BaseMolecule& mol, bool query); - void _checkSGroupIndices(BaseMolecule& mol, Array& sgs); void _writeRGroupIndices2000(Output& output, BaseMolecule& mol); void _writeAttachmentValues2000(Output& output, BaseMolecule& fragment); - void _writeGenericSGroup3000(SGroup& sgroup, int idx, Output& output); + void _writeGenericSGroup3000(SGroup& sgroup, const SGroupWriteEntry& entry, int idx, Output& output); void _writeDataSGroupDisplay(DataSGroup& datasgroup, Output& out); void _writeFormattedString(Output& output, Array& str, int length); static bool _checkAttPointOrder(BaseMolecule& mol, int rsite); diff --git a/core/indigo-core/molecule/src/cmf_saver.cpp b/core/indigo-core/molecule/src/cmf_saver.cpp index 0c7ce6316b..e4c47478f6 100644 --- a/core/indigo-core/molecule/src/cmf_saver.cpp +++ b/core/indigo-core/molecule/src/cmf_saver.cpp @@ -825,10 +825,13 @@ void CmfSaver::_updateSGroupsXyzMinMax(Molecule& mol, Vec3f& min, Vec3f& max) DataSGroup& s = (DataSGroup&)sg; _updateBaseSGroupXyzMinMax(s, min, max); - Vec3f display_pos(s.display_pos->x, s.display_pos->y, 0); + if (s.display_pos.hasValue()) + { + Vec3f display_pos(s.display_pos->x, s.display_pos->y, 0); - min.min(display_pos); - max.max(display_pos); + min.min(display_pos); + max.max(display_pos); + } } } } diff --git a/core/indigo-core/molecule/src/cml_saver.cpp b/core/indigo-core/molecule/src/cml_saver.cpp index b6ac729bd5..cc10901170 100644 --- a/core/indigo-core/molecule/src/cml_saver.cpp +++ b/core/indigo-core/molecule/src/cml_saver.cpp @@ -565,24 +565,26 @@ void CmlSaver::_addMoleculeElement(XMLElement* elem, BaseMolecule& mol, bool que if (_mol->countSGroups() > 0) { - for (i = _mol->sgroups.begin(); i != _mol->sgroups.end(); i = _mol->sgroups.next(i)) + auto entries = getOrderedSGroups(_mol->sgroups); + for (auto& entry : entries) { - SGroup& sgroup = _mol->sgroups.getSGroup(i); - - if (sgroup.parent_group == 0) - _addSgroupElement(molecule, *_mol, sgroup); + if (entry.write_parent == 0) + { + SGroup& sgroup = _mol->sgroups.getSGroup(entry.pool_idx); + _addSgroupElement(molecule, *_mol, sgroup, entry.write_index, entries); + } } } } -void CmlSaver::_addSgroupElement(XMLElement* molecule, BaseMolecule& mol, SGroup& sgroup) +void CmlSaver::_addSgroupElement(XMLElement* molecule, BaseMolecule& mol, SGroup& sgroup, int write_index, const std::vector& entries) { XMLElement* sg = _doc->NewElement("molecule"); molecule->LinkEndChild(sg); QS_DEF(Array, buf); ArrayOutput out(buf); - out.printf("sg%d", sgroup.index); + out.printf("sg%d", write_index); buf.push(0); sg->SetAttribute("id", buf.ptr()); @@ -651,8 +653,11 @@ void CmlSaver::_addSgroupElement(XMLElement* molecule, BaseMolecule& mol, SGroup sg->SetAttribute("queryOp", queryoper); } - sg->SetAttribute("x", dsg.display_pos->x); - sg->SetAttribute("y", dsg.display_pos->y); + if (dsg.display_pos.hasValue()) + { + sg->SetAttribute("x", dsg.display_pos->x); + sg->SetAttribute("y", dsg.display_pos->y); + } if (!dsg.detached) { @@ -685,28 +690,26 @@ void CmlSaver::_addSgroupElement(XMLElement* molecule, BaseMolecule& mol, SGroup sg->SetAttribute("fieldData", dsg.data.ptr()); } - MoleculeSGroups* sgroups = &mol.sgroups; - - for (int i = mol.sgroups.begin(); i != mol.sgroups.end(); i = mol.sgroups.next(i)) + for (auto& child_entry : entries) { - SGroup& sg_child = sgroups->getSGroup(i); - - if ((sg_child.parent_group != 0) && (sg_child.parent_group == sgroup.index)) - _addSgroupElement(sg, mol, sg_child); + if (child_entry.write_parent == write_index) + { + SGroup& sg_child = mol.sgroups.getSGroup(child_entry.pool_idx); + _addSgroupElement(sg, mol, sg_child, child_entry.write_index, entries); + } } } else if (sgroup.sgroup_type == SGroup::SG_TYPE_GEN) { sg->SetAttribute("role", "GenericSgroup"); - MoleculeSGroups* sgroups = &mol.sgroups; - - for (int i = mol.sgroups.begin(); i != mol.sgroups.end(); i = mol.sgroups.next(i)) + for (auto& child_entry : entries) { - SGroup& sg_child = sgroups->getSGroup(i); - - if ((sg_child.parent_group != 0) && (sg_child.parent_group == sgroup.index)) - _addSgroupElement(sg, mol, sg_child); + if (child_entry.write_parent == write_index) + { + SGroup& sg_child = mol.sgroups.getSGroup(child_entry.pool_idx); + _addSgroupElement(sg, mol, sg_child, child_entry.write_index, entries); + } } } else if (sgroup.sgroup_type == SGroup::SG_TYPE_SUP) @@ -721,14 +724,13 @@ void CmlSaver::_addSgroupElement(XMLElement* molecule, BaseMolecule& mol, SGroup sg->SetAttribute("title", name); } - MoleculeSGroups* sgroups = &mol.sgroups; - - for (int i = mol.sgroups.begin(); i != mol.sgroups.end(); i = mol.sgroups.next(i)) + for (auto& child_entry : entries) { - SGroup& sg_child = sgroups->getSGroup(i); - - if ((sg_child.parent_group != 0) && (sg_child.parent_group == sgroup.index)) - _addSgroupElement(sg, mol, sg_child); + if (child_entry.write_parent == write_index) + { + SGroup& sg_child = mol.sgroups.getSGroup(child_entry.pool_idx); + _addSgroupElement(sg, mol, sg_child, child_entry.write_index, entries); + } } } else if (sgroup.sgroup_type == SGroup::SG_TYPE_SRU) @@ -752,14 +754,13 @@ void CmlSaver::_addSgroupElement(XMLElement* molecule, BaseMolecule& mol, SGroup sg->SetAttribute("connect", "hh"); } - MoleculeSGroups* sgroups = &mol.sgroups; - - for (int i = mol.sgroups.begin(); i != mol.sgroups.end(); i = mol.sgroups.next(i)) + for (auto& child_entry : entries) { - SGroup& sg_child = sgroups->getSGroup(i); - - if ((sg_child.parent_group != 0) && (sg_child.parent_group == sgroup.index)) - _addSgroupElement(sg, mol, sg_child); + if (child_entry.write_parent == write_index) + { + SGroup& sg_child = mol.sgroups.getSGroup(child_entry.pool_idx); + _addSgroupElement(sg, mol, sg_child, child_entry.write_index, entries); + } } } else if (sgroup.sgroup_type == SGroup::SG_TYPE_MUL) @@ -787,14 +788,13 @@ void CmlSaver::_addSgroupElement(XMLElement* molecule, BaseMolecule& mol, SGroup sg->SetAttribute("patoms", pbuf.ptr()); } - MoleculeSGroups* sgroups = &mol.sgroups; - - for (int i = mol.sgroups.begin(); i != mol.sgroups.end(); i = mol.sgroups.next(i)) + for (auto& child_entry : entries) { - SGroup& sg_child = sgroups->getSGroup(i); - - if ((sg_child.parent_group != 0) && (sg_child.parent_group == sgroup.index)) - _addSgroupElement(sg, mol, sg_child); + if (child_entry.write_parent == write_index) + { + SGroup& sg_child = mol.sgroups.getSGroup(child_entry.pool_idx); + _addSgroupElement(sg, mol, sg_child, child_entry.write_index, entries); + } } } } diff --git a/core/indigo-core/molecule/src/molecule_cdxml_saver.cpp b/core/indigo-core/molecule/src/molecule_cdxml_saver.cpp index 7006818a22..810faaa133 100644 --- a/core/indigo-core/molecule/src/molecule_cdxml_saver.cpp +++ b/core/indigo-core/molecule/src/molecule_cdxml_saver.cpp @@ -1134,15 +1134,18 @@ void MoleculeCdxmlSaver::addFragmentNodes(BaseMolecule& mol, tinyxml2::XMLElemen { XMLElement* t = _doc->NewElement("t"); node->LinkEndChild(t); - Vec2f pos(sa.display_position->x + offset.x, -sa.display_position->y - offset.y); - pos.scale(_bond_length); - Vec2f v1(pos.x - _bond_length / 2, pos.y - _bond_length / 2); - Vec2f v2(pos.x + _bond_length / 2, pos.y + _bond_length / 2); - std::string pos_str = std::to_string(pos.x) + " " + std::to_string(pos.y); - Rect2f bbox(v1, v2); - std::string bbox_str = boundingBoxToString(bbox); - if (sa.display_position->x != 0.0f && sa.display_position->y != 0.0f) - node->SetAttribute("p", pos_str.c_str()); + if (sa.display_position.hasValue()) + { + Vec2f pos(sa.display_position->x + offset.x, -sa.display_position->y - offset.y); + pos.scale(_bond_length); + Vec2f v1(pos.x - _bond_length / 2, pos.y - _bond_length / 2); + Vec2f v2(pos.x + _bond_length / 2, pos.y + _bond_length / 2); + std::string pos_str = std::to_string(pos.x) + " " + std::to_string(pos.y); + Rect2f bbox(v1, v2); + std::string bbox_str = boundingBoxToString(bbox); + if (sa.display_position->x != 0.0f && sa.display_position->y != 0.0f) + node->SetAttribute("p", pos_str.c_str()); + } t->SetAttribute("LabelJustification", "Left"); t->SetAttribute("LabelAlignment", "Above"); XMLElement* s = _doc->NewElement("s"); diff --git a/core/indigo-core/molecule/src/molecule_json_saver.cpp b/core/indigo-core/molecule/src/molecule_json_saver.cpp index f042c9a504..c6eb84a881 100644 --- a/core/indigo-core/molecule/src/molecule_json_saver.cpp +++ b/core/indigo-core/molecule/src/molecule_json_saver.cpp @@ -120,126 +120,10 @@ void MoleculeJsonSaver::saveFormatMode(KETVersion& version, Array& output) output.readString(ver.c_str(), true); } -void MoleculeJsonSaver::_checkSGroupIndices(BaseMolecule& mol, Array& sgs_list) -{ - QS_DEF(Array, orig_ids); - QS_DEF(Array, added_ids); - QS_DEF(Array, sgs_mapping); - QS_DEF(Array, sgs_changed); - - sgs_list.clear(); - orig_ids.clear(); - added_ids.clear(); - sgs_mapping.clear_resize(mol.sgroups.end()); - sgs_mapping.zerofill(); - sgs_changed.clear_resize(mol.sgroups.end()); - sgs_changed.zerofill(); - - int iw = 1; - for (int i = mol.sgroups.begin(); i != mol.sgroups.end(); i = mol.sgroups.next(i)) - { - SGroup& sgroup = mol.sgroups.getSGroup(i); - if (sgroup.parent_group == 0) - { - sgs_mapping[i] = iw; - iw++; - } - } - for (int i = mol.sgroups.begin(); i != mol.sgroups.end(); i = mol.sgroups.next(i)) - { - if (sgs_mapping[i] == 0) - { - sgs_mapping[i] = iw; - iw++; - } - } - - for (int i = mol.sgroups.begin(); i != mol.sgroups.end(); i = mol.sgroups.next(i)) - { - SGroup& sgroup = mol.sgroups.getSGroup(i); - if (sgroup.index == 0) - { - sgroup.index = sgs_mapping[i]; - } - else - { - for (int j = mol.sgroups.begin(); j != mol.sgroups.end(); j = mol.sgroups.next(j)) - { - SGroup& sg = mol.sgroups.getSGroup(j); - if (sg.parent_group == sgroup.index && sgs_changed[j] == 0) - { - sg.parent_group = sgs_mapping[i]; - sgs_changed[j] = 1; - } - } - sgroup.index = sgs_mapping[i]; - } - // Per BIOVIA spec: if ext_index not set, auto-assign from index - if (!sgroup.ext_index.hasValue()) - sgroup.ext_index = sgroup.index; - orig_ids.push(sgroup.index); - } - - for (int i = mol.sgroups.begin(); i != mol.sgroups.end(); i = mol.sgroups.next(i)) - { - SGroup& sgroup = mol.sgroups.getSGroup(i); - if (sgroup.parent_group == 0) - { - sgs_list.push(i); - added_ids.push(sgroup.index); - } - else - { - if (orig_ids.find(sgroup.parent_group) == VALUE_UNKNOWN || sgroup.parent_group == sgroup.index) - { - sgroup.parent_group = 0; - sgs_list.push(i); - added_ids.push(sgroup.index); - } - } - } - - for (;;) - { - for (int i = mol.sgroups.begin(); i != mol.sgroups.end(); i = mol.sgroups.next(i)) - { - SGroup& sgroup = mol.sgroups.getSGroup(i); - if (sgroup.parent_group == 0) - continue; - - if (added_ids.find(sgroup.index) != VALUE_UNKNOWN) - continue; - - if (added_ids.find(sgroup.parent_group) != VALUE_UNKNOWN) - { - sgs_list.push(i); - added_ids.push(sgroup.index); - } - } - if (sgs_list.size() == mol.countSGroups()) - break; - } -} - void MoleculeJsonSaver::saveSGroups(BaseMolecule& mol, JsonWriter& writer) { - QS_DEF(Array, sgs_sorted); - - // Save ext_index state before _checkSGroupIndices auto-assigns it - struct SGroupExtState - { - int pool_idx; - Nullable ext_index; - }; - std::vector saved_ext; - for (int si = mol.sgroups.begin(); si != mol.sgroups.end(); si = mol.sgroups.next(si)) - { - SGroup& sg = mol.sgroups.getSGroup(si); - saved_ext.push_back({si, sg.ext_index}); - } - - _checkSGroupIndices(mol, sgs_sorted); - int sGroupsCount = mol.countSGroups(); + auto write_order = getOrderedSGroups(mol.sgroups); + int sGroupsCount = static_cast(write_order.size()); bool componentDefined = false; if (mol.isQueryMolecule()) { @@ -255,11 +139,9 @@ void MoleculeJsonSaver::saveSGroups(BaseMolecule& mol, JsonWriter& writer) { writer.Key("sgroups"); writer.StartArray(); - // int idx = 1; - for (int i = 0; i < sgs_sorted.size(); i++) + for (const auto& entry : write_order) { - int sg_idx = sgs_sorted[i]; - auto& sgrp = mol.sgroups.getSGroup(sg_idx); + auto& sgrp = mol.sgroups.getSGroup(entry.pool_idx); saveSGroup(sgrp, writer); } // save queryComponent @@ -283,16 +165,6 @@ void MoleculeJsonSaver::saveSGroups(BaseMolecule& mol, JsonWriter& writer) } writer.EndArray(); } - - // Restore ext_index state after writing (prevent save-time auto-assign from persisting) - for (const auto& s : saved_ext) - { - if (mol.sgroups.hasSGroup(s.pool_idx)) - { - SGroup& sg = mol.sgroups.getSGroup(s.pool_idx); - sg.ext_index = s.ext_index; - } - } } void MoleculeJsonSaver::saveSGroup(SGroup& sgroup, JsonWriter& writer) @@ -346,10 +218,13 @@ void MoleculeJsonSaver::saveSGroup(SGroup& sgroup, JsonWriter& writer) writer.String(query_oper); } - writer.Key("x"); - writeFloat(writer, dsg.display_pos->x); - writer.Key("y"); - writeFloat(writer, dsg.display_pos->y); + if (dsg.display_pos.hasValue()) + { + writer.Key("x"); + writeFloat(writer, dsg.display_pos->x); + writer.Key("y"); + writeFloat(writer, dsg.display_pos->y); + } if (!dsg.detached) { diff --git a/core/indigo-core/molecule/src/molecule_sgroups.cpp b/core/indigo-core/molecule/src/molecule_sgroups.cpp index 293cf5bb26..c5188aed15 100644 --- a/core/indigo-core/molecule/src/molecule_sgroups.cpp +++ b/core/indigo-core/molecule/src/molecule_sgroups.cpp @@ -22,6 +22,10 @@ #include "base_cpp/tree.h" #include "molecule/molecule_sgroups.h" +#include +#include +#include + using namespace indigo; static SGroup::SgType mappingForSgTypes[] = { @@ -58,6 +62,7 @@ SGroup::SGroup() sgroup_subtype = 0; brk_style = 0; index = 0; + ext_index = 0; parent_group = 0; parent_idx = -1; contracted = DisplayOption::Undefined; @@ -725,3 +730,106 @@ bool MoleculeSGroups::_cmpIndices(Array& t_inds, Array& q_inds) } return true; } + +std::vector indigo::getOrderedSGroups(MoleculeSGroups& sgroups) +{ + // Phase 1: Build pool_idx → write_index mapping (sequential, roots first) + std::vector pool_indices; + for (int i = sgroups.begin(); i != sgroups.end(); i = sgroups.next(i)) + pool_indices.push_back(i); + + int pool_end = pool_indices.empty() ? 0 : (*std::max_element(pool_indices.begin(), pool_indices.end()) + 1); + + std::vector sgs_mapping(pool_end, 0); + + // Roots first (parent_group == 0 or not set) + int iw = 1; + for (int i : pool_indices) + { + SGroup& sg = sgroups.getSGroup(i); + int pg = sg.parent_group.hasValue() ? sg.parent_group.get() : 0; + if (pg == 0) + { + sgs_mapping[i] = iw++; + } + } + // Then children + for (int i : pool_indices) + { + if (sgs_mapping[i] == 0) + { + sgs_mapping[i] = iw++; + } + } + // Phase 2: Build old_index → write_index remap for parent_group resolution + std::map index_remap; + for (int i : pool_indices) + { + SGroup& sg = sgroups.getSGroup(i); + int key = (sg.index != 0) ? sg.index : sgs_mapping[i]; + index_remap[key] = sgs_mapping[i]; + } + + // Phase 3: Build entries with write fields + std::vector all_entries; + all_entries.reserve(pool_indices.size()); + for (int i : pool_indices) + { + SGroup& sg = sgroups.getSGroup(i); + SGroupWriteEntry entry; + entry.pool_idx = i; + entry.write_index = sgs_mapping[i]; + + // ext_index: use explicit value if set, otherwise auto-assign from write_index + entry.write_ext_index = (sg.ext_index != 0) ? sg.ext_index : entry.write_index; + + // parent_group: remap to new write indices + int pg = sg.parent_group.hasValue() ? sg.parent_group.get() : 0; + if (pg == 0) + { + entry.write_parent = 0; + } + else + { + auto it = index_remap.find(pg); + if (it != index_remap.end() && it->second != entry.write_index) + entry.write_parent = it->second; + else + entry.write_parent = 0; // orphan or self-ref → root + } + all_entries.push_back(entry); + } + + // Phase 4: Topological sort — parents before children + std::vector result; + result.reserve(all_entries.size()); + + std::set added_indices; + + // Add roots first + for (auto& e : all_entries) + { + if (e.write_parent == 0) + { + result.push_back(e); + added_indices.insert(e.write_index); + } + } + + // Then iteratively add children whose parents are already added + while (result.size() < all_entries.size()) + { + for (auto& e : all_entries) + { + if (added_indices.count(e.write_index)) + continue; + if (added_indices.count(e.write_parent)) + { + result.push_back(e); + added_indices.insert(e.write_index); + } + } + } + + return result; +} diff --git a/core/indigo-core/molecule/src/molfile_saver.cpp b/core/indigo-core/molecule/src/molfile_saver.cpp index 39ebb5819f..bfd4c2b45d 100644 --- a/core/indigo-core/molecule/src/molfile_saver.cpp +++ b/core/indigo-core/molecule/src/molfile_saver.cpp @@ -875,36 +875,19 @@ void MolfileSaver::_writeCtab(Output& output, BaseMolecule& mol, bool query) output.writeStringCR("M V30 END COLLECTION"); } - QS_DEF(Array, sgs_sorted); + auto write_order = getOrderedSGroups(mol.sgroups); - // Save ext_index state before _checkSGroupIndices auto-assigns it - struct SGroupExtState - { - int pool_idx; - Nullable ext_index; - }; - std::vector saved_ext; - for (int si = mol.sgroups.begin(); si != mol.sgroups.end(); si = mol.sgroups.next(si)) - { - SGroup& sg = mol.sgroups.getSGroup(si); - saved_ext.push_back({si, sg.ext_index}); - } - - _checkSGroupIndices(mol, sgs_sorted); - - if (mol.countSGroups() > 0) + if (write_order.size() > 0) { MoleculeSGroups* sgroups = &mol.sgroups; int idx = 1; output.writeStringCR("M V30 BEGIN SGROUP"); - for (i = 0; i < sgs_sorted.size(); i++) + for (const auto& entry : write_order) { ArrayOutput out(buf); - int sg_idx = sgs_sorted[i]; - SGroup& sgroup = sgroups->getSGroup(sg_idx); - // ext_index written as-is (managed by _checkSGroupIndices) - _writeGenericSGroup3000(sgroup, idx++, out); + SGroup& sgroup = sgroups->getSGroup(entry.pool_idx); + _writeGenericSGroup3000(sgroup, entry, idx++, out); if (sgroup.sgroup_type == SGroup::SG_TYPE_GEN) { _writeMultiString(output, buf.ptr(), buf.size()); @@ -1089,16 +1072,6 @@ void MolfileSaver::_writeCtab(Output& output, BaseMolecule& mol, bool query) _removeImplicitSGroups(mol, implicit_sgroups_indexes); } - // Restore ext_index state after writing (prevent save-time auto-assign from persisting) - for (const auto& s : saved_ext) - { - if (mol.sgroups.hasSGroup(s.pool_idx)) - { - SGroup& sg = mol.sgroups.getSGroup(s.pool_idx); - sg.ext_index = s.ext_index; - } - } - output.writeStringCR("M V30 END CTAB"); int n_rgroups = mol.rgroups.getRGroupCount(); @@ -1130,12 +1103,11 @@ void MolfileSaver::_writeCtab(Output& output, BaseMolecule& mol, bool query) } } -void MolfileSaver::_writeGenericSGroup3000(SGroup& sgroup, int idx, Output& output) +void MolfileSaver::_writeGenericSGroup3000(SGroup& sgroup, const SGroupWriteEntry& entry, int idx, Output& output) { int i; - int write_ext = sgroup.ext_index.hasValue() ? sgroup.ext_index.get() : sgroup.index; - output.printf("%d %s %d", sgroup.index, SGroup::typeToString(sgroup.sgroup_type), write_ext); + output.printf("%d %s %d", entry.write_index, SGroup::typeToString(sgroup.sgroup_type), entry.write_ext_index); if (sgroup.atoms.size() > 0) { @@ -1163,9 +1135,9 @@ void MolfileSaver::_writeGenericSGroup3000(SGroup& sgroup, int idx, Output& outp else if (sgroup.sgroup_subtype == SGroup::SG_SUBTYPE_BLO) output.printf(" SUBTYPE=BLO"); } - if (sgroup.parent_group > 0) + if (entry.write_parent > 0) { - output.printf(" PARENT=%d", (sgroup.parent_group.hasValue() ? sgroup.parent_group.get() : 0)); + output.printf(" PARENT=%d", entry.write_parent); } for (i = 0; i < sgroup.brackets.size(); i++) { @@ -1709,22 +1681,12 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) sgroup_ids.push(i); } - QS_DEF(Array, sgs_sorted); - - // Save ext_index state before _checkSGroupIndices auto-assigns it - struct SGroupExtState - { - int pool_idx; - Nullable ext_index; - }; - std::vector saved_ext; - for (int si = mol.sgroups.begin(); si != mol.sgroups.end(); si = mol.sgroups.next(si)) - { - SGroup& sg = mol.sgroups.getSGroup(si); - saved_ext.push_back({si, sg.ext_index}); - } + auto write_order = getOrderedSGroups(mol.sgroups); - _checkSGroupIndices(mol, sgs_sorted); + // Build pool_idx → entry lookup for random access + std::map entry_map; + for (const auto& e : write_order) + entry_map[e.pool_idx] = &e; if (sgroup_ids.size() > 0) { @@ -1735,17 +1697,17 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) for (i = j; i < std::min(sgroup_ids.size(), j + 8); i++) { SGroup* sgroup = &mol.sgroups.getSGroup(sgroup_ids[i]); - output.printf(" %3d %s", sgroup->index, SGroup::typeToString(sgroup->sgroup_type)); + output.printf(" %3d %s", entry_map[sgroup_ids[i]]->write_index, SGroup::typeToString(sgroup->sgroup_type)); } output.writeCR(); } for (j = 0; j < sgroup_ids.size(); j++) { - SGroup* sgroup = &mol.sgroups.getSGroup(sgroup_ids[j]); - if (sgroup->parent_group > 0) + auto* entry = entry_map[sgroup_ids[j]]; + if (entry->write_parent > 0) { - child_ids.push(sgroup->index); - parent_ids.push(sgroup->parent_group); + child_ids.push(entry->write_index); + parent_ids.push(entry->write_parent); } } for (j = 0; j < child_ids.size(); j += 8) @@ -1762,10 +1724,8 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) output.printf("M SLB%3d", std::min(sgroup_ids.size(), j + 8) - j); for (i = j; i < std::min(sgroup_ids.size(), j + 8); i++) { - SGroup* sgroup = &mol.sgroups.getSGroup(sgroup_ids[i]); - // ext_index written as-is (managed by _checkSGroupIndices) - int write_ext = sgroup->ext_index.hasValue() ? sgroup->ext_index.get() : sgroup->index; - output.printf(" %3d %3d", sgroup->index, write_ext); + auto* entry = entry_map[sgroup_ids[i]]; + output.printf(" %3d %3d", entry->write_index, entry->write_ext_index); } output.writeCR(); } @@ -1777,8 +1737,17 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) for (i = j; i < std::min(sru_count, j + 8); i++) { RepeatingUnit* ru = (RepeatingUnit*)&mol.sgroups.getSGroup(i, SGroup::SG_TYPE_SRU); - - output.printf(" %3d ", ru->index); + // Find write_index for this SRU by pointer match + int ru_wi = 0; + for (const auto& e : write_order) + { + if (&mol.sgroups.getSGroup(e.pool_idx) == ru) + { + ru_wi = e.write_index; + break; + } + } + output.printf(" %3d ", ru_wi); if (ru->connectivity == SGroup::HEAD_TO_HEAD) output.printf("HH "); @@ -1793,10 +1762,11 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) for (i = mol.sgroups.begin(); i != mol.sgroups.end(); i = mol.sgroups.next(i)) { SGroup& sgroup = mol.sgroups.getSGroup(i); + int wi = entry_map[i]->write_index; for (j = 0; j < sgroup.atoms.size(); j += 8) { int k; - output.printf("M SAL %3d%3d", sgroup.index, std::min(sgroup.atoms.size(), j + 8) - j); + output.printf("M SAL %3d%3d", wi, std::min(sgroup.atoms.size(), j + 8) - j); for (k = j; k < std::min(sgroup.atoms.size(), j + 8); k++) output.printf(" %3d", _atom_mapping[sgroup.atoms[k]]); output.writeCR(); @@ -1804,7 +1774,7 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) for (j = 0; j < sgroup.getBonds().size(); j += 8) { int k; - output.printf("M SBL %3d%3d", sgroup.index, std::min(sgroup.getBonds().size(), j + 8) - j); + output.printf("M SBL %3d%3d", wi, std::min(sgroup.getBonds().size(), j + 8) - j); for (k = j; k < std::min(sgroup.getBonds().size(), j + 8); k++) output.printf(" %3d", _bond_mapping[sgroup.getBonds()[k]]); output.writeCR(); @@ -1814,9 +1784,9 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) if (sgroup.sgroup_type != SGroup::SG_TYPE_MUL && sgroup.label.size() > 1) { if (sgroup.label.find(' ') > -1) - output.printfCR("M SMT %3d \"%s\"", sgroup.index, sgroup.label.ptr()); + output.printfCR("M SMT %3d \"%s\"", wi, sgroup.label.ptr()); else - output.printfCR("M SMT %3d %s", sgroup.index, sgroup.label.ptr()); + output.printfCR("M SMT %3d %s", wi, sgroup.label.ptr()); } if (sgroup.sgroup_type == SGroup::SG_TYPE_SUP) @@ -1824,18 +1794,18 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) Superatom& superatom = (Superatom&)sgroup; if (superatom.sa_class.size() > 1) - output.printfCR("M SCL %3d %s", superatom.index, superatom.sa_class.ptr()); + output.printfCR("M SCL %3d %s", wi, superatom.sa_class.ptr()); if (superatom.bond_connections.size() > 0) { for (j = 0; j < superatom.bond_connections.size(); j++) { - output.printfCR("M SBV %3d %3d %9.4f %9.4f", superatom.index, _bond_mapping[superatom.bond_connections[j].bond_idx], + output.printfCR("M SBV %3d %3d %9.4f %9.4f", wi, _bond_mapping[superatom.bond_connections[j].bond_idx], superatom.bond_connections[j].bond_dir.x, superatom.bond_connections[j].bond_dir.y); } } if (superatom.contracted == DisplayOption::Expanded) { - output.printfCR("M SDS EXP 1 %3d", superatom.index); + output.printfCR("M SDS EXP 1 %3d", wi); } if (superatom.attachment_points.size() > 0) { @@ -1846,7 +1816,7 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) { if (next_line) { - output.printf("M SAP %3d%3d", superatom.index, std::min(nrem, 6)); + output.printf("M SAP %3d%3d", wi, std::min(nrem, 6)); next_line = false; } @@ -1876,21 +1846,17 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) { DataSGroup& datasgroup = (DataSGroup&)sgroup; - output.printf("M SDT %3d ", datasgroup.index); + output.printf("M SDT %3d ", wi); _writeFormattedString(output, datasgroup.name, 30); - _writeFormattedString(output, datasgroup.type, 2); - _writeFormattedString(output, datasgroup.description, 20); - _writeFormattedString(output, datasgroup.querycode, 2); - _writeFormattedString(output, datasgroup.queryoper, 15); output.writeCR(); - output.printf("M SDD %3d ", datasgroup.index); + output.printf("M SDD %3d ", wi); _writeDataSGroupDisplay(datasgroup, output); output.writeCR(); @@ -1905,13 +1871,12 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) if (ptr[j] == '\n') break; - // Print ptr[0..i] output.writeString("M "); if (j != 69 || j == k) output.writeString("SED "); else output.writeString("SCD "); - output.printf("%3d ", datasgroup.index); + output.printf("%3d ", wi); output.write(ptr, j); if (ptr[j] == '\n') @@ -1929,45 +1894,35 @@ void MolfileSaver::_writeCtab2000(Output& output, BaseMolecule& mol, bool query) for (j = 0; j < mg.parent_atoms.size(); j += 8) { int k; - output.printf("M SPA %3d%3d", mg.index, std::min(mg.parent_atoms.size(), j + 8) - j); + output.printf("M SPA %3d%3d", wi, std::min(mg.parent_atoms.size(), j + 8) - j); for (k = j; k < std::min(mg.parent_atoms.size(), j + 8); k++) output.printf(" %3d", _atom_mapping[mg.parent_atoms[k]]); output.writeCR(); } - output.printf("M SMT %3d %d\n", mg.index, (mg.multiplier.hasValue() ? mg.multiplier.get() : 0)); + output.printf("M SMT %3d %d\n", wi, (mg.multiplier.hasValue() ? mg.multiplier.get() : 0)); } for (j = 0; j < sgroup.brackets.size(); j++) { - output.printf("M SDI %3d 4 %9.4f %9.4f %9.4f %9.4f\n", sgroup.index, sgroup.brackets[j][0].x, sgroup.brackets[j][0].y, - sgroup.brackets[j][1].x, sgroup.brackets[j][1].y); + output.printf("M SDI %3d 4 %9.4f %9.4f %9.4f %9.4f\n", wi, sgroup.brackets[j][0].x, sgroup.brackets[j][0].y, sgroup.brackets[j][1].x, + sgroup.brackets[j][1].y); } if (sgroup.brackets.size() > 0 && sgroup.brk_style > 0) { - output.printf("M SBT 1 %3d %3d\n", sgroup.index, (sgroup.brk_style.hasValue() ? sgroup.brk_style.get() : 0)); + output.printf("M SBT 1 %3d %3d\n", wi, (sgroup.brk_style.hasValue() ? sgroup.brk_style.get() : 0)); } if (sgroup.sgroup_subtype > 0) { if (sgroup.sgroup_subtype == SGroup::SG_SUBTYPE_ALT) - output.printf("M SST 1 %3d ALT\n", sgroup.index); + output.printf("M SST 1 %3d ALT\n", wi); else if (sgroup.sgroup_subtype == SGroup::SG_SUBTYPE_RAN) - output.printf("M SST 1 %3d RAN\n", sgroup.index); + output.printf("M SST 1 %3d RAN\n", wi); else if (sgroup.sgroup_subtype == SGroup::SG_SUBTYPE_BLO) - output.printf("M SST 1 %3d BLO\n", sgroup.index); + output.printf("M SST 1 %3d BLO\n", wi); } } } _removeImplicitSGroups(mol, implicit_sgroups_indexes); - - // Restore ext_index state after writing (prevent save-time auto-assign from persisting) - for (const auto& s : saved_ext) - { - if (mol.sgroups.hasSGroup(s.pool_idx)) - { - SGroup& sg = mol.sgroups.getSGroup(s.pool_idx); - sg.ext_index = s.ext_index; - } - } } void MolfileSaver::_writeFormattedString(Output& output, Array& str, int length) @@ -1995,107 +1950,6 @@ void MolfileSaver::_writeFormattedString(Output& output, Array& str, int l output.writeChar(' '); } -void MolfileSaver::_checkSGroupIndices(BaseMolecule& mol, Array& sgs_list) -{ - QS_DEF(Array, orig_ids); - QS_DEF(Array, added_ids); - QS_DEF(Array, sgs_mapping); - QS_DEF(Array, sgs_changed); - - sgs_list.clear(); - orig_ids.clear(); - added_ids.clear(); - sgs_mapping.clear_resize(mol.sgroups.end()); - sgs_mapping.zerofill(); - sgs_changed.clear_resize(mol.sgroups.end()); - sgs_changed.zerofill(); - - int iw = 1; - for (int i = mol.sgroups.begin(); i != mol.sgroups.end(); i = mol.sgroups.next(i)) - { - SGroup& sgroup = mol.sgroups.getSGroup(i); - if (sgroup.parent_group == 0) - { - sgs_mapping[i] = iw; - iw++; - } - } - for (int i = mol.sgroups.begin(); i != mol.sgroups.end(); i = mol.sgroups.next(i)) - { - if (sgs_mapping[i] == 0) - { - sgs_mapping[i] = iw; - iw++; - } - } - - for (int i = mol.sgroups.begin(); i != mol.sgroups.end(); i = mol.sgroups.next(i)) - { - SGroup& sgroup = mol.sgroups.getSGroup(i); - if (sgroup.index == 0) - { - sgroup.index = sgs_mapping[i]; - } - else - { - for (int j = mol.sgroups.begin(); j != mol.sgroups.end(); j = mol.sgroups.next(j)) - { - SGroup& sg = mol.sgroups.getSGroup(j); - if (sg.parent_group == sgroup.index && sgs_changed[j] == 0) - { - sg.parent_group = sgs_mapping[i]; - sgs_changed[j] = 1; - } - } - sgroup.index = sgs_mapping[i]; - } - // Per BIOVIA spec: if ext_index not set, auto-assign from index - if (!sgroup.ext_index.hasValue()) - sgroup.ext_index = sgroup.index; - orig_ids.push(sgroup.index); - } - - for (int i = mol.sgroups.begin(); i != mol.sgroups.end(); i = mol.sgroups.next(i)) - { - SGroup& sgroup = mol.sgroups.getSGroup(i); - if (sgroup.parent_group == 0) - { - sgs_list.push(i); - added_ids.push(sgroup.index); - } - else - { - if (orig_ids.find(sgroup.parent_group) == -1 || sgroup.parent_group == sgroup.index) - { - sgroup.parent_group = 0; - sgs_list.push(i); - added_ids.push(sgroup.index); - } - } - } - - for (;;) - { - for (int i = mol.sgroups.begin(); i != mol.sgroups.end(); i = mol.sgroups.next(i)) - { - SGroup& sgroup = mol.sgroups.getSGroup(i); - if (sgroup.parent_group == 0) - continue; - - if (added_ids.find(sgroup.index) != -1) - continue; - - if (added_ids.find(sgroup.parent_group) != -1) - { - sgs_list.push(i); - added_ids.push(sgroup.index); - } - } - if (sgs_list.size() == mol.countSGroups()) - break; - } -} - int MolfileSaver::_getStereocenterParity(BaseMolecule& mol, int idx) { int type = mol.stereocenters.getType(idx); @@ -2257,8 +2111,9 @@ bool MolfileSaver::_checkAttPointOrder(BaseMolecule& mol, int rsite) void MolfileSaver::_writeDataSGroupDisplay(DataSGroup& datasgroup, Output& out) { - out.printf("%10.4f%10.4f %c%c%c", datasgroup.display_pos->x, datasgroup.display_pos->y, datasgroup.detached ? 'D' : 'A', datasgroup.relative ? 'R' : 'A', - datasgroup.display_units ? 'U' : ' '); + float dp_x = datasgroup.display_pos.hasValue() ? datasgroup.display_pos->x : 0.0f; + float dp_y = datasgroup.display_pos.hasValue() ? datasgroup.display_pos->y : 0.0f; + out.printf("%10.4f%10.4f %c%c%c", dp_x, dp_y, datasgroup.detached ? 'D' : 'A', datasgroup.relative ? 'R' : 'A', datasgroup.display_units ? 'U' : ' '); if (datasgroup.num_chars == 0) out.printf(" ALL 1 %c %1d ", (datasgroup.tag.hasValue() ? datasgroup.tag.get() : 0), (datasgroup.dasp_pos.hasValue() ? datasgroup.dasp_pos.get() : 0)); diff --git a/core/render2d/src/render_internal.cpp b/core/render2d/src/render_internal.cpp index 93c757fec5..283dae2e0c 100644 --- a/core/render2d/src/render_internal.cpp +++ b/core/render2d/src/render_internal.cpp @@ -594,7 +594,8 @@ void MoleculeRenderInternal::_initSGroups(Tree& sgroups, Rect2f parent) } else if (group.relative) { - _objDistTransform(ti.bbp, group.display_pos.get()); + if (group.display_pos.hasValue()) + _objDistTransform(ti.bbp, group.display_pos.get()); if (group.atoms.size() > 0) { ti.bbp.add(_ad(group.atoms[0]).pos); @@ -606,7 +607,8 @@ void MoleculeRenderInternal::_initSGroups(Tree& sgroups, Rect2f parent) } else { - _objCoordTransform(ti.bbp, group.display_pos.get()); + if (group.display_pos.hasValue()) + _objCoordTransform(ti.bbp, group.display_pos.get()); } parent = ILLEGAL_RECT(); From b38b77ecec3f856f5b4fdfd07e152796e85be4fe Mon Sep 17 00:00:00 2001 From: even1024 Date: Fri, 15 May 2026 00:24:46 +0200 Subject: [PATCH 17/24] fix review: cycle guard, xbonds iter, display_pos roundtrip, ext_index merge --- api/c/indigo/src/indigo_molecule.cpp | 23 +++++++++++++++++++ api/c/indigo/src/indigo_molecule.h | 16 +++++++++++++ .../indigo/src/indigo_molecule_operations.cpp | 2 +- .../molecule/src/base_molecule.cpp | 1 + .../molecule/src/molecule_json_loader.cpp | 1 + .../molecule/src/molecule_sgroups.cpp | 15 ++++++++++++ 6 files changed, 57 insertions(+), 1 deletion(-) diff --git a/api/c/indigo/src/indigo_molecule.cpp b/api/c/indigo/src/indigo_molecule.cpp index e14bc45b91..720e5682b5 100644 --- a/api/c/indigo/src/indigo_molecule.cpp +++ b/api/c/indigo/src/indigo_molecule.cpp @@ -1171,6 +1171,29 @@ IndigoObject* IndigoSGroupBondsIter::next() return new IndigoBond(_mol, _sgroup.getBonds()[_idx]); } +IndigoSGroupXBondsIter::IndigoSGroupXBondsIter(BaseMolecule& mol, SGroup& sgroup) : IndigoObject(SGROUP_ATOMS_ITER), _mol(mol), _sgroup(sgroup) +{ + _idx = -1; +} + +IndigoSGroupXBondsIter::~IndigoSGroupXBondsIter() +{ +} + +bool IndigoSGroupXBondsIter::hasNext() +{ + return _idx + 1 < _sgroup.xbonds.size(); +} + +IndigoObject* IndigoSGroupXBondsIter::next() +{ + if (!hasNext()) + return 0; + + _idx++; + return new IndigoBond(_mol, _sgroup.xbonds[_idx]); +} + int _indigoIterateAtoms(Indigo& self, int molecule, int type) { return self.addObject(new IndigoAtomsIter(&self.getObject(molecule).getBaseMolecule(), type)); diff --git a/api/c/indigo/src/indigo_molecule.h b/api/c/indigo/src/indigo_molecule.h index b75c53263d..bd478a8262 100644 --- a/api/c/indigo/src/indigo_molecule.h +++ b/api/c/indigo/src/indigo_molecule.h @@ -559,6 +559,22 @@ class IndigoSGroupBondsIter : public IndigoObject int _idx; }; +// Iterates xbonds (crossing bonds) directly, not polymorphic getBonds() +class IndigoSGroupXBondsIter : public IndigoObject +{ +public: + IndigoSGroupXBondsIter(BaseMolecule& mol, SGroup& sgroup); + ~IndigoSGroupXBondsIter() override; + + IndigoObject* next() override; + bool hasNext() override; + +protected: + BaseMolecule& _mol; + SGroup& _sgroup; + int _idx; +}; + class IndigoMoleculeComponent : public IndigoObject { public: diff --git a/api/c/indigo/src/indigo_molecule_operations.cpp b/api/c/indigo/src/indigo_molecule_operations.cpp index 32571196d3..fc2095a5c5 100644 --- a/api/c/indigo/src/indigo_molecule_operations.cpp +++ b/api/c/indigo/src/indigo_molecule_operations.cpp @@ -1858,7 +1858,7 @@ CEXPORT int indigoIterateSGroupCrossBonds(int sgroup) INDIGO_BEGIN { IndigoSGroup& isg = IndigoSGroup::cast(self.getObject(sgroup)); - return self.addObject(new IndigoSGroupBondsIter(isg.mol, isg.get())); + return self.addObject(new IndigoSGroupXBondsIter(isg.mol, isg.get())); } INDIGO_END(-1); } diff --git a/core/indigo-core/molecule/src/base_molecule.cpp b/core/indigo-core/molecule/src/base_molecule.cpp index 8ca25a8cbf..ad0e401583 100644 --- a/core/indigo-core/molecule/src/base_molecule.cpp +++ b/core/indigo-core/molecule/src/base_molecule.cpp @@ -177,6 +177,7 @@ void BaseMolecule::mergeSGroupsWithSubmolecule(BaseMolecule& mol, Array& ma SGroup& sg = sgroups.getSGroup(idx); sg.parent_idx = supersg.parent_idx; sg.index = supersg.index; + sg.ext_index = supersg.ext_index; sg.parent_group = supersg.parent_group; sg.label.copy(supersg.label); diff --git a/core/indigo-core/molecule/src/molecule_json_loader.cpp b/core/indigo-core/molecule/src/molecule_json_loader.cpp index 572fadd3c1..0adaba51ba 100644 --- a/core/indigo-core/molecule/src/molecule_json_loader.cpp +++ b/core/indigo-core/molecule/src/molecule_json_loader.cpp @@ -1125,6 +1125,7 @@ void MoleculeJsonLoader::parseSGroups(const rapidjson::Value& sgroups, BaseMolec if (s.HasMember("queryOp")) dsg.queryoper.readString(s["queryOp"].GetString(), true); + if (s.HasMember("x") || s.HasMember("y")) { Vec2f dp; if (s.HasMember("x")) diff --git a/core/indigo-core/molecule/src/molecule_sgroups.cpp b/core/indigo-core/molecule/src/molecule_sgroups.cpp index c5188aed15..9e078f2b0a 100644 --- a/core/indigo-core/molecule/src/molecule_sgroups.cpp +++ b/core/indigo-core/molecule/src/molecule_sgroups.cpp @@ -819,6 +819,7 @@ std::vector indigo::getOrderedSGroups(MoleculeSGroups& sgroups // Then iteratively add children whose parents are already added while (result.size() < all_entries.size()) { + size_t prev_size = result.size(); for (auto& e : all_entries) { if (added_indices.count(e.write_index)) @@ -829,6 +830,20 @@ std::vector indigo::getOrderedSGroups(MoleculeSGroups& sgroups added_indices.insert(e.write_index); } } + // No progress — cyclic or orphan refs; break cycles by adding as roots + if (result.size() == prev_size) + { + for (auto& e : all_entries) + { + if (!added_indices.count(e.write_index)) + { + e.write_parent = 0; + result.push_back(e); + added_indices.insert(e.write_index); + } + } + break; + } } return result; From f6fa867d7db8cf19412dd2b36c4974bbded75890 Mon Sep 17 00:00:00 2001 From: even1024 Date: Fri, 15 May 2026 01:17:07 +0200 Subject: [PATCH 18/24] fix sgroup xbonds roundtrip --- api/c/indigo/src/indigo_molecule.cpp | 4 +-- .../ref/basic/3604_sgroup_atoms_bonds.py.out | 5 +++ .../tests/basic/3604_sgroup_atoms_bonds.py | 23 ++++++++++++ .../molecule/src/molfile_loader_v3000.cpp | 10 +++++- .../molecule/src/molfile_saver.cpp | 36 ++++++++++++++----- 5 files changed, 67 insertions(+), 11 deletions(-) diff --git a/api/c/indigo/src/indigo_molecule.cpp b/api/c/indigo/src/indigo_molecule.cpp index 720e5682b5..f8624be707 100644 --- a/api/c/indigo/src/indigo_molecule.cpp +++ b/api/c/indigo/src/indigo_molecule.cpp @@ -1148,7 +1148,7 @@ IndigoObject* IndigoSGroupAtomsIter::next() return new IndigoAtom(_mol, _sgroup.atoms[_idx]); } -IndigoSGroupBondsIter::IndigoSGroupBondsIter(BaseMolecule& mol, SGroup& sgroup) : IndigoObject(SGROUP_ATOMS_ITER), _mol(mol), _sgroup(sgroup) +IndigoSGroupBondsIter::IndigoSGroupBondsIter(BaseMolecule& mol, SGroup& sgroup) : IndigoObject(SGROUP_BONDS_ITER), _mol(mol), _sgroup(sgroup) { _idx = -1; } @@ -1171,7 +1171,7 @@ IndigoObject* IndigoSGroupBondsIter::next() return new IndigoBond(_mol, _sgroup.getBonds()[_idx]); } -IndigoSGroupXBondsIter::IndigoSGroupXBondsIter(BaseMolecule& mol, SGroup& sgroup) : IndigoObject(SGROUP_ATOMS_ITER), _mol(mol), _sgroup(sgroup) +IndigoSGroupXBondsIter::IndigoSGroupXBondsIter(BaseMolecule& mol, SGroup& sgroup) : IndigoObject(SGROUP_BONDS_ITER), _mol(mol), _sgroup(sgroup) { _idx = -1; } diff --git a/api/tests/integration/ref/basic/3604_sgroup_atoms_bonds.py.out b/api/tests/integration/ref/basic/3604_sgroup_atoms_bonds.py.out index 0777583df6..62f65cb461 100644 --- a/api/tests/integration/ref/basic/3604_sgroup_atoms_bonds.py.out +++ b/api/tests/integration/ref/basic/3604_sgroup_atoms_bonds.py.out @@ -57,6 +57,11 @@ type: 2 atoms: 3 bonds: 2 sgroup count: 1 +****** Molfile roundtrip: DAT with containment and cross bonds ******** +DAT molfile V3000: True +DAT line: M V30 1 DAT 1 ATOMS=(3 2 3 4) XBONDS=(2 1 4) CBONDS=(2 2 3) FIELDDISP=" 0- +DAT containment bonds: 1 2 +DAT cross bonds: 0 3 ****** ext_index: V3000 roundtrip with explicit extindex ******** ext_index before save: 42 roundtrip: index=1 extindex=42 diff --git a/api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py b/api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py index 7229943ff0..2afbd6e201 100644 --- a/api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py +++ b/api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py @@ -231,6 +231,29 @@ print("sgroup count: {0}".format(sg_count)) +print("****** Molfile roundtrip: DAT with containment and cross bonds ********") + +indigo.setOption("molfile-saving-mode", "auto") +mol = indigo.loadMolecule("CCCCCC") +sg = mol.addSGroup("DAT", 0) +sg.setSGroupAtoms([1, 2, 3]) +sg.setSGroupBonds([1, 2]) +sg.createCrossBonds() + +molfile = mol.molfile() +print("DAT molfile V3000: {0}".format("V3000" in molfile)) +sgroup_lines = [l.strip() for l in molfile.split("\n") if " DAT " in l] +for l in sgroup_lines: + print("DAT line: {0}".format(l)) + +mol2 = indigo.loadMolecule(molfile) +for sg in mol2.iterateDataSGroups(): + cbonds = sorted([str(b.index()) for b in sg.iterateBonds()]) + xbonds = sorted([str(b.index()) for b in sg.iterateSGroupCrossBonds()]) + print("DAT containment bonds: {0}".format(" ".join(cbonds))) + print("DAT cross bonds: {0}".format(" ".join(xbonds))) + + # ===== ext_index roundtrip V3000 ===== diff --git a/core/indigo-core/molecule/src/molfile_loader_v3000.cpp b/core/indigo-core/molecule/src/molfile_loader_v3000.cpp index fb423d1788..08ab1394c0 100644 --- a/core/indigo-core/molecule/src/molfile_loader_v3000.cpp +++ b/core/indigo-core/molecule/src/molfile_loader_v3000.cpp @@ -1191,11 +1191,19 @@ void MolfileLoader::_readSGroup3000(const char* str) } else if ((strcmp(entity.ptr(), "XBONDS") == 0) || (strcmp(entity.ptr(), "CBONDS") == 0)) { + Array* bonds = 0; + if (strcmp(entity.ptr(), "XBONDS") == 0) + bonds = &sgroup->xbonds; + else if (dsg != 0) + bonds = &dsg->cbonds; + else + bonds = &sgroup->getBonds(); + scanner.skip(1); // ( n = scanner.readInt1(); while (n-- > 0) { - sgroup->getBonds().push(scanner.readInt() - 1); + bonds->push(scanner.readInt() - 1); scanner.skipSpace(); } scanner.skip(1); // ) diff --git a/core/indigo-core/molecule/src/molfile_saver.cpp b/core/indigo-core/molecule/src/molfile_saver.cpp index bfd4c2b45d..7c9a00034a 100644 --- a/core/indigo-core/molecule/src/molfile_saver.cpp +++ b/core/indigo-core/molecule/src/molfile_saver.cpp @@ -206,6 +206,18 @@ void MolfileSaver::_saveMolecule(BaseMolecule& bmol, bool query) BaseMolecule* pmol = &bmol; std::unique_ptr mol(bmol.neu()); mol->clone_KeepIndices(bmol); + + bool has_dat_xbonds = false; + for (int i = pmol->sgroups.begin(); i != pmol->sgroups.end(); i = pmol->sgroups.next(i)) + { + SGroup& sgroup = pmol->sgroups.getSGroup(i); + if (sgroup.sgroup_type == SGroup::SG_TYPE_DAT && sgroup.xbonds.size() > 0) + { + has_dat_xbonds = true; + break; + } + } + if (mode == MODE_2000) { _v2000 = true; @@ -219,7 +231,7 @@ void MolfileSaver::_saveMolecule(BaseMolecule& bmol, bool query) // auto-detect the format: save to v3000 molfile only // if v2000 is not enough _v2000 = !(pmol->hasHighlighting() || pmol->stereocenters.haveEnhancedStereocenter() || - (pmol->vertexCount() > 999 || pmol->edgeCount() > 999 || pmol->tgroups.getTGroupCount())); + (pmol->vertexCount() > 999 || pmol->edgeCount() > 999 || pmol->tgroups.getTGroupCount()) || has_dat_xbonds); } if (mol->tgroups.getTGroupCount() && mol->convertTemplateAtomsToSuperatoms(!_v2000)) @@ -1116,16 +1128,24 @@ void MolfileSaver::_writeGenericSGroup3000(SGroup& sgroup, const SGroupWriteEntr output.printf(" %d", _atom_mapping[sgroup.atoms[i]]); output.printf(")"); } - if (sgroup.getBonds().size() > 0) + if (sgroup.xbonds.size() > 0) { - if (sgroup.sgroup_type == SGroup::SG_TYPE_DAT) - output.printf(" CBONDS=(%d", sgroup.getBonds().size()); - else - output.printf(" XBONDS=(%d", sgroup.getBonds().size()); - for (i = 0; i < sgroup.getBonds().size(); i++) - output.printf(" %d", _bond_mapping[sgroup.getBonds()[i]]); + output.printf(" XBONDS=(%d", sgroup.xbonds.size()); + for (i = 0; i < sgroup.xbonds.size(); i++) + output.printf(" %d", _bond_mapping[sgroup.xbonds[i]]); output.printf(")"); } + if (sgroup.sgroup_type == SGroup::SG_TYPE_DAT) + { + DataSGroup& dsgroup = (DataSGroup&)sgroup; + if (dsgroup.cbonds.size() > 0) + { + output.printf(" CBONDS=(%d", dsgroup.cbonds.size()); + for (i = 0; i < dsgroup.cbonds.size(); i++) + output.printf(" %d", _bond_mapping[dsgroup.cbonds[i]]); + output.printf(")"); + } + } if (sgroup.sgroup_subtype > 0) { if (sgroup.sgroup_subtype == SGroup::SG_SUBTYPE_ALT) From b079f39ecccaebcb8bbfe5af47836d999d58fc8e Mon Sep 17 00:00:00 2001 From: even1024 Date: Fri, 15 May 2026 02:52:40 +0200 Subject: [PATCH 19/24] clean sgroup extindex test --- .../integration/ref/basic/3604_sgroup_atoms_bonds.py.out | 2 -- .../integration/tests/basic/3604_sgroup_atoms_bonds.py | 7 ------- 2 files changed, 9 deletions(-) diff --git a/api/tests/integration/ref/basic/3604_sgroup_atoms_bonds.py.out b/api/tests/integration/ref/basic/3604_sgroup_atoms_bonds.py.out index 62f65cb461..69ff5b3a84 100644 --- a/api/tests/integration/ref/basic/3604_sgroup_atoms_bonds.py.out +++ b/api/tests/integration/ref/basic/3604_sgroup_atoms_bonds.py.out @@ -11,8 +11,6 @@ MUL type: 4 MUL atoms: 0 GEN type: 0 GEN atoms: 0 -****** addSGroup: with explicit extindex ******** -extindex: 0 ****** setSGroupAtoms: set atoms on empty SGroup ******** atoms after set: 3 atom symbols: C C C diff --git a/api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py b/api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py index 2afbd6e201..35ca79a122 100644 --- a/api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py +++ b/api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py @@ -53,13 +53,6 @@ print("GEN type: {0}".format(sg_gen.getSGroupType())) print("GEN atoms: {0}".format(sg_gen.countAtoms())) -print("****** addSGroup: with explicit extindex ********") - -mol2 = indigo.loadMolecule("CCCCCC") -sg = mol2.addSGroup("SUP", 42) -print("extindex: {0}".format(sg.getSGroupOriginalId())) - - # ===== setSGroupAtoms ===== print("****** setSGroupAtoms: set atoms on empty SGroup ********") From c6bd9ea3150be7f243290b37de8d7c662719292f Mon Sep 17 00:00:00 2001 From: even1024 Date: Fri, 15 May 2026 03:52:12 +0200 Subject: [PATCH 20/24] black format --- api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py b/api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py index 35ca79a122..ef2053218c 100644 --- a/api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py +++ b/api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py @@ -224,7 +224,9 @@ print("sgroup count: {0}".format(sg_count)) -print("****** Molfile roundtrip: DAT with containment and cross bonds ********") +print( + "****** Molfile roundtrip: DAT with containment and cross bonds ********" +) indigo.setOption("molfile-saving-mode", "auto") mol = indigo.loadMolecule("CCCCCC") From 758c3b17239a4a8d97f94a6d02bc459d736cc38a Mon Sep 17 00:00:00 2001 From: even1024 Date: Fri, 15 May 2026 10:03:50 +0200 Subject: [PATCH 21/24] update sgroup layout ref --- .../integration/tests/layout/ref/3291-selection.ket | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/api/tests/integration/tests/layout/ref/3291-selection.ket b/api/tests/integration/tests/layout/ref/3291-selection.ket index 8bc9269580..c334251b4b 100644 --- a/api/tests/integration/tests/layout/ref/3291-selection.ket +++ b/api/tests/integration/tests/layout/ref/3291-selection.ket @@ -564,8 +564,6 @@ ], "fieldName": "MDLBG_STEREO_KEY", "fieldData": "SR", - "x": -24.4893, - "y": 15.9961, "display": true }, { @@ -575,8 +573,6 @@ ], "fieldName": "MDLBG_STEREO_KEY", "fieldData": "SR", - "x": -24.3783, - "y": 15.0602, "display": true }, { @@ -586,8 +582,6 @@ ], "fieldName": "MDLBG_STEREO_KEY", "fieldData": "SR", - "x": -24.2673, - "y": 16.7372, "display": true }, { @@ -597,8 +591,6 @@ ], "fieldName": "MDLBG_STEREO_KEY", "fieldData": "SR", - "x": -24.1645, - "y": 14.4936, "display": true } ] @@ -870,4 +862,4 @@ } ] } -} \ No newline at end of file +} From 4c7ac5a531ff71e9049704058d973524644e92b4 Mon Sep 17 00:00:00 2001 From: even1024 Date: Fri, 15 May 2026 10:12:13 +0200 Subject: [PATCH 22/24] fix displayed sgroup position --- .../integration/tests/layout/ref/3291-selection.ket | 10 +++++++++- core/indigo-core/molecule/src/molecule_json_loader.cpp | 7 ++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/api/tests/integration/tests/layout/ref/3291-selection.ket b/api/tests/integration/tests/layout/ref/3291-selection.ket index c334251b4b..8bc9269580 100644 --- a/api/tests/integration/tests/layout/ref/3291-selection.ket +++ b/api/tests/integration/tests/layout/ref/3291-selection.ket @@ -564,6 +564,8 @@ ], "fieldName": "MDLBG_STEREO_KEY", "fieldData": "SR", + "x": -24.4893, + "y": 15.9961, "display": true }, { @@ -573,6 +575,8 @@ ], "fieldName": "MDLBG_STEREO_KEY", "fieldData": "SR", + "x": -24.3783, + "y": 15.0602, "display": true }, { @@ -582,6 +586,8 @@ ], "fieldName": "MDLBG_STEREO_KEY", "fieldData": "SR", + "x": -24.2673, + "y": 16.7372, "display": true }, { @@ -591,6 +597,8 @@ ], "fieldName": "MDLBG_STEREO_KEY", "fieldData": "SR", + "x": -24.1645, + "y": 14.4936, "display": true } ] @@ -862,4 +870,4 @@ } ] } -} +} \ No newline at end of file diff --git a/core/indigo-core/molecule/src/molecule_json_loader.cpp b/core/indigo-core/molecule/src/molecule_json_loader.cpp index 0adaba51ba..4b367533c8 100644 --- a/core/indigo-core/molecule/src/molecule_json_loader.cpp +++ b/core/indigo-core/molecule/src/molecule_json_loader.cpp @@ -1125,9 +1125,10 @@ void MoleculeJsonLoader::parseSGroups(const rapidjson::Value& sgroups, BaseMolec if (s.HasMember("queryOp")) dsg.queryoper.readString(s["queryOp"].GetString(), true); - if (s.HasMember("x") || s.HasMember("y")) + bool display_units = s.HasMember("display") && s["display"].GetBool(); + if (s.HasMember("x") || s.HasMember("y") || display_units) { - Vec2f dp; + Vec2f dp(0.0f, 0.0f); if (s.HasMember("x")) dp.x = s["x"].GetFloat(); @@ -1145,7 +1146,7 @@ void MoleculeJsonLoader::parseSGroups(const rapidjson::Value& sgroups, BaseMolec dsg.relative = s["placement"].GetBool(); if (s.HasMember("display")) - dsg.display_units = s["display"].GetBool(); + dsg.display_units = display_units; if (s.HasMember("tag")) { From c563eb78e9516946657b56f33a0685155b833749 Mon Sep 17 00:00:00 2001 From: even1024 Date: Fri, 15 May 2026 11:51:22 +0200 Subject: [PATCH 23/24] python 2 fix --- api/tests/integration/ref/basic/3604_sgroup_atoms_bonds.py.out | 2 +- api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/tests/integration/ref/basic/3604_sgroup_atoms_bonds.py.out b/api/tests/integration/ref/basic/3604_sgroup_atoms_bonds.py.out index 69ff5b3a84..4c7f99e320 100644 --- a/api/tests/integration/ref/basic/3604_sgroup_atoms_bonds.py.out +++ b/api/tests/integration/ref/basic/3604_sgroup_atoms_bonds.py.out @@ -56,7 +56,7 @@ atoms: 3 bonds: 2 sgroup count: 1 ****** Molfile roundtrip: DAT with containment and cross bonds ******** -DAT molfile V3000: True +DAT molfile V3000: 1 DAT line: M V30 1 DAT 1 ATOMS=(3 2 3 4) XBONDS=(2 1 4) CBONDS=(2 2 3) FIELDDISP=" 0- DAT containment bonds: 1 2 DAT cross bonds: 0 3 diff --git a/api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py b/api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py index ef2053218c..3e10ddcd18 100644 --- a/api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py +++ b/api/tests/integration/tests/basic/3604_sgroup_atoms_bonds.py @@ -236,7 +236,7 @@ sg.createCrossBonds() molfile = mol.molfile() -print("DAT molfile V3000: {0}".format("V3000" in molfile)) +print("DAT molfile V3000: {0}".format(int("V3000" in molfile))) sgroup_lines = [l.strip() for l in molfile.split("\n") if " DAT " in l] for l in sgroup_lines: print("DAT line: {0}".format(l)) From 6061569d293a20de4c79e157e46c3ebd0fa18644 Mon Sep 17 00:00:00 2001 From: even1024 Date: Fri, 15 May 2026 14:35:44 +0200 Subject: [PATCH 24/24] ci fix --- .github/workflows/indigo-ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/indigo-ci.yaml b/.github/workflows/indigo-ci.yaml index f004e52a31..9a3cd3c4ce 100644 --- a/.github/workflows/indigo-ci.yaml +++ b/.github/workflows/indigo-ci.yaml @@ -951,7 +951,7 @@ jobs: build_indigo_utils_x86_64: strategy: fail-fast: false - matrix: ${{ fromJSON(needs.set_matrix.outputs.matrix) }} + matrix: ${{ fromJSON(needs.set_matrix.outputs.matrix || '{"os":["ubuntu-latest","windows-latest"]}') }} runs-on: ${{ matrix.os }} # needs: [build_bingo_postgres_linux_x86_64, build_bingo_postgres_windows_msvc_x86_64, build_bingo_postgres_macos_x86_64] needs: [build_bingo_postgres_linux_x86_64, build_bingo_postgres_windows_msvc_x86_64, set_matrix ]