/* A* ------------------------------------------------------------------- B* This file contains source code for the PyMOL computer program C* copyright 1998-2000 by Warren Lyford Delano of DeLano Scientific. D* ------------------------------------------------------------------- E* It is unlawful to modify or remove this copyright notice. F* ------------------------------------------------------------------- G* Please see the accompanying LICENSE file for further information. H* ------------------------------------------------------------------- I* Additional authors of this source file include: -* -* -* Z* ------------------------------------------------------------------- */ #include"os_python.h" #include"os_predef.h" #include"os_std.h" #include"os_gl.h" #include"Base.h" #include"Parse.h" #include"Vector.h" #include"MemoryDebug.h" #include"Err.h" #include"Map.h" #include"Selector.h" #include"ObjectMolecule.h" #include"Ortho.h" #include"Util.h" #include"Matrix.h" #include"Scene.h" #include"P.h" #include"PConv.h" #include"Executive.h" #include"Setting.h" #include"Sphere.h" #include"main.h" #include"CGO.h" #include"Editor.h" #include"Sculpt.h" #include"OVLexicon.h" #include"ListMacros.h" #include"File.h" #include "Lex.h" #include "MolV3000.h" #include "HydrogenAdder.h" #include "Feedback.h" #include "Util2.h" #include "Property.h" #ifdef _WEBGL #endif #define cMaxNegResi 100 #define ntrim ParseNTrim #define nextline ParseNextLine #define ncopy ParseNCopy #define nskip ParseNSkip #include #include #include #include #include #include #ifndef NO_MMLIBS #include "mmpymolx.h" #endif static CoordSet *ObjectMoleculeMMDStr2CoordSet(PyMOLGlobals * G, const char *buffer, AtomInfoType ** atInfoPtr, const char **restart); static void ObjectMoleculeTransformTTTf(ObjectMolecule * I, float *ttt, int state); static int ObjectMoleculeGetAtomGeometry(const ObjectMolecule * I, int state, int at); static void ObjectMoleculeInferHBondFromChem(ObjectMolecule * I); /** * Order function for sorting bonds by atom indices (ignores other properties * like bond order). * * Pre-condition: index pairs are in order (index[0] < index[1]) */ static int BondTypeInOrder(PyMOLGlobals * G, const BondType * bonds, int i1, int i2) { auto lhs = bonds[i1].index; auto rhs = bonds[i2].index; return lhs[0] < rhs[0] || (lhs[0] == rhs[0] && lhs[1] < rhs[1]); } /** * Remove duplicated bonds. Disregards if two atoms would be bonded by two * bonds of different bond order (only one will be kept). */ static void ObjectMoleculeRemoveDuplicateBonds(PyMOLGlobals * G, ObjectMolecule * I) { // make sure index pairs are in order for (int i = 0; i < I->NBond; ++i) { auto& bond = I->Bond[i]; if (bond.index[0] > bond.index[1] && !bond.symop_2) { std::swap(bond.index[0], bond.index[1]); } } // get sorted indices int * sorted = pymol::malloc(I->NBond); UtilSortIndexGlobals(G, I->NBond, I->Bond.data(), sorted, (UtilOrderFnGlobals *) BondTypeInOrder); // purge duplicated bonds and mark for removal for (int i = 0, j = -1; i < I->NBond; ++i) { int b = sorted[i]; auto ptr = I->Bond + sorted[i]; bool equal = false; if (j != -1) { const auto& other = I->Bond[j]; if (ptr->index[0] == other.index[0] && ptr->index[1] == other.index[1]) equal = true; } if (!equal) { j = b; } else { AtomInfoPurgeBond(G, ptr); // mark for removal ptr->index[0] = ptr->index[1] = 0; } } FreeP(sorted); // remove purged bonds from array int j = 0; for (int i = 0; i < I->NBond; ++i) { auto& bond = I->Bond[i]; if (bond.index[0] != bond.index[1]) { if (j != i) std::swap(I->Bond[j], bond); ++j; } } I->NBond = j; VLASize(I->Bond, BondType, I->NBond); } /** * Convert a discrete object to non-discrete. Will merge atoms from states * by identifiers. Atom level properties like color, b, q, etc. are lost * on the merged atoms. */ static bool ObjectMoleculeSetNotDiscrete(PyMOLGlobals * G, ObjectMolecule * I) { if (!I->DiscreteFlag) return true; VLAFreeP(I->DiscreteAtmToIdx); VLAFreeP(I->DiscreteCSet); I->DiscreteFlag = false; for (int atm = 0; atm < I->NAtom; ++atm) { I->AtomInfo[atm].discrete_state = 0; } int * outdex; int * aindex = AtomInfoGetSortedIndex(G, I, I->AtomInfo, I->NAtom, &outdex); // merge duplicate atoms for (int i = 0, j = -1; i < I->NAtom; ++i) { int atm = aindex[i]; auto ai = I->AtomInfo + atm; if (j == -1 || !AtomInfoMatch(G, ai, I->AtomInfo + j, false, false)) { j = atm; } else { ai->deleteFlag = true; } // remapping of merged indices outdex[atm] = j; } // point coordsets to merged atoms for (int state = 0; state < I->NCSet; ++state) { auto cs = I->CSet[state]; if (!cs) continue; for (int idx = 0; idx < cs->NIndex; ++idx) { int atm = cs->IdxToAtm[idx]; cs->IdxToAtm[idx] = outdex[atm]; } } // point bonds to merged atoms for (int i = 0; i < I->NBond; ++i) { auto& bond = I->Bond[i]; bond.index[0] = outdex[bond.index[0]]; bond.index[1] = outdex[bond.index[1]]; } AtomInfoFreeSortedIndexes(G, &aindex, &outdex); ObjectMoleculeRemoveDuplicateBonds(G, I); I->updateAtmToIdx(); ObjectMoleculePurge(I); return true; } /** * Make a non-discrete object discrete, or vice versa. */ int ObjectMoleculeSetDiscrete(PyMOLGlobals * G, ObjectMolecule * I, int discrete) { int state, idx, ao, an, ao1, ao2, an1, an2; int maxnatom, natom = I->NAtom, nbond = I->NBond; int *aostate2an = nullptr; char *bondseen = nullptr; BondType *bond; CoordSet *cs; if (!discrete) { if (!I->DiscreteFlag) return true; return ObjectMoleculeSetNotDiscrete(G, I); } if (I->DiscreteFlag) return true; // upper bound for number of discrete atoms maxnatom = I->NAtom * I->NCSet; // mapping (for bonds): atom_old -> atom_new ok_assert(1, aostate2an = pymol::malloc(I->NAtom)); ok_assert(1, bondseen = pymol::calloc(I->NBond)); // discrete setup I->DiscreteFlag = discrete; ok_assert(1, I->DiscreteAtmToIdx = pymol::vla(maxnatom)); ok_assert(1, I->DiscreteCSet = pymol::vla(maxnatom)); // for all coordinate sets for (state = 0; state < I->NCSet; state++) { cs = I->CSet[state]; if (!cs) continue; // init the atom_old -> atom_new array for (ao = 0; ao < I->NAtom; ao++) aostate2an[ao] = -1; // for all atoms in coordinate set for (idx = 0; idx < cs->NIndex; idx++) { ao = an = cs->IdxToAtm[idx]; if (I->DiscreteCSet[ao]) { // seen before, have to copy an = natom++; VLACheck(I->AtomInfo, AtomInfoType, an); ok_assert(1, I->AtomInfo); AtomInfoCopy(G, I->AtomInfo + ao, I->AtomInfo + an); cs->IdxToAtm[idx] = an; } I->AtomInfo[an].discrete_state = state + 1; // 1-based :-( I->DiscreteCSet[an] = cs; I->DiscreteAtmToIdx[an] = idx; // mapping (for bonds): (atom_old, state) -> atom_new aostate2an[ao] = an; } // unused in discrete coordsets cs->AtmToIdx.clear(); // for all old bonds for (idx = 0; idx < I->NBond; idx++) { bond = I->Bond + idx; // old atom indices ao1 = bond->index[0]; ao2 = bond->index[1]; // new atom indices an1 = aostate2an[ao1]; an2 = aostate2an[ao2]; // do both atoms exist in this coord set? if (an1 == -1 || an2 == -1) continue; if (bondseen[idx]) { // seen before, have to copy VLACheck(I->Bond, BondType, nbond); ok_assert(1, I->Bond); bond = I->Bond + (nbond++); AtomInfoBondCopy(G, I->Bond + idx, bond); } else { bondseen[idx] = true; } bond->index[0] = an1; bond->index[1] = an2; } } // not needed anymore mfree(aostate2an); mfree(bondseen); // update N* count fields I->NAtom = natom; I->NBond = nbond; // trim VLAs memory to actual size if (I->NBond) VLASize(I->Bond, BondType, I->NBond); if (I->NAtom) VLASize(I->AtomInfo, AtomInfoType, I->NAtom); I->setNDiscrete(I->NAtom); I->invalidate(cRepAll, cRepInvAll, -1); return true; ok_except1: PRINTFB(G, FB_ObjectMolecule, FB_Errors) " %s: memory allocation failed\n", __func__ ENDFB(G); return false; } int ObjectMoleculeCheckFullStateSelection(ObjectMolecule * I, int sele, int state) { PyMOLGlobals *G = I->G; int result = false; if((state >= 0) && (state < I->NCSet)) { const AtomInfoType *ai = I->AtomInfo.data(); CoordSet *cs = I->CSet[state]; if(cs) { int a; int at; result = true; for(a = 0; a < cs->NIndex; a++) { at = cs->IdxToAtm[a]; if(!SelectorIsMember(G, ai[at].selEntry, sele)) { result = false; break; } } } } return result; } /* PARAMS * ch -- ptr to empty str of length 'len' * len -- str len * RETURNS * ch, with the caption in place * NOTES * User owns the buffer so must clean up after it */ char *ObjectMolecule::getCaption(char * ch, int len) const { auto I = this; int objState; int n = 0; int show_state = 0; int show_as_fraction = 0; const char *frozen_str = ""; int state = ObjectGetCurrentState(I, false); int counter_mode = SettingGet_i(I->G, I->Setting.get(), nullptr, cSetting_state_counter_mode); int frozen = SettingGetIfDefined_i(I->G, I->Setting.get(), cSetting_state, &objState); /* if frozen print (blue) STATE / NSTATES * if not frozen, print STATE/NSTATES * if beyond NSTATES, print * /NSTATES. */ if(frozen) { /* frozen color */ frozen_str = "\\789"; } else { if (I->DiscreteFlag) { /* discrete states */ frozen_str = "\\993"; } else { /* normal case */ frozen_str = ""; } } switch(counter_mode) { case 0: /* off */ show_state = show_as_fraction = 0; break; case 2: /* just state */ show_state = 1; show_as_fraction = 0; break; case -1: /* fraction, full-on */ case 1: default: show_state = show_as_fraction = 1; break; } /* bail on null string or no room */ if (!ch || len==0) return nullptr; ch[0] = 0; /* if the state is valid, setup the label */ if(state >= 0) { if (state < I->NCSet) { const CoordSet *cs = I->CSet[state]; if(cs) { if(show_state) { if (show_as_fraction) { if (strlen(cs->Name)) { /* NAME */ n = snprintf(ch, len, "%s %s%d/%d", cs->Name, frozen_str, state+1, I->NCSet); } else { /* no name */ n = snprintf(ch, len, "%s%d/%d", frozen_str, state+1, I->NCSet); } } else { /* not fraction */ if (strlen(cs->Name)) { n = snprintf(ch, len, "%s %s%d", cs->Name, frozen_str, state+1); } else { /* no name */ n = snprintf(ch, len, "%s%d", frozen_str, state+1); } } } else { /* no state */ n = snprintf(ch, len, "%s", cs->Name); } } else { /* no coord set, can be an N-state object missing a CSet */ } } else { /* state > NCSet, out of range due to other object or global setting */ if(show_state) { if(show_as_fraction) { n = snprintf(ch, len, "%s--/%d", frozen_str, I->NCSet); } else { /* no fraction */ n = snprintf(ch, len, "%s--", frozen_str); } } } } else if (state == -1) { // all states n = snprintf(ch, len, "%s*/%d", frozen_str, I->NCSet); } else { /* blank out the title if outside the valid # of states */ } if (n > len) return nullptr; return ch; } #define MAX_BOND_DIST 50 /* find sets of atoms with identical skeletons */ struct match_info { AtomInfoType *ai_a; AtomInfoType *ai_b; BondType *bi_a; BondType *bi_b; const int *nbr_a, *nbr_b; int *matched; // values: -1 .. 2 using mark_type = signed char; std::vector atom_mark_a; std::vector atom_mark_b; std::vector bond_mark_a; std::vector bond_mark_b; }; #define recmat3(x,y,z) \ (recursive_match(u_a[0],u_b[x],x_a[0],x_b[x],mi) && \ recursive_match(u_a[1],u_b[y],x_a[1],x_b[y],mi) && \ recursive_match(u_a[2],u_b[z],x_a[2],x_b[z],mi)) #define recmat4(w,x,y,z) \ (recursive_match(u_a[0],u_b[w],x_a[0],x_b[w],mi) && \ recursive_match(u_a[1],u_b[x],x_a[1],x_b[x],mi) && \ recursive_match(u_a[2],u_b[y],x_a[2],x_b[y],mi) && \ recursive_match(u_a[3],u_b[z],x_a[3],x_b[z],mi)) static void undo_match(int *start, match_info * mi) { /* remove the matched atom set */ int *match = mi->matched; while(match > start) { int a = match[-4]; int b = match[-3]; int aa = match[-2]; int bb = match[-1]; mi->atom_mark_a[a] = false; mi->atom_mark_b[b] = false; mi->bond_mark_a[aa] = false; mi->bond_mark_b[bb] = false; match -= 4; } mi->matched = start; } static int recursive_match(int a, int b, int aa, int bb, match_info * mi) { if (mi->atom_mark_a[a] && mi->atom_mark_b[b]) { if (aa >= 0 && bb >= 0 && !(mi->bond_mark_a[aa] || mi->bond_mark_b[bb])) { /* note bond */ mi->atom_mark_a[a] = true; mi->atom_mark_b[b] = true; mi->bond_mark_a[aa] = true; mi->bond_mark_b[bb] = true; mi->matched[0] = a; /* atoms */ mi->matched[1] = b; mi->matched[2] = aa; /* bonds */ mi->matched[3] = bb; mi->matched += 4; } return true; } else if (mi->atom_mark_a[a] != mi->atom_mark_b[b]) return false; else if(mi->ai_a[a].protons != mi->ai_b[b].protons) return false; else { int ni_a = mi->nbr_a[a]; int ni_b = mi->nbr_b[b]; int num_a = mi->nbr_a[ni_a++]; int num_b = mi->nbr_b[ni_b++]; if((num_a != num_b) || (num_a > 4)) /* must have the same number of neighbors (four or less) */ return false; else { int match_flag = false; int u_a[4], u_b[4], x_a[4], x_b[4]; int n_u_a = 0; int n_u_b = 0; int *before = mi->matched; mi->atom_mark_a[a] = true; mi->atom_mark_b[b] = true; mi->bond_mark_a[aa] = true; mi->bond_mark_b[bb] = true; mi->matched[0] = a; /* atoms */ mi->matched[1] = b; mi->matched[2] = aa; /* bonds */ mi->matched[3] = bb; mi->matched += 4; /* collect unvisited bonds */ while(num_a--) { int i_a = mi->nbr_a[ni_a + 1]; if(!mi->bond_mark_a[i_a]) { /* bond not yet visited */ u_a[n_u_a] = mi->nbr_a[ni_a]; /* atom index */ x_a[n_u_a++] = i_a; } ni_a += 2; } while(num_b--) { int i_b = mi->nbr_b[ni_b + 1]; if(!mi->bond_mark_b[i_b]) { u_b[n_u_b] = mi->nbr_b[ni_b]; x_b[n_u_b++] = i_b; } ni_b += 2; } /* NOTE: implicitly relying upon C short-circuit evaluation */ if(n_u_a == n_u_b) { if(!n_u_a) match_flag = true; else if(n_u_a == 1) { match_flag = recursive_match(u_a[0], u_b[0], x_a[0], x_b[0], mi); } else if(n_u_a == 2) { match_flag = (recursive_match(u_a[0], u_b[0], x_a[0], x_b[0], mi) && recursive_match(u_a[1], u_b[1], x_a[1], x_b[1], mi)) || (recursive_match(u_a[0], u_b[1], x_a[0], x_b[1], mi) && recursive_match(u_a[1], u_b[0], x_a[1], x_b[0], mi)); } else if(n_u_a == 3) { match_flag = recmat3(0, 1, 2) || recmat3(0, 2, 1) || recmat3(1, 0, 2) || recmat3(1, 2, 0) || recmat3(2, 0, 1) || recmat3(2, 1, 0); } else if(n_u_a == 4) { match_flag = recmat4(0, 1, 2, 3) || recmat4(0, 2, 1, 3) || recmat4(1, 0, 2, 3) || recmat4(1, 2, 0, 3) || recmat4(2, 0, 1, 3) || recmat4(2, 1, 0, 3); if(!match_flag) { match_flag = recmat4(0, 1, 3, 2) || recmat4(0, 2, 3, 1) || recmat4(1, 0, 3, 2) || recmat4(1, 2, 3, 0) || recmat4(2, 0, 3, 1) || recmat4(2, 1, 3, 0); if(!match_flag) { match_flag = recmat4(0, 3, 1, 2) || recmat4(0, 3, 2, 1) || recmat4(1, 3, 0, 2) || recmat4(1, 3, 2, 0) || recmat4(2, 3, 0, 1) || recmat4(2, 3, 1, 0); if(!match_flag) { match_flag = recmat4(3, 0, 1, 2) || recmat4(3, 0, 2, 1) || recmat4(3, 1, 0, 2) || recmat4(3, 1, 2, 0) || recmat4(3, 2, 0, 1) || recmat4(3, 2, 1, 0); } } } } } if(!match_flag) { /* clean the match tree */ undo_match(before, mi); } return match_flag; } } } int ObjectMoleculeXferValences(ObjectMolecule * Ia, int sele1, int sele2, int target_state, ObjectMolecule * Ib, int sele3, int source_state, int quiet) { int *matched = nullptr; int match_found = false; PyMOLGlobals *G = Ia->G; if(Ia == Ib) return false; { int max_match = Ia->NAtom + Ia->NBond; if(max_match < (Ib->NAtom + Ib->NBond)) max_match = (Ib->NAtom + Ib->NBond); matched = pymol::calloc(max_match * 4); } { int a, b; AtomInfoType *ai_a = Ia->AtomInfo.data(); AtomInfoType *ai_b = Ib->AtomInfo.data(); BondType *bi_a = Ia->Bond.data(); BondType *bi_b = Ib->Bond.data(); match_info mi; mi.atom_mark_a.resize(Ia->NAtom); mi.atom_mark_b.resize(Ib->NAtom); mi.bond_mark_a.resize(Ia->NBond); mi.bond_mark_b.resize(Ib->NBond); // confirm zero-initialization assert(std::none_of(mi.atom_mark_a.begin(), mi.atom_mark_a.end(), [](bool m) { return m; })); mi.ai_a = ai_a; mi.ai_b = ai_b; mi.bi_a = bi_a; mi.bi_b = bi_b; mi.nbr_a = Ia->getNeighborArray(); mi.nbr_b = Ib->getNeighborArray(); mi.matched = matched; for(a = 0; a < Ia->NAtom; a++) { if(!mi.atom_mark_a[a]) { int a_entry = ai_a[a].selEntry; if(SelectorIsMember(G, a_entry, sele1) || SelectorIsMember(G, a_entry, sele2)) { for(b = 0; b < Ib->NAtom; b++) { if(SelectorIsMember(G, ai_b[b].selEntry, sele3)) { if(recursive_match(a, b, -1, -1, &mi)) { int *match = mi.matched; match_found = true; /* now graft bond orders for bonds in matching atoms */ while(match > matched) { int at_a = match[-4]; int at_b = match[-3]; int bd_a = match[-2]; int bd_b = match[-1]; if((bd_a >= 0) && (bd_a >= 0)) { int a1 = bi_a[bd_a].index[0]; int a2 = bi_a[bd_a].index[1]; int a1_entry = ai_a[a1].selEntry; int a2_entry = ai_a[a2].selEntry; if((SelectorIsMember(G, a1_entry, sele1) && SelectorIsMember(G, a2_entry, sele2)) || (SelectorIsMember(G, a2_entry, sele1) && SelectorIsMember(G, a1_entry, sele2))) { /* only update bonds which actually sit between the two selections */ bi_a[bd_a].order = bi_b[bd_b].order; ai_a[at_a].chemFlag = false; } } mi.atom_mark_b[at_b] = false; /* release source for future matching */ if(bd_b >= 0) mi.bond_mark_b[bd_b] = false; match -= 4; } break; } } } } } } } FreeP(matched); return match_found; } void ObjectMoleculeTransformState44f(ObjectMolecule * I, int state, const float *matrix, int log_trans, int homogenous, int transformed) { int a; float tmp_matrix[16]; CoordSet *cs; int use_matrices = SettingGet_i(I->G, I->Setting.get(), nullptr, cSetting_matrix_mode); if(use_matrices<0) use_matrices = 0; if(!use_matrices) { ObjectMoleculeTransformSelection(I, state, -1, matrix, log_trans, I->Name, homogenous, true); } else { double dbl_matrix[16]; if(state == -2) state = ObjectGetCurrentState(I, false); /* ensure homogenous matrix to preserve programmer sanity */ if(!homogenous) { convertTTTfR44d(matrix, dbl_matrix); copy44d44f(dbl_matrix, tmp_matrix); matrix = tmp_matrix; } else { copy44f44d(matrix, dbl_matrix); } if(state < 0) { /* all states */ for(a = 0; a < I->NCSet; a++) { cs = I->CSet[a]; if(cs) ObjectStateLeftCombineMatrixR44d(cs, dbl_matrix); } } else if(state < I->NCSet) { /* single state */ cs = I->CSet[state]; if(cs) ObjectStateLeftCombineMatrixR44d(cs, dbl_matrix); } else if(I->NCSet == 1) { /* static singleton state */ cs = I->CSet[0]; if(cs && SettingGet_b(I->G, I->Setting.get(), nullptr, cSetting_static_singletons)) { ObjectStateLeftCombineMatrixR44d(cs, dbl_matrix); } } } } /*========================================================================*/ static int ObjectMoleculeFixSeleHydrogens(ObjectMolecule * I, int sele, int state) { int a; int seleFlag = false; const AtomInfoType *ai0; int ok = true; ai0 = I->AtomInfo; for(a = 0; a < I->NAtom; a++) { if(SelectorIsMember(I->G, ai0->selEntry, sele)) { seleFlag = true; break; } ai0++; } if(seleFlag) { seleFlag = false; if(!ObjectMoleculeVerifyChemistry(I, state)) { ErrMessage(I->G, " AddHydrogens", "missing chemical geometry information."); } else { ai0 = I->AtomInfo; for(a = 0; a < I->NAtom; a++) { if(!ai0->isHydrogen()) { /* only do heavies */ if(SelectorIsMember(I->G, ai0->selEntry, sele)) { for(StateIterator iter(I->G, I->Setting.get(), state, I->NCSet); iter.next();) { auto cs = I->CSet[iter.state]; if (!cs) continue; seleFlag |= ObjectMoleculeSetMissingNeighborCoords(I, cs, a, true); } } } ai0++; } } if(seleFlag) I->invalidate(cRepAll, cRepInvAll, -1); } return ok; } static const char *skip_fortran(int num, int per_line, const char *p) { int a, b; b = 0; for(a = 0; a < num; a++) { if((++b) == per_line) { b = 0; p = nextline(p); } } if(b || (!num)) p = nextline(p); return (p); } void ObjectMoleculeOpRecInit(ObjectMoleculeOpRec * op) { UtilZeroMem((char *) op, sizeof(ObjectMoleculeOpRec)); } bool ObjectMoleculeGetNeighborVector( ObjectMolecule* I, int atom, int state, float* v_result) { float v_atom[3] = {0.0, 0.0, 0.0}; auto const* cs = I->getCoordSet(state); if (cs == nullptr) { return false; } if (!CoordSetGetAtomVertex(cs, atom, v_atom)) { return false; } for (auto const& neighbor : AtomNeighbors(I, atom)) { auto const a2 = neighbor.atm; // ignore hydrogens if (I->AtomInfo[a2].protons != cAN_H && CoordSetGetAtomVertex(cs, a2, v_result)) { return true; } } return false; } /** * Returns the most proton-rich element with the lowest priority value (OG1 * before OG2, CG, HB1) */ int ObjectMoleculeGetTopNeighbor(PyMOLGlobals * G, ObjectMolecule * I, int start, int excluded) { int highest_at = -1, highest_prot = 0, lowest_pri = 9999; for (auto const& neighbor : AtomNeighbors(I, start)) { auto const at = neighbor.atm; auto const *ai = I->AtomInfo.data() + at; if((highest_at < 0) && (at != excluded)) { highest_prot = ai->protons; lowest_pri = ai->priority; highest_at = at; } else if(((ai->protons > highest_prot) || ((ai->protons == highest_prot) && (ai->priority < lowest_pri))) && (at != excluded)) { highest_prot = ai->protons; highest_at = at; lowest_pri = ai->priority; } } return highest_at; } /** * Selection mapping for `load_traj` to only load coordinates for a subset of * atoms. Returns a mapping of old to new coordinate indices and modifies the * index-related members of `cs`. * * @param[in] obj Object Molecule for evaluation of the selection * @param[in,out] cs Candidate coordinate set (with valid index arrays) * @param[in] selection Named selection * @return index mapping, or nullptr if `selection` is not valid */ std::unique_ptr LoadTrajSeleHelper( const ObjectMolecule* obj, CoordSet* cs, const char* selection) { auto G = obj->G; int sele0 = SelectorIndexByName(G, selection); // We can skip "all" (0) here since it's a trivial case if (sele0 <= 0) { return nullptr; } auto xref = std::unique_ptr(new int[cs->NIndex]); int idx_new = 0; for (int idx = 0; idx < cs->NIndex; ++idx) { auto atm = cs->IdxToAtm[idx]; if (SelectorIsMember(G, obj->AtomInfo[atm].selEntry, sele0)) { cs->IdxToAtm[idx_new] = atm; cs->AtmToIdx[atm] = idx_new; xref[idx] = idx_new; ++idx_new; } else { cs->AtmToIdx[atm] = -1; xref[idx] = -1; } } cs->NIndex = idx_new; cs->IdxToAtm.resize(cs->NIndex); cs->Coord.resize(cs->NIndex * 3); return xref; } /*========================================================================*/ ObjectMolecule *ObjectMoleculeLoadTRJFile(PyMOLGlobals * G, ObjectMolecule * I, const char *fname, int frame, int interval, int average, int start, int stop, int max, const char *sele, int image, const float *shift, int quiet) { FILE *f; char *buffer; const char *p; char cc[MAXLINELEN]; int n_read; int to_go; int skip_first_line = true; int periodic = false; int angles = true; float f0, f1, f2, *fp; float box[3], angle[3]; float r_cent[3], r_trans[3]; int r_act, r_val, r_cnt; float *r_fp_start = nullptr, *r_fp_stop = nullptr; int a, b, c, i; int zoom_flag = false; int cnt = 0; int n_avg = 0; int icnt; int ncnt = 0; float zerovector[3] = { 0.0, 0.0, 0.0 }; CoordSet *cs = nullptr; if (I->DiscreteFlag) { PRINTFB(G, FB_ObjectMolecule, FB_Errors) " %s: Discrete objects not supported\n", __func__ ENDFB(G); return I; } if(!shift) shift = zerovector; if(interval < 1) interval = 1; icnt = interval; #define BUFSIZE 4194304 #define GETTING_LOW 10000 f = pymol_fopen(fname, "rb"); if(!f) { ErrMessage(G, __func__, "Unable to open file!"); } else { if(I->CSTmpl) { cs = CoordSetCopy(I->CSTmpl); } else if (I->NCSet > 0) { cs = CoordSetCopy(I->CSet[0]); } else { PRINTFB(G, FB_ObjectMolecule, FB_Errors) " ObjMolLoadTRJFile: Missing topology" ENDFB(G); return (I); } auto xref = LoadTrajSeleHelper(I, cs, sele); PRINTFB(G, FB_ObjectMolecule, FB_Blather) " ObjMolLoadTRJFile: Loading from \"%s\".\n", fname ENDFB(G); buffer = pymol::malloc(BUFSIZE + 1); /* 1 MB read buffer */ p = buffer; buffer[0] = 0; n_read = 0; to_go = 0; a = 0; b = 0; c = 0; f1 = 0.0; f2 = 0.0; while(1) { to_go = n_read - (p - buffer); if(to_go < GETTING_LOW) if(!feof(f)) { if(to_go) memcpy(buffer, p, to_go); n_read = fread(buffer + to_go, 1, BUFSIZE - to_go, f); n_read = to_go + n_read; buffer[n_read] = 0; p = buffer; if(skip_first_line) { p = nextline(p); skip_first_line = false; } to_go = n_read - (p - buffer); } if(!to_go) break; p = ncopy(cc, p, 8); if((++b) == 10) { b = 0; p = nextline(p); } f0 = f1; f1 = f2; if(sscanf(cc, "%f", &f2) == 1) { if((++c) == 3) { c = 0; if((cnt + 1) >= start) { if(icnt <= 1) { if(xref) { if(xref[a] >= 0) fp = cs->coordPtr(xref[a]); else fp = nullptr; } else { fp = cs->coordPtr(a); } if(fp) { if(n_avg) { *(fp++) += f0; *(fp++) += f1; *(fp++) += f2; } else { *(fp++) = f0; *(fp++) = f1; *(fp++) = f2; } } } } if((++a) == I->NAtom) { cnt++; a = 0; if(b) p = nextline(p); b = 0; if(cs->PeriodicBoxType != CoordSet::NoPeriodicity) { /* read periodic box */ c = 0; periodic = true; angles = true; p = ncopy(cc, p, 8); if(sscanf(cc, "%f", &box[0]) != 1) periodic = false; p = ncopy(cc, p, 8); if(sscanf(cc, "%f", &box[1]) != 1) periodic = false; p = ncopy(cc, p, 8); if(sscanf(cc, "%f", &box[2]) != 1) periodic = false; p = ncopy(cc, p, 8); if(sscanf(cc, "%f", &angle[0]) != 1) angles = false; p = ncopy(cc, p, 8); if(sscanf(cc, "%f", &angle[1]) != 1) angles = false; p = ncopy(cc, p, 8); if(sscanf(cc, "%f", &angle[2]) != 1) angles = false; if(periodic) { cs->Symmetry = std::make_unique(G); cs->Symmetry->Crystal.setDims(box); if(angles) { cs->Symmetry->Crystal.setAngles(angle); } p = nextline(p); b = 0; } if(cs->PeriodicBoxType == CoordSet::Octahedral) periodic = false; /* can't handle this yet... */ } if((stop > 0) && (cnt >= stop)) break; if(cnt >= start) { icnt--; if(icnt > 0) { PRINTFB(G, FB_ObjectMolecule, FB_Details) " ObjectMolecule: skipping set %d...\n", cnt ENDFB(G); } else { icnt = interval; n_avg++; } if(icnt == interval) { if(n_avg < average) { PRINTFB(G, FB_ObjectMolecule, FB_Details) " ObjectMolecule: averaging set %d...\n", cnt ENDFB(G); } else { /* compute average */ if(n_avg > 1) { fp = cs->Coord.data(); for(i = 0; i < cs->NIndex; i++) { *(fp++) /= n_avg; *(fp++) /= n_avg; *(fp++) /= n_avg; } } if(periodic && image) { /* Perform residue-based period image transformation */ i = 0; r_cnt = 0; r_act = 0; /* 0 unspec, 1=load, 2=image, 3=leave */ r_val = -1; while(r_act != 3) { if(i >= cs->NIndex) { if(r_cnt) r_act = 2; else r_act = 3; } if(r_act == 0) { /* start new residue */ r_cnt = 0; r_act = 1; /* now load */ } if(r_act == 1) { if(i < cs->NIndex) { /* is there a coordinate for atom? */ if(xref) { if(xref[i] >= 0) fp = cs->coordPtr(xref[i]); else fp = nullptr; } else { fp = cs->coordPtr(i); } if(fp) { /* yes there is... */ if(r_cnt) { if(r_val != I->AtomInfo[cs->IdxToAtm[i]].resv) { r_act = 2; /* end of residue-> time to image */ } else { r_cnt++; r_cent[0] += *(fp++); r_cent[1] += *(fp++); r_cent[2] += *(fp++); r_fp_stop = fp; /* stop here */ i++; } } else { r_val = I->AtomInfo[cs->IdxToAtm[i]].resv; r_cnt++; r_fp_start = fp; /* start here */ r_cent[0] = *(fp++); r_cent[1] = *(fp++); r_cent[2] = *(fp++); r_fp_stop = fp; /* stop here */ i++; } } else { i++; } } else { r_act = 2; /* image */ } } if(r_act == 2) { /* time to image */ if(r_cnt) { r_cent[0] /= r_cnt; r_cent[1] /= r_cnt; r_cent[2] /= r_cnt; const auto& periodicbox = cs->getSymmetry()->Crystal; transform33f3f(periodicbox.realToFrac(), r_cent, r_cent); r_trans[0] = fmodf(1000.0F + shift[0] + r_cent[0], 1.0F); r_trans[1] = fmodf(1000.0F + shift[1] + r_cent[1], 1.0F); r_trans[2] = fmodf(1000.0F + shift[2] + r_cent[2], 1.0F); r_trans[0] -= r_cent[0]; r_trans[1] -= r_cent[1]; r_trans[2] -= r_cent[2]; transform33f3f(periodicbox.fracToReal(), r_trans, r_trans); fp = r_fp_start; while(fp < r_fp_stop) { *(fp++) += r_trans[0]; *(fp++) += r_trans[1]; *(fp++) += r_trans[2]; } } r_act = 0; /* reset */ r_cnt = 0; } } } /* add new coord set */ cs->invalidateRep(cRepAll, cRepInvRep); if(frame < 0) frame = I->NCSet; if(!I->NCSet) { zoom_flag = true; } VLACheck(I->CSet, CoordSet *, frame); if(I->NCSet <= frame) I->NCSet = frame + 1; delete I->CSet[frame]; I->CSet[frame] = cs; ncnt++; if(average < 2) { PRINTFB(G, FB_ObjectMolecule, FB_Details) " ObjectMolecule: read set %d into state %d...\n", cnt, frame + 1 ENDFB(G); } else { PRINTFB(G, FB_ObjectMolecule, FB_Details) " ObjectMolecule: averaging set %d...\n", cnt ENDFB(G); PRINTFB(G, FB_ObjectMolecule, FB_Details) " ObjectMolecule: average loaded into state %d...\n", frame + 1 ENDFB(G); } frame++; cs = CoordSetCopy(cs); n_avg = 0; if((stop > 0) && (cnt >= stop)) break; if((max > 0) && (ncnt >= max)) break; } } } else { PRINTFB(G, FB_ObjectMolecule, FB_Details) " ObjectMolecule: skipping set %d...\n", cnt ENDFB(G); } } } } else { PRINTFB(G, FB_ObjectMolecule, FB_Errors) " ObjMolLoadTRJFile-Error: Failed to read expected coordinate value.\n This traj. does not match the loaded parameter/topology file.\n Likely cause: either the atom count or the periodic box settings\n are inconsistent between the two files.\n" ENDFB(G); break; } } mfree(buffer); } delete cs; SceneChanged(G); SceneCountFrames(G); if(zoom_flag) if(SettingGetGlobal_i(G, cSetting_auto_zoom)) { ExecutiveWindowZoom(G, I->Name, 0.0, -1, 0, 0, quiet); /* auto zoom (all states) */ } return (I); } ObjectMolecule *ObjectMoleculeLoadRSTFile(PyMOLGlobals * G, ObjectMolecule * I, const char *fname, int frame, int quiet, char mode) { /* * mode = 0: AMBER coordinate/restart file (one frame only) * mode = 1: AMBER trajectory * mode = 2: AMBER trajectory with box * http://ambermd.org/formats.html */ int ok = true; char *buffer, *p; char cc[MAXLINELEN]; float f0, f1, f2, *fp; int a, b, c; int zoom_flag = false; CoordSet *cs = nullptr; char ncolumn = 6; // number of coordinates per line char nbyte = 12; // width of one coordinate if(mode > 0) { ncolumn = 10; nbyte = 8; } #define BUFSIZE 4194304 #define GETTING_LOW 10000 else { if(I->CSTmpl) { cs = CoordSetCopy(I->CSTmpl); } else if (I->NCSet > 0) { cs = CoordSetCopy(I->CSet[0]); } else { PRINTFB(G, FB_ObjectMolecule, FB_Errors) " ObjMolLoadRSTFile: Missing topology" ENDFB(G); return (I); } CHECKOK(ok, cs); if (ok){ PRINTFB(G, FB_ObjectMolecule, FB_Blather) " ObjMolLoadRSTFile: Loading from \"%s\".\n", fname ENDFB(G); p = buffer = FileGetContents(fname, nullptr); if(!buffer) ok = ErrMessage(G, __func__, "Unable to open file!"); } if (ok){ p = nextline(p); if (mode == 0) // skip NATOM,TIME p = nextline(p); } a = 0; b = 0; c = 0; f1 = 0.0; f2 = 0.0; while(ok && *p) { p = ncopy(cc, p, nbyte); if((++b) == ncolumn) { b = 0; p = nextline(p); } f0 = f1; f1 = f2; if(sscanf(cc, "%f", &f2) == 1) { if((++c) == 3) { c = 0; fp = cs->coordPtr(a); *(fp++) = f0; *(fp++) = f1; *(fp++) = f2; if((++a) == I->NAtom) { a = 0; if(b) p = nextline(p); if(mode == 2) // skip box p = nextline(p); b = 0; /* add new coord set */ cs->invalidateRep(cRepAll, cRepInvRep); if(frame < 0) frame = I->NCSet; if(!I->NCSet) { zoom_flag = true; } VLACheck(I->CSet, CoordSet *, frame); CHECKOK(ok, I->CSet); if (ok){ if(I->NCSet <= frame) I->NCSet = frame + 1; delete I->CSet[frame]; I->CSet[frame] = cs; } PRINTFB(G, FB_ObjectMolecule, FB_Details) " ObjectMolecule: read coordinates into state %d...\n", frame + 1 ENDFB(G); if (ok) cs = CoordSetCopy(cs); CHECKOK(ok, cs); if (mode == 0) // restart file has only one frame break; frame += 1; } } } else { PRINTFB(G, FB_ObjectMolecule, FB_Errors) " ObjMolLoadRSTFile: atom/coordinate mismatch.\n" ENDFB(G); break; } } mfree(buffer); } delete cs; SceneChanged(G); SceneCountFrames(G); if(zoom_flag){ if(SettingGetGlobal_i(G, cSetting_auto_zoom)) { ExecutiveWindowZoom(G, I->Name, 0.0, -1, 0, 0, quiet); /* auto zoom (all states) */ } } return (I); } static const char *findflag(PyMOLGlobals * G, const char *p, const char *flag, const char *format) { char cc[MAXLINELEN]; char pat[MAXLINELEN] = "%FLAG "; int l; PRINTFD(G, FB_ObjectMolecule) " findflag: flag %s format %s\n", flag, format ENDFD; strcat(pat, flag); l = strlen(pat); while(*p) { p = ncopy(cc, p, l); if(WordMatch(G, cc, pat, true) < 0) { p = nextline(p); break; } p = nextline(p); if(!*p) { PRINTFB(G, FB_ObjectMolecule, FB_Errors) " ObjectMolecule-Error: Unrecognized file format (can't find \"%s\").\n", pat ENDFB(G); } } strcpy(pat, "%FORMAT("); strcat(pat, format); strcat(pat, ")"); l = strlen(pat); while(*p) { p = ncopy(cc, p, l); if(WordMatch(G, cc, pat, true) < 0) { p = nextline(p); break; } p = nextline(p); if(!*p) { PRINTFB(G, FB_ObjectMolecule, FB_Errors) " ObjectMolecule-Error: Unrecognized file format (can't find \"%s\").\n", pat ENDFB(G); } } return (p); } /*========================================================================*/ static CoordSet *ObjectMoleculeTOPStr2CoordSet(PyMOLGlobals * G, const char *buffer, AtomInfoType ** atInfoPtr) { const char *p; int nAtom; int a, b, c, bi, last_i, at_i, aa, rc; float *coord = nullptr; float *f; CoordSet *cset = nullptr; AtomInfoType *atInfo = nullptr, *ai; BondType *bond = nullptr, *bd; int nBond = 0; int auto_show = RepGetAutoShowMask(G); int amber7 = false; WordType title; ResName *resn; char cc[MAXLINELEN]; int ok = true; int i0, i1, i2; /* trajectory parameters */ int NTYPES, NBONH, MBONA, NTHETH, MTHETA; int NPHIH, MPHIA, NHPARM, NPARM, NNB, NRES; int NBONA, NTHETA, NPHIA, NUMBND, NUMANG, NPTRA; int NATYP, NPHB, IFPERT, NBPER, NGPER, NDPER; int MBPER, MGPER, MDPER, IFBOX = 0, NMXRS, IFCAP; int NEXTRA, IPOL = 0; int wid, col; float BETA; float BOX1, BOX2, BOX3; cset = CoordSetNew(G); p = buffer; nAtom = 0; if(atInfoPtr) atInfo = *atInfoPtr; if(!atInfo) ErrFatal(G, "TOPStr2CoordSet", "need atom information record!"); /* failsafe for old version.. */ ncopy(cc, p, 8); if(strcmp(cc, "%VERSION") == 0) { amber7 = true; PRINTFB(G, FB_ObjectMolecule, FB_Details) " ObjectMolecule: Attempting to read Amber7 topology file.\n" ENDFB(G); } else { PRINTFB(G, FB_ObjectMolecule, FB_Details) " ObjectMolecule: Assuming this is an Amber6 topology file.\n" ENDFB(G); } /* read title */ if(amber7) { p = findflag(G, buffer, "TITLE", "20a4"); } p = ncopy(cc, p, 20); title[0] = 0; sscanf(cc, "%s", title); p = nextline(p); if(amber7) { p = findflag(G, buffer, "POINTERS", "10I8"); p = ncopy(cc, p, 8); ok = ok && (sscanf(cc, "%d", &nAtom) == 1); p = ncopy(cc, p, 8); ok = ok && (sscanf(cc, "%d", &NTYPES) == 1); p = ncopy(cc, p, 8); ok = ok && (sscanf(cc, "%d", &NBONH) == 1); p = ncopy(cc, p, 8); ok = ok && (sscanf(cc, "%d", &MBONA) == 1); p = ncopy(cc, p, 8); ok = ok && (sscanf(cc, "%d", &NTHETH) == 1); p = ncopy(cc, p, 8); ok = ok && (sscanf(cc, "%d", &MTHETA) == 1); p = ncopy(cc, p, 8); ok = ok && (sscanf(cc, "%d", &NPHIH) == 1); p = ncopy(cc, p, 8); ok = ok && (sscanf(cc, "%d", &MPHIA) == 1); p = ncopy(cc, p, 8); ok = ok && (sscanf(cc, "%d", &NHPARM) == 1); p = ncopy(cc, p, 8); ok = ok && (sscanf(cc, "%d", &NPARM) == 1); p = nextline(p); p = ncopy(cc, p, 8); ok = ok && (sscanf(cc, "%d", &NNB) == 1); p = ncopy(cc, p, 8); ok = ok && (sscanf(cc, "%d", &NRES) == 1); p = ncopy(cc, p, 8); ok = ok && (sscanf(cc, "%d", &NBONA) == 1); p = ncopy(cc, p, 8); ok = ok && (sscanf(cc, "%d", &NTHETA) == 1); p = ncopy(cc, p, 8); ok = ok && (sscanf(cc, "%d", &NPHIA) == 1); p = ncopy(cc, p, 8); ok = ok && (sscanf(cc, "%d", &NUMBND) == 1); p = ncopy(cc, p, 8); ok = ok && (sscanf(cc, "%d", &NUMANG) == 1); p = ncopy(cc, p, 8); ok = ok && (sscanf(cc, "%d", &NPTRA) == 1); p = ncopy(cc, p, 8); ok = ok && (sscanf(cc, "%d", &NATYP) == 1); p = ncopy(cc, p, 8); ok = ok && (sscanf(cc, "%d", &NPHB) == 1); p = nextline(p); p = ncopy(cc, p, 8); ok = ok && (sscanf(cc, "%d", &IFPERT) == 1); p = ncopy(cc, p, 8); ok = ok && (sscanf(cc, "%d", &NBPER) == 1); p = ncopy(cc, p, 8); ok = ok && (sscanf(cc, "%d", &NGPER) == 1); p = ncopy(cc, p, 8); ok = ok && (sscanf(cc, "%d", &NDPER) == 1); p = ncopy(cc, p, 8); ok = ok && (sscanf(cc, "%d", &MBPER) == 1); p = ncopy(cc, p, 8); ok = ok && (sscanf(cc, "%d", &MGPER) == 1); p = ncopy(cc, p, 8); ok = ok && (sscanf(cc, "%d", &MDPER) == 1); p = ncopy(cc, p, 8); ok = ok && (sscanf(cc, "%d", &IFBOX) == 1); p = ncopy(cc, p, 8); ok = ok && (sscanf(cc, "%d", &NMXRS) == 1); p = ncopy(cc, p, 8); ok = ok && (sscanf(cc, "%d", &IFCAP) == 1); p = nextline(p); p = ncopy(cc, p, 8); ok = ok && (sscanf(cc, "%d", &NEXTRA) == 1); } else { p = ncopy(cc, p, 6); ok = ok && (sscanf(cc, "%d", &nAtom) == 1); p = ncopy(cc, p, 6); ok = ok && (sscanf(cc, "%d", &NTYPES) == 1); p = ncopy(cc, p, 6); ok = ok && (sscanf(cc, "%d", &NBONH) == 1); p = ncopy(cc, p, 6); ok = ok && (sscanf(cc, "%d", &MBONA) == 1); p = ncopy(cc, p, 6); ok = ok && (sscanf(cc, "%d", &NTHETH) == 1); p = ncopy(cc, p, 6); ok = ok && (sscanf(cc, "%d", &MTHETA) == 1); p = ncopy(cc, p, 6); ok = ok && (sscanf(cc, "%d", &NPHIH) == 1); p = ncopy(cc, p, 6); ok = ok && (sscanf(cc, "%d", &MPHIA) == 1); p = ncopy(cc, p, 6); ok = ok && (sscanf(cc, "%d", &NHPARM) == 1); p = ncopy(cc, p, 6); ok = ok && (sscanf(cc, "%d", &NPARM) == 1); p = ncopy(cc, p, 6); ok = ok && (sscanf(cc, "%d", &NNB) == 1); p = ncopy(cc, p, 6); ok = ok && (sscanf(cc, "%d", &NRES) == 1); p = nextline(p); p = ncopy(cc, p, 6); ok = ok && (sscanf(cc, "%d", &NBONA) == 1); p = ncopy(cc, p, 6); ok = ok && (sscanf(cc, "%d", &NTHETA) == 1); p = ncopy(cc, p, 6); ok = ok && (sscanf(cc, "%d", &NPHIA) == 1); p = ncopy(cc, p, 6); ok = ok && (sscanf(cc, "%d", &NUMBND) == 1); p = ncopy(cc, p, 6); ok = ok && (sscanf(cc, "%d", &NUMANG) == 1); p = ncopy(cc, p, 6); ok = ok && (sscanf(cc, "%d", &NPTRA) == 1); p = ncopy(cc, p, 6); ok = ok && (sscanf(cc, "%d", &NATYP) == 1); p = ncopy(cc, p, 6); ok = ok && (sscanf(cc, "%d", &NPHB) == 1); p = ncopy(cc, p, 6); ok = ok && (sscanf(cc, "%d", &IFPERT) == 1); p = ncopy(cc, p, 6); ok = ok && (sscanf(cc, "%d", &NBPER) == 1); p = ncopy(cc, p, 6); ok = ok && (sscanf(cc, "%d", &NGPER) == 1); p = ncopy(cc, p, 6); ok = ok && (sscanf(cc, "%d", &NDPER) == 1); p = nextline(p); p = ncopy(cc, p, 6); ok = ok && (sscanf(cc, "%d", &MBPER) == 1); p = ncopy(cc, p, 6); ok = ok && (sscanf(cc, "%d", &MGPER) == 1); p = ncopy(cc, p, 6); ok = ok && (sscanf(cc, "%d", &MDPER) == 1); p = ncopy(cc, p, 6); ok = ok && (sscanf(cc, "%d", &IFBOX) == 1); p = ncopy(cc, p, 6); ok = ok && (sscanf(cc, "%d", &NMXRS) == 1); p = ncopy(cc, p, 6); ok = ok && (sscanf(cc, "%d", &IFCAP) == 1); p = ncopy(cc, p, 6); if(sscanf(cc, "%d", &NEXTRA) != 1) NEXTRA = 0; } if(!ok) { ErrMessage(G, "TOPStrToCoordSet", "Error reading counts lines"); ok_raise(1); } else { PRINTFB(G, FB_ObjectMolecule, FB_Blather) " TOPStr2CoordSet: read counts line nAtom %d NBONA %d NBONH %d\n", nAtom, NBONA, NBONH ENDFB(G); } switch (IFBOX) { case 2: cset->PeriodicBoxType = CoordSet::Octahedral; PRINTFB(G, FB_ObjectMolecule, FB_Details) " TOPStrToCoordSet: Warning: can't currently image a truncated octahedron...\n" ENDFB(G); break; case 1: cset->PeriodicBoxType = CoordSet::Orthogonal; break; case 0: default: cset->PeriodicBoxType = CoordSet::NoPeriodicity; break; } p = nextline(p); if(ok) { VLACheck(atInfo, AtomInfoType, nAtom); if(amber7) { p = findflag(G, buffer, "ATOM_NAME", "20a4"); } /* read atoms */ b = 0; for(a = 0; a < nAtom; a++) { p = ntrim(cc, p, 4); atInfo[a].name = LexIdx(G, cc); if((++b) == 20) { b = 0; p = nextline(p); } } if(b) p = nextline(p); if(!ok) { ErrMessage(G, "TOPStrToCoordSet", "Error reading atom names"); } else { PRINTFB(G, FB_ObjectMolecule, FB_Blather) " TOPStr2CoordSet: read atom names.\n" ENDFB(G); } /* read charges */ if(amber7) { p = findflag(G, buffer, "CHARGE", "5E16.8"); } b = 0; for(a = 0; a < nAtom; a++) { p = ncopy(cc, p, 16); ai = atInfo + a; if(!sscanf(cc, "%f", &ai->partialCharge)) ok = false; else { ai->partialCharge /= 18.2223F; /* convert to electron charge */ } if((++b) == 5) { b = 0; p = nextline(p); } } if(!ok) { ErrMessage(G, "TOPStrToCoordSet", "Error reading charges"); } else { PRINTFB(G, FB_ObjectMolecule, FB_Blather) " TOPStr2CoordSet: read charges.\n" ENDFB(G); } if(b) p = nextline(p); if(!amber7) { /* skip masses */ p = skip_fortran(nAtom, 5, p); } /* read LJ atom types */ if(amber7) { p = findflag(G, buffer, "ATOM_TYPE_INDEX", "10I8"); col = 10; wid = 8; } else { col = 12; wid = 6; } b = 0; for(a = 0; a < nAtom; a++) { p = ncopy(cc, p, wid); ai = atInfo + a; if(!sscanf(cc, "%d", &ai->customType)) ok = false; if((++b) == col) { b = 0; p = nextline(p); } } if(b) p = nextline(p); if(!ok) { ErrMessage(G, "TOPStrToCoordSet", "Error LJ atom types"); } else { PRINTFB(G, FB_ObjectMolecule, FB_Blather) " TOPStr2CoordSet: read LJ atom types.\n" ENDFB(G); } if(!amber7) { /* skip excluded atom counts */ p = skip_fortran(nAtom, 12, p); /* skip NB param arrays */ p = skip_fortran(NTYPES * NTYPES, 12, p); } /* read residue labels */ if(amber7) { p = findflag(G, buffer, "RESIDUE_LABEL", "20a4"); } resn = pymol::malloc(NRES); b = 0; for(a = 0; a < NRES; a++) { p = ncopy(cc, p, 4); if(!sscanf(cc, "%s", resn[a])) resn[a][0] = 0; if((++b) == 20) { b = 0; p = nextline(p); } } if(b) p = nextline(p); if(!ok) { ErrMessage(G, "TOPStrToCoordSet", "Error reading residue labels"); } else { PRINTFB(G, FB_ObjectMolecule, FB_Blather) " TOPStr2CoordSet: read residue labels.\n" ENDFB(G); } /* read residue assignments */ if(amber7) { p = findflag(G, buffer, "RESIDUE_POINTER", "10I8"); col = 10; wid = 8; } else { col = 12; wid = 6; } b = 0; last_i = 0; rc = 0; for(a = 0; a < NRES; a++) { p = ncopy(cc, p, wid); if(sscanf(cc, "%d", &at_i)) { if(last_i) for(aa = (last_i - 1); aa < (at_i - 1); aa++) { ai = atInfo + aa; ai->resn = LexIdx(G, resn[a - 1]); ai->resv = rc; } rc++; last_i = at_i; } if((++b) == col) { b = 0; p = nextline(p); } } if(b) p = nextline(p); if(last_i) for(aa = (last_i - 1); aa < nAtom; aa++) { ai = atInfo + aa; ai->resn = LexIdx(G, resn[NRES - 1]); ai->resv = rc; } rc++; if(!ok) { ErrMessage(G, "TOPStrToCoordSet", "Error reading residues"); } else { PRINTFB(G, FB_ObjectMolecule, FB_Blather) " TOPStr2CoordSet: read residues.\n" ENDFB(G); } FreeP(resn); if(!amber7) { /* skip bond force constants */ p = skip_fortran(NUMBND, 5, p); /* skip bond lengths */ p = skip_fortran(NUMBND, 5, p); /* skip angle force constant */ p = skip_fortran(NUMANG, 5, p); /* skip angle eq */ p = skip_fortran(NUMANG, 5, p); /* skip dihedral force constant */ p = skip_fortran(NPTRA, 5, p); /* skip dihedral periodicity */ p = skip_fortran(NPTRA, 5, p); /* skip dihedral phases */ p = skip_fortran(NPTRA, 5, p); /* skip SOLTYs */ p = skip_fortran(NATYP, 5, p); /* skip LJ terms r12 */ p = skip_fortran((NTYPES * (NTYPES + 1)) / 2, 5, p); /* skip LJ terms r6 */ p = skip_fortran((NTYPES * (NTYPES + 1)) / 2, 5, p); } /* read bonds */ if(amber7) { p = findflag(G, buffer, "BONDS_INC_HYDROGEN", "10I8"); col = 10; wid = 8; } else { col = 12; wid = 6; } nBond = NBONH + NBONA; bond = VLACalloc(BondType, nBond); bi = 0; b = 0; c = 0; i0 = 0; i1 = 0; for(a = 0; a < 3 * NBONH; a++) { p = ncopy(cc, p, wid); i2 = i1; i1 = i0; if(!sscanf(cc, "%d", &i0)) ok = false; if((++c) == 3) { c = 0; bd = bond + bi; bd->index[0] = (abs(i2) / 3); bd->index[1] = (abs(i1) / 3); bd->order = 1; bi++; } if((++b) == col) { b = 0; p = nextline(p); } } if(b) p = nextline(p); if(!ok) { ErrMessage(G, "TOPStrToCoordSet", "Error hydrogen containing bonds"); } else { PRINTFB(G, FB_ObjectMolecule, FB_Blather) " TOPStr2CoordSet: read %d hydrogen containing bonds.\n", NBONH ENDFB(G); } if(amber7) { p = findflag(G, buffer, "BONDS_WITHOUT_HYDROGEN", "10I8"); col = 10; wid = 8; } else { col = 12; wid = 6; } b = 0; c = 0; for(a = 0; a < 3 * NBONA; a++) { p = ncopy(cc, p, wid); i2 = i1; i1 = i0; if(!sscanf(cc, "%d", &i0)) ok = false; if((++c) == 3) { c = 0; bd = bond + bi; bd->index[0] = (abs(i2) / 3); bd->index[1] = (abs(i1) / 3); bd->order = 1; // PYMOL-2707 bi++; } if((++b) == col) { b = 0; p = nextline(p); } } if(b) p = nextline(p); if(!ok) { ErrMessage(G, "TOPStrToCoordSet", "Error hydrogen free bonds"); } else { PRINTFB(G, FB_ObjectMolecule, FB_Blather) " TOPStr2CoordSet: read %d hydrogen free bonds.\n", NBONA ENDFB(G); } if(!amber7) { /* skip hydrogen angles */ p = skip_fortran(4 * NTHETH, 12, p); /* skip non-hydrogen angles */ p = skip_fortran(4 * NTHETA, 12, p); /* skip hydrogen dihedrals */ p = skip_fortran(5 * NPHIH, 12, p); /* skip non hydrogen dihedrals */ p = skip_fortran(5 * NPHIA, 12, p); /* skip nonbonded exclusions */ p = skip_fortran(NNB, 12, p); /* skip hydrogen bonds ASOL */ p = skip_fortran(NPHB, 5, p); /* skip hydrogen bonds BSOL */ p = skip_fortran(NPHB, 5, p); /* skip HBCUT */ p = skip_fortran(NPHB, 5, p); } /* read AMBER atom types */ if(amber7) { p = findflag(G, buffer, "AMBER_ATOM_TYPE", "20a4"); } b = 0; for(a = 0; a < nAtom; a++) { OrthoLineType temp; p = ncopy(cc, p, 4); ai = atInfo + a; if(sscanf(cc, "%s", temp) != 1) ok = false; else { ai->textType = LexIdx(G, temp); } if((++b) == 20) { b = 0; p = nextline(p); } } if(b) p = nextline(p); if(!ok) { ErrMessage(G, "TOPStrToCoordSet", "Error reading atom types"); } else { PRINTFB(G, FB_ObjectMolecule, FB_Blather) " TOPStr2CoordSet: read atom types.\n" ENDFB(G); } if(!amber7) { /* skip TREE classification */ p = skip_fortran(nAtom, 20, p); /* skip tree joining information */ p = skip_fortran(nAtom, 12, p); /* skip last atom rotated blah blah blah */ p = skip_fortran(nAtom, 12, p); } if(IFBOX > 0) { int IPTRES, NSPM = 0, NSPSOL; if(amber7) { p = findflag(G, buffer, "SOLVENT_POINTERS", "3I8"); wid = 8; } else { wid = 6; } p = ncopy(cc, p, wid); ok = ok && (sscanf(cc, "%d", &IPTRES) == 1); p = ncopy(cc, p, wid); ok = ok && (sscanf(cc, "%d", &NSPM) == 1); p = ncopy(cc, p, wid); ok = ok && (sscanf(cc, "%d", &NSPSOL) == 1); ok_assert(1, ok); p = nextline(p); if(amber7) { p = findflag(G, buffer, "ATOMS_PER_MOLECULE", "10I8"); col = 10; } else { col = 12; } /* skip num atoms per box */ p = skip_fortran(NSPM, col, p); if(amber7) { p = findflag(G, buffer, "BOX_DIMENSIONS", "5E16.8"); } wid = 16; p = ncopy(cc, p, 16); ok = ok && (sscanf(cc, "%f", &BETA) == 1); p = ncopy(cc, p, 16); ok = ok && (sscanf(cc, "%f", &BOX1) == 1); p = ncopy(cc, p, 16); ok = ok && (sscanf(cc, "%f", &BOX2) == 1); p = ncopy(cc, p, 16); ok = ok && (sscanf(cc, "%f", &BOX3) == 1); if(ok) { float angle[3] = {90.f, BETA, 90.f}; if((BETA > 109.47) && (BETA < 109.48)) { cset->PeriodicBoxType = CoordSet::Octahedral; angle[0] = 109.47122; angle[1] = 109.47122; angle[2] = 109.47122; } else if(BETA == 60.0) { angle[0] = 60.0; /* rhombic dodecahedron (from ptraj.c) */ angle[1] = 90.0; angle[2] = 60.0; } cset->Symmetry = std::make_unique(G); cset->Symmetry->Crystal.setDims(BOX1, BOX2, BOX3); cset->Symmetry->Crystal.setAngles(angle); } /* skip periodic box */ p = nextline(p); } if(!amber7) { if(IFCAP > 0) { p = nextline(p); p = nextline(p); p = nextline(p); } if(IFPERT > 0) { /* skip perturbed bond atoms */ p = skip_fortran(2 * NBPER, 12, p); /* skip perturbed bond atom pointers */ p = skip_fortran(2 * NBPER, 12, p); /* skip perturbed angles */ p = skip_fortran(3 * NGPER, 12, p); /* skip perturbed angle pointers */ p = skip_fortran(2 * NGPER, 12, p); /* skip perturbed dihedrals */ p = skip_fortran(4 * NDPER, 12, p); /* skip perturbed dihedral pointers */ p = skip_fortran(2 * NDPER, 12, p); /* skip residue names */ p = skip_fortran(NRES, 20, p); /* skip atom names */ p = skip_fortran(nAtom, 20, p); /* skip atom symbols */ p = skip_fortran(nAtom, 20, p); /* skip unused field */ p = skip_fortran(nAtom, 5, p); /* skip perturbed flags */ p = skip_fortran(nAtom, 12, p); /* skip LJ atom flags */ p = skip_fortran(nAtom, 12, p); /* skip perturbed charges */ p = skip_fortran(nAtom, 5, p); } if(IPOL > 0) { /* skip atomic polarizabilities */ p = skip_fortran(nAtom, 5, p); } if((IPOL > 0) && (IFPERT > 0)) { /* skip atomic polarizabilities */ p = skip_fortran(nAtom, 5, p); } } /* for future reference %FLAG LES_NTYP %FORMAT(10I8) %FLAG LES_TYPE %FORMAT(10I8) %FLAG LES_FAC %FORMAT(5E16.8) %FLAG LES_CNUM %FORMAT(10I8) %FLAG LES_ID %FORMAT(10I8) Here is the additional information for LES topology formats: First, if NPARM ==1, LES entries are in topology (NPARM is the 10th entry in the initial list of control parameters); otherwise the standard format applies. So, with NPARM=1, you just need to read a few more things at the very end of topology file: LES_NTYP (format: I6) ... one number, number of LES types and four arrays: LES_TYPE (12I6) ... NATOM integer entries LES_FAC (E16.8) ... LES_NTYPxLES_NTYP float entries LES_CNUM (12I6) ... NATOM integer entries LES_ID (12I6) ... NATOM integer entries and that's it. Your parser must have skipped this information because it was at the end of the file. Maybe that's good enough. */ coord = VLAlloc(float, 3 * nAtom); f = coord; for(a = 0; a < nAtom; a++) { *(f++) = 0.0; *(f++) = 0.0; *(f++) = 0.0; ai = atInfo + a; ai->id = a + 1; /* assign 1-based identifiers */ ai->rank = a; AtomInfoAssignParameters(G, ai); AtomInfoAssignColors(G, ai); ai->visRep = auto_show; } } if(ok) { cset->NIndex = nAtom; cset->Coord = pymol::vla_take_ownership(coord); cset->TmpBond = pymol::vla_take_ownership(bond); cset->NTmpBond = nBond; } else { ok_except1: delete cset; cset = nullptr; ErrMessage(G, __func__, "failed"); } if(atInfoPtr) *atInfoPtr = atInfo; return (cset); } /*========================================================================*/ static ObjectMolecule *ObjectMoleculeReadTOPStr(PyMOLGlobals * G, ObjectMolecule * I, char *TOPStr, int frame, int discrete) { CoordSet *cset = nullptr; pymol::vla atInfo(1); int ok = true; int isNew = true; unsigned int nAtom = 0; if(!I) isNew = true; else isNew = false; if(ok) { if(isNew) { I = (ObjectMolecule *) new ObjectMolecule(G, discrete); CHECKOK(ok, I); if (ok) std::swap(atInfo, I->AtomInfo); } if(ok && isNew) { I->Color = AtomInfoUpdateAutoColor(G); } if (ok) cset = ObjectMoleculeTOPStr2CoordSet(G, TOPStr, &atInfo); CHECKOK(ok, cset); } /* include coordinate set */ if(ok) { nAtom = cset->NIndex; if(I->DiscreteFlag && atInfo) { unsigned int a; int fp1 = frame + 1; AtomInfoType *ai = atInfo.data(); for(a = 0; a < nAtom; a++) { (ai++)->discrete_state = fp1; } } cset->Obj = I; cset->enumIndices(); cset->invalidateRep(cRepAll, cRepInvRep); if(isNew) { std::swap(I->AtomInfo, atInfo); } else if (ok){ ok &= ObjectMoleculeMerge(I, std::move(atInfo), cset, false, cAIC_AllMask, true); } if(isNew) I->NAtom = nAtom; /* if(frame<0) frame=I->NCSet; VLACheck(I->CSet,CoordSet*,frame); if(I->NCSet<=frame) I->NCSet=frame+1; if(I->CSet[frame]) I->CSet[frame]->fFree(I->CSet[frame]); I->CSet[frame] = cset; */ if(ok && isNew) ok = ObjectMoleculeConnect(I, cset, false); if(cset->Symmetry && (!I->Symmetry)) { I->Symmetry.reset(new CSymmetry(*cset->Symmetry)); CHECKOK(ok, I->Symmetry); } delete I->CSTmpl; I->CSTmpl = cset; /* save template coordinate set */ SceneCountFrames(G); if (ok) ok &= ObjectMoleculeExtendIndices(I, -1); if (ok) ok &= ObjectMoleculeSort(I); if (ok){ ObjectMoleculeUpdateIDNumbers(I); ObjectMoleculeUpdateNonbonded(I); } } if (!ok){ DeleteP(I) } return (I); } ObjectMolecule *ObjectMoleculeLoadTOPFile(PyMOLGlobals * G, ObjectMolecule * obj, const char *fname, int frame, int discrete) { ObjectMolecule *I = nullptr; char *buffer; buffer = FileGetContents(fname, nullptr); if(!buffer) ErrMessage(G, __func__, "Unable to open file!"); else { PRINTFB(G, FB_ObjectMolecule, FB_Blather) " %s: Loading from %s.\n", __func__, fname ENDFB(G); I = ObjectMoleculeReadTOPStr(G, obj, buffer, frame, discrete); mfree(buffer); } return (I); } void ObjectMoleculeSculptClear(ObjectMolecule * I) { PRINTFD(I->G, FB_ObjectMolecule) " %s: entered.\n", __func__ ENDFD; if(I->Sculpt) DeleteP(I->Sculpt); } void ObjectMoleculeSculptImprint(ObjectMolecule * I, int state, int match_state, int match_by_segment) { PRINTFD(I->G, FB_ObjectMolecule) " %s: entered.\n", __func__ ENDFD; if(!I->Sculpt) I->Sculpt = new CSculpt(I->G); SculptMeasureObject(I->Sculpt, I, state, match_state, match_by_segment); } float ObjectMoleculeSculptIterate(ObjectMolecule * I, int state, int n_cycle, float *center) { PRINTFD(I->G, FB_ObjectMolecule) " %s: entered.\n", __func__ ENDFD; if(I->Sculpt) { return SculptIterateObject(I->Sculpt, I, state, n_cycle, center); } else return 0.0F; } /** * - Assigns new id to all atoms with AtomInfoType.id == -1 * - Assigns ObjectMolecule.AtomCounter if -1 * * Cost: O(NAtom + NBond) */ void ObjectMoleculeUpdateIDNumbers(ObjectMolecule * I) { int a; int max; AtomInfoType *ai; if(I->AtomCounter < 0) { max = -1; ai = I->AtomInfo.data(); for(a = 0; a < I->NAtom; a++) { if(ai->id > max) max = ai->id; ai++; } I->AtomCounter = max + 1; } ai = I->AtomInfo.data(); for(a = 0; a < I->NAtom; a++) { if(ai->id < 0) ai->id = I->AtomCounter++; ai++; } } /*========================================================================*/ int ObjectMoleculeGetPhiPsi(ObjectMolecule * I, int ca, float *phi, float *psi, int state) { int np = -1; int cm = -1; int c = -1; int n = -1; int result = false; const AtomInfoType *ai; float v_ca[3]; float v_n[3]; float v_c[3]; float v_cm[3]; float v_np[3]; auto G = I->G; ai = I->AtomInfo; if(ai[ca].name == G->lex_const.CA) { /* find C */ for (auto const& neighbor : AtomNeighbors(I, ca)) { if (ai[neighbor.atm].name == G->lex_const.C) { c = neighbor.atm; break; } } /* find N */ for (auto const& neighbor : AtomNeighbors(I, ca)) { if (ai[neighbor.atm].name == G->lex_const.N) { n = neighbor.atm; break; } } /* find NP */ if(c >= 0) { for (auto const& neighbor : AtomNeighbors(I, c)) { if (ai[neighbor.atm].name == G->lex_const.N) { np = neighbor.atm; break; } } } /* find CM */ if(n >= 0) { for (auto const& neighbor : AtomNeighbors(I, n)) { if (ai[neighbor.atm].name == G->lex_const.C) { cm = neighbor.atm; break; } } } if((ca >= 0) && (np >= 0) && (c >= 0) && (n >= 0) && (cm >= 0)) { if(ObjectMoleculeGetAtomVertex(I, state, ca, v_ca) && ObjectMoleculeGetAtomVertex(I, state, n, v_n) && ObjectMoleculeGetAtomVertex(I, state, c, v_c) && ObjectMoleculeGetAtomVertex(I, state, cm, v_cm) && ObjectMoleculeGetAtomVertex(I, state, np, v_np)) { (*phi) = rad_to_deg(get_dihedral3f(v_c, v_ca, v_n, v_cm)); (*psi) = rad_to_deg(get_dihedral3f(v_np, v_c, v_ca, v_n)); result = true; } } } return (result); } /*========================================================================*/ int ObjectMoleculeCheckBondSep(ObjectMolecule * I, int a0, int a1, int dist) { int result = false; int n0; int stack[MAX_BOND_DIST + 1]; int history[MAX_BOND_DIST + 1]; int depth = 0; int distinct; int a; if(dist > MAX_BOND_DIST) return false; auto* const Neighbor = I->getNeighborArray(); depth = 1; history[depth] = a0; stack[depth] = Neighbor[a0] + 1; /* go to first neighbor */ while(depth) { /* keep going until we've traversed tree */ while(Neighbor[stack[depth]] >= 0) { /* end of branches? go back up one bond */ n0 = Neighbor[stack[depth]]; /* get current neighbor index */ stack[depth] += 2; /* set up next neighbor */ distinct = true; /* check to see if current candidate is distinct from ancestors */ for(a = 1; a < depth; a++) { if(history[a] == n0) distinct = false; } if(distinct) { if(depth < dist) { /* are not yet at the proper distance? */ if(distinct) { depth++; stack[depth] = Neighbor[n0] + 1; /* then keep moving outward */ history[depth] = n0; } } else if(n0 == a1) /* otherwise, see if we have a match */ result = true; } } depth--; } return result; } /*========================================================================*/ void ObjectGotoState(pymol::CObject* I, int state) { auto nstates = I->getNFrame(); if (nstates > 1 || !SettingGet(I->G, cSetting_static_singletons)) { if(state > nstates) state = nstates - 1; if(state < 0) state = nstates - 1; SceneSetFrame(I->G, 0, state); } } /*========================================================================*/ CObjectState* ObjectMolecule::_getObjectState(int state) { return CSet[state]; } /*========================================================================*/ pymol::copyable_ptr* ObjectMolecule::getSettingHandle(int state) { auto I = this; if (state < -1) { state = I->getCurrentState(); } if(state < 0) { return (&I->Setting); } else if(state < I->NCSet) { if(I->CSet[state]) { return (&I->CSet[state]->Setting); } else { return (nullptr); } } else { return (nullptr); } } /*========================================================================*/ CSymmetry const* ObjectMolecule::getSymmetry(int state) const { auto cs = getCoordSet(state); if (cs && cs->Symmetry) { return cs->Symmetry.get(); } return Symmetry.get(); } /** * @return False if state does not exist */ bool ObjectMolecule::setSymmetry(CSymmetry const& symmetry, int state) { bool const all_states = (state == cSelectorUpdateTableAllStates); bool success = false; if (all_states) { Symmetry.reset(new CSymmetry(symmetry)); success = true; } for (StateIterator iter(G, Setting.get(), state, NCSet); iter.next();) { auto cs = CSet[iter.state]; if (cs) { cs->Symmetry.reset(all_states ? nullptr : new CSymmetry(symmetry)); cs->invalidateRep(cRepCell, cRepInvRep); success = true; } } return success; } /*========================================================================*/ pymol::Result<> ObjectMoleculeSetStateTitle(ObjectMolecule * I, int state, const char *text) { auto cs = I->getCoordSet(state); if (!cs) { return pymol::make_error("Invalid state ", state + 1); } cs->setTitle(text); return {}; } /*========================================================================*/ const char *ObjectMoleculeGetStateTitle(const ObjectMolecule * I, int state) { auto cs = I->getCoordSet(state); if (!cs) { PRINTFB(I->G, FB_ObjectMolecule, FB_Errors) "Error: invalid state %d\n", state + 1 ENDFB(I->G); return nullptr; } return cs->Name; } /*========================================================================*/ void ObjectMoleculeRenderSele(ObjectMolecule* I, int curState, int sele, bool vis_only, CGO& selIndicatorsCGO) { PyMOLGlobals *G = I->G; CoordSet *cs; int a, nIndex; const float *coord, *v; int flag = true; int all_vis = !vis_only; int visRep; float tmp_matrix[16], v_tmp[3], *matrix = nullptr; int use_matrices = SettingGet_i(I->G, I->Setting.get(), nullptr, cSetting_matrix_mode); if(use_matrices<0) use_matrices = 0; if (SettingGetIfDefined_i(G, I->Setting.get(), cSetting_all_states, &a)) { curState = a ? -1 : SettingGet_i(G, I->Setting.get(), nullptr, cSetting_state); } else if (SettingGetIfDefined_i(G, I->Setting.get(), cSetting_state, &a)) { curState = a - 1; } if(G->HaveGUI && G->ValidContext) { const AtomInfoType *atInfo = I->AtomInfo.data(); for(StateIterator iter(G, I->Setting.get(), curState, I->NCSet); iter.next();) { if((cs = I->CSet[iter.state])) { const auto* idx2atm = cs->IdxToAtm.data(); nIndex = cs->NIndex; coord = cs->Coord; if(use_matrices && !cs->Matrix.empty()) { copy44d44f(cs->Matrix.data(), tmp_matrix); matrix = tmp_matrix; } else matrix = nullptr; if(I->TTTFlag) { if(!matrix) { convertTTTfR44f(I->TTT, tmp_matrix); } else { float ttt[16]; convertTTTfR44f(I->TTT, ttt); left_multiply44f44f(ttt, tmp_matrix); } matrix = tmp_matrix; } for(a = 0; a < nIndex; a++) { if(SelectorIsMember(G, atInfo[*(idx2atm++)].selEntry, sele)) { if(all_vis) flag = true; else { visRep = atInfo[idx2atm[-1]].visRep; flag = false; if(visRep & (cRepCylBit | cRepSphereBit | cRepSurfaceBit | cRepLabelBit | cRepNonbondedSphereBit | cRepCartoonBit | cRepRibbonBit | cRepLineBit | cRepMeshBit | cRepDotBit | cRepNonbondedBit)){ flag = true; } } if(flag) { v = coord + a + a + a; if(matrix) { transform44f3f(matrix, v, v_tmp); CGOVertexv(&selIndicatorsCGO, v_tmp); } else { CGOVertexv(&selIndicatorsCGO, v); } } } } } } } } /*========================================================================*/ static CoordSet *ObjectMoleculeXYZStr2CoordSet(PyMOLGlobals * G, const char *buffer, AtomInfoType ** atInfoPtr, const char **restart) { const char *p, *p_store; int nAtom; int a, c; float *coord = nullptr; CoordSet *cset = nullptr; AtomInfoType *atInfo = nullptr, *ai; char cc[MAXLINELEN]; int atomCount; BondType *bond = nullptr; int nBond = 0; int b1, b2; WordType tmp_name; int auto_show = RepGetAutoShowMask(G); int tinker_xyz = true; int valid_atom; int have_n_atom = false; BondType *ii; p = buffer; nAtom = 0; atInfo = *atInfoPtr; p_store = p; p = ncopy(cc, p, MAXLINELEN - 1); if(sscanf(cc, "%d", &nAtom) != 1) { nAtom = 0; tinker_xyz = false; p = p_store; } else { have_n_atom = true; p = nskip(p, 2); p = ncopy(tmp_name, p, sizeof(WordType) - 1); p = nextline(p); } if(tinker_xyz && nAtom) { /* test Tinker XYZ formatting assumption */ const char *pp = p; int dummy_int; float dummy_float; AtomName dummy_name; pp = ncopy(cc, pp, 6); if(!sscanf(cc, "%d", &dummy_int)) tinker_xyz = false; /* id */ pp = nskip(pp, 2); pp = ncopy(cc, pp, 3); if(sscanf(cc, "%s", dummy_name) != 1) tinker_xyz = false; /* name */ pp = ncopy(cc, pp, 12); if(sscanf(cc, "%f", &dummy_float) != 1) tinker_xyz = false; /* x */ pp = ncopy(cc, pp, 12); if(sscanf(cc, "%f", &dummy_float) != 1) tinker_xyz = false; /* y */ pp = ncopy(cc, pp, 12); if(sscanf(cc, "%f", &dummy_float) != 1) tinker_xyz = false; /* z */ pp = ncopy(cc, pp, 6); if(sscanf(cc, "%d", &dummy_int) != 1) tinker_xyz = false; /* numeric type */ } if(!tinker_xyz) { const char *pp = p; int have_atom_line = true; float dummy_float; AtomName dummy_name; pp = ParseWordCopy(cc, pp, sizeof(AtomName) - 1); if(sscanf(cc, "%s", dummy_name) != 1) have_atom_line = false; /* name */ pp = ParseWordCopy(cc, pp, MAXLINELEN - 1); if(sscanf(cc, "%f", &dummy_float) != 1) have_atom_line = false; /* x */ pp = ParseWordCopy(cc, pp, MAXLINELEN - 1); if(sscanf(cc, "%f", &dummy_float) != 1) have_atom_line = false; /* y */ pp = ParseWordCopy(cc, pp, MAXLINELEN - 1); if(sscanf(cc, "%f", &dummy_float) != 1) have_atom_line = false; /* z */ if(!have_atom_line) { /* copy the comment line into the title field */ p = ncopy(tmp_name, p, sizeof(WordType) - 1); p = nextline(p); } } if(nAtom) { coord = VLAlloc(float, 3 * nAtom); if(atInfo) VLACheck(atInfo, AtomInfoType, nAtom); } else { coord = VLAlloc(float, 3); } if(tinker_xyz) { nBond = 0; bond = VLACalloc(BondType, 6 * nAtom); /* is this a safe assumption? */ ii = bond; } PRINTFB(G, FB_ObjectMolecule, FB_Blather) " ObjectMoleculeReadXYZ: Found %i atoms...\n", nAtom ENDFB(G); a = 0; atomCount = 0; while(*p) { VLACheck(atInfo, AtomInfoType, atomCount); ai = atInfo + atomCount; if(!tinker_xyz) { valid_atom = true; p = ParseWordCopy(cc, p, MAXLINELEN - 1); UtilCleanStr(cc); if(!cc[0]) valid_atom = false; if(valid_atom) { strncpy(ai->elem, cc, cElemNameLen); ai->name = LexIdx(G, cc); ai->rank = atomCount; ai->id = atomCount + 1; VLACheck(coord, float, a * 3 + 2); p = ParseWordCopy(cc, p, MAXLINELEN - 1); if(sscanf(cc, "%f", coord + a) != 1) valid_atom = false; p = ParseWordCopy(cc, p, MAXLINELEN - 1); if(sscanf(cc, "%f", coord + a + 1) != 1) valid_atom = false; p = ParseWordCopy(cc, p, MAXLINELEN - 1); if(sscanf(cc, "%f", coord + a + 2) != 1) valid_atom = false; ai->resn = LexIdx(G, "UNK"); ai->alt[0] = 0; ai->chain = 0; ai->resv = atomCount + 1; ai->q = 1.0; ai->b = 0.0; ai->segi = 0; ai->visRep = auto_show; /* in the absense of external tinker information, assume hetatm */ ai->hetatm = 1; AtomInfoAssignParameters(G, ai); AtomInfoAssignColors(G, ai); } } else { /* tinker XYZ */ valid_atom = true; p = ncopy(cc, p, 6); if(!sscanf(cc, "%d", &ai->id)) break; ai->rank = atomCount; p = nskip(p, 2); /* to 12 */ p = ntrim(cc, p, 3); ai->name = LexIdx(G, cc); ai->resn = LexIdx(G, "UNK"); ai->alt[0] = 0; ai->chain = 0; ai->resv = atomCount + 1; valid_atom = true; p = ncopy(cc, p, 12); sscanf(cc, "%f", coord + a); p = ncopy(cc, p, 12); sscanf(cc, "%f", coord + (a + 1)); p = ncopy(cc, p, 12); sscanf(cc, "%f", coord + (a + 2)); ai->q = 1.0; ai->b = 0.0; ai->segi = 0; ai->elem[0] = 0; /* let atom info guess/infer atom type */ ai->visRep = auto_show; p = ncopy(cc, p, 6); sscanf(cc, "%d", &ai->customType); /* in the absense of external tinker information, assume hetatm */ ai->hetatm = 1; AtomInfoAssignParameters(G, ai); AtomInfoAssignColors(G, ai); b1 = atomCount; for(c = 0; c < 6; c++) { p = ncopy(cc, p, 6); if(!cc[0]) break; if(!sscanf(cc, "%d", &b2)) break; if(b1 < (b2 - 1)) { VLACheck(bond, BondType, nBond); ii = bond + nBond; nBond++; ii->index[0] = b1; ii->index[1] = b2 - 1; ii->order = 1; /* missing bond order information */ ii++; } } } if(valid_atom) { PRINTFD(G, FB_ObjectMolecule) " ObjectMolecule-DEBUG: %s %s %d %s %8.3f %8.3f %8.3f %6.2f %6.2f %s\n", LexStr(G, ai->name), LexStr(G, ai->resn), ai->resv, LexStr(G, ai->chain), *(coord + a), *(coord + a + 1), *(coord + a + 2), ai->b, ai->q, LexStr(G, ai->segi) ENDFD; a += 3; atomCount++; } p = nextline(p); if(have_n_atom && (atomCount >= nAtom)) { int dummy; ncopy(cc, p, MAXLINELEN - 1); if(sscanf(cc, "%d", &dummy) == 1) *restart = p; break; } } PRINTFB(G, FB_ObjectMolecule, FB_Blather) " XYZStr2CoordSet: Read %d bonds.\n", nBond ENDFB(G); if(!tinker_xyz) nAtom = atomCount; /* use number of atoms actually read */ cset = CoordSetNew(G); cset->NIndex = nAtom; cset->Coord = pymol::vla_take_ownership(coord); cset->TmpBond = pymol::vla_take_ownership(bond); cset->NTmpBond = nBond; strcpy(cset->Name, tmp_name); if(atInfoPtr) *atInfoPtr = atInfo; return (cset); } /*========================================================================*/ int ObjectMoleculeAreAtomsBonded(ObjectMolecule * I, int i0, int i1) { int result = false; int a; const BondType *b; b = I->Bond; for(a = 0; a < I->NBond; a++) { if(i0 == b->index[0]) { if(i1 == b->index[1]) { result = true; break; } } if(i1 == b->index[0]) { if(i0 == b->index[1]) { result = true; break; } } b++; } return (result); } /*========================================================================*/ int ObjectMoleculeRenameAtoms(ObjectMolecule * I, int *flag, int force) { PyMOLGlobals * G = I->G; AtomInfoType *ai; int a; int result; if(force) { ai = I->AtomInfo.data(); if(!flag) { for(a = 0; a < I->NAtom; a++) { LexAssign(G, ai->name, 0); ai++; } } else { for(a = 0; a < I->NAtom; a++) { if(flag[a]) LexAssign(G, ai->name, 0); ai++; } } } result = AtomInfoUniquefyNames(I->G, nullptr, 0, I->AtomInfo.data(), flag, I->NAtom); return result; } /*========================================================================*/ /** * Transform coordinates of `cs` in place and then append it to `tcs`. * * @param coord_orig Original cs coordinates * @param at0 Anchor atom index * @param mat1_inv Source coordinate system * @param valence_vec Valence vector. If nullptr, don't move. If null-vector, don't merge. */ static bool AddCoordinateIntoCoordSet(CoordSet* tcs, CoordSet* cs, const float* coord_orig, int at0, const float* mat1_inv, const float* valence_vec) { if (!tcs) { return true; } if (valence_vec) { if (lengthsq3f(valence_vec) < 0.1) { // null-vector return true; } // anchor auto idx0 = tcs->atmToIdx(at0); assert(idx0 >= 0); const float* va0 = tcs->coordPtr(idx0); // Target coordinate system float matT[16], mat[16]; identity44f(matT); copy3f(valence_vec, matT); get_system1f3f(matT, matT + 4, matT + 8); copy3(va0, matT + 12); transpose44f44f(matT, mat); // combine with source coordinate system right_multiply44f44f(mat, mat1_inv); // transform coordset for (int idx = 0; idx < cs->NIndex; idx++) { transform44f3f(mat, &coord_orig[3 * idx], cs->coordPtr(idx)); } } return CoordSetMerge(tcs->Obj, tcs, cs); } /** * @param at0 Atom index * @param index0 If different from at0: Index of atom to replace (e.g. Hydrogen) * @param[out] out (3x1) Normalized open valence vector * @return True if valid open valence vector found */ static bool GetTargetValenceVector( const CoordSet* tcs, int at0, int index0, float* out) { if (!tcs) { return false; } if (at0 != index0) { // anchor auto ca0 = tcs->atmToIdx(at0); if (ca0 < 0) { return false; } // hydrogen (or other replaced atom) auto ch0 = tcs->atmToIdx(index0); if (ch0 < 0) { return false; } const float* vh0 = tcs->coordPtr(ch0); const float* va0 = tcs->coordPtr(ca0); subtract3f(vh0, va0, out); return true; } return CoordSetFindOpenValenceVector(tcs, at0, out); } /*========================================================================*/ /** * Merge src into I. * * Optionally creates a bond between index0 and index1, or the atoms bound to * index0 and index1 if they are "single geometry" atoms like hydrogens. * * @param index0 Atom index in I * @param index1 Atom index in src * @param create_bond If false, then don't create a bond, just combine the * objects. Implies move_flag=false. * @param move_flag If true, then transform the source coordinates to form a * reasonable bond geometry. */ pymol::Result<> ObjectMoleculeFuse(ObjectMolecule* I, int const index0, const ObjectMolecule* src, int const index1, bool create_bond, bool move_flag) { constexpr int state1 = 0; constexpr bool edit = true; auto* G = I->G; if (!create_bond) { move_flag = false; } auto const* scs = src->getCoordSet(state1); if (!scs) { return pymol::make_error("no source coordset"); } auto* ai0 = I->AtomInfo.data(); auto const* ai1 = src->AtomInfo.data(); /** * If we want a bond and atm has cAtomInfoSingle geometry, then return the * atom index of its (only) neighbor. Otherwise, return atm. */ auto const get_anchor_atm = [create_bond]( const ObjectMolecule* obj, int atm) { if (create_bond) { if (obj->AtomInfo[atm].geom == cAtomInfoSingle) { auto neighbors = AtomNeighbors(obj, atm); if (neighbors.size() == 1) { atm = neighbors[0].atm; } } } return atm; }; int const at0 = get_anchor_atm(I, index0); int const at1 = get_anchor_atm(src, index1); assert(!(at0 < 0 || at1 < 0)); auto const anch1 = scs->atmToIdx(at1); if (anch1 < 0) { return pymol::make_error("no coordinate for source anchor atom"); } /* copy atoms and atom info into a 1:1 direct mapping */ auto cs = std::make_unique(G); cs->setNIndex(scs->NIndex); cs->enumIndices(); auto nai = pymol::vla(scs->NIndex); for (int idx = 0; idx < scs->NIndex; ++idx) { copy3f(scs->coordPtr(idx), cs->coordPtr(idx)); auto atm1 = scs->IdxToAtm[idx]; AtomInfoCopy(G, ai1 + atm1, nai + idx); if (edit) { if (atm1 == at1) { nai[idx].temp1 = 2; /* clear marks */ } else { nai[idx].temp1 = 0; /* clear marks */ } } } /* copy internal bond information */ cs->TmpBond.reserve(src->NBond); cs->NTmpBond = 0; for (int b = 0; b != src->NBond; ++b) { auto const& b1 = src->Bond[b]; auto a0 = scs->atmToIdx(b1.index[0]); auto a1 = scs->atmToIdx(b1.index[1]); if (a0 >= 0 && a1 >= 0) { auto& b0 = cs->TmpBond[cs->NTmpBond++]; b0 = b1; b0.index[0] = a0; b0.index[1] = a1; } } // source coordinate system float mat1_inv[16]; if (create_bond) { const float * va1 = scs->coordPtr(anch1); if (at0 != index0) { ai0[index0].deleteFlag = true; } identity44f(mat1_inv); if (at1 != index1) { auto const hydr1 = scs->atmToIdx(index1); if (hydr1 == -1) { return pymol::make_error("no source attachment vector found"); } nai[hydr1].deleteFlag = true; const float* vh1 = scs->coordPtr(hydr1); subtract3f(va1, vh1, mat1_inv); } else { auto found = CoordSetFindOpenValenceVector(scs, at1, mat1_inv); if (!found) { return pymol::make_error("no source attachment vector found"); } scale3f(mat1_inv, -1.0F, mat1_inv); } // bond length float const d = AtomInfoGetBondLength(G, ai0 + at0, ai1 + at1); // rotation matrix get_system1f3f(mat1_inv, mat1_inv + 4, mat1_inv + 8); // translation vector float mat1_trans[16]; identity44f(mat1_trans); mat1_trans[3] = (mat1_inv[0] * d - va1[0]); mat1_trans[7] = (mat1_inv[1] * d - va1[1]); mat1_trans[11] = (mat1_inv[2] * d - va1[2]); right_multiply44f44f(mat1_inv, mat1_trans); /* set up the linking bond */ cs->TmpLinkBond.resize(1); cs->NTmpLinkBond = 1; auto* bond = cs->TmpLinkBond.data(); BondTypeInit2(bond, at0, anch1); } AtomInfoUniquefyNames(I, nai.data(), cs->NIndex); /* set up tags which will enable use to continue editing bond */ if (edit) { for (int atm = 0; atm < I->NAtom; ++atm) { ai0[atm].temp1 = 0; } ai0[at0].temp1 = 1; } int const state0 = I->DiscreteFlag ? (ai0[at0].discrete_state - 1) : cSelectorUpdateTableAllStates; assert(!I->DiscreteFlag || I->DiscreteCSet[at0] == I->CSet[state0]); // Get target valence vectors for all relevant coordinate sets. Do this before // merging atoms to not leave the object molecule in an half-done state in // case we can't find valence vectors. std::vector> target_vectors; if (move_flag) { bool found_any_target_vector = false; target_vectors.resize(I->NCSet); for (StateIterator iter(G, nullptr, state0, I->NCSet); iter.next();) { float* vec = target_vectors[iter.state].data(); if (GetTargetValenceVector(I->CSet[iter.state], at0, index0, vec)) { found_any_target_vector = true; } else { zero3(vec); } } if (!found_any_target_vector) { return pymol::make_error("no target attachment vector found"); } } // will free nai, cs->TmpBond and cs->TmpLinkBond // invalidates the ai0 pointer! bool ok = ObjectMoleculeMerge( I, std::move(nai), cs.get(), false, cAIC_AllMask, true) && ObjectMoleculeExtendIndices(I, state0); p_return_val_if_fail(ok, pymol::Error::MEMORY); // Get untransformed copy of source coordinates pymol::vla coord_orig; if (move_flag) { coord_orig = cs->Coord; p_return_val_if_fail(coord_orig, pymol::Error::MEMORY); } // Add coordinates into the coordinate set(s) for (StateIterator iter(G, nullptr, state0, I->NCSet); iter.next();) { const float* const vec = move_flag ? target_vectors[iter.state].data() : nullptr; ok = AddCoordinateIntoCoordSet(I->CSet[iter.state], cs.get(), coord_orig.data(), at0, mat1_inv, vec); p_return_val_if_fail(ok, pymol::Error::MEMORY); } ok = ObjectMoleculeSort(I); p_return_val_if_fail(ok, pymol::Error::MEMORY); ObjectMoleculeUpdateIDNumbers(I); if (at0 != index0 || at1 != index1) { ObjectMoleculePurge(I); } // edit the resulting bond if (edit) { int atm_pk1 = -1; int atm_pk2 = -1; for (int atm = 0; atm < I->NAtom; ++atm) { if (I->AtomInfo[atm].temp1 == 1) { atm_pk2 = atm; } else if (I->AtomInfo[atm].temp1 == 2) { atm_pk1 = atm; } } if (atm_pk2 >= 0 && atm_pk1 >= 0) { auto sele1 = pymol::string_format("%s`%d", I->Name, atm_pk1 + 1); auto sele2 = pymol::string_format("%s`%d", I->Name, atm_pk2 + 1); EditorSelect(G, sele1.data(), sele2.data(), "", "", false, true, true); } } return {}; } /*========================================================================*/ int ObjectMoleculeVerifyChemistry(ObjectMolecule * I, int state) { int result = false; const AtomInfoType *ai; int a; int flag; if(state < 0) { /* use the first defined state */ for(a = 0; a < I->NCSet; a++) { if(I->CSet[a]) { state = a; break; } } } ai = I->AtomInfo; flag = true; for(a = 0; a < I->NAtom; a++) { if(!ai->chemFlag) { flag = false; } ai++; } if((!flag) && (state >= 0) && (state < I->NCSet)) { if(I->CSet[state]) { ObjectMoleculeInferChemFromBonds(I, state); ObjectMoleculeInferChemFromNeighGeom(I, state); ObjectMoleculeInferHBondFromChem(I); /* ObjectMoleculeInferChemForProtein(I,0); */ } flag = true; ai = I->AtomInfo; for(a = 0; a < I->NAtom; a++) { if(!ai->chemFlag) { flag = false; break; } ai++; } } if(flag) result = true; return (result); } /*========================================================================*/ int ObjectMoleculeAttach(ObjectMolecule * I, int index, pymol::vla&& nai) { int a; AtomInfoType *ai; BondType* bond; float v[3], v0[3], d; CoordSet *cs = nullptr; int ok = false; ai = I->AtomInfo + index; ok_assert(1, cs = CoordSetNew(I->G)); ok_assert(1, cs->Coord = pymol::vla(3)); cs->NIndex = 1; ok_assert(1, cs->TmpLinkBond = pymol::vla(1)); cs->NTmpLinkBond = 1; bond = cs->TmpLinkBond.data(); BondTypeInit2(bond, index, 0); cs->enumIndices(); ok_assert(1, ObjectMoleculePrepareAtom(I, index, nai.data())); d = AtomInfoGetBondLength(I->G, ai, nai); ok_assert(1, ObjectMoleculeMerge(I, std::move(nai), cs, false, cAIC_AllMask, true)); // will free nai and cs->TmpLinkBond ok_assert(1, ObjectMoleculeExtendIndices(I, -1)); for(a = 0; a < I->NCSet; a++) { /* add atom to each coordinate set */ if (const auto* cs_a = I->CSet[a]) { CoordSetGetAtomVertex(cs_a, index, v0); CoordSetFindOpenValenceVector(cs_a, index, v); scale3f(v, d, v); add3f(v0, v, cs->Coord.data()); ok_assert(1, CoordSetMerge(I, I->CSet[a], cs)); } } ok_assert(1, ObjectMoleculeSort(I)); ObjectMoleculeUpdateIDNumbers(I); ok = true; ok_except1: delete cs; return ok; } /*========================================================================*/ int ObjectMoleculeFillOpenValences(ObjectMolecule * I, int index) { int a; AtomInfoType *ai; int result = 0; int flag = true; float v[3], v0[3], d; CoordSet *cs = nullptr; int ok = true; if((index >= 0) && (index <= I->NAtom)) { while(ok) { ai = I->AtomInfo + index; auto const nn = AtomNeighbors(I, index).size(); assert(flag); if((nn >= ai->valence) || (!flag)) break; flag = false; if (ok) cs = CoordSetNew(I->G); CHECKOK(ok, cs); if (ok){ cs->Coord = pymol::vla(3); CHECKOK(ok, cs->Coord); cs->NIndex = 1; if (ok) cs->TmpLinkBond = pymol::vla(1); CHECKOK(ok, cs->TmpLinkBond); if (ok){ cs->NTmpLinkBond = 1; auto* bond = cs->TmpLinkBond.data(); BondTypeInit2(bond, index, 0); } } if(ok) cs->enumIndices(); auto atInfo = pymol::vla(1); AtomInfoType* nai = atInfo.data(); if (ok){ UtilNCopy(nai->elem, "H", 2); nai->geom = cAtomInfoSingle; nai->valence = 1; ok &= ObjectMoleculePrepareAtom(I, index, nai); d = AtomInfoGetBondLength(I->G, ai, nai); if (ok) ok &= ObjectMoleculeMerge(I, std::move(atInfo), cs, false, cAIC_AllMask, true); /* will free nai and cs->TmpLinkBond */ } if (ok) ok &= ObjectMoleculeExtendIndices(I, -1); for(a = 0; ok && a < I->NCSet; a++) { /* add atom to each coordinate set */ if (auto* cs_a = I->CSet[a]) { CoordSetGetAtomVertex(cs_a, index, v0); CoordSetFindOpenValenceVector(cs_a, index, v); scale3f(v, d, v); add3f(v0, v, cs->Coord.data()); ok &= CoordSetMerge(I, cs_a, cs); } } delete cs; result++; flag = true; } } ObjectMoleculeUpdateIDNumbers(I); return (result); } #define MaxOcc 100 /*========================================================================*/ /** * @param[out] normal (3x1) Normal vector of planar geometry at atom `atm` * * @return True if the atom has planar geometry and the normal could be computed * * @pre neighbors are defined */ static bool get_planer_normal(const CoordSet* cs, int const atm, float* normal) { int found = false; int nOcc = 0; float occ[MaxOcc * 3]; float v0[3], v1[3], v2[3], n0[3]; if (CoordSetGetAtomVertex(cs, atm, v0)) { const auto* I = cs->Obj; // look for an attached non-hydrogen as a base for (auto const& neighbor : AtomNeighbors(I, atm)) { if (CoordSetGetAtomVertex(cs, neighbor.atm, v1)) { subtract3f(v1, v0, n0); normalize3f(n0); /* n0's point away from center atom */ copy3f(n0, occ + 3 * nOcc); nOcc++; if(nOcc == MaxOcc) /* safety valve */ break; } } switch (I->AtomInfo[atm].geom) { case cAtomInfoPlanar: if(nOcc > 1) { cross_product3f(occ, occ + 3, normal); if(nOcc > 2) { cross_product3f(occ, occ + 6, v2); if(dot_product3f(normal, v2) < 0) { subtract3f(normal, v2, normal); } else { add3f(normal, v2, normal); } cross_product3f(occ + 3, occ + 6, v2); if(dot_product3f(normal, v2) < 0) { subtract3f(normal, v2, normal); } else { add3f(normal, v2, normal); } } normalize3f(normal); found = true; } break; } } return found; } /*========================================================================*/ /** * @param atm Atom index * @param[out] v (3x1) Normalized open valence vector * @param seek (3x1) Primer for the direction, or nullptr for random priming. * @param ignore_index Atom index of neighbor to ignore * @return True if valid open valence vector found */ bool CoordSetFindOpenValenceVector( const CoordSet* cs, int atm, float* v, const float* seek, int ignore_index) { int nOcc = 0; float occ[MaxOcc * 3]; int last_occ = -1; float v0[3], v1[3], n0[3] = {0.0F,0.0F,0.0F}, t[3]; int result = false; float y[3], z[3]; /* default is +X */ v[0] = 1.0; v[1] = 0.0; v[2] = 0.0; if(cs) { const auto* I = cs->Obj; if (atm >= 0 && atm <= I->NAtom) { if (CoordSetGetAtomVertex(cs, atm, v0)) { const auto* ai = I->AtomInfo.data() + atm; // look for an attached non-hydrogen as a base for (auto const& neighbor : AtomNeighbors(I, atm)) { auto const a1 = neighbor.atm; if(a1 != ignore_index) { if (CoordSetGetAtomVertex(cs, a1, v1)) { last_occ = a1; subtract3f(v1, v0, n0); normalize3f(n0); /* n0's point away from center atom */ copy3f(n0, occ + 3 * nOcc); nOcc++; if(nOcc == MaxOcc) /* safety valve */ break; } } } if((!nOcc) || (nOcc > 4) || (ai->geom == cAtomInfoNone)) { if(!seek) get_random3f(v); else copy3f(seek, v); result = true; } else { switch (nOcc) { case 1: /* only one current occupied position */ switch (ai->geom) { case cAtomInfoTetrahedral: if(!seek) { get_system1f3f(occ, y, z); scale3f(occ, -0.334F, v); scale3f(z, 0.943F, t); add3f(t, v, v); } else { /* point hydrogen towards sought vector */ copy3f(seek, z); get_system2f3f(occ, z, y); scale3f(occ, -0.334F, v); scale3f(z, 0.943F, t); add3f(t, v, v); } result = true; break; case cAtomInfoPlanar: { if(!seek) { if (last_occ >= 0 && get_planer_normal(cs, last_occ, n0)) { copy3f(n0, y); get_system2f3f(occ, y, z); } else { get_system1f3f(occ, y, z); } scale3f(occ, -0.500F, v); scale3f(z, 0.866F, t); add3f(t, v, v); } else { copy3f(seek, z); get_system2f3f(occ, z, y); scale3f(occ, -0.500F, v); scale3f(z, 0.866F, t); add3f(t, v, v); } result = true; } break; case cAtomInfoLinear: scale3f(occ, -1.0F, v); result = true; break; default: if(!seek) get_random3f(v); else copy3f(seek, v); result = false; break; } break; case 2: /* only two current occupied positions */ switch (ai->geom) { case cAtomInfoTetrahedral: add3f(occ, occ + 3, t); get_system2f3f(t, occ, z); scale3f(t, -1.0F, v); if(seek) { if(dot_product3f(z, seek) < 0.0F) { invert3f(z); } } scale3f(z, 1.41F, t); add3f(t, v, v); result = true; break; case cAtomInfoPlanar: add3f(occ, occ + 3, t); scale3f(t, -1.0F, v); result = true; break; default: if(!seek) { add3f(occ, occ + 3, t); scale3f(t, -1.0F, v); if(length3f(t) < 0.1) get_random3f(v); } else copy3f(seek, v); /* hypervalent */ result = false; break; } break; case 3: /* only three current occupied positions */ switch (ai->geom) { case cAtomInfoTetrahedral: add3f(occ, occ + 3, t); add3f(occ + 6, t, t); scale3f(t, -1.0F, v); result = true; break; default: if(!seek) { add3f(occ, occ + 3, t); add3f(occ + 6, t, t); scale3f(t, -1.0F, v); if(length3f(t) < 0.1) get_random3f(v); } else copy3f(seek, v); /* hypervalent */ result = false; break; } break; case 4: if(!seek) get_random3f(v); else copy3f(seek, v); /* hypervalent */ result = false; break; } } } } } normalize3f(v); return (result); #undef MaxOcc } /*========================================================================*/ void ObjectMoleculeCreateSpheroid(ObjectMolecule * I, int average) { CoordSet *cs; int a, b, c, a0; SphereRec *sp; float *v, *v0, *s, *f, ang, min_dist, *max_sq; int *i; float *center = nullptr; float d0[3], n0[3], d1[3], d2[3]; float p0[3], p1[3], p2[3]; int t0, t1, t2, bt0, bt1, bt2; float dp, l, *fsum = nullptr; float spheroid_smooth; float spheroid_fill; float spheroid_ratio = 0.1F; /* minimum ratio of width over length */ float spheroid_minimum = 0.02F; /* minimum size - to insure valid normals */ int row, *count = nullptr, base; int nRow; int first = 0; int last = 0; int current; int cscount; int n_state = 0; sp = GetSpheroidSphereRec(I->G); nRow = I->NAtom * sp->nDot; center = pymol::malloc(I->NAtom * 3); count = pymol::malloc(I->NAtom); fsum = pymol::malloc(nRow); max_sq = pymol::malloc(I->NAtom); spheroid_smooth = SettingGetGlobal_f(I->G, cSetting_spheroid_smooth); spheroid_fill = SettingGetGlobal_f(I->G, cSetting_spheroid_fill); /* first compute average coordinate */ if(average < 1) average = I->NCSet; current = 0; cscount = 0; while(current < I->NCSet) { if(I->CSet[current]) { if(!cscount) first = current; cscount++; last = current + 1; } if(cscount == average || current == I->NCSet - 1) { PRINTFB(I->G, FB_ObjectMolecule, FB_Details) " ObjectMolecule: computing spheroid from states %d to %d.\n", first + 1, last ENDFB(I->G); auto spheroid = std::vector(nRow); v = center; i = count; for(a = 0; a < I->NAtom; a++) { *(v++) = 0.0; *(v++) = 0.0; *(v++) = 0.0; *(i++) = 0; } for(b = first; b < last; b++) { cs = I->CSet[b]; if(cs) { v = cs->Coord.data(); for(a = 0; a < cs->NIndex; a++) { a0 = cs->IdxToAtm[a]; v0 = center + 3 * a0; add3f(v, v0, v0); (*(count + a0))++; v += 3; } } } i = count; v = center; for(a = 0; a < I->NAtom; a++) if(*i) { (*(v++)) /= (*i); (*(v++)) /= (*i); (*(v++)) /= (*i++); } else { v += 3; i++; } /* now go through and compute radial distances */ f = fsum; s = spheroid.data(); for(a = 0; a < nRow; a++) { *(f++) = 0.0; *(s++) = 0.0; } v = max_sq; for(a = 0; a < I->NAtom; a++) *(v++) = 0.0; for(b = first; b < last; b++) { cs = I->CSet[b]; if(cs) { v = cs->Coord.data(); for(a = 0; a < cs->NIndex; a++) { a0 = cs->IdxToAtm[a]; base = (a0 * sp->nDot); v0 = center + (3 * a0); subtract3f(v, v0, d0); /* subtract from average */ l = lengthsq3f(d0); if(l > max_sq[a0]) max_sq[a0] = l; if(l > 0.0) { float isq = (float) (1.0 / sqrt1d(l)); scale3f(d0, isq, n0); for(c = 0; c < sp->nDot; c++) { /* average over spokes */ dp = dot_product3f(sp->dot[c], n0); row = base + c; if(dp >= 0.0) { ang = (float) ((acos(dp) / spheroid_smooth) * (cPI / 2.0)); if(ang > spheroid_fill) ang = spheroid_fill; /* take envelop to zero over that angle */ if(ang <= (cPI / 2.0)) { dp = (float) cos(ang); fsum[row] += dp * dp; spheroid[row] += l * dp * dp * dp; } } } } v += 3; } } } f = fsum; s = spheroid.data(); for(a = 0; a < I->NAtom; a++) { min_dist = (float) (spheroid_ratio * sqrt(max_sq[a])); if(min_dist < spheroid_minimum) min_dist = spheroid_minimum; for(b = 0; b < sp->nDot; b++) { if(*f > R_SMALL4) { (*s) = (float) (sqrt1d((*s) / (*(f++)))); /* we put the "rm" in "rms" */ } else { f++; } if(*s < min_dist) *s = min_dist; s++; } } /* set frame 0 coordinates to the average */ cs = I->CSet[first]; if(cs) { v = cs->Coord.data(); for(a = 0; a < cs->NIndex; a++) { a0 = cs->IdxToAtm[a]; v0 = center + 3 * a0; copy3f(v0, v); v += 3; } } /* now compute surface normals */ auto norm = std::vector(nRow * 3); for(a = 0; a < nRow; a++) { zero3f(norm.data() + a * 3); } for(a = 0; a < I->NAtom; a++) { base = a * sp->nDot; for(b = 0; b < sp->NTri; b++) { t0 = sp->Tri[b * 3]; t1 = sp->Tri[b * 3 + 1]; t2 = sp->Tri[b * 3 + 2]; bt0 = base + t0; bt1 = base + t1; bt2 = base + t2; copy3f(sp->dot[t0], p0); copy3f(sp->dot[t1], p1); copy3f(sp->dot[t2], p2); /* scale3f(sp->dot[t0].v,spheroid[bt0],p0); scale3f(sp->dot[t1].v,spheroid[bt1],p1); scale3f(sp->dot[t2].v,spheroid[bt2],p2); */ subtract3f(p1, p0, d1); subtract3f(p2, p0, d2); cross_product3f(d1, d2, n0); normalize3f(n0); v = norm.data() + bt0 * 3; add3f(n0, v, v); v = norm.data() + bt1 * 3; add3f(n0, v, v); v = norm.data() + bt2 * 3; add3f(n0, v, v); } } f = norm.data(); for(a = 0; a < I->NAtom; a++) { base = a * sp->nDot; for(b = 0; b < sp->nDot; b++) { normalize3f(f); f += 3; } } if(I->CSet[first]) { I->CSet[first]->Spheroid = std::move(spheroid); I->CSet[first]->SpheroidNormal = std::move(norm); } for(b = first + 1; b < last; b++) { delete I->CSet[b]; I->CSet[b] = nullptr; } if(n_state != first) { I->CSet[n_state] = I->CSet[first]; I->CSet[first] = nullptr; } n_state++; cscount = 0; } current++; } I->NCSet = n_state; FreeP(center); FreeP(count); FreeP(fsum); FreeP(max_sq); I->invalidate(cRepSphere, cRepInvProp, -1); } /*========================================================================*/ void ObjectMoleculeReplaceAtom(ObjectMolecule * I, int index, AtomInfoType&& ai) { if((index >= 0) && (index <= I->NAtom)) { I->AtomInfo[index] = std::move(ai); I->invalidate(cRepAll, cRepInvAtoms, -1); /* could we put in a refinement step here? */ } } /*========================================================================*/ int ObjectMoleculePrepareAtom(ObjectMolecule * I, int index, AtomInfoType * ai, bool uniquefy) { /* match existing properties of the old atom */ AtomInfoType *ai0; int ok = true; if((index >= 0) && (index <= I->NAtom)) { ai0 = I->AtomInfo + index; ai->resv = ai0->resv; ai->hetatm = ai0->hetatm; ai->flags = ai0->flags; if (!ai->geom) ai->geom = ai0->geom; ai->discrete_state = ai0->discrete_state; ai->q = ai0->q; ai->b = ai0->b; strcpy(ai->alt, ai0->alt); ai->inscode = ai0->inscode; LexAssign(I->G, ai->segi, ai0->segi); LexAssign(I->G, ai->chain, ai0->chain); LexAssign(I->G, ai->resn, ai0->resn); ai->visRep = ai0->visRep; ai->id = -1; ai->rank = -1; AtomInfoAssignParameters(I->G, ai); if (uniquefy) { AtomInfoUniquefyNames(I->G, I->AtomInfo, I->NAtom, ai, nullptr, 1); } if((ai->elem[0] == ai0->elem[0]) && (ai->elem[1] == ai0->elem[1])) ai->color = ai0->color; else if((ai->elem[0] == 'C') && (ai->elem[1] == 0)) { int found = false; for (auto const& neighbor : AtomNeighbors(I, index)) { AtomInfoType const* ai1 = I->AtomInfo.data() + neighbor.atm; if(ai1->protons == cAN_C) { ai->color = ai1->color; found = true; break; } } if(ok && !found) { /* if no carbon nearby, then color according to the object color */ ai->color = I->Color; } } else { AtomInfoAssignColors(I->G, ai); } } return ok; } /*========================================================================*/ int ObjectMoleculePreposReplAtom(ObjectMolecule * I, int index, AtomInfoType * ai) { float v0[3], v1[3], v[3]; float d0[3], d, n0[3]; int cnt; float t[3], sum[3]; int a; int ncycle; int ok = true; if (ok){ for(a = 0; a < I->NCSet; a++) { if(I->CSet[a]) { if(ObjectMoleculeGetAtomVertex(I, a, index, v0)) { copy3f(v0, v); /* default is direct superposition */ ncycle = -1; while(ncycle) { cnt = 0; zero3f(sum); /* look for an attached non-hydrogen as a base */ for (auto const& neighbor : AtomNeighbors(I, index)) { auto const a1 = neighbor.atm; auto const* ai1 = I->AtomInfo.data() + a1; if(ai1->protons != 1) if(ObjectMoleculeGetAtomVertex(I, a, a1, v1)) { d = AtomInfoGetBondLength(I->G, ai, ai1); subtract3f(v0, v1, n0); normalize3f(n0); scale3f(n0, d, d0); add3f(d0, v1, t); add3f(t, sum, sum); cnt++; } } if(cnt) { scale3f(sum, 1.0F / cnt, sum); copy3f(sum, v0); if((cnt > 1) && (ncycle < 0)) ncycle = 5; } ncycle = abs(ncycle) - 1; } if(cnt) copy3f(sum, v); ObjectMoleculeSetAtomVertex(I, a, index, v); } } } } return ok; } /*========================================================================*/ #if 1 void ObjectMoleculeSaveUndo(ObjectMolecule * I, int state, int log) { CoordSet *cs; PyMOLGlobals *G = I->G; FreeP(I->UndoCoord[I->UndoIter]); I->UndoState[I->UndoIter] = -1; if(state < 0) state = 0; if(I->NCSet == 1) state = 0; state = state % I->NCSet; cs = I->CSet[state]; if(cs) { I->UndoCoord[I->UndoIter] = pymol::malloc(cs->NIndex * 3); memcpy(I->UndoCoord[I->UndoIter], cs->Coord, sizeof(float) * cs->NIndex * 3); I->UndoState[I->UndoIter] = state; I->UndoNIndex[I->UndoIter] = cs->NIndex; } I->UndoIter = cUndoMask & (I->UndoIter + 1); ExecutiveSetLastObjectEdited(G, I); if(log) { OrthoLineType line; if(SettingGetGlobal_i(I->G, cSetting_logging)) { sprintf(line, "cmd.push_undo(\"%s\",%d)\n", I->Name, state + 1); PLog(G, line, cPLog_no_flush); } } } /*========================================================================*/ void ObjectMoleculeUndo(ObjectMolecule * I, int dir) { CoordSet *cs; int state; FreeP(I->UndoCoord[I->UndoIter]); I->UndoState[I->UndoIter] = -1; state = SceneGetState(I->G); if(state < 0) state = 0; if(I->NCSet == 1) state = 0; state = state % I->NCSet; cs = I->CSet[state]; if(cs) { I->UndoCoord[I->UndoIter] = pymol::malloc(cs->NIndex * 3); memcpy(I->UndoCoord[I->UndoIter], cs->Coord, sizeof(float) * cs->NIndex * 3); I->UndoState[I->UndoIter] = state; I->UndoNIndex[I->UndoIter] = cs->NIndex; } I->UndoIter = cUndoMask & (I->UndoIter + dir); if(!I->UndoCoord[I->UndoIter]) I->UndoIter = cUndoMask & (I->UndoIter - dir); if(I->UndoState[I->UndoIter] >= 0) { state = I->UndoState[I->UndoIter]; if(state < 0) state = 0; if(I->NCSet == 1) state = 0; state = state % I->NCSet; cs = I->CSet[state]; if(cs) { if(cs->NIndex == I->UndoNIndex[I->UndoIter]) { memcpy(cs->Coord.data(), I->UndoCoord[I->UndoIter], sizeof(float) * cs->NIndex * 3); I->UndoState[I->UndoIter] = -1; FreeP(I->UndoCoord[I->UndoIter]); cs->invalidateRep(cRepAll, cRepInvCoord); SceneChanged(I->G); } } } } #endif int ObjectMoleculeAddBond(ObjectMolecule * I, int sele0, int sele1, int order, pymol::zstring_view symop) { int a1, a2; AtomInfoType *ai1, *ai2; int s1, s2; int c = 0; /* TO DO: optimize for performance -- we shouldn't be doing full table scans */ ai1 = I->AtomInfo.data(); for(a1 = 0; a1 < I->NAtom; a1++) { s1 = ai1->selEntry; if(SelectorIsMember(I->G, s1, sele0)) { ai2 = I->AtomInfo.data(); for(a2 = 0; a2 < I->NAtom; a2++) { s2 = ai2->selEntry; if(SelectorIsMember(I->G, s2, sele1)) { if(!I->Bond){ I->Bond = pymol::vla(1); } if(I->Bond) { BondType* bnd = I->Bond.check(I->NBond); BondTypeInit2(bnd, a1, a2, order); assert(!bnd->symop_2); if (!symop.empty()) { bnd->symop_2.reset(symop.c_str()); } I->NBond++; c++; I->AtomInfo[a1].chemFlag = false; I->AtomInfo[a2].chemFlag = false; I->AtomInfo[a1].bonded = true; I->AtomInfo[a2].bonded = true; } } ai2++; } } ai1++; } if(c) { I->invalidate(cRepAll, cRepInvBondsNoNonbonded, -1); } return (c); } /*========================================================================*/ pymol::Result<> ObjectMoleculeAddBondByIndices( ObjectMolecule* I, unsigned atm1, unsigned atm2, int order) { if (atm1 >= I->NAtom || atm2 >= I->NAtom) { return pymol::make_error("atom index out of bounds"); } if (!I->Bond) { I->Bond = pymol::vla(1); } else { I->Bond.check(I->NBond); } if (!I->Bond) { return pymol::Error::MEMORY; } auto& bnd = I->Bond[I->NBond++]; BondTypeInit2(&bnd, atm1, atm2, order); I->AtomInfo[atm1].chemFlag = false; I->AtomInfo[atm2].chemFlag = false; I->AtomInfo[atm1].bonded = true; I->AtomInfo[atm2].bonded = true; I->invalidate(cRepAll, cRepInvBondsNoNonbonded, -1); return {}; } /*========================================================================*/ int ObjectMoleculeAdjustBonds(ObjectMolecule * I, int sele0, int sele1, int mode, int order, pymol::zstring_view symop) { auto const G = I->G; int a0, a1; int cnt = 0; BondType *b0; int both; int a; if(I->Bond) { b0 = I->Bond.data(); for(a = 0; a < I->NBond; a++) { a0 = b0->index[0]; a1 = b0->index[1]; both = 0; if (SelectorIsMember(G, I->AtomInfo[a0].selEntry, sele0) && SelectorIsMember(G, I->AtomInfo[a1].selEntry, sele1)) { both = 2; } else if ( // SelectorIsMember(G, I->AtomInfo[a1].selEntry, sele0) && SelectorIsMember(G, I->AtomInfo[a0].selEntry, sele1)) { std::swap(a0, a1); both = 2; } if(both == 2) { cnt++; switch (mode) { case 0: /* cycle */ switch(SettingGet_i(I->G, I->Setting.get(), nullptr, cSetting_editor_bond_cycle_mode)) { case 1: /* 1 arom 2 3 */ switch(b0->order) { case 1: b0->order = 4; break; case 4: b0->order = 2; break; case 2: b0->order = 3; break; default: b0->order = 1; break; } break; case 2: /* 1 2 3 arom */ b0->order++; if(b0->order > 4) b0->order = 1; break; default: /* old way -> 1 2 3 */ b0->order++; if(b0->order > 3) b0->order = 1; break; } I->AtomInfo[a0].chemFlag = false; I->AtomInfo[a1].chemFlag = false; break; case 1: /* set */ b0->order = order; I->AtomInfo[a0].chemFlag = false; I->AtomInfo[a1].chemFlag = false; break; } if (!symop.empty()) { b0->symop_2.reset(symop.c_str()); } } b0++; } if(cnt) { I->invalidate(cRepLine, cRepInvBonds, -1); I->invalidate(cRepCyl, cRepInvBonds, -1); I->invalidate(cRepNonbonded, cRepInvBonds, -1); I->invalidate(cRepNonbondedSphere, cRepInvBonds, -1); I->invalidate(cRepRibbon, cRepInvBonds, -1); I->invalidate(cRepCartoon, cRepInvBonds, -1); } } return (cnt); } /*========================================================================*/ int ObjectMoleculeRemoveBonds(ObjectMolecule * I, int sele0, int sele1) { int a0, a1; int offset = 0; BondType *b0, *b1; int both; int s; int a; if(I->Bond) { offset = 0; b0 = I->Bond.data(); b1 = I->Bond.data(); for(a = 0; a < I->NBond; a++) { a0 = b0->index[0]; a1 = b0->index[1]; both = 0; s = I->AtomInfo[a0].selEntry; if(SelectorIsMember(I->G, s, sele0)) both++; s = I->AtomInfo[a1].selEntry; if(SelectorIsMember(I->G, s, sele1)) both++; if(both < 2) { /* reverse combo */ both = 0; s = I->AtomInfo[a1].selEntry; if(SelectorIsMember(I->G, s, sele0)) both++; s = I->AtomInfo[a0].selEntry; if(SelectorIsMember(I->G, s, sele1)) both++; } if(both == 2) { AtomInfoPurgeBond(I->G, b0); offset--; b0++; I->AtomInfo[a0].chemFlag = false; I->AtomInfo[a1].chemFlag = false; } else if(offset) { *(b1++) = *(b0++); /* copy bond info */ } else { *(b1++) = *(b0++); /* copy bond info */ } } if(offset) { I->NBond += offset; VLASize(I->Bond, BondType, I->NBond); I->invalidate(cRepLine, cRepInvBonds, -1); I->invalidate(cRepCyl, cRepInvBonds, -1); I->invalidate(cRepNonbonded, cRepInvBonds, -1); I->invalidate(cRepNonbondedSphere, cRepInvBonds, -1); I->invalidate(cRepRibbon, cRepInvBonds, -1); I->invalidate(cRepCartoon, cRepInvBonds, -1); } } return (-offset); } /*========================================================================*/ void ObjectMoleculePurge(ObjectMolecule * I) { PyMOLGlobals *G = I->G; int offset = 0; // remove the object selection and free up any selection entries. // note that we don't delete atom selection members -- those may be needed in the new object. SelectorDelete(G, I->Name); // old-to-new mapping auto oldToNew = std::vector(size_t(I->NAtom), -1); for (int atm = 0; atm < I->NAtom; ++atm) { auto &ai0 = I->AtomInfo[atm]; if(ai0.deleteFlag) { AtomInfoPurge(G, &ai0); offset--; assert(oldToNew[atm] == -1); } else { if(offset) { I->AtomInfo[atm + offset] = std::move(ai0); } oldToNew[atm] = atm + offset; } } if(offset) { I->NAtom += offset; I->AtomInfo.resize(I->NAtom); for (int a = 0; a < I->NCSet; ++a) { if (auto cs = I->CSet[a]) CoordSetAdjustAtmIdx(cs, oldToNew.data()); } if (I->CSTmpl) { CoordSetAdjustAtmIdx(I->CSTmpl, oldToNew.data()); } } I->updateAtmToIdx(); // step 4, bonds offset = 0; auto b0 = I->Bond.data(); auto b1 = I->Bond.data(); for (int b = 0; b < I->NBond; ++b) { auto a0 = b0->index[0]; auto a1 = b0->index[1]; if(a0 < 0 || a1 < 0 || (oldToNew[a0] < 0) || (oldToNew[a1] < 0)) { /* deleting bond */ AtomInfoPurgeBond(I->G, b0); offset--; b0++; } else { if(offset) { *b1 = *b0; } b1->index[0] = oldToNew[a0]; /* copy bond info */ b1->index[1] = oldToNew[a1]; b0++; b1++; } } if(offset) { I->NBond += offset; VLASize(I->Bond, BondType, I->NBond); } I->invalidate(cRepAll, cRepInvAtoms, -1); } /*========================================================================*/ /** * Determines hybridization from coordinates in those few cases where it is * unambiguous. */ int ObjectMoleculeGetAtomGeometry(const ObjectMolecule* I, int state, int at) { int result = -1; float v0[3], v1[3], v2[3], v3[3]; float d1[3], d2[3], d3[3]; float cp1[3], cp2[3], cp3[3]; float avg; float dp; auto const neighbors = AtomNeighbors(I, at); auto const nn = neighbors.size(); if(nn == 4) result = cAtomInfoTetrahedral; else if(nn == 3) { /* check cross products */ ObjectMoleculeGetAtomVertex(I, state, at, v0); ObjectMoleculeGetAtomVertex(I, state, neighbors[0].atm, v1); ObjectMoleculeGetAtomVertex(I, state, neighbors[1].atm, v2); ObjectMoleculeGetAtomVertex(I, state, neighbors[2].atm, v3); subtract3f(v1, v0, d1); subtract3f(v2, v0, d2); subtract3f(v3, v0, d3); cross_product3f(d1, d2, cp1); cross_product3f(d2, d3, cp2); cross_product3f(d3, d1, cp3); normalize3f(cp1); normalize3f(cp2); normalize3f(cp3); avg = (dot_product3f(cp1, cp2) + dot_product3f(cp2, cp3) + dot_product3f(cp3, cp1)) / 3.0F; if(avg > 0.75) result = cAtomInfoPlanar; else result = cAtomInfoTetrahedral; } else if(nn == 2) { ObjectMoleculeGetAtomVertex(I, state, at, v0); ObjectMoleculeGetAtomVertex(I, state, neighbors[0].atm, v1); ObjectMoleculeGetAtomVertex(I, state, neighbors[1].atm, v2); subtract3f(v1, v0, d1); subtract3f(v2, v0, d2); normalize3f(d1); normalize3f(d2); dp = dot_product3f(d1, d2); if(dp < -0.75) result = cAtomInfoLinear; } return (result); } /*========================================================================*/ static float compute_avg_center_dot_cross_fn(ObjectMolecule * I, CoordSet * cs, int n_atom, int *atix) { float result = 0.0F; float *v[9]; int missing_flag = false; { int a, i; for(i = 0; i < n_atom; i++) { int a1 = atix[i]; a = cs->atmToIdx(a1); if(a < 0) { missing_flag = true; break; } else { v[i] = cs->coordPtr(a); } } } if(!missing_flag) { int i; float d10[3], d20[3], cp[8][3]; float avg = 0.0; v[n_atom] = v[1]; for(i = 1; i < n_atom; i++) { subtract3f(v[i], v[0], d10); subtract3f(v[i + 1], v[0], d20); normalize3f(d10); normalize3f(d20); cross_product3f(d10, d20, cp[i]); normalize3f(cp[i]); if(i > 1) { if(dot_product3f(cp[i - 1], cp[i]) < 0.0) invert3f(cp[i]); } } copy3f(cp[1], cp[n_atom]); for(i = 1; i < n_atom; i++) { avg += dot_product3f(cp[i], cp[i + 1]); } result = avg / (n_atom - 1); } return result; } static int verify_planer_bonds(const ObjectMolecule* I, const CoordSet* cs, int n_atom, const int* atix, const int* neighbor, const float* dir, float cutoff) { int a, i; for(i = 0; i < n_atom; i++) { int a1 = atix[i]; a = cs->atmToIdx(a1); if(a >= 0) { const float* v0 = cs->coordPtr(a); int n = neighbor[a1] + 1; int a2; while((a2 = neighbor[n]) >= 0) { n += 2; a = cs->atmToIdx(a2); if(a >= 0) { const float* v1 = cs->coordPtr(a); float d10[] = { 0.f, 0.f, 0.f }; subtract3f(v1, v0, d10); normalize3f(d10); { float dot = fabs(dot_product3f(d10, dir)); if(dot > cutoff) { switch (I->AtomInfo[a1].protons) { case cAN_C: case cAN_N: case cAN_O: case cAN_S: switch (I->AtomInfo[a2].protons) { case cAN_C: case cAN_N: case cAN_O: case cAN_S: return 0; break; } break; } } } } } } } return 1; } static float compute_avg_ring_dot_cross_fn(ObjectMolecule * I, CoordSet * cs, int n_atom, int *atix, float *dir) { float result = 0.0F; float *v[9]; int missing_flag = false; { int a, i; for(i = 0; i < n_atom; i++) { int a1 = atix[i]; a = cs->atmToIdx(a1); if(a < 0) { missing_flag = true; break; } else { v[i] = cs->coordPtr(a); } } } if(!missing_flag) { int i; float d10[3], d21[3], cp[8][3]; float avg = 0.0; v[n_atom] = v[0]; v[n_atom + 1] = v[1]; for(i = 0; i < n_atom; i++) { subtract3f(v[i + 0], v[i + 1], d10); subtract3f(v[i + 2], v[i + 1], d21); normalize3f(d10); normalize3f(d21); cross_product3f(d10, d21, cp[i]); normalize3f(cp[i]); if(i) { if(dot_product3f(cp[i - 1], cp[i]) < 0.0) invert3f(cp[i]); } add3f(cp[i], dir, dir); } copy3f(cp[0], cp[n_atom]); for(i = 0; i < n_atom; i++) { avg += dot_product3f(cp[i], cp[i + 1]); } result = avg / n_atom; } normalize3f(dir); return result; } typedef struct { int cyclic, planer, aromatic; } ObservedInfo; void ObjectMoleculeGuessValences(ObjectMolecule * I, int state, int *flag1, int *flag2, int reset) { /* this a hacked 80% solution ...it will get things wrong, but it is better than nothing! */ const float planer_cutoff = 0.96F; CoordSet *cs = nullptr; ObservedInfo *obs_atom = nullptr; ObservedInfo *obs_bond = nullptr; int *flag = nullptr; int warning1 = 0, warning2 = 0; /* WORKAROUND of a possible -funroll-loops inlining optimizer bug in gcc 3.3.3 */ float (*compute_avg_center_dot_cross) (ObjectMolecule *, CoordSet *, int, int *); float (*compute_avg_ring_dot_cross) (ObjectMolecule *, CoordSet *, int, int *, float *); compute_avg_center_dot_cross = compute_avg_center_dot_cross_fn; compute_avg_ring_dot_cross = compute_avg_ring_dot_cross_fn; /* end WORKAROUND */ if((state >= 0) && (state < I->NCSet)) { cs = I->CSet[state]; } if(cs) { obs_atom = pymol::calloc(I->NAtom); obs_bond = pymol::calloc(I->NBond); } flag = pymol::calloc(I->NAtom); if(flag) { if(!flag1) { int a, *flag_a = flag; const AtomInfoType *ai = I->AtomInfo.data(); /* default behavior: only reset hetatm valences */ for(a = 0; a < I->NAtom; a++) { *(flag_a++) = (ai++)->hetatm; } } else if(flag1 && flag2) { int a, *flag_a = flag, *flag1_a = flag1, *flag2_a = flag2; for(a = 0; a < I->NAtom; a++) { *(flag_a++) = (*(flag1_a++) || *(flag2_a++)); } } else if(flag1) { int a, *flag_a = flag, *flag1_a = flag1; for(a = 0; a < I->NAtom; a++) { *(flag_a++) = *(flag1_a++); } } } if(!flag1) flag1 = flag; if(!flag2) flag2 = flag; if(reset) { /* reset chemistry information and bond orders for selected atoms */ { int a; AtomInfoType *ai = I->AtomInfo.data(); for(a = 0; a < I->NAtom; a++) { if(flag[a]) ai->chemFlag = 0; ai++; } } { int b; BondType *bi = I->Bond.data(); for(b = 0; b < I->NBond; b++) { int at0 = bi->index[0]; int at1 = bi->index[1]; if((flag1[at0] && flag2[at1]) || (flag1[at1] && flag2[at0])) bi->order = 1; bi++; } } } if(cs && obs_bond && obs_atom && flag && flag1 && flag2) { int a; int const* const neighbor = I->getNeighborArray(); AtomInfoType *atomInfo = I->AtomInfo.data(); BondType *bondInfo = I->Bond.data(); for(a = 0; a < I->NAtom; a++) { AtomInfoType *ai = atomInfo + a; if((!ai->chemFlag) && (flag[a])) { /* determine whether or not atom participates in a planer system with 5 or 6 atoms */ { int mem[9]; int nbr[7]; const int ESCAPE_MAX = 500; const int* atmToIdx = I->DiscreteFlag ? nullptr : cs->AtmToIdx.data(); int escape_count = ESCAPE_MAX; /* don't get bogged down with structures that have unreasonable connectivity */ mem[0] = a; nbr[0] = neighbor[mem[0]] + 1; while(((mem[1] = neighbor[nbr[0]]) >= 0) && ((!atmToIdx) || (atmToIdx[mem[0]] >= 0))) { nbr[1] = neighbor[mem[1]] + 1; while(((mem[2] = neighbor[nbr[1]]) >= 0) && ((!atmToIdx) || (atmToIdx[mem[1]] >= 0))) { if(mem[2] != mem[0]) { nbr[2] = neighbor[mem[2]] + 1; while(((mem[3] = neighbor[nbr[2]]) >= 0) && ((!atmToIdx) || (atmToIdx[mem[2]] >= 0))) { if(mem[3] != mem[1]) { nbr[3] = neighbor[mem[3]] + 1; while(((mem[4] = neighbor[nbr[3]]) >= 0) && ((!atmToIdx) || (atmToIdx[mem[3]] >= 0))) { if((mem[4] != mem[2]) && (mem[4] != mem[1]) && (mem[4] != mem[0])) { nbr[4] = neighbor[mem[4]] + 1; while(((mem[5] = neighbor[nbr[4]]) >= 0) && ((!atmToIdx) || (atmToIdx[mem[4]] >= 0))) { if(!(escape_count--)) goto escape; if((mem[5] != mem[3]) && (mem[5] != mem[2]) && (mem[5] != mem[1])) { if(mem[5] == mem[0]) { /* five-cycle */ int i; float dir[] = { 0.f, 0.f, 0.f } ; float avg_dot_cross = compute_avg_ring_dot_cross(I, cs, 5, mem, dir); for(i = 0; i < 5; i++) { obs_atom[mem[i]].cyclic = true; obs_bond[neighbor[nbr[0] + 1]].cyclic = true; } if(avg_dot_cross > planer_cutoff) { if(verify_planer_bonds (I, cs, 5, mem, neighbor, dir, 0.35F)) { for(i = 0; i < 5; i++) { obs_atom[mem[i]].planer = true; obs_bond[neighbor[nbr[i] + 1]].planer = true; } } } } nbr[5] = neighbor[mem[5]] + 1; while(((mem[6] = neighbor[nbr[5]]) >= 0) && ((!atmToIdx) || (atmToIdx[mem[5]] >= 0))) { if((mem[6] != mem[4]) && (mem[6] != mem[3]) && (mem[6] != mem[2]) && (mem[6] != mem[1])) { if(mem[6] == mem[0]) { /* six-cycle */ int i; float dir[3] = { 0.f, 0.f, 0.f } ; float avg_dot_cross = compute_avg_ring_dot_cross(I, cs, 6, mem, dir); for(i = 0; i < 6; i++) { obs_atom[mem[i]].cyclic = true; obs_bond[neighbor[nbr[i] + 1]].cyclic = true; } if(avg_dot_cross > planer_cutoff) { if(verify_planer_bonds (I, cs, 6, mem, neighbor, dir, 0.35F)) { for(i = 0; i < 6; i++) { obs_atom[mem[i]].planer = true; obs_bond[neighbor[nbr[i] + 1]].planer = true; } } } } } nbr[5] += 2; } } nbr[4] += 2; } } nbr[3] += 2; } } nbr[2] += 2; } } nbr[1] += 2; } nbr[0] += 2; } escape: escape_count = ESCAPE_MAX; /* don't get bogged down with structures that have unreasonable connectivity */ warning1 = 1; } } } for(a = 0; a < I->NAtom; a++) { AtomInfoType *ai = atomInfo + a; if((!ai->chemFlag) && flag[a]) { ObservedInfo *ob_at = obs_atom + a; { switch (ai->protons) { case cAN_P: case cAN_S: case cAN_N: case cAN_C: { int atm[5]; int bnd[5]; int n = neighbor[a]; int nn = neighbor[n++]; if(nn > 4) nn = 4; atm[0] = a; { int i; for(i = 1; i <= nn; i++) { atm[i] = neighbor[n]; bnd[i] = neighbor[n + 1]; n += 2; } } { int o1_at = -1, o2_at = -1, o3_at = -1, o4_at = -1; int o1_bd = 0, o2_bd = 0, o3_bd = 0, o4_bd = 0; float o1_len = 0.0F, o2_len = 0.0F; float n1_v[3] = { 0.0F, 0.0F, 0.0F }; int n1_at = -1, n2_at = -1, n3_at = -1; int n1_bd = 0, n2_bd = 0, n3_bd = 0; float n1_len = 0.0F, n2_len = 0.0F, n3_len = 0.0F; int c1_at = -1, c2_at = -1; float c1_v[3] = { 0.0F, 0.0F, 0.0F }; float *v0 = nullptr; { int idx0 = cs->atmToIdx(a); if(idx0 >= 0) v0 = cs->coordPtr(idx0); } { int i; for(i = 1; i <= nn; i++) { float *v1 = nullptr; { int idx1 = cs->atmToIdx(atm[i]); if(idx1 >= 0) v1 = cs->coordPtr(idx1); } if(v0 && v1) { float diff[3]; subtract3f(v1, v0, diff); { float bond_len = length3f(diff); switch (atomInfo[atm[i]].protons) { case cAN_C: if(c1_at < 0) { c1_at = atm[i]; copy3f(diff, c1_v); } else if(c2_at < 0) { c2_at = atm[i]; } break; case cAN_O: if(o1_at < 0) { o1_at = atm[i]; o1_len = bond_len; o1_bd = bnd[i]; } else if(o2_at < 0) { o2_at = atm[i]; o2_len = bond_len; o2_bd = bnd[i]; } else if(o3_at < 0) { o3_at = atm[i]; o3_bd = bnd[i]; } else if(o4_at < 0) { o4_at = atm[i]; o4_bd = bnd[i]; } break; case cAN_N: if(n1_at < 0) { copy3f(diff, n1_v); n1_at = atm[i]; n1_len = bond_len; n1_bd = bnd[i]; } else if(n2_at < 0) { n2_at = atm[i]; n2_len = bond_len; n2_bd = bnd[i]; } else if(n3_at < 0) { n3_at = atm[i]; n3_len = bond_len; n3_bd = bnd[i]; } break; } } } } } { float avg_dot_cross = 0.0F; switch (ai->protons) { case cAN_C: /* planer carbons */ if(nn == 3) { avg_dot_cross = compute_avg_center_dot_cross(I, cs, 4, atm); if(avg_dot_cross > planer_cutoff) { if((n1_at >= 0) && (o1_at >= 0) && (o2_at < 0)) { /* simple amide? */ if(o1_len < 1.38F) { if(neighbor[neighbor[o1_at]] == 1) if((flag1[a] && flag2[o1_at]) || (flag2[a] && flag1[o1_at])) bondInfo[o1_bd].order = 2; } } else if((n1_at >= 0) && (o1_at >= 0) && (o2_at >= 0)) { /* carbamyl */ if((o1_len < 1.38F) && (neighbor[neighbor[o1_at]] == 1) && (o2_len < 1.38F) && (neighbor[neighbor[o2_at]] == 1)) { if((flag1[a] && flag2[o1_at]) || (flag2[a] && flag1[o1_at])) bondInfo[o1_bd].order = 4; if((flag1[a] && flag2[o2_at]) || (flag2[a] && flag1[o2_at])) bondInfo[o2_bd].order = 4; } else if((o1_len < 1.38F) && (neighbor[neighbor[o1_at]] == 1)) { if((flag1[a] && flag2[o1_at]) || (flag2[a] && flag1[o1_at])) bondInfo[o1_bd].order = 2; } else if((o2_len < 1.38F) && (neighbor[neighbor[o2_at]] == 1)) if((flag1[a] && flag2[o2_at]) || (flag2[a] && flag1[o2_at])) bondInfo[o2_bd].order = 2; } else if((n1_at < 0) && (o1_at >= 0) && (o2_at < 0)) { /* ketone */ if((o1_len < 1.31F) && (neighbor[neighbor[o1_at]] == 1)) { if((flag1[a] && flag2[o1_at]) || (flag2[a] && flag1[o1_at])) bondInfo[o1_bd].order = 2; } } else if((o1_at >= 0) && (o2_at >= 0) && (n1_at < 0)) { /* simple carboxylate? */ if((o1_len < 1.38F) && (o2_len < 1.38F) && (neighbor[neighbor[o1_at]] == 1) && (neighbor[neighbor[o2_at]] == 1)) { if((flag1[a] && flag2[o1_at]) || (flag2[a] && flag1[o1_at])) bondInfo[o1_bd].order = 4; if((flag1[a] && flag2[o2_at]) || (flag2[a] && flag1[o2_at])) bondInfo[o2_bd].order = 4; } else if((o1_len < 1.38F) && (neighbor[neighbor[o1_at]] == 1)) { /* esters */ if((flag1[a] && flag2[o1_at]) || (flag2[a] && flag1[o1_at])) bondInfo[o1_bd].order = 2; } else if((o2_len < 1.38F) && (neighbor[neighbor[o2_at]] == 1)) { if((flag1[a] && flag2[o2_at]) || (flag2[a] && flag1[o2_at])) bondInfo[o2_bd].order = 2; } } else if((n1_at >= 0) && (n2_at >= 0) && (n3_at < 0) && (c1_at >= 0) && (n1_len < 1.43F) && (n2_len < 1.43F) && obs_atom[c1_at].planer && obs_atom[c1_at].cyclic && (!ob_at->cyclic) && (!obs_atom[n1_at].cyclic) && (!obs_atom[n2_at].cyclic)) { if((flag1[a] && flag2[n1_at]) || (flag2[a] && flag1[n1_at])) bondInfo[n1_bd].order = 4; atomInfo[n1_at].valence = 3; atomInfo[n1_at].geom = cAtomInfoPlanar; atomInfo[n1_at].chemFlag = 2; if((flag1[a] && flag2[n2_at]) || (flag2[a] && flag1[n2_at])) bondInfo[n2_bd].order = 4; atomInfo[n2_at].valence = 3; atomInfo[n2_at].geom = cAtomInfoPlanar; atomInfo[n2_at].chemFlag = 2; } else if((n1_at >= 0) && (n2_at >= 0) && (n3_at >= 0)) { /* guanido with no hydrogens */ if((n1_len < 1.44F) && (n2_len < 1.44F) && (n3_len < 1.44F)) { if((neighbor[neighbor[n1_at]] == 1) && (neighbor[neighbor[n2_at]] == 1) && (neighbor[neighbor[n3_at]] >= 2)) { if((flag1[a] && flag2[n1_at]) || (flag2[a] && flag1[n1_at])) bondInfo[n1_bd].order = 4; atomInfo[n1_at].valence = 3; atomInfo[n1_at].geom = cAtomInfoPlanar; atomInfo[n1_at].chemFlag = 2; if((flag1[a] && flag2[n2_at]) || (flag2[a] && flag1[n2_at])) bondInfo[n2_bd].order = 4; atomInfo[n2_at].valence = 3; atomInfo[n2_at].geom = cAtomInfoPlanar; atomInfo[n2_at].chemFlag = 2; } else if((neighbor[neighbor[n1_at]] == 1) && (neighbor[neighbor[n2_at]] >= 2) && (neighbor[neighbor[n3_at]] == 1)) { if((flag1[a] && flag2[n1_at]) || (flag2[a] && flag1[n1_at])) bondInfo[n1_bd].order = 4; atomInfo[n1_at].valence = 3; atomInfo[n1_at].geom = cAtomInfoPlanar; atomInfo[n1_at].chemFlag = 2; if((flag1[a] && flag2[n3_at]) || (flag2[a] && flag1[n3_at])) bondInfo[n3_bd].order = 4; atomInfo[n3_at].valence = 3; atomInfo[n3_at].geom = cAtomInfoPlanar; atomInfo[n3_at].chemFlag = 2; } else if((neighbor[neighbor[n1_at]] >= 2) && (neighbor[neighbor[n2_at]] == 1) && (neighbor[neighbor[n3_at]] == 1)) { if((flag1[a] && flag2[n2_at]) || (flag2[a] && flag1[n2_at])) bondInfo[n2_bd].order = 4; atomInfo[n2_at].valence = 3; atomInfo[n2_at].geom = cAtomInfoPlanar; atomInfo[n2_at].chemFlag = 2; if((flag1[a] && flag2[n3_at]) || (flag2[a] && flag1[n3_at])) bondInfo[n3_bd].order = 4; atomInfo[n3_at].valence = 3; atomInfo[n3_at].geom = cAtomInfoPlanar; atomInfo[n3_at].chemFlag = 2; } } } } } /* any carbon */ /* handle imines and nitriles */ if((nn >= 2) && (nn <= 3) && (n1_at >= 0) && (o1_at < 0) && (n2_at < 0) && (n1_len < 1.36F) && (!ob_at->cyclic) && (!obs_bond[n1_bd].cyclic) && (!obs_atom[n1_at].planer) && ((nn == 2) || ((nn == 3) && (avg_dot_cross > planer_cutoff)))) { float n1_dot_cross = 1.0F; int n2 = neighbor[n1_at]; int nn2 = neighbor[n2++]; { /* check nitrogen planarity */ int atm2[5]; if(nn2 > 2) { nn2 = 3; atm2[0] = n1_at; { int i2; for(i2 = 1; i2 <= nn2; i2++) { atm2[i2] = neighbor[n2]; n2 += 2; } } n1_dot_cross = compute_avg_center_dot_cross(I, cs, 4, atm2); } } if(n1_dot_cross > planer_cutoff) { if((flag1[a] && flag2[n1_at]) || (flag2[a] && flag1[n1_at])) bondInfo[n1_bd].order = 2; if((n1_len < 1.24F) && (c1_at >= 0) && (nn2 == 1)) { normalize3f(n1_v); normalize3f(c1_v); if(dot_product3f(n1_v, c1_v) < -0.9) { if((flag1[a] && flag2[n1_at]) || (flag2[a] && flag1[n1_at])) bondInfo[n1_bd].order = 3; } } } } break; case cAN_N: if(nn == 3) { avg_dot_cross = compute_avg_center_dot_cross(I, cs, 4, atm); if((avg_dot_cross > planer_cutoff)) { if((o1_at >= 0) && (o2_at >= 0) && (o3_at < 0)) { /* nitro */ if(neighbor[neighbor[o1_at]] == 1) { if((flag1[a] && flag2[o1_at]) || (flag2[a] && flag1[o1_at])) bondInfo[o1_bd].order = 4; } if(neighbor[neighbor[o2_at]] == 1) { if((flag1[a] && flag2[o2_at]) || (flag2[a] && flag1[o2_at])) bondInfo[o2_bd].order = 4; } } } } break; case cAN_S: case cAN_P: if((o1_at >= 0) && (o2_at >= 0) && (o3_at >= 0) && (o4_at >= 0)) { /* sulfate, phosphate */ int o1 = -1, o2 = -1, o3 = -1; int a1 = 0; if(neighbor[neighbor[o1_at]] == 1) { o1 = o1_bd; a1 = o1_at; } if(neighbor[neighbor[o2_at]] == 1) { if(o1 < 0) { o1 = o2_bd; a1 = o2_at; } else if(o2 < 0) { o2 = o2_bd; } } if(neighbor[neighbor[o3_at]] == 1) { if(o1 < 0) { o1 = o3_bd; a1 = o3_at; } else if(o2 < 0) o2 = o3_bd; else if(o3 < 0) o3 = o3_bd; } if(neighbor[neighbor[o4_at]] == 1) { if(o1 < 0) { o1 = o4_bd; a1 = o4_at; } else if(o2 < 0) o2 = o4_bd; else if(o3 < 0) o3 = o4_bd; } if(o2 >= 0) { if((flag1[a] && flag2[a1]) || (flag2[a] && flag1[a1])) bondInfo[o1].order = 2; if(o2 == o2_bd) { atomInfo[o2_at].formalCharge = -1; } else if(o2 == o3_bd) { atomInfo[o3_at].formalCharge = -1; } else if(o2 == o4_bd) { atomInfo[o4_at].formalCharge = -1; } } } else if((o1_at >= 0) && (o2_at >= 0) && (o3_at >= 0) && (o4_at < 0)) { /* sulfonamide */ int o1 = -1, o2 = -1; int a1 = 0, a2 = 0; if(neighbor[neighbor[o1_at]] == 1) { o1 = o1_bd; a1 = o1_at; } if(neighbor[neighbor[o2_at]] == 1) { if(o1 < 0) { o1 = o2_bd; a1 = o2_at; } else if(o2 < 0) { o2 = o2_bd; a2 = o2_at; } } if(neighbor[neighbor[o3_at]] == 1) { if(o1 < 0) { o1 = o3_bd; a1 = o3_at; } else if(o2 < 0) { o2 = o3_bd; a2 = o3_at; } } if(o1 >= 0) { if((flag1[a] && flag2[a1]) || (flag2[a] && flag1[a1])) bondInfo[o1].order = 2; } if(o2 >= 0) { if((flag1[a] && flag2[a2]) || (flag2[a] && flag1[a2])) bondInfo[o2].order = 2; } } else if((o1_at >= 0) && (o2_at >= 0) && (o3_at < 0)) { /* sulphone */ if(neighbor[neighbor[o1_at]] == 1) { if((flag1[a] && flag2[o1_at]) || (flag2[a] && flag1[o1_at])) bondInfo[o1_bd].order = 2; } if(neighbor[neighbor[o2_at]] == 1) { if((flag1[a] && flag2[o2_at]) || (flag2[a] && flag1[o2_at])) bondInfo[o2_bd].order = 2; } } break; } } } } } /* this sets aromatic bonds for cyclic planer systems */ if(ob_at->cyclic && ob_at->planer) { switch (ai->protons) { case cAN_C: case cAN_N: case cAN_O: case cAN_S: { int n, a0, b0; n = neighbor[a] + 1; while(1) { a0 = neighbor[n]; if(a0 < 0) break; b0 = neighbor[n + 1]; n += 2; if(obs_atom[a0].cyclic && obs_atom[a0].planer && obs_bond[b0].cyclic) { obs_atom[a0].aromatic = true; switch (I->AtomInfo[a0].protons) { case cAN_C: case cAN_N: case cAN_O: case cAN_S: if((flag1[a] && flag2[a0]) || (flag2[a] && flag1[a0])) I->Bond[b0].order = 4; break; } } } } break; } } } } } /* now try to address some simple cases with aromatic nitrogens */ for(a = 0; a < I->NAtom; a++) { AtomInfoType *ai = atomInfo + a; if((!ai->chemFlag) && (ai->protons == cAN_N) && (ai->formalCharge == 0) && flag[a] && obs_atom[a].cyclic && obs_atom[a].aromatic) { int n = neighbor[a]; int nn = neighbor[n++]; if(nn == 2) { /* only two explicit neighbors */ int mem[9]; int nbr[7]; const int ESCAPE_MAX = 500; const int* atmToIdx = I->DiscreteFlag ? nullptr : cs->AtmToIdx.data(); int escape_count = ESCAPE_MAX; /* don't get bogged down with structures that have unreasonable connectivity */ mem[0] = a; nbr[0] = neighbor[mem[0]] + 1; while(((mem[1] = neighbor[nbr[0]]) >= 0) && ((!atmToIdx) || (atmToIdx[mem[0]] >= 0))) { nbr[1] = neighbor[mem[1]] + 1; while(((mem[2] = neighbor[nbr[1]]) >= 0) && ((!atmToIdx) || (atmToIdx[mem[1]] >= 0))) { if(mem[2] != mem[0]) { nbr[2] = neighbor[mem[2]] + 1; while(((mem[3] = neighbor[nbr[2]]) >= 0) && ((!atmToIdx) || (atmToIdx[mem[2]] >= 0))) { if(mem[3] != mem[1]) { nbr[3] = neighbor[mem[3]] + 1; while(((mem[4] = neighbor[nbr[3]]) >= 0) && ((!atmToIdx) || (atmToIdx[mem[3]] >= 0))) { if((mem[4] != mem[2]) && (mem[4] != mem[1]) && (mem[4] != mem[0])) { nbr[4] = neighbor[mem[4]] + 1; while(((mem[5] = neighbor[nbr[4]]) >= 0) && ((!atmToIdx) || (atmToIdx[mem[4]] >= 0))) { if(!(escape_count--)) goto escape2; /* BUG FIX: need a new escape2, instead of mistakenly going back to the first escape, which is in the loop above */ if((mem[5] != mem[3]) && (mem[5] != mem[2]) && (mem[5] != mem[1])) { if(mem[5] == mem[0] && (!ai->chemFlag)) { /* unassigned aromatic nitrogen-containing five-cycle */ /* c1ccnc1 becomes c1ccn[H]c1 */ if((atomInfo[mem[1]].protons == cAN_C) && (atomInfo[mem[2]].protons == cAN_C) && (atomInfo[mem[3]].protons == cAN_C) && (atomInfo[mem[4]].protons == cAN_C) && obs_atom[mem[1]].aromatic && obs_atom[mem[2]].aromatic && obs_atom[mem[3]].aromatic && obs_atom[mem[4]].aromatic) { ai->valence = 3; ai->chemFlag = 2; ai->geom = cAtomInfoPlanar; } /* c1ncnc1 becomes c1n[H]cnc1 */ if((atomInfo[mem[1]].protons == cAN_C) && (atomInfo[mem[2]].protons == cAN_N) && (atomInfo[mem[2]].formalCharge == 0) && (!atomInfo[mem[2]].chemFlag) && (atomInfo[mem[3]].protons == cAN_C) && (atomInfo[mem[4]].protons == cAN_C) && obs_atom[mem[1]].aromatic && obs_atom[mem[2]].aromatic && obs_atom[mem[3]].aromatic && obs_atom[mem[4]].aromatic) { int n2 = neighbor[mem[2]]; int nn2 = neighbor[n2++]; if(nn2 == 2) { /* second nitrogen also ambiguous */ ai->valence = 3; ai->chemFlag = 2; ai->geom = cAtomInfoPlanar; } } /* c1cnnc1 becomes c1cn[H]nc1 */ if((atomInfo[mem[1]].protons == cAN_N) && (atomInfo[mem[1]].formalCharge == 0) && (!atomInfo[mem[1]].chemFlag) && (atomInfo[mem[2]].protons == cAN_C) && (atomInfo[mem[3]].protons == cAN_C) && (atomInfo[mem[4]].protons == cAN_C) && obs_atom[mem[1]].aromatic && obs_atom[mem[2]].aromatic && obs_atom[mem[3]].aromatic && obs_atom[mem[4]].aromatic) { int n2 = neighbor[mem[1]]; int nn2 = neighbor[n2++]; if(nn2 == 2) { /* second nitrogen also ambiguous */ ai->valence = 3; ai->chemFlag = 2; ai->geom = cAtomInfoPlanar; } } } } nbr[4] += 2; } } nbr[3] += 2; } } nbr[2] += 2; } } nbr[1] += 2; } nbr[0] += 2; } escape2: /* BUG FIX: Need separate escape for this loop */ escape_count = ESCAPE_MAX; /* don't get bogged down with structures that have unreasonable connectivity */ warning2 = 1; } } } } if (warning1 || warning2){ PRINTFB(I->G, FB_ObjectMolecule, FB_Blather) " %s(%d,%d): Unreasonable connectivity in heteroatom,\n unsuccessful in guessing valences.\n", __func__, warning1, warning2 ENDFB(I->G); } FreeP(obs_bond); FreeP(obs_atom); FreeP(flag); } /*========================================================================*/ void ObjectMoleculeInferChemForProtein(ObjectMolecule * I, int state) { /* Infers chemical relations for a molecules under protein assumptions. * * NOTE: this routine needs an all-atom model (with hydrogens!) * and it will make mistakes on non-protein atoms (if they haven't * already been assigned) */ int changedFlag = true; /* first, try to find all amids and acids */ while(changedFlag) { changedFlag = false; for (int a = 0; a < I->NAtom; a++) { auto const* const ai = I->AtomInfo.data() + a; if(ai->chemFlag) { if(ai->geom == cAtomInfoPlanar) if(ai->protons == cAN_C) { auto const neighbors = AtomNeighbors(I, a); if (neighbors.size() > 1) { AtomInfoType* ai1 = nullptr; for (auto const& neighbor : neighbors) { auto* const ai0 = I->AtomInfo.data() + neighbor.atm; if((ai0->protons == cAN_O) && (!ai0->chemFlag)) { ai1 = ai0; /* found candidate carbonyl */ break; } } if (ai1) { for (auto const& neighbor : neighbors) { auto* const ai0 = I->AtomInfo.data() + neighbor.atm; if (ai0 != ai1) { if(ai0->protons == cAN_O) { if(!ai0->chemFlag) { ai0->chemFlag = true; /* acid */ ai0->geom = cAtomInfoPlanar; ai0->valence = 1; ai1->chemFlag = true; ai1->geom = cAtomInfoPlanar; ai1->valence = 1; changedFlag = true; break; } } else if(ai0->protons == cAN_N) { if(!ai0->chemFlag) { ai0->chemFlag = true; /* amide N */ ai0->geom = cAtomInfoPlanar; ai0->valence = 3; ai1->chemFlag = true; /* amide =O */ ai1->geom = cAtomInfoPlanar; ai1->valence = 1; changedFlag = true; break; } else if(ai0->geom == cAtomInfoPlanar) { ai1->chemFlag = true; /* amide =O */ ai1->geom = cAtomInfoPlanar; ai1->valence = 1; changedFlag = true; break; } } } } } } } } } } /* then handle aldehydes and amines (partial amides - both missing a valence) */ changedFlag = true; while(changedFlag) { changedFlag = false; for (int a = 0; a < I->NAtom; a++) { auto* const ai = I->AtomInfo.data() + a; if(!ai->chemFlag) { if(ai->protons == cAN_C) { auto const neighbors = AtomNeighbors(I, a); if (neighbors.size() > 1) { for (auto const& neighbor : neighbors) { auto* const ai0 = I->AtomInfo.data() + neighbor.atm; if((ai0->protons == cAN_O) && (!ai0->chemFlag)) { /* =O */ ai->chemFlag = true; ai->geom = cAtomInfoPlanar; ai->valence = 1; ai0->chemFlag = true; ai0->geom = cAtomInfoPlanar; ai0->valence = 3; changedFlag = true; break; } } } } else if(ai->protons == cAN_N) { if((!ai->chemFlag) || ai->geom != cAtomInfoLinear) { if(ai->formalCharge == 0) { ai->chemFlag = true; ai->geom = cAtomInfoPlanar; ai->valence = 3; } } } } } } } /*========================================================================*/ /** * Assigns: * - geom * - valence * - chemFlag */ void ObjectMoleculeInferChemFromNeighGeom(ObjectMolecule * I, int state) { /* infers chemical relations from neighbors and geometry * NOTE: very limited in scope */ int a; int changedFlag = true; int geom; int carbonVal[10]; AtomInfoType *ai, *ai2; carbonVal[cAtomInfoTetrahedral] = 4; carbonVal[cAtomInfoPlanar] = 3; carbonVal[cAtomInfoLinear] = 2; while(changedFlag) { changedFlag = false; for(a = 0; a < I->NAtom; a++) { ai = I->AtomInfo + a; if(!ai->chemFlag) { geom = ObjectMoleculeGetAtomGeometry(I, state, a); switch (ai->protons) { case cAN_K: ai->chemFlag = 1; ai->geom = cAtomInfoNone; ai->valence = 0; break; case cAN_H: case cAN_F: case cAN_I: case cAN_Br: ai->chemFlag = 1; ai->geom = cAtomInfoSingle; ai->valence = 1; break; case cAN_O: { auto const neighbors = AtomNeighbors(I, a); auto const nn = neighbors.size(); if(nn != 1) { /* water, hydroxy, ether */ ai->chemFlag = 1; ai->geom = cAtomInfoTetrahedral; ai->valence = 2; } else { /* hydroxy or carbonyl? check carbon geometry */ ai2 = I->AtomInfo.data() + neighbors[0].atm; if(ai2->chemFlag) { if((ai2->geom == cAtomInfoTetrahedral) || (ai2->geom == cAtomInfoLinear)) { ai->chemFlag = 1; /* hydroxy */ ai->geom = cAtomInfoTetrahedral; ai->valence = 2; } } } break; } case cAN_C: if(geom >= 0) { ai->geom = geom; ai->valence = carbonVal[geom]; ai->chemFlag = true; } else { auto const neighbors = AtomNeighbors(I, a); auto const nn = neighbors.size(); if(nn == 1) { /* only one neighbor */ ai2 = I->AtomInfo.data() + neighbors[0].atm; if(ai2->chemFlag && (ai2->geom == cAtomInfoTetrahedral)) { ai->chemFlag = true; /* singleton carbon bonded to tetC must be tetC */ ai->geom = cAtomInfoTetrahedral; ai->valence = 4; } } } break; case cAN_N: if(geom == cAtomInfoPlanar) { ai->chemFlag = true; ai->geom = cAtomInfoPlanar; ai->valence = 3; } else if(geom == cAtomInfoTetrahedral) { ai->chemFlag = true; ai->geom = cAtomInfoTetrahedral; ai->valence = 4; } break; case cAN_S: { auto const nn = AtomNeighbors(I, a).size(); if(nn == 4) { /* sulfone */ ai->chemFlag = true; ai->geom = cAtomInfoTetrahedral; ai->valence = 4; } else if(nn == 3) { /* suloxide */ ai->chemFlag = true; ai->geom = cAtomInfoTetrahedral; ai->valence = 3; } else if(nn == 2) { /* thioether */ ai->chemFlag = true; ai->geom = cAtomInfoTetrahedral; ai->valence = 2; } break; } case cAN_Cl: ai->chemFlag = 1; if(ai->formalCharge == 0) { ai->geom = cAtomInfoSingle; ai->valence = 1; } else { ai->geom = cAtomInfoNone; ai->valence = 0; } break; } if(ai->chemFlag) changedFlag = true; } } } } /*========================================================================*/ /** * Assigns, based on valence, geom, formalCharge, and bonded atoms: * - hb_donor * - hb_acceptor */ void ObjectMoleculeInferHBondFromChem(ObjectMolecule * I) { int a; AtomInfoType *ai; int a1; int has_hydro; /* initialize accumulators on uncategorized atoms */ const lexborrow_t lex_pseudo = LexBorrow(I->G, "pseudo"); ai = I->AtomInfo.data(); for(a = 0; a < I->NAtom; a++) { auto const neighbors = AtomNeighbors(I, a); int nn = neighbors.size(); ai->hb_donor = false; ai->hb_acceptor = false; has_hydro = (nn < ai->valence); /* implicit hydrogens? */ if(!has_hydro) { /* explicit hydrogens? */ switch (ai->protons) { case cAN_N: case cAN_O: for (auto const& neighbor : neighbors) { a1 = neighbor.atm; if(I->AtomInfo[a1].protons == 1) { has_hydro = true; break; } if (I->AtomInfo[a1].name == lex_pseudo && --nn < ai->valence) { has_hydro = true; break; } } break; } } switch (ai->protons) { /* cat-ions, lewis acids etc. */ case cAN_Fe: case cAN_Ca: case cAN_Cu: case cAN_K: case cAN_Na: case cAN_Mg: case cAN_Zn: case cAN_Hg: case cAN_Sr: case cAN_Ba: ai->hb_donor = true; break; case cAN_N: if(has_hydro) ai->hb_donor = true; else { int delocalized = false; int has_double_bond = false; int neighbor_has_double_bond = false; int mem[3]; int nbr[3]; int const* neighbor = I->getNeighborArray(); mem[0] = a; nbr[0] = neighbor[mem[0]] + 1; while(((mem[1] = neighbor[nbr[0]]) >= 0)) { int b_order = I->Bond[neighbor[nbr[0] + 1]].order; if(b_order > 1) { /* any double/triple/aromatic bonds? */ delocalized = true; } if(b_order == 2) { has_double_bond = true; } nbr[1] = neighbor[mem[1]] + 1; while(((mem[2] = neighbor[nbr[1]]) >= 0)) { if(mem[2] != mem[0]) { int b_order2 = I->Bond[neighbor[nbr[1] + 1]].order; if(b_order2 == 2) { neighbor_has_double_bond = true; } } nbr[1] += 2; } nbr[0] += 2; } if((ai->formalCharge <= 0) && delocalized && (nn < 3)) { /* delocalized nitrogen can likely serve as an acceptor */ ai->hb_acceptor = true; } if(delocalized && (neighbor_has_double_bond) && (!has_double_bond) && (ai->geom == cAtomInfoPlanar) && (nn == 2) && (ai->formalCharge >= 0)) { /* there's a fair chance of a resonance structure with this nitrogen as a donor */ ai->hb_donor = true; } if((ai->geom != cAtomInfoPlanar) && (nn == 3) && (ai->formalCharge >= 0) && (!delocalized)) { /* tertiary amine case -- assume potential donor */ ai->hb_donor = true; } } break; case cAN_O: if(ai->formalCharge <= 0) ai->hb_acceptor = true; if(has_hydro) ai->hb_donor = true; else { int has_double_bond = false; int neighbor_has_aromatic_bond = false; int mem[3]; int nbr[3]; auto* const neighbor = I->getNeighborArray(); mem[0] = a; nbr[0] = neighbor[mem[0]] + 1; while(((mem[1] = neighbor[nbr[0]]) >= 0)) { int b_order = I->Bond[neighbor[nbr[0] + 1]].order; if(b_order == 2) { has_double_bond = true; } nbr[1] = neighbor[mem[1]] + 1; while(((mem[2] = neighbor[nbr[1]]) >= 0)) { if(mem[2] != mem[0]) { int b_order2 = I->Bond[neighbor[nbr[1] + 1]].order; if(b_order2 == 4) { neighbor_has_aromatic_bond = true; } } nbr[1] += 2; } nbr[0] += 2; } if(has_double_bond && neighbor_has_aromatic_bond && (ai->formalCharge >= 0)) { /* allow for phenolic resonance structures (and the like) */ ai->hb_donor = true; } } break; } ai++; } } /*========================================================================*/ /** * Assigns: * - geom * - valence * - chemFlag */ void ObjectMoleculeInferChemFromBonds(ObjectMolecule * I, int state) { int a, b; const BondType *b0; AtomInfoType *ai, *ai0, *ai1 = nullptr; int a0, a1; int expect, order; int changedFlag; /* initialize accumulators on uncategorized atoms */ ai = I->AtomInfo.data(); for(a = 0; a < I->NAtom; a++) { if(!ai->chemFlag) { ai->geom = 0; ai->valence = 0; } ai++; } // Ignore "pseudo" atoms (Desmond virtual sites) const lexborrow_t lex_pseudo = LexBorrow(I->G, "pseudo"); std::vector pseudo_neighbor_count( lex_pseudo == LEX_BORROW_NOTFOUND ? 0 : I->NAtom); /* find maximum bond order for each atom */ b0 = I->Bond; for(b = 0; b < I->NBond; b++) { a0 = b0->index[0]; a1 = b0->index[1]; ai0 = I->AtomInfo + a0; ai1 = I->AtomInfo + a1; order = b0->order; b0++; // count "pseudo" neighbors if (!pseudo_neighbor_count.empty()) { if (ai0->name == lex_pseudo) { pseudo_neighbor_count[a1] += 1; } if (ai1->name == lex_pseudo) { pseudo_neighbor_count[a0] += 1; } } if(!ai0->chemFlag) { if(order > ai0->geom) ai0->geom = order; ai0->valence += order; } if(!ai1->chemFlag) { if(order > ai1->geom) ai1->geom = order; ai1->valence += order; } if(order == 3) { /* override existing chemistry * this is a temp fix to a pressing problem... we need to rethink the chemisty assignment ordering (should bond information come first? */ ai0->geom = cAtomInfoLinear; ai1->geom = cAtomInfoLinear; if(ai0->chemFlag != 2) { switch (ai0->protons) { case cAN_C: ai0->valence = 2; break; default: ai0->valence = 1; } ai0->chemFlag = true; } if(ai1->chemFlag != 2) { switch (ai1->protons) { case cAN_C: ai1->valence = 2; break; default: ai1->valence = 1; } ai1->chemFlag = true; } } else if(order == 4) { ai0->geom = cAtomInfoPlanar; ai1->geom = cAtomInfoPlanar; if(ai0->chemFlag != 2) { switch (ai0->protons) { case cAN_O: ai0->valence = 1; break; case cAN_N: ai0->valence = (ai0->formalCharge == 1) ? 3 : 2; // TODO wrong for neutral -NH- break; case cAN_C: ai0->valence = 3; break; case cAN_S: ai0->valence = 2; break; default: ai0->valence = 4; } ai0->chemFlag = true; } if(ai1->chemFlag != 2) { switch (ai1->protons) { case cAN_O: ai1->valence = 1; break; case cAN_N: ai1->valence = (ai1->formalCharge == 1) ? 3 : 2; // TODO wrong for neutral -NH- break; case cAN_C: ai1->valence = 3; break; default: ai1->valence = 1; } ai1->chemFlag = true; } } } /* now set up valences and geometries */ ai = I->AtomInfo.data(); for(a = 0; a < I->NAtom; a++) { if(!ai->chemFlag) { expect = AtomInfoGetExpectedValence(I->G, ai); int nn = AtomNeighbors(I, a).size(); // don't count "pseudo" atom neighbors if (!pseudo_neighbor_count.empty()) { nn -= pseudo_neighbor_count[a]; } if(ai->geom == 3) { ai->geom = cAtomInfoLinear; switch (ai->protons) { case cAN_C: ai->valence = 2; break; default: ai->valence = 1; } ai->chemFlag = true; } else { if(expect < 0) expect = -expect; /* for now, just ignore this issue */ /* printf("%d %d %d %d\n",ai->geom,ai->valence,nn,expect); */ if(ai->valence == expect) { /* sum of bond orders equals valence */ ai->chemFlag = true; ai->valence = nn; switch (ai->geom) { /* max bond order observed */ case 0: ai->geom = cAtomInfoNone; break; case 2: ai->geom = cAtomInfoPlanar; break; case 3: ai->geom = cAtomInfoLinear; break; default: if(expect == 1) ai->geom = cAtomInfoSingle; else ai->geom = cAtomInfoTetrahedral; break; } } else if(ai->valence < expect) { /* missing a bond */ ai->chemFlag = true; ai->valence = nn + (expect - ai->valence); switch (ai->geom) { case 2: ai->geom = cAtomInfoPlanar; break; case 3: ai->geom = cAtomInfoLinear; break; default: if(expect == 1) ai->geom = cAtomInfoSingle; else ai->geom = cAtomInfoTetrahedral; break; } } else if(ai->valence > expect) { ai->chemFlag = true; ai->valence = nn; switch (ai->geom) { case 2: ai->geom = cAtomInfoPlanar; break; case 3: ai->geom = cAtomInfoLinear; break; default: if(expect == 1) ai->geom = cAtomInfoSingle; else ai->geom = cAtomInfoTetrahedral; break; } if(nn > 3) ai->geom = cAtomInfoTetrahedral; } } } ai++; } /* now go through and make sure conjugated amines are planer */ changedFlag = true; while(changedFlag) { changedFlag = false; ai = I->AtomInfo.data(); for(a = 0; a < I->NAtom; a++) { if(ai->chemFlag) { if(ai->protons == cAN_N) if(ai->formalCharge < 1) if(ai->geom == cAtomInfoTetrahedral) { /* search for uncharged tetrahedral nitrogen */ for (auto const& neighbor : AtomNeighbors(I, a)) { auto const* ai0 = I->AtomInfo.data() + neighbor.atm; if((ai0->chemFlag) && (ai0->geom == cAtomInfoPlanar) && ((ai0->protons == cAN_C) || (ai0->protons == cAN_N))) { ai->geom = cAtomInfoPlanar; /* found probable delocalization */ if(ai->formalCharge == 0) ai->valence = 3; /* just in case... */ changedFlag = true; break; } } } } ai++; } } /* now go through and make sure conjugated anions are planer */ changedFlag = true; while(changedFlag) { changedFlag = false; ai = I->AtomInfo.data(); for(a = 0; a < I->NAtom; a++) { if(ai->chemFlag) { if(ai->protons == cAN_O) if(ai->formalCharge == -1) if((ai->geom == cAtomInfoTetrahedral) || (ai->geom == cAtomInfoSingle)) { /* search for anionic tetrahedral oxygen */ for (auto const& neighbor : AtomNeighbors(I, a)) { auto const* ai0 = I->AtomInfo + neighbor.atm; if((ai0->chemFlag) && (ai0->geom == cAtomInfoPlanar) && ((ai0->protons == cAN_C) || (ai0->protons == cAN_N))) { ai->geom = cAtomInfoPlanar; /* found probable delocalization */ changedFlag = true; break; } } } } ai++; } } } /*========================================================================*/ int ObjectMoleculeTransformSelection(ObjectMolecule * I, int state, int sele, const float *matrix, int log, const char *sname, int homogenous, int global) { /* called from "translate [5,5,5], objSele" */ /* if sele == -1, then the whole object state is transformed */ PyMOLGlobals *G = I->G; int a, s; int flag = false; CoordSet *cs; const AtomInfoType *ai; int logging; int all_states = false, inp_state; int ok = true; float homo_matrix[16], tmp_matrix[16]; const float* input_matrix = matrix; inp_state = state; if(state == -2) state = ObjectGetCurrentState(I, false); if(state < 0) { all_states = true; state = -1; } PRINTFD(G, FB_ObjectMolecule) "ObjMolTransSele-Debug: state %d\n", state ENDFD; while(1) { if(all_states) { state++; if(state >= I->NCSet) break; } if(state < I->NCSet) { cs = I->CSet[state]; if(cs) { int use_matrices = SettingGet_i(G, I->Setting.get(), nullptr, cSetting_matrix_mode); if(use_matrices<0) use_matrices = 0; if(global &&!homogenous) { /* convert matrix to homogenous */ convertTTTfR44f(matrix, homo_matrix); matrix = homo_matrix; input_matrix = homo_matrix; homogenous = true; } if(global &&((use_matrices && !cs->Matrix.empty()) || I->TTTFlag)) { /* if input coordinates are in the global system, they may need to be converted to local coordinates */ matrix = input_matrix; /* global to object */ if(I->TTTFlag) { float ttt[16]; if(matrix != tmp_matrix) { copy44f(matrix, tmp_matrix); } convertTTTfR44f(I->TTT, ttt); { float ttt_inv[16]; invert_special44f44f(ttt, ttt_inv); left_multiply44f44f(ttt_inv, tmp_matrix); right_multiply44f44f(tmp_matrix, ttt); } matrix = tmp_matrix; } /* object to state */ if(use_matrices) { if(!cs->Matrix.empty()) { double tmp_double[16], tmp_inv[16]; copy44f44d(matrix, tmp_double); invert_special44d44d(cs->Matrix.data(), tmp_inv); left_multiply44d44d(tmp_inv, tmp_double); right_multiply44d44d(tmp_double, cs->Matrix.data()); copy44d44f(tmp_double, tmp_matrix); matrix = tmp_matrix; } } } if(sele >= 0) { /* transforming select atoms */ ai = I->AtomInfo; for(a = 0; a < I->NAtom; a++) { s = ai->selEntry; if(!(ai->protekted == cAtomProtected_explicit)) if(SelectorIsMember(G, s, sele)) { if(homogenous) CoordSetTransformAtomR44f(cs, a, matrix); else CoordSetTransformAtomTTTf(cs, a, matrix); flag = true; } ai++; } } else { /* transforming whole coordinate set */ if(!use_matrices) { ai = I->AtomInfo; for(a = 0; a < I->NAtom; a++) { if(!(ai->protekted == cAtomProtected_explicit)) { if(homogenous) CoordSetTransformAtomR44f(cs, a, matrix); else CoordSetTransformAtomTTTf(cs, a, matrix); } ai++; } flag = true; CoordSetRecordTxfApplied(cs, matrix, homogenous); } else { ObjectMoleculeTransformState44f(I, state, matrix, false, homogenous, false); } } if(flag) { cs->invalidateRep(cRepAll, cRepInvCoord); ExecutiveUpdateCoordDepends(G, I); } } } if(!all_states) break; } if(log) { OrthoLineType line; WordType sele_str = ",'"; logging = SettingGetGlobal_i(G, cSetting_logging); if(sele >= 0) { strcat(sele_str, sname); } strcat(sele_str, "'"); switch (logging) { case cPLog_pml: sprintf(line, "_ cmd.transform_object('%s',[\\\n_ %15.9f,%15.9f,%15.9f,%15.9f,\\\n_ %15.9f,%15.9f,%15.9f,%15.9f,\\\n_ %15.9f,%15.9f,%15.9f,%15.9f,\\\n_ %15.9f,%15.9f,%15.9f,%15.9f\\\n_ ],%d,%d%s,%d)\n", I->Name, matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5], matrix[6], matrix[7], matrix[8], matrix[9], matrix[10], matrix[11], matrix[12], matrix[13], matrix[14], matrix[15], inp_state + 1, 0, sele_str, homogenous); PLog(G, line, cPLog_no_flush); break; case cPLog_pym: sprintf(line, "cmd.transform_object('%s',[\n%15.9f,%15.9f,%15.9f,%15.9f,\n%15.9f,%15.9f,%15.9f,%15.9f,\n%15.9f,%15.9f,%15.9f,%15.9f,\n%15.9f,%15.9f,%15.9f,%15.9f\n],%d,%d%s,%d)\n", I->Name, matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5], matrix[6], matrix[7], matrix[8], matrix[9], matrix[10], matrix[11], matrix[12], matrix[13], matrix[14], matrix[15], inp_state + 1, 0, sele_str, homogenous); PLog(G, line, cPLog_no_flush); break; default: break; } } return (ok); } /*========================================================================*/ int ObjectMoleculeGetAtomIndex(const ObjectMolecule* I, SelectorID_t sele) { if(sele < 0) return (-1); for (int a = 0; a < I->NAtom; a++) { auto const s = I->AtomInfo[a].selEntry; if(SelectorIsMember(I->G, s, sele)) return (a); } return (-1); } /*========================================================================*/ /** * Update all AtomInfoType.bonded * * Cost: O(NAtom + NBond) */ void ObjectMoleculeUpdateNonbonded(ObjectMolecule * I) { int a; const BondType *b; AtomInfoType *ai; int nAtom = I->NAtom; int nBond = I->NBond; ai = I->AtomInfo.data(); for(a = 0; a < nAtom; a++) (ai++)->bonded = false; b = I->Bond; ai = I->AtomInfo.data(); for(a = 0; a < nBond; a++) { ai[b->index[0]].bonded = true; ai[b->index[1]].bonded = true; b++; } } /** * Get the raw ObjectMolecule::Neighbor storage structure, which is generated * from the ObjectMolecule::Bond array. This structure is cached, to clear the * cache, call ObjectMolecule::invalidate(level=cRepInvBonds). * * Direct usage of this array should be avoided, use the AtomNeighbors() class * instead which is a higher-level and more readable interface to the same * data. * * Changed in PyMOL 2.1.1: Ignore zero-order bonds (PYMOL-3025) * * @return Pointer to cached array, or nullptr if memory allocation failed * * @verbatim 0 list offset for atom 0 = n 1 list offset for atom 1 = n + m + 1 ... n-1 list offset for atom n-1 n count for atom 0 n+1 neighbor of atom 0 n+2 bond index n+3 neighbor of atom 0 n+4 bond index ... n+m -1 terminator for atom 0 n+m+1 count for atom 1 n+m+2 neighbor of atom 1 n+m+3 bond index n+m+4 neighbor of atom 1 n+m+5 bond index etc. NOTE: all atoms have an offset and a terminator whether or not they have any bonds: Here's an example of an ALA [ offet for atom1, offset for atom2, ..., offset for atom9, \ {10, 16, 26, 32, 36, 46, 50, 54, 58, 62, \ (atom1) nbonds=2, neighbor index1=5, bond index=1, neighbor index2=1, bond index=0, null terminator=-1 \ 2, 5, 1, 1, 0, -1, (atom2) nbonds=4, nbr index1=6, bond id=4; nbr idx2=4, bond idx=3; nbr index3=2, bond idx=2; nbr index=0, bond index=0, null=-1 \ 4, 6, 4, 4, 3, 2, 2, 0, 0, -1, (atom3) ... 2, 3, 5, 1, 2, -1, 1, 2, 5, -1, 4, 9, 8, 8, 7, 7, 6, 1, 3, -1, 1, 0, 1, -1, 1, 1, 4, -1, 1, 4, 6, -1, 1, 4, 7, -1, 1, 4, 8, -1 } @endverbatim */ int const* ObjectMolecule::getNeighborArray() const { auto const I = this; /* If no neighbors have been calculated, fill in the table */ if(!I->Neighbor) { /* Create/check the VLA */ auto const size = (I->NAtom * 3) + (I->NBond * 4); const_cast(this)->Neighbor.reset(new int[size]); auto* const neighbor = this->Neighbor.get(); p_return_val_if_fail(neighbor, nullptr); /* initialize; zero out neighbors */ std::fill_n(neighbor, I->NAtom, 0); /* count neighbors for each atom */ const auto* bnd = I->Bond.data(); for(int b = 0; b < I->NBond; b++) { if (bnd->order && !bnd->hasSymOp()) { neighbor[bnd->index[0]]++; neighbor[bnd->index[1]]++; } bnd++; } /* set up offsets and list terminators */ auto c = I->NAtom; for (int a = 0; a < I->NAtom; a++) { auto const d = neighbor[a]; /* get number of neighbors */ neighbor[c] = d; /* store neighbor count */ neighbor[a] = c + d + d + 1; /* set initial position to end of list, we'll fill backwards */ neighbor[neighbor[a]] = -1; /* store terminator */ c += d + d + 2; } /* now load neighbors in a sequential list for each atom (reverse order) */ bnd = I->Bond.data(); for(int b = 0; b < I->NBond; b++, ++bnd) { auto const l0 = bnd->index[0]; auto const l1 = bnd->index[1]; if (!bnd->order || bnd->hasSymOp()) continue; neighbor[l0]--; neighbor[neighbor[l0]] = b; /* store bond indices (for I->Bond) */ neighbor[l0]--; neighbor[neighbor[l0]] = l1; /* store neighbor references (I->AtomInfo, etc.) */ neighbor[l1]--; neighbor[neighbor[l1]] = b; /* store bond indices (for I->Bond) */ neighbor[l1]--; neighbor[neighbor[l1]] = l0; /* store neighbor references (I->AtomInfo, etc.) */ } // adjust down to point to the count, not the first entry for (int a = 0; a < I->NAtom; a++) { if (neighbor[a] >= 0) --neighbor[a]; } } return this->Neighbor.get(); } /*========================================================================*/ #ifndef _PYMOL_NOPY static void copy_and_trim_repr(char* dest, PyObject* obj) { PyObject* repr = PyObject_Repr(obj); const char* src = PyString_AS_STRING(obj); int srclen; if (src[0] == '\'') src++; srclen = strlen(src); if (src[srclen - 1] == '\'') srclen -= 1; strncpy(dest, src, srclen); dest[srclen] = 0; Py_DECREF(repr); } static CoordSet *ObjectMoleculeChemPyModel2CoordSet(PyMOLGlobals * G, PyObject * model, AtomInfoType ** atInfoPtr) { int nAtom, nBond; int a, c; float *coord = nullptr; CoordSet *cset = nullptr; AtomInfoType *atInfo = nullptr, *ai; float *f; BondType *ii, *bond = nullptr; int ok = true; int auto_show = RepGetAutoShowMask(G); int hetatm; int ignore_ids; PyObject *atomList = nullptr; PyObject *bondList = nullptr; PyObject *atom = nullptr; PyObject *bnd = nullptr; PyObject *index = nullptr; PyObject *crd = nullptr; PyObject *tmp = nullptr; ignore_ids = !SettingGetGlobal_b(G, cSetting_preserve_chempy_ids); nAtom = 0; nBond = 0; if(atInfoPtr) atInfo = *atInfoPtr; atomList = PyObject_GetAttrString(model, "atom"); if(atomList && PyList_Check(atomList)) nAtom = PyList_Size(atomList); else ok = ErrMessage(G, __func__, "can't get atom list"); if(ok) { coord = VLAlloc(float, 3 * nAtom); if(atInfo) VLACheck(atInfo, AtomInfoType, nAtom); } if(ok) { f = coord; for(a = 0; a < nAtom; a++) { atom = PyList_GetItem(atomList, a); if(!atom) ok = ErrMessage(G, __func__, "can't get atom"); crd = PyObject_GetAttrString(atom, "coord"); if(!crd) ok = ErrMessage(G, __func__, "can't get coordinates"); else { for(c = 0; c < 3; c++) { tmp = PySequence_GetItem(crd, c); if(tmp) ok = PConvPyObjectToFloat(tmp, f++); Py_XDECREF(tmp); if(!ok) { ErrMessage(G, __func__, "can't read coordinates"); break; } } } Py_XDECREF(crd); ai = atInfo + a; ai->id = a; /* chempy models are zero-based */ if(!ignore_ids) { if(ok) { /* get chempy atom id if extant */ if(PTruthCallStr(atom, "has", "id")) { tmp = PyObject_GetAttrString(atom, "id"); if(tmp) ok = PConvPyObjectToInt(tmp, &ai->id); if(!ok) ErrMessage(G, __func__, "can't read atom identifier"); Py_XDECREF(tmp); } else { ai->id = -1; } } } ai->rank = a; /* ranks are always zero-based */ if(ok) { tmp = PyObject_GetAttrString(atom, "name"); if(tmp) { WordType tmp_word; ok = PConvPyObjectToStrMaxClean(tmp, tmp_word, sizeof(WordType) - 1); AtomInfoCleanAtomName(tmp_word); ai->name = LexIdx(G, tmp_word); } if(!ok) ErrMessage(G, __func__, "can't read name"); Py_XDECREF(tmp); } if(ok) { ai->textType = 0; if(PTruthCallStr(atom, "has", "text_type")) { tmp = PyObject_GetAttrString(atom, "text_type"); if(tmp) { OrthoLineType temp; ok = PConvPyObjectToStrMaxClean(tmp, temp, sizeof(OrthoLineType) - 1); ai->textType = LexIdx(G, temp); } if(!ok) ErrMessage(G, __func__, "can't read text_type"); Py_XDECREF(tmp); } } if(ok) { ai->custom = 0; if(PTruthCallStr(atom, "has", "custom")) { tmp = PyObject_GetAttrString(atom, "custom"); if(tmp) { OrthoLineType temp; ok = PConvPyObjectToStrMaxClean(tmp, temp, sizeof(OrthoLineType) - 1); ai->custom = LexIdx(G, temp); } if(!ok) ErrMessage(G, __func__, "can't read custom"); Py_XDECREF(tmp); } } if(ok) { if(PTruthCallStr(atom, "has", "vdw")) { tmp = PyObject_GetAttrString(atom, "vdw"); if(tmp) ok = PConvPyObjectToFloat(tmp, &ai->vdw); if(!ok) ErrMessage(G, __func__, "can't read vdw radius"); Py_XDECREF(tmp); } else { ai->vdw = 0.0f; } } if(ok) { if(PTruthCallStr(atom, "has", "bohr")) { tmp = PyObject_GetAttrString(atom, "bohr"); if(tmp) ok = PConvPyObjectToFloat(tmp, &ai->elec_radius); if(!ok) ErrMessage(G, __func__, "can't read elec. radius"); Py_XDECREF(tmp); } else { ai->elec_radius = 0.0F; } } if(ok) { if(PTruthCallStr(atom, "has", "stereo")) { tmp = PyObject_GetAttrString(atom, "stereo"); if(tmp) { char tmp_char; ok = PConvPyObjectToChar(tmp, &tmp_char); ai->stereo = tmp_char; } if(!ok) ErrMessage(G, __func__, "can't read stereo"); Py_XDECREF(tmp); } else { ai->stereo = 0; } } if(ok) { if(PTruthCallStr(atom, "has", "numeric_type")) { tmp = PyObject_GetAttrString(atom, "numeric_type"); if(tmp) ok = PConvPyObjectToInt(tmp, &ai->customType); if(!ok) ErrMessage(G, __func__, "can't read numeric_type"); Py_XDECREF(tmp); } else { ai->customType = cAtomInfoNoType; } } if(ok) { if(PTruthCallStr(atom, "has", "formal_charge")) { tmp = PyObject_GetAttrString(atom, "formal_charge"); if(tmp) ok = PConvPyObjectToChar(tmp, (char *) &ai->formalCharge); if(!ok) ErrMessage(G, __func__, "can't read formal_charge"); Py_XDECREF(tmp); } else { ai->formalCharge = 0; } } if(ok) { if(PTruthCallStr(atom, "has", "partial_charge")) { tmp = PyObject_GetAttrString(atom, "partial_charge"); if(tmp) ok = PConvPyObjectToFloat(tmp, &ai->partialCharge); if(!ok) ErrMessage(G, __func__, "can't read partial_charge"); Py_XDECREF(tmp); } else { ai->partialCharge = 0.0; } } if(ok) { if(PTruthCallStr(atom, "has", "flags")) { tmp = PyObject_GetAttrString(atom, "flags"); if(tmp) ok = PConvPyObjectToInt(tmp, (int *) &ai->flags); if(!ok) ErrMessage(G, __func__, "can't read flags"); Py_XDECREF(tmp); } else { ai->flags = 0; } } if(ok) { char buf[8] = ""; tmp = PyObject_GetAttrString(atom, "resn"); if(tmp) ok = PConvPyObjectToStrMaxClean(tmp, buf, sizeof(buf) - 1); ai->resn = LexIdx(G, buf); if(!ok) ErrMessage(G, __func__, "can't read resn"); Py_XDECREF(tmp); } if(ok) { tmp = PyObject_GetAttrString(atom, "ins_code"); if(tmp) { ResIdent tmp_ins_code; ok = PConvPyObjectToStrMaxClean(tmp, tmp_ins_code, sizeof(ResIdent) - 1); if(!ok) ErrMessage(G, __func__, "can't read ins_code"); else if(tmp_ins_code[0] != '?') { ai->setInscode(tmp_ins_code[0]); } } Py_XDECREF(tmp); } if(ok) { if(PTruthCallStr(atom, "has", "resi_number")) { tmp = PyObject_GetAttrString(atom, "resi_number"); if(tmp) ok = PConvPyObjectToInt(tmp, &ai->resv); if(!ok) ErrMessage(G, __func__, "can't read resi_number"); Py_XDECREF(tmp); } } if(ok) { OrthoLineType temp = ""; tmp = PyObject_GetAttrString(atom, "segi"); if(tmp) PConvPyObjectToStrMaxClean(tmp, temp, sizeof(OrthoLineType) - 1); ai->segi = LexIdx(G, temp); if(!ok) ErrMessage(G, __func__, "can't read segi"); Py_XDECREF(tmp); } if(ok) { tmp = PyObject_GetAttrString(atom, "b"); if(tmp) ok = PConvPyObjectToFloat(tmp, &ai->b); if(!ok) ErrMessage(G, __func__, "can't read b value"); Py_XDECREF(tmp); } if(ok) { tmp = PyObject_GetAttrString(atom, "u"); if(tmp) { ok = PConvPyObjectToFloat(tmp, &ai->b); if(!ok) ErrMessage(G, __func__, "can't read u value"); else ai->b *= (8 * cPI * cPI); /* B-factor = 8 pi^2 U-factor */ } else { assert(PyErr_Occurred() && PyErr_ExceptionMatches(PyExc_AttributeError)); PyErr_Clear(); } Py_XDECREF(tmp); } if(ok) { tmp = PyObject_GetAttrString(atom, "u_aniso"); if(tmp) { float u[6]; if(PConvPyListToFloatArrayInPlace(tmp, u, 6)) { // only allocate if not all zero if(u[0] || u[1] || u[2] || u[3] || u[4] || u[5]) std::copy_n(u, 6, ai->get_anisou()); } Py_DECREF(tmp); } else { assert(PyErr_Occurred() && PyErr_ExceptionMatches(PyExc_AttributeError)); PyErr_Clear(); } } if(ok) { tmp = PyObject_GetAttrString(atom, "q"); if(tmp) ok = PConvPyObjectToFloat(tmp, &ai->q); if(!ok) ErrMessage(G, __func__, "can't read occupancy"); Py_XDECREF(tmp); } if(ok) { OrthoLineType temp = ""; tmp = PyObject_GetAttrString(atom, "chain"); if(tmp) PConvPyObjectToStrMaxClean(tmp, temp, sizeof(OrthoLineType) - 1); ai->chain = LexIdx(G, temp); if(!ok) ErrMessage(G, __func__, "can't read chain"); Py_XDECREF(tmp); } if(ok) { tmp = PyObject_GetAttrString(atom, "hetatm"); if(tmp) ok = PConvPyObjectToInt(tmp, &hetatm); if(!ok) ErrMessage(G, __func__, "can't read hetatm"); else { ai->hetatm = hetatm; if(!PTruthCallStr(atom, "has", "flags")) { if(ai->hetatm) ai->flags = cAtomFlag_ignore; } } Py_XDECREF(tmp); } if(ok) { tmp = PyObject_GetAttrString(atom, "alt"); if(tmp) ok = PConvPyObjectToStrMaxClean(tmp, ai->alt, sizeof(Chain) - 1); if(!ok) ErrMessage(G, __func__, "can't read alternate conformation"); Py_XDECREF(tmp); } if(ok) { tmp = PyObject_GetAttrString(atom, "symbol"); if(tmp) ok = PConvPyObjectToStrMaxClean(tmp, ai->elem, sizeof(ElemName) - 1); if(!ok) ErrMessage(G, __func__, "can't read symbol"); Py_XDECREF(tmp); } if(ok) { tmp = PyObject_GetAttrString(atom, "ss"); if(tmp) ok = PConvPyObjectToStrMaxClean(tmp, ai->ssType, sizeof(SSType) - 1); if(!ok) ErrMessage(G, __func__, "can't read secondary structure"); Py_XDECREF(tmp); } if(ok && PyObject_HasAttrString(atom, "label")) { tmp = PyObject_GetAttrString(atom, "label"); if(tmp) { if (PyString_Check(tmp)) ai->label = LexIdx(G, PyString_AsString(tmp)); } Py_XDECREF(tmp); } if(ok && PyObject_HasAttrString(atom, "sphere_scale")) { tmp = PyObject_GetAttrString(atom, "sphere_scale"); if(tmp) { float value; if(PConvPyFloatToFloat(tmp, &value)) { SettingSet(G, cSetting_sphere_scale, value, ai); } } Py_XDECREF(tmp); } if(ok && PyObject_HasAttrString(atom, "cartoon_color")) { tmp = PyObject_GetAttrString(atom, "cartoon_color"); if(tmp) { int color_index; ok = PConvPyObjectToInt(tmp, &color_index); if(!ok) ErrMessage(G, __func__, "bad cartoon color info"); else { SettingSet(G, cSetting_cartoon_color, color_index, ai); } } Py_XDECREF(tmp); } if(ok && PyObject_HasAttrString(atom, "cartoon_transparency")) { tmp = PyObject_GetAttrString(atom, "cartoon_transparency"); if(tmp) { float alpha_val; ok = PConvPyObjectToFloat(tmp, &alpha_val); if(!ok) ErrMessage(G, __FUNCTION__, "bad alpha value"); else { SettingSet(G, cSetting_cartoon_transparency, alpha_val, ai); } } Py_XDECREF(tmp); } if(ok && PyObject_HasAttrString(atom, "cartoon_trgb")) { tmp = PyObject_GetAttrString(atom, "cartoon_trgb"); if(tmp) { unsigned int trgb; ok = PConvPyObjectToInt(tmp, (signed int *) &trgb); if(!ok) ErrMessage(G, __func__, "bad cartoon color info"); else { char color_name[24]; sprintf(color_name, "0x%08x", trgb); SettingSet(G, cSetting_cartoon_color, ColorGetIndex(G, color_name), ai); } } Py_XDECREF(tmp); } if(ok && PyObject_HasAttrString(atom, "label_trgb")) { tmp = PyObject_GetAttrString(atom, "label_trgb"); if(tmp) { unsigned int trgb; ok = PConvPyObjectToInt(tmp, (signed int *) &trgb); if(!ok) ErrMessage(G, __func__, "bad label color info"); else { char color_name[24]; sprintf(color_name, "0x%08x", trgb); SettingSet(G, cSetting_label_color, ColorGetIndex(G, color_name), ai); } } Py_XDECREF(tmp); } if(ok && PyObject_HasAttrString(atom, "ribbon_color")) { tmp = PyObject_GetAttrString(atom, "ribbon_color"); if(tmp) { int color_index; ok = PConvPyObjectToInt(tmp, &color_index); if(!ok) ErrMessage(G, __func__, "bad ribbon color info"); else { SettingSet(G, cSetting_ribbon_color, color_index, ai); } } Py_XDECREF(tmp); } if(ok && PyObject_HasAttrString(atom, "ribbon_trgb")) { tmp = PyObject_GetAttrString(atom, "ribbon_trgb"); if(tmp) { unsigned int trgb; ok = PConvPyObjectToInt(tmp, (signed int *) &trgb); if(!ok) ErrMessage(G, __func__, "bad cartoon color info"); else { char color_name[24]; sprintf(color_name, "0x%08x", trgb); SettingSet(G, cSetting_ribbon_color, ColorGetIndex(G, color_name), ai); } } Py_XDECREF(tmp); } atInfo[a].visRep = auto_show; if(ok && PyObject_HasAttrString(atom, "visible")) { unsigned int vis; tmp = PyObject_GetAttrString(atom, "visible"); if(tmp) { ok = PConvPyObjectToInt(tmp, (signed int *) &vis); if(!ok) ErrMessage(G, __func__, "bad visibility info"); else { atInfo[a].visRep = vis; if (!(ai->flags & cAtomFlag_class)) { ai->flags |= cAtomFlag_inorganic; // suppress auto_show_classified } } } Py_XDECREF(tmp); } if(ok && atInfo) { AtomInfoAssignParameters(G, ai); AtomInfoAssignColors(G, ai); } if(ok && PyObject_HasAttrString(atom, "trgb")) { /* were we given a Transparency-Red-Blue-Green value? */ unsigned int trgb; tmp = PyObject_GetAttrString(atom, "trgb"); if(tmp) { ok = PConvPyObjectToInt(tmp, (signed int *) &trgb); if(!ok) ErrMessage(G, __func__, "bad color info"); else { char color_name[24]; sprintf(color_name, "0x%08x", trgb); atInfo[a].color = ColorGetIndex(G, color_name); } Py_DECREF(tmp); } } if(!ok) break; assert(!PyErr_Occurred()); } } if(nAtom) { bondList = PyObject_GetAttrString(model, "bond"); if(bondList && PyList_Check(bondList)) nBond = PyList_Size(bondList); else ok = ErrMessage(G, __func__, "can't get bond list"); if(ok) { bond = VLACalloc(BondType, nBond); ii = bond; for(a = 0; a < nBond; a++) { bnd = PyList_GetItem(bondList, a); if(!bnd) ok = ErrMessage(G, __func__, "can't get bond"); index = PyObject_GetAttrString(bnd, "index"); if(!index) ok = ErrMessage(G, __func__, "can't get bond indices"); else { for(c = 0; c < 2; c++) { tmp = PyList_GetItem(index, c); if(tmp) ok = PConvPyObjectToInt(tmp, &ii->index[c]); if(!ok) { ErrMessage(G, __func__, "can't read coordinates"); break; } } } if(ok) { int order = 0; tmp = PyObject_GetAttrString(bnd, "order"); if(tmp) ok = PConvPyObjectToInt(tmp, &order); if(!ok) ErrMessage(G, __func__, "can't read bond order"); Py_XDECREF(tmp); ii->order = order; } auto const set_symop = [&](const char* key) { if (ok && PyObject_HasAttrString(bnd, key)) { ok = false; if (auto tmp = PyObject_GetAttrString(bnd, key)) { if (auto const* code = PyUnicode_AsUTF8(tmp)) { ii->symop_2.reset(code); ok = true; } Py_DECREF(tmp); } } }; set_symop("symmetry_2"); if(ok && PyObject_HasAttrString(bnd, "stick_radius")) { tmp = PyObject_GetAttrString(bnd, "stick_radius"); if(tmp) { float value; if(PConvPyFloatToFloat(tmp, &value)) { SettingSet(G, cSetting_stick_radius, value, ii); } } Py_XDECREF(tmp); } if(ok && PyObject_HasAttrString(bnd, "valence")) { tmp = PyObject_GetAttrString(bnd, "valence"); if(tmp) { int value; if(PConvPyIntToInt(tmp, &value)) { SettingSet(G, cSetting_valence, value, ii); } } Py_XDECREF(tmp); } Py_XDECREF(index); ii++; } } } Py_XDECREF(atomList); Py_XDECREF(bondList); if(ok) { cset = CoordSetNew(G); cset->NIndex = nAtom; cset->Coord = pymol::vla_take_ownership(coord); cset->NTmpBond = nBond; cset->TmpBond = pymol::vla_take_ownership(bond); { PyObject *propList = nullptr, *propName, *propValue, *propKeyValue; OrthoLineType nameString; int nProps = 0; // need to check since fragments and other molecules use unpickle // and pml files might not have molecule_properties if (PyObject_HasAttrString(model, "molecule_properties")) { propList = PyObject_GetAttrString(model, "molecule_properties"); if (propList && PyList_Check(propList)) nProps = PyList_Size(propList); else ok = ErrMessage(G, __func__, "can't get molecule properties"); } if (nProps) { for (a = 0; a < nProps; a++) { propKeyValue = PyList_GetItem(propList, a); propName = PyList_GetItem(propKeyValue, 0); propValue = PyList_GetItem(propKeyValue, 1); copy_and_trim_repr(nameString, propName); PropertySet(G, cset, nameString, propValue); } } Py_XDECREF(propList); } } else { VLAFreeP(bond); VLAFreeP(coord); } if(atInfoPtr) *atInfoPtr = atInfo; if(PyErr_Occurred()) PyErr_Print(); return (cset); } #endif /*========================================================================*/ ObjectMolecule *ObjectMoleculeLoadChemPyModel(PyMOLGlobals * G, ObjectMolecule * I, PyObject * model, int frame, int discrete) { #ifdef _PYMOL_NOPY return nullptr; #else CoordSet *cset = nullptr; auto atInfo = pymol::vla(10); int ok = true; int isNew = true; unsigned int nAtom = 0; int fractional = false; int connect_mode = -1; int auto_bond = false; PyObject *tmp, *mol; if(!I) isNew = true; else isNew = false; if(ok) { if(isNew) { I = (ObjectMolecule *) new ObjectMolecule(G, discrete); std::swap(atInfo, I->AtomInfo); isNew = true; } else { if (discrete > -1) ObjectMoleculeSetDiscrete(G, I, discrete); } if(isNew) { I->Color = AtomInfoUpdateAutoColor(G); } cset = ObjectMoleculeChemPyModel2CoordSet(G, model, &atInfo); if(!cset) ok = false; else { mol = PyObject_GetAttrString(model, "molecule"); if(mol) { if(PyObject_HasAttrString(mol, "title")) { tmp = PyObject_GetAttrString(mol, "title"); if(tmp) { UtilNCopy(cset->Name, PyString_AsString(tmp), sizeof(WordType)); Py_DECREF(tmp); if(!strcmp(cset->Name, "untitled")) /* ignore untitled */ cset->Name[0] = 0; } } Py_DECREF(mol); } else if (PyErr_Occurred() == PyExc_AttributeError) { PyErr_Clear(); } if(PyObject_HasAttrString(model, "spheroid") && PyObject_HasAttrString(model, "spheroid_normals")) { tmp = PyObject_GetAttrString(model, "spheroid"); if(tmp) { PConvFromPyObject(G, tmp, cset->Spheroid); Py_DECREF(tmp); } tmp = PyObject_GetAttrString(model, "spheroid_normals"); if(tmp) { PConvFromPyObject(G, tmp, cset->SpheroidNormal); Py_DECREF(tmp); } } if(PyObject_HasAttrString(model, "spacegroup") && PyObject_HasAttrString(model, "cell")) { CSymmetry *symmetry = new CSymmetry(G); if(symmetry) { tmp = PyObject_GetAttrString(model, "spacegroup"); if(tmp) { const char *tmp_str = nullptr; if(PConvPyStrToStrPtr(tmp, &tmp_str)) { symmetry->setSpaceGroup(tmp_str); } Py_DECREF(tmp); } tmp = PyObject_GetAttrString(model, "cell"); if(tmp) { float cell[6]; if(PConvPyListToFloatArrayInPlace(tmp, cell, 6)) { symmetry->Crystal.setDims(cell); symmetry->Crystal.setAngles(cell + 3); } Py_DECREF(tmp); } cset->Symmetry = std::unique_ptr(symmetry); } } if(PyObject_HasAttrString(model, "fractional")) { tmp = PyObject_GetAttrString(model, "fractional"); if(tmp) { int tmp_int = 0; if(PConvPyIntToInt(tmp, &tmp_int)) { fractional = tmp_int; } Py_DECREF(tmp); } } if(PyObject_HasAttrString(model, "connect_mode")) { tmp = PyObject_GetAttrString(model, "connect_mode"); if(tmp) { int tmp_int = 0; if(PConvPyIntToInt(tmp, &tmp_int)) { auto_bond = true; connect_mode = tmp_int; } Py_DECREF(tmp); } } nAtom = cset->NIndex; } } /* include coordinate set */ if(ok) { if(frame < 0) frame = I->NCSet; if(I->DiscreteFlag && atInfo) { unsigned int a; int fp1 = frame + 1; AtomInfoType *ai = atInfo.data(); for(a = 0; a < nAtom; a++) { (ai++)->discrete_state = fp1; } } cset->Obj = I; cset->enumIndices(); cset->invalidateRep(cRepAll, cRepInvRep); if(isNew) { std::swap(I->AtomInfo, atInfo); } else { ObjectMoleculeMerge(I, std::move(atInfo), cset, false, cAIC_AllMask, true); } if(isNew) I->NAtom = nAtom; VLACheck(I->CSet, CoordSet *, frame); if(I->NCSet <= frame) I->NCSet = frame + 1; delete I->CSet[frame]; I->CSet[frame] = cset; if (fractional && cset->Symmetry) { CoordSetFracToReal(cset, &cset->Symmetry->Crystal); } if(ok && isNew) ok &= ObjectMoleculeConnect(I, cset, auto_bond, connect_mode); if(cset->Symmetry && (!I->Symmetry)) { I->Symmetry.reset(new CSymmetry(*cset->Symmetry)); } SceneCountFrames(G); if (ok) ok &= ObjectMoleculeExtendIndices(I, frame); if (ok) ok &= ObjectMoleculeSort(I); if (ok){ ObjectMoleculeUpdateIDNumbers(I); ObjectMoleculeUpdateNonbonded(I); } } return (I); #endif } /*========================================================================*/ /** * Update coordinates of an exisiting coordset or insert/append a new * coordset. * * coords: flat coordinate array of length NIndex * 3 * coords_len: must be NIndex * 3 * frame: coordset index */ ObjectMolecule *ObjectMoleculeLoadCoords(PyMOLGlobals * G, ObjectMolecule * I, const float * coords, int coords_len, int frame) { CoordSet *cset = nullptr; int a; bool is_new = false; if(frame < 0) { frame = I->NCSet; } else if (frame < I->NCSet) { cset = I->CSet[frame]; } if (!cset) { // template coordinate set, if available cset = I->CSTmpl; // find any coordinate set for(a = 0; !cset && a < I->NCSet; ++a) cset = I->CSet[a]; ok_assert(1, cset); cset = CoordSetCopy(cset); is_new = true; } // check atom count if(coords_len != cset->NIndex * 3) { ErrMessage(G, "LoadCoords", "atom count mismatch"); ok_raise(1); } // copy coordinates for(a = 0; a < coords_len; ++a) { cset->Coord[a] = coords[a]; } cset->invalidateRep(cRepAll, cRepInvRep); // include coordinate set if (is_new) { VLACheck(I->CSet, CoordSet *, frame); if(I->NCSet <= frame) I->NCSet = frame + 1; I->CSet[frame] = cset; SceneCountFrames(G); } // success return (I); // error handling ok_except1: if(is_new && cset) delete cset; ErrMessage(G, "LoadCoords", "failed"); return nullptr; } /*========================================================================*/ /* see above... but look up object by name */ ObjectMolecule *ObjectMoleculeLoadCoords(PyMOLGlobals * G, const char * name, const float * coords, int coords_len, int frame) { pymol::CObject * cobj = ExecutiveFindObjectByName(G, name); if(!cobj || cobj->type != cObjectMolecule) { ErrMessage(G, "LoadCoords", "named object molecule not found."); return nullptr; } return ObjectMoleculeLoadCoords(G, (ObjectMolecule *) cobj, coords, coords_len, frame); } /*========================================================================*/ /* see above... but take coordinates from Python list * * coords: 2d Python float sequence with shape (NIndex, 3) */ ObjectMolecule *ObjectMoleculeLoadCoords(PyMOLGlobals * G, ObjectMolecule * I, PyObject * coords, int frame) { #ifdef _PYMOL_NOPY return nullptr; #else CoordSet *cset = nullptr; int a, b, l; PyObject *v, *w; float *f; bool is_new = false; if(!PySequence_Check(coords)) { ErrMessage(G, "LoadCoords", "passed argument is not a sequence"); ok_raise(1); } if(frame < 0) { frame = I->NCSet; } else if (frame < I->NCSet) { cset = I->CSet[frame]; } if (!cset) { // template coordinate set, if available cset = I->CSTmpl; // find any coordinate set for(a = 0; !cset && a < I->NCSet; ++a) cset = I->CSet[a]; ok_assert(1, cset); cset = CoordSetCopy(cset); is_new = true; } // check atom count l = PySequence_Size(coords); if(l != cset->NIndex) { ErrMessage(G, "LoadCoords", "atom count mismatch"); ok_raise(1); } // copy coordinates f = cset->Coord.data(); for(a = 0; a < l; a++) { v = PySequence_ITEM(coords, a); for(b = 0; b < 3; b++) { if(!(w = PySequence_GetItem(v, b))) break; f[a * 3 + b] = (float) PyFloat_AsDouble(w); Py_DECREF(w); } Py_DECREF(v); ok_assert(2, !PyErr_Occurred()); } cset->invalidateRep(cRepAll, cRepInvRep); // include coordinate set if (is_new) { VLACheck(I->CSet, CoordSet *, frame); if(I->NCSet <= frame) I->NCSet = frame + 1; I->CSet[frame] = cset; SceneCountFrames(G); } // success return (I); // error handling ok_except2: PyErr_Print(); ok_except1: if(is_new && cset) delete cset; ErrMessage(G, "LoadCoords", "failed"); return nullptr; #endif } /*========================================================================*/ int ObjectMoleculeExtendIndices(ObjectMolecule * I, int state) { int a; CoordSet *cs; if(I->DiscreteFlag && (state >= 0)) { /* if object is discrete, then we don't need to extend each state, just the current one (which updates object DiscreteAtmToIdx) */ cs = I->CSTmpl; ok_assert(1, (!cs) || cs->extendIndices(I->NAtom)); if(state < I->NCSet) { cs = I->CSet[state]; ok_assert(1, (!cs) || cs->extendIndices(I->NAtom)); } } else { /* do all states */ for(a = -1; a < I->NCSet; a++) { cs = (a < 0) ? I->CSTmpl : I->CSet[a]; ok_assert(1, (!cs) || cs->extendIndices(I->NAtom)); } } return true; ok_except1: return false; } /*========================================================================*/ static char* sdl_mol_prop_name(const char* prop, short* isprop) { int i, j, len; char* name = nullptr; /* for now, need to handle all special characters */ if (strchr(prop, '/') || strchr(prop, '\\')) { if (isprop) *isprop = false; name = (char*) strdup(prop); return name; } /* Rule 1: If , return substring between angle brackets */ for (i = 0; i < strlen(prop); i++) { if (prop[i] == '<') { for (len = 0, j = i + 1; j < strlen(prop); j++) { if (prop[j] == '>') { len = j - i - 1; /* change to j-i+1 to keep <> */ name = (char*) malloc(len + 1); strncpy(name, prop + i + 1, len); /* change to prop+i to keep <> */ name[len] = '\0'; if (isprop) *isprop = true; return name; } } } } /* Rule 2: Else if prop contains DT\d+, return that. */ for (i = 0; i < strlen(prop); i++) { if (prop[i] == 'D' && prop[i + 1] == 'T' && isdigit(prop[i + 2])) { for (len = 0, j = i + 3; j < strlen(prop); j++) { if (!isdigit(prop[j])) { len = j - i; name = (char*) malloc(len + 1); strncpy(name, prop + i, len); name[len] = '\0'; if (isprop) *isprop = true; return name; } } } } /* Rule 3: Else, strip [ribs]_*_ prefix itself. */ for (i = 0; i < strlen(prop); i++) { if (prop[i] == '_') { for (j = i + 1; j < strlen(prop); j++) { if (prop[j] == '_') { name = (char*) malloc(strlen(prop + j + 1) + 1); strcpy(name, prop + j + 1); if (isprop) *isprop = true; return name; } } } } /* Rule 4: else return prop itself */ if (isprop) *isprop = false; name = (char*) strdup(prop); return name; } static short is_sdl_mol_prop_name(const char* prop) { short isprop; char* cctmp = pymol::malloc(strlen(prop) + 1); strcpy(cctmp, prop); free(sdl_mol_prop_name(cctmp, &isprop)); FreeP(cctmp); return isprop; } /*========================================================================*/ static CoordSet *ObjectMoleculeMOLStr2CoordSet(PyMOLGlobals * G, const char *buffer, AtomInfoType ** atInfoPtr, const char **restart , short loadpropertiesall, OVLexicon *loadproplex ) { const char *p; int nAtom, nBond; int a, cnt, atm, chg; float *coord = nullptr; CoordSet *cset = nullptr; AtomInfoType *atInfo = nullptr; char cc[MAXLINELEN], cc1[MAXLINELEN], resn[MAXLINELEN] = "UNK"; float *f; BondType *ii; BondType *bond = nullptr; int ok = true; int auto_show = RepGetAutoShowMask(G); WordType nameTmp; p = buffer; nAtom = 0; if(atInfoPtr) atInfo = *atInfoPtr; /* p=ParseWordCopy(nameTmp,p,sizeof(WordType)-1); */ p = ParseNCopy(nameTmp, p, sizeof(WordType) - 1); PRINTFB(G, FB_ObjectMolecule, FB_Blather) " ObjMolMOLStr2CoordSet: title '%s'\n", nameTmp ENDFB(G) p = nextline(p); p = nextline(p); p = nextline(p); if(ok) { p = ncopy(cc, p, 3); if(sscanf(cc, "%d", &nAtom) != 1) ok = ErrMessage(G, "ReadMOLFile", "bad atom count"); } if(ok) { p = ncopy(cc, p, 3); if(sscanf(cc, "%d", &nBond) != 1) ok = ErrMessage(G, "ReadMOLFile", "bad bond count"); } if(ok) { coord = VLAlloc(float, 3 * nAtom); if(atInfo) VLACheck(atInfo, AtomInfoType, nAtom); } p = nextline(p); /* read coordinates and atom names */ if(ok) { f = coord; for(a = 0; a < nAtom; a++) { if(ok) { p = ncopy(cc, p, 10); if(sscanf(cc, "%f", f++) != 1) ok = ErrMessage(G, "ReadMOLFile", "bad coordinate"); } if(ok) { p = ncopy(cc, p, 10); if(sscanf(cc, "%f", f++) != 1) ok = ErrMessage(G, "ReadMOLFile", "bad coordinate"); } if(ok) { p = ncopy(cc, p, 10); if(sscanf(cc, "%f", f++) != 1) ok = ErrMessage(G, "ReadMOLFile", "bad coordinate"); } if(ok) { p = nskip(p, 1); p = ntrim(cc, p, 3); strncpy(atInfo[a].elem, cc, cElemNameLen); atInfo[a].name = LexIdx(G, cc); atInfo[a].visRep = auto_show; } if(ok) { int tmp_int; p = nskip(p, 2); p = ncopy(cc, p, 3); if(sscanf(cc, "%hhi", &atInfo[a].formalCharge) == 1) { if(atInfo[a].formalCharge) { atInfo[a].formalCharge = 4 - atInfo[a].formalCharge; } } p = ncopy(cc, p, 3); if(sscanf(cc, "%d", &tmp_int) != 1) atInfo[a].stereo = 0; else atInfo[a].stereo = tmp_int; } if(ok && atInfo) { atInfo[a].id = a + 1; atInfo[a].rank = a; atInfo[a].resn = LexIdx(G, resn); atInfo[a].hetatm = true; AtomInfoAssignParameters(G, atInfo + a); AtomInfoAssignColors(G, atInfo + a); atInfo[a].alt[0] = 0; atInfo[a].segi = 0; atInfo[a].inscode = 0; } p = nextline(p); if(!ok) break; } } if(ok) { bond = VLACalloc(BondType, nBond); ii = bond; for(a = 0; a < nBond; a++) { if(ok) { p = ncopy(cc, p, 3); if(sscanf(cc, "%d", &ii->index[0]) != 1) ok = ErrMessage(G, "ReadMOLFile", "bad bond atom"); } if(ok) { p = ncopy(cc, p, 3); if(sscanf(cc, "%d", &ii->index[1]) != 1) ok = ErrMessage(G, "ReadMOLFile", "bad bond atom"); } if(ok) { int order = 0; p = ncopy(cc, p, 3); if(sscanf(cc, "%d", &order) != 1) ok = ErrMessage(G, "ReadMOLFile", "bad bond order"); ii->order = order; } if(ok) { p = ncopy(cc, p, 3); // stereo } ii++; if(!ok) break; p = nextline(p); } ii = bond; for(a = 0; a < nBond; a++) { ii->index[0]--; /* adjust bond indexs down one */ ii->index[1]--; ii++; } } while(*p) { /* read M CHG records */ auto p_line = p; p = ncopy(cc, p, 6); if(cc[5] == ' ') cc[5] = 0; if((!strcmp(cc, "M END")) || (!strcmp(cc, "M END"))) { /* denotes end of MOL block */ break; } if((!strcmp(cc, "M CHG")) || (!strcmp(cc, "M CHG"))) { p = ncopy(cc, p, 3); if(sscanf(cc, "%d", &cnt) == 1) { while(cnt--) { p = ncopy(cc, p, 4); p = ncopy(cc1, p, 4); if(!((*cc) || (*cc1))) break; if((sscanf(cc, "%d", &atm) == 1) && (sscanf(cc1, "%d", &chg) == 1)) { atm--; if((atm >= 0) && (atm < nAtom)) atInfo[atm].formalCharge = chg; } } } } else if (!strcmp(cc, "M V30")) { p = MOLV3000Parse(G, p_line, atInfo, bond, coord, nAtom, nBond); if (!p) { ok = false; break; } continue; } p = nextline(p); } if(ok) { (*restart) = p; cset = CoordSetNew(G); cset->NIndex = nAtom; cset->Coord = pymol::vla_take_ownership(coord); cset->NTmpBond = nBond; cset->TmpBond = pymol::vla_take_ownership(bond); strcpy(cset->Name, nameTmp); p = nextline(p); // Need to read in the properties { OVreturn_word ovresult; const char* ptmp = p; const char* np = ptmp; char *cc = VLACalloc(char, 1024), *cctmp = VLACalloc(char, 1024); int ccp = 0; char* prop_name = nullptr; short is_prop_name, line = 0; int nchs; cc[0] = 0; ncopy(cctmp, np, 4); cctmp[4] = 0; while (strcmp(cctmp, "$$$$")) { ptmp = np; np = nextline(np); if (np == ptmp) break; nchs = (np - ptmp - 1); if (nchs) { VLACheck(cctmp, char, nchs + 1); ncopy(cctmp, ptmp, nchs); cctmp[nchs] = 0; is_prop_name = is_sdl_mol_prop_name(cctmp); if (is_prop_name) { // is_prop_name if (prop_name) { short load = loadpropertiesall; if (!load && loadproplex) { ovresult = OVLexicon_BorrowFromCString(loadproplex, prop_name); load = (ovresult.status == OVstatus_SUCCESS); } if (load) { PropertyCheckUniqueID(G, cset); PropertySetromString(G, cset->prop_id, prop_name, cc); ccp = 0; cc[0] = 0; line = 0; } free(prop_name); } prop_name = sdl_mol_prop_name(cctmp, nullptr); } else { if (prop_name) { int sz = strlen(cctmp); if (line == 1) { VLACheck(cc, char, ccp + 2); cc[ccp++] = '\n'; } VLACheck(cc, char, ccp + sz + 2); ncopy(&cc[ccp], cctmp, sz); ccp += sz; VLACheck(cc, char, ccp + 2); if (line) { cc[ccp++] = '\n'; } cc[ccp] = 0; line++; } } } ncopy(cctmp, np, 4); } if (prop_name) { short load = loadpropertiesall; if (!load && loadproplex) { ovresult = OVLexicon_BorrowFromCString(loadproplex, prop_name); load = (ovresult.status == OVstatus_SUCCESS); } if (load) { PropertyCheckUniqueID(G, cset); PropertySetromString(G, cset->prop_id, prop_name, cc); ccp = 0; cc[0] = 0; line = 0; } free(prop_name); } VLAFreeP(cc); VLAFreeP(cctmp); } } else { VLAFreeP(bond); VLAFreeP(coord); (*restart) = nullptr; } if(atInfoPtr) *atInfoPtr = atInfo; return (cset); } /*========================================================================*/ static CoordSet *ObjectMoleculeSDF2Str2CoordSet(PyMOLGlobals * G, const char *buffer, AtomInfoType ** atInfoPtr, const char **next_mol , short loadpropertiesall, OVLexicon *loadproplex ) { char cc[MAXLINELEN]; const char *p; CoordSet *result = nullptr; result = ObjectMoleculeMOLStr2CoordSet(G, buffer, atInfoPtr, next_mol , loadpropertiesall, loadproplex ); p = *next_mol; if(p) { while(*p) { /* we simply need to skip until we've read past the end of the SDF record */ p = ncopy(cc, p, 4); p = nextline(p); if(!strcmp(cc, "$$$$")) { /* SDF record separator... */ break; } } if(!*p) p = nullptr; } *next_mol = p; return result; } /** * Get the sum of bond orders for every atom. For this purpose, aromatic * bonds will be considered either single or double bond, based on * the entire system (heuristic method, room for improvement). */ static std::vector get_bond_order_sums(ObjectMolecule * obj) { std::vector valences(obj->NAtom); std::vector freevalences(obj->NAtom); std::vector orders(obj->NBond); // bond order sums as if all aromatic bonds were single bonds for (int b = 0; b < obj->NBond; ++b) { auto bond = obj->Bond + b; auto order = bond->order; orders[b] = order; if (order > 3) order = 1; valences[bond->index[0]] += order; valences[bond->index[1]] += order; } // determine free valences as if all aromatic bonds were single bonds for (int atm = 0; atm < obj->NAtom; ++atm) { int tmp = 0; switch (obj->AtomInfo[atm].protons) { case cAN_C: tmp = 4; break; case cAN_N: case cAN_P: tmp = 5; break; case cAN_O: case cAN_S: tmp = 6; break; } if (tmp) { tmp -= valences[atm]; if (tmp) { freevalences[atm] = (tmp - 1) % 2 + 1; } } } // (This is a heuristic, gets most of the ZINC dataset correct) // Do two passes over all aromatic bonds. // 1st pass: assign double bonds between atoms with `freevalence == 1` // 2nd pass: assign double bonds between atoms with `freevalence >= 1` for (int secondpass = 0; secondpass < 2; ++secondpass) { for (int b = 0; b < obj->NBond; ++b) { if (orders[b] == 4) { auto atm1 = obj->Bond[b].index[0]; auto atm2 = obj->Bond[b].index[1]; if (secondpass ? (freevalences[atm1] >= 1 && freevalences[atm2] >= 1 ) : (freevalences[atm1] == 1 && freevalences[atm2] == 1)) { freevalences[atm1] = 0; freevalences[atm2] = 0; valences[atm1] += 1; valences[atm2] += 1; orders[b] = 2; } } } } return valences; } /** * For each atom, set `formalCharge` based on mol2 `textType` * * See also: getMOL2Type() in layer2/Mol2Typing.cpp */ static void ObjectMoleculeMOL2SetFormalCharges(PyMOLGlobals *G, ObjectMolecule *obj){ // check if structure has explicit hydrogens bool has_hydrogens = false; for (int at = 0; at < obj->NAtom; ++at) { auto ai = obj->AtomInfo + at; if (ai->isHydrogen()) { has_hydrogens = true; break; } } if (!has_hydrogens) { // could eventually do PDB nomenclature charge assignment here... return; } // Unfortunately, aromatic bonds (type 4) can't be used to determine // formal charges. The following uses a heuristic to guess bond orders // for aromatic bonds. auto valences = get_bond_order_sums(obj); // (period currently incompatible with G->lex_const) auto lex_N_4 = LexBorrow(G, "N.4"); for (int at = 0; at < obj->NAtom; ++at) { int fcharge = 0; auto ai = obj->AtomInfo + at; if (ai->protons == cAN_N) { if (ai->textType == lex_N_4) { fcharge = 1; } else if (valences[at] == 2) { fcharge = -1; } else if (valences[at] == 4) { fcharge = 1; } } else if (ai->protons == cAN_O) { if (valences[at] == 1) { fcharge = -1; } } ai->formalCharge = fcharge; } } /*========================================================================*/ static CoordSet *ObjectMoleculeMOL2Str2CoordSet(PyMOLGlobals * G, const char *buffer, AtomInfoType ** atInfoPtr, const char **next_mol) { const char *p; int nAtom, nBond, nSubst, nFeat, nSet; int a; float *coord = nullptr; CoordSet *cset = nullptr; AtomInfoType *atInfo = nullptr; char cc[MAXLINELEN]; float *f; const char *last_p; BondType *ii; BondType *bond = nullptr; int ok = true; int auto_show = RepGetAutoShowMask(G); int have_molecule = false; WordType nameTmp; bool has_non_hetatm = false; // Maestro writes <0> as subst_name for waters const lexidx_t empty_subst_name = LexIdx(G, "<0>"); p = buffer; nAtom = 0; if(atInfoPtr) atInfo = *atInfoPtr; while((*p) && ok) { last_p = p; /* top level -- looking for an RTI, assuming p points to the beginning of a line */ p = ParseWordCopy(cc, p, MAXLINELEN); if(cc[0] == '@') { if(WordMatchExact(G, cc, "@MOLECULE", true) || WordMatchExact(G, cc, "@MOLECULE", true)) { if(have_molecule) { *next_mol = last_p; break; /* next record of multi-mol2 */ } p = ParseNextLine(p); p = ParseNTrim(nameTmp, p, sizeof(WordType) - 1); /* get mol name */ p = ParseNextLine(p); p = ParseWordCopy(cc, p, MAXLINELEN); if(sscanf(cc, "%d", &nAtom) != 1) ok = ErrMessage(G, "ReadMOL2File", "bad atom count"); else { coord = VLAlloc(float, 3 * nAtom); if(atInfo) VLACheck(atInfo, AtomInfoType, nAtom); p = ParseWordCopy(cc, p, MAXLINELEN); if(sscanf(cc, "%d", &nBond) != 1) { nBond = 0; } p = ParseWordCopy(cc, p, MAXLINELEN); if(sscanf(cc, "%d", &nSubst) != 1) { nSubst = 0; } p = ParseWordCopy(cc, p, MAXLINELEN); if(sscanf(cc, "%d", &nFeat) != 1) { nFeat = 0; } p = ParseWordCopy(cc, p, MAXLINELEN); if(sscanf(cc, "%d", &nSet) != 1) { nSet = 0; } } p = ParseNextLine(p); p = ParseNextLine(p); have_molecule = true; } else if(WordMatchExact(G, cc, "@ATOM", true) || WordMatchExact(G, cc, "@ATOM", true)) { if(!have_molecule) { ok = ErrMessage(G, "ReadMOL2File", "@ATOM before @MOLECULE!"); break; } p = ParseNextLine(p); f = coord; for(a = 0; a < nAtom; a++) { AtomInfoType *ai = atInfo + a; if(ok) { p = ParseWordCopy(cc, p, MAXLINELEN); if(sscanf(cc, "%d", &ai->id) != 1) { ok = ErrMessage(G, "ReadMOL2File", "bad atom id"); } } if(ok) { p = ParseWordCopy(cc, p, MAXLINELEN); cc[cAtomNameLen] = 0; UtilCleanStr(cc); if(cc[0]) LexAssign(G, ai->name, cc); else ok = ErrMessage(G, "ReadMOL2File", "bad atom name"); } if(ok) { p = ParseWordCopy(cc, p, MAXLINELEN); if(sscanf(cc, "%f", f++) != 1) ok = ErrMessage(G, "ReadMOL2File", "bad x coordinate"); } if(ok) { p = ParseWordCopy(cc, p, MAXLINELEN); if(sscanf(cc, "%f", f++) != 1) ok = ErrMessage(G, "ReadMOL2File", "bad y coordinate"); } if(ok) { p = ParseWordCopy(cc, p, MAXLINELEN); if(sscanf(cc, "%f", f++) != 1) ok = ErrMessage(G, "ReadMOL2File", "bad z coordinate"); } if(ok) { OrthoLineType temp; p = ParseWordCopy(cc, p, OrthoLineLength - 1); if(sscanf(cc, "%s", temp) != 1) ok = ErrMessage(G, "ReadMOL2File", "bad atom type"); else { /* convert atom type to elem symbol */ char *tt = temp; char *el = ai->elem; int elc = 0; while(*tt && ((*tt) != '.')) { *(el++) = *(tt++); elc++; if(elc > cElemNameLen) break; } *el = 0; if(el[2]) el[0] = 0; ai->textType = LexIdx(G, temp); } } if(ok) { char cc_resi[8]; p = ParseWordCopy(cc_resi, p, sizeof(cc_resi) - 1); if(cc_resi[0]) { /* subst_id is residue identifier */ ai->setResi(cc_resi); p = ParseWordCopy(cc, p, MAXLINELEN); if(cc[0]) { if (nSubst == 0) { // without substructure information (e.g. OpenBabel exported MOL2): // if subst_name includes the subst_id (e.g. 5 ALA5) then strip the number int len_resi = strlen(cc_resi); int len_resn = strlen(cc); if (len_resn > len_resi) { if (strcmp(cc_resi, cc + len_resn - len_resi) == 0) { cc[len_resn - len_resi] = 0; } } } LexAssign(G, ai->resn, cc); p = ParseWordCopy(cc, p, MAXLINELEN); if(cc[0]) { if(sscanf(cc, "%f", &ai->partialCharge) != 1) ok = ErrMessage(G, "ReadMOL2File", "bad atom charge"); } // status_bit p = ParseWordCopy(cc, p, MAXLINELEN); if (cc[0]) { // for water, substitute resn "<0>" with "HOH" for // correct classification as "solvent" if (ai->resn == empty_subst_name && strstr(cc, "WATER")) { LexAssign(G, ai->resn, G->lex_const.HOH); } } } } } p = ParseNextLine(p); ai->visRep = auto_show; ai->id = a + 1; ai->rank = a; ai->hetatm = true; AtomInfoAssignParameters(G, atInfo + a); AtomInfoAssignColors(G, atInfo + a); ai->alt[0] = 0; ai->segi = 0; } } else if(WordMatchExact(G, cc, "@SET", true) || WordMatchExact(G, cc, "@SET", true)) { if(!have_molecule) { ok = ErrMessage(G, "ReadMOL2File", "@SET before @MOLECULE!"); break; } p = ParseNextLine(p); if(ok) { for(a = 0; a < nSet; a++) { if(ok) { char ss = 0; p = ParseWordCopy(cc, p, 50); cc[5] = 0; if(!strcmp("HELIX", cc)) ss = 'H'; if(!strcmp("SHEET", cc)) ss = 'S'; p = ParseWordCopy(cc, p, 50); if(!strcmp("STATIC", cc)) { int nMember; p = ParseNextLine(p); p = ParseWordCopy(cc, p, 20); if(sscanf(cc, "%d", &nMember) != 1) ok = ErrMessage(G, "ReadMOL2File", "bad member count"); else { while(ok && (nMember > 0)) { p = ParseWordCopy(cc, p, 20); if((!cc[0]) && (!*p)) { ok = false; break; } if(cc[0] != '\\') { int atom_id; if(sscanf(cc, "%d", &atom_id) != 1) { ok = ErrMessage(G, "ReadMOL2File", "bad member"); } else { atom_id--; if((atom_id >= 0) && (atom_id < nAtom)) { atInfo[atom_id].ssType[0] = ss; atInfo[atom_id].ssType[1] = 0; } nMember--; } } else { p = ParseNextLine(p); } } } p = ParseNextLine(p); } else { p = ParseNextLine(p); p = ParseNextLine(p); } } } } } else if(WordMatchExact(G, cc, "@BOND", true) || WordMatchExact(G, cc, "@BOND", true)) { if(!have_molecule) { ok = ErrMessage(G, "ReadMOL2File", "@BOND before @MOLECULE!"); break; } p = ParseNextLine(p); if(ok) { bond = VLACalloc(BondType, nBond); ii = bond; for(a = 0; a < nBond; a++) { if(ok) { p = ParseWordCopy(cc, p, 20); } if(ok) { p = ParseWordCopy(cc, p, 20); if(sscanf(cc, "%d", &ii->index[0]) != 1) ok = ErrMessage(G, "ReadMOL2File", "bad source atom id"); } if(ok) { p = ParseWordCopy(cc, p, 20); if(sscanf(cc, "%d", &ii->index[1]) != 1) ok = ErrMessage(G, "ReadMOL2File", "bad target atom id"); } if(ok) { p = ParseWordCopy(cc, p, 20); if(!cc[1]) { switch (cc[0]) { case '1': ii->order = 1; break; case '2': ii->order = 2; break; case '3': ii->order = 3; break; case '4': ii->order = 4; break; } } else if(WordMatchExact(G, "ar", cc, true)) { ii->order = 4; } else if(WordMatchExact(G, "am", cc, true)) { ii->order = 1; } else if(WordMatchExact(G, "un", cc, true)) { ii->order = 1; } else if(WordMatchExact(G, "nc", cc, true)) { ii->order = 0; /* is this legal in PyMOL? */ } else if(WordMatchExact(G, "du", cc, true)) { ii->order = 1; } else ok = ErrMessage(G, "ReadMOL2File", "bad bond type"); } ii++; if(!ok) break; p = ParseNextLine(p); } ii = bond; for(a = 0; a < nBond; a++) { ii->index[0]--; /* adjust bond indexs down one */ ii->index[1]--; ii++; } } } else if(WordMatchExact(G, cc, "@SUBSTRUCTURE", true) || WordMatchExact(G, cc, "@SUBSTRUCTURE", true)) { if(!have_molecule) { ok = ErrMessage(G, "ReadMOL2File", "@SUBSTSRUCTURE before @MOLECULE!"); break; } p = ParseNextLine(p); if(ok) { WordType subst_name; SegIdent segment; /* what MOL2 calls chain */ lexidx_t chain = 0; WordType subst_type; ResIdent resi; ResName resn; int id, dict_type, root, resv; int end_line, seg_flag, subst_flag, resi_flag; int chain_flag, resn_flag; std::unordered_map> resv_map; { int b; AtomInfoType *ai = atInfo; for(b = 0; b < nAtom; b++) { resv_map[ai->resv].push_back(b); ai++; } } for(a = 0; a < nSubst; a++) { bool hetatm = true; segment[0] = 0; subst_name[0] = 0; LexAssign(G, chain, 0); resn[0] = 0; resi[0] = 0; end_line = false; seg_flag = false; subst_flag = false; resi_flag = false; chain_flag = false; resn_flag = false; if(ok) { const char *save_p = p; p = ParseWordCopy(cc, p, 20); if(sscanf(cc, "%d", &id) != 1) { if(cc[0] != '@') ok = ErrMessage(G, "ReadMOL2File", "bad substructure id"); else { p = save_p; break; /* missing substructure... */ } } } if(ok) { p = ParseWordCopy(cc, p, sizeof(WordType) - 1); if(sscanf(cc, "%s", subst_name) != 1) ok = ErrMessage(G, "ReadMOL2File", "bad substructure name"); } if(ok) { p = ParseWordCopy(cc, p, 20); if(sscanf(cc, "%d", &root) != 1) ok = ErrMessage(G, "ReadMOL2File", "bad target root atom id"); else root--; /* convert 1-based to 0-based */ } /* optional data */ if(ok && (!end_line)) { p = ParseWordCopy(cc, p, sizeof(WordType) - 1); if(sscanf(cc, "%s", subst_type) != 1) { end_line = true; subst_type[0] = 0; } else { subst_flag = 1; } } if(ok && (!end_line)) { p = ParseWordCopy(cc, p, 20); if(sscanf(cc, "%d", &dict_type) != 1) end_line = true; } if(ok && (!end_line)) { p = ParseWordCopy(cc, p, sizeof(WordType) - 1); cc[cSegiLen] = 0; if(sscanf(cc, "%s", segment) != 1) { end_line = true; segment[0] = 0; } else if(strcmp(segment, "****")) { seg_flag = true; if(!segment[1]) { /* if segment is single letter, then also assign to chain field */ LexAssign(G, chain, segment); chain_flag = true; } } } if(ok && (!end_line)) { p = ParseWordCopy(cc, p, sizeof(WordType) - 1); cc[cResnLen] = 0; if(!strcmp(cc, "****")) { /* whoops -- no residue name... */ char *pp; UtilNCopy(resn, subst_name, sizeof(ResName)); pp = resn; /* any numbers? */ while(*pp) { if(((*pp) >= '0') && ((*pp) <= '9')) break; pp++; } /* trim them off */ while(pp >= resn) { if(((*pp) >= '0') && ((*pp) <= '9')) *pp = 0; else break; pp--; } if(resn[0]) resn_flag = true; } else { if(sscanf(cc, "%s", resn) != 1) { end_line = true; resn[0] = 0; } else { resn_flag = true; } } } if(ok && (root >= 0) && (root < nAtom)) { if((strlen(subst_name) == 4) && ((subst_name[0] >= '1') && (subst_name[0] <= '9')) && ((((subst_name[1] >= 'A') && (subst_name[1] <= 'Z')) || ((subst_name[1] >= 'a') && (subst_name[1] <= 'z'))) || (((subst_name[2] >= 'A') && (subst_name[2] <= 'Z')) || ((subst_name[2] >= 'a') && (subst_name[2] <= 'z'))) || (((subst_name[3] >= 'A') && (subst_name[3] <= 'Z')) || ((subst_name[3] >= 'a') && (subst_name[3] <= 'z'))))) { /* if subst_name looks like a PDB code, then it isn't a residue identifier so do nothing... */ } else { if(!subst_flag) { /* Merck stuffs the chain ID and the residue ID in the subst_name field, so handle that case first */ /* get the residue value */ ParseIntCopy(cc, subst_name, 20); if(sscanf(cc, "%d", &resv) == 1) { /* we have the resv, so now get the chain if there is one */ char *pp = subst_name; if(!((pp[0] >= '0') && (pp[0] <= '9'))) { char tmp[2] = {pp[0], 0}; LexAssign(G, chain, tmp); chain_flag = true; pp++; } UtilNCopy(resi, pp, cResiLen); /* now get the textual residue identifier */ if(resi[0]) { resi_flag = true; } } } else { /* normal mode: assume that the substructure ID is a vanilla residue identifier */ char *pp = subst_name; if(resn_flag) { /* if we have a resn, then remove it from the subst_name */ char *qq = resn; while((*pp) && (*qq)) { if(*pp == *qq) { pp++; qq++; } else break; } } ParseIntCopy(cc, pp, 20); if(sscanf(cc, "%d", &resv) == 1) { UtilNCopy(resi, pp, cResiLen); /* now get the textual residue identifier */ if(resi[0]) { resi_flag = true; } } if (strcmp(subst_type, "RESIDUE") == 0) { hetatm = false; has_non_hetatm = true; } } } /* for now, assume that atom ids are 1-based and sequential */ if(ok) { if(resi_flag || chain_flag || resn_flag || seg_flag) { auto it = resv_map.find(id); if (it != resv_map.end()) { for (auto b : it->second) { assert(b >= 0 && b < nAtom); { AtomInfoType *ai = atInfo + b; if(resi_flag) ai->setResi(resi); if(chain_flag) { LexAssign(G, ai->chain, chain); } if(resn_flag) LexAssign(G, ai->resn, resn); if(seg_flag) LexAssign(G, ai->segi, segment); ai->hetatm = hetatm; } } } } } } if(!ok) break; p = ParseNextLine(p); } LexDec(G, chain); } } else p = ParseNextLine(p); } else p = ParseNextLine(p); } LexDec(G, empty_subst_name); if (has_non_hetatm) { // contains "residues" (assume polymer) so ignore hetatm for surfacing for (auto ai = atInfo, ai_end = atInfo + nAtom; ai != ai_end; ++ai) { if (ai->hetatm) { ai->flags |= cAtomFlag_ignore; } } } if(ok) { cset = CoordSetNew(G); cset->NIndex = nAtom; cset->Coord = pymol::vla_take_ownership(coord); cset->NTmpBond = nBond; cset->TmpBond = pymol::vla_take_ownership(bond); strcpy(cset->Name, nameTmp); } else { VLAFreeP(bond); VLAFreeP(coord); } if(atInfoPtr) *atInfoPtr = atInfo; return (cset); } /** * Read one molecule in MOL2, MOL, SDF, MMD or XYZ format from the string pointed * to by (*next_entry). All these formats (except MOL) support multiple * concatenated entries in one file. If multiplex=1, then read only one * molecule (one state) and set the next_entry pointer to the beginning of the * next entry. Otherwise, read a multi-state molecule. */ ObjectMolecule *ObjectMoleculeReadStr(PyMOLGlobals * G, ObjectMolecule * I, const char **next_entry, cLoadType_t content_format, int frame, int discrete, int quiet, int multiplex, char *new_name, short loadpropertiesall, OVLexicon *loadproplex) { int ok = true; CoordSet *cset = nullptr; pymol::vla atInfo; int isNew; int nAtom; const char *restart = nullptr, *start = *next_entry; int repeatFlag = true; int successCnt = 0; char tmpName[WordLength]; int deferred_tasks = false; int skip_out; int connect = false; int set_formal_charges = false; *next_entry = nullptr; int aic_mask = cAIC_MOLMask; while(repeatFlag) { repeatFlag = false; skip_out = false; if(!I) isNew = true; else isNew = false; if(isNew) { I = (ObjectMolecule *) new ObjectMolecule(G, (discrete > 0)); std::swap(atInfo, I->AtomInfo); } else { atInfo = pymol::vla(10); } if(isNew) { I->Color = AtomInfoUpdateAutoColor(G); } restart = nullptr; switch (content_format) { case cLoadTypeMOL2: case cLoadTypeMOL2Str: cset = ObjectMoleculeMOL2Str2CoordSet(G, start, &atInfo, &restart); if (cset){ set_formal_charges = true; } break; case cLoadTypeMOL: case cLoadTypeMOLStr: cset = ObjectMoleculeMOLStr2CoordSet(G, start, &atInfo, &restart , loadpropertiesall, loadproplex ); restart = nullptr; break; case cLoadTypeSDF2: case cLoadTypeSDF2Str: cset = ObjectMoleculeSDF2Str2CoordSet(G, start, &atInfo, &restart , loadpropertiesall, loadproplex ); break; case cLoadTypeXYZ: case cLoadTypeXYZStr: cset = ObjectMoleculeXYZStr2CoordSet(G, start, &atInfo, &restart); if(!cset->TmpBond) connect = true; break; case cLoadTypeMMD: case cLoadTypeMMDStr: cset = ObjectMoleculeMMDStr2CoordSet(G, start, &atInfo, &restart); aic_mask = cAIC_MMDMask; break; } if(!cset) { if(!isNew) VLAFreeP(atInfo); if(!successCnt) { if (isNew) std::swap(I->AtomInfo, atInfo); DeleteP(I); ok = false; } else { skip_out = true; } } if(ok && !skip_out) { if((discrete>0 && !I->DiscreteFlag) || // should set to discrete if explicitly defined ((discrete < 0) && (restart) && isNew && (multiplex <= 0))) { /* if default discrete behavior and new object, with multi-coordinate set file, and not multiplex, then set discrete */ ObjectMoleculeSetDiscrete(G, I, true); } if(frame < 0) frame = I->NCSet; if(I->NCSet <= frame) I->NCSet = frame + 1; VLACheck(I->CSet, CoordSet *, frame); nAtom = cset->NIndex; if(I->DiscreteFlag && atInfo) { int a; int fp1 = frame + 1; AtomInfoType *ai = atInfo.data(); for(a = 0; a < nAtom; a++) { (ai++)->discrete_state = fp1; } } if(multiplex > 0) UtilNCopy(tmpName, cset->Name, WordLength); cset->Obj = I; cset->enumIndices(); cset->invalidateRep(cRepAll, cRepInvRep); if(isNew) { std::swap(I->AtomInfo, atInfo); } else { ObjectMoleculeMerge(I, std::move(atInfo), cset, false, aic_mask, false); /* NOTE: will release atInfo */ } if(isNew) I->NAtom = nAtom; if(frame < 0) frame = I->NCSet; VLACheck(I->CSet, CoordSet *, frame); if(I->NCSet <= frame) I->NCSet = frame + 1; delete I->CSet[frame]; I->CSet[frame] = cset; if(ok && isNew) ok &= ObjectMoleculeConnect(I, cset, connect); if (ok) { if (I->DiscreteFlag) { ok &= ObjectMoleculeExtendIndices(I, frame); } } deferred_tasks = true; successCnt++; if(!quiet) { if(successCnt > 1) { if(successCnt == 2) { PRINTFB(G, FB_ObjectMolecule, FB_Actions) " %s: read through molecule %d.\n", __func__, 1 ENDFB(G); } if(UtilShouldWePrintQuantity(successCnt)) { PRINTFB(G, FB_ObjectMolecule, FB_Actions) " %s: read through molecule %d.\n", __func__, successCnt ENDFB(G); } } } if(multiplex > 0) { UtilNCopy(new_name, tmpName, WordLength); if(restart) { *next_entry = restart; } } else if(restart) { repeatFlag = true; start = restart; frame = frame + 1; } } } if(deferred_tasks && I) { /* defer time-consuming tasks until all states have been loaded */ if (!I->DiscreteFlag) { ObjectMoleculeExtendIndices(I, cStateAll); } ObjectMoleculeSort(I); if (set_formal_charges){ ObjectMoleculeMOL2SetFormalCharges(G, I); } SceneCountFrames(G); I->invalidate(cRepAll, cRepInvAll, -1); ObjectMoleculeUpdateIDNumbers(I); ObjectMoleculeUpdateNonbonded(I); } return (I); } /*========================================================================*/ typedef int CompareFn(PyMOLGlobals *, const AtomInfoType *, const AtomInfoType *); int ObjectMoleculeMerge(ObjectMolecule * I, pymol::vla&& ai, CoordSet * cs, int bondSearchFlag, int aic_mask, int invalidate) { PyMOLGlobals *G = I->G; int *index, *outdex; int a, b, lb = 0, ac; int c, nb, a1, a2; int found; int nAt, nBd, nBond; int expansionFlag = false; int ok = true; const auto n_index = cs->getNIndex(); const auto oldNAtom = I->NAtom; const auto oldNBond = I->NBond; /* first, sort the coodinate set */ index = AtomInfoGetSortedIndex(G, I, ai, n_index, &outdex); CHECKOK(ok, index); if (!ok) return false; auto ai2 = pymol::vla(n_index); if (ok){ for(a = 0; a < n_index; a++) ai2[a] = std::move(ai[index[a]]); /* creates a sorted list of atom info records */ } ai = std::move(ai2); /* now, match it up with the current object's atomic information */ if (ok){ for(a = 0; a < n_index; a++) { index[a] = -1; } } if (ok) { const auto n_atom = I->NAtom; const AtomInfoType* const atInfo = I->AtomInfo.data(); CompareFn *fCompare; if(SettingGetGlobal_b(G, cSetting_pdb_hetatm_sort)) { fCompare = AtomInfoCompareIgnoreRank; } else { fCompare = AtomInfoCompareIgnoreRankHet; } c = 0; b = 0; lb = 0; for(a = 0; a < n_index; a++) { int reverse = false; const auto* const ai_a = ai + a; found = false; if(!I->DiscreteFlag) { /* don't even try matching for discrete objects */ while(b < n_atom) { ac = (fCompare(G, ai_a, atInfo + b)); if(!ac) { found = true; break; } else if(ac < 0) { /* atom is before current, so try going the other way */ reverse = true; break; } else if(!b) { int idx; ac = (fCompare(G, ai_a, atInfo + n_atom - 1)); if(ac > 0) { /* atom is after all atoms in list, so don't even bother searching */ break; } /* next, try to jump to an appropriate position in the list */ idx = ((a * n_atom) / n_index) - 1; if((idx > 0) && (b != idx) && (idx < n_atom)) { ac = (fCompare(G, ai_a, atInfo + idx)); if(ac > 0) b = idx; } } lb = b; /* last b before atom */ b++; } if(reverse && !found) { /* searching going backwards... */ while(b >= 0) { ac = (fCompare(G, ai_a, atInfo + b)); if(!ac) { found = true; break; } else if(ac > 0) { /* atom after current -- no good */ break; } lb = b; b--; } if(b < 0) b = 0; } } if(found) { index[a] = b; /* store real atom index b for a in index[a] */ b++; } else { index[a] = I->NAtom + c; /* otherwise, this is a new atom */ c++; b = lb; } } } /* first, reassign atom info for matched atoms */ /* allocate additional space */ if (ok){ if(c) { expansionFlag = true; nAt = I->NAtom + c; } else { nAt = I->NAtom; } if(expansionFlag) { VLACheck(I->AtomInfo, AtomInfoType, nAt); CHECKOK(ok, I->AtomInfo); } } if (ok){ for(a = 0; a < n_index; a++) { /* a is in original file space */ a1 = outdex[cs->IdxToAtm[a]]; /* a1 is in sorted atom info space */ a2 = index[a1]; cs->IdxToAtm[a] = a2; /* a2 is in object space */ if(a2 < oldNAtom) AtomInfoCombine(G, I->AtomInfo + a2, std::move(ai[a1]), aic_mask); else I->AtomInfo[a2] = std::move(ai[a1]); } } if(ok && I->DiscreteFlag) { ok = I->setNDiscrete(nAt); } I->NAtom = nAt; if (ok){ cs->updateNonDiscreteAtmToIdx(nAt); } VLAFreeP(ai); /* note that we're trusting AtomInfoCombine to have already purged all atom-dependent storage (such as strings) */ AtomInfoFreeSortedIndexes(G, &index, &outdex); /* now find and integrate and any new bonds */ if(ok && expansionFlag) { /* expansion flag means we have introduced at least 1 new atom */ pymol::vla bond; ok &= ObjectMoleculeConnect(I, nBond, bond, cs, bondSearchFlag, -1); if(nBond) { index = pymol::malloc(nBond); CHECKOK(ok, index); c = 0; b = 0; nb = 0; if (ok){ for(a = 0; a < nBond; a++) { /* iterate over new bonds */ found = false; if(!I->DiscreteFlag) { /* don't even try matching for discrete objects */ b = nb; /* pick up where we left off */ while(b < I->NBond) { ac = BondCompare(bond + a, I->Bond + b); if(!ac) { /* zero is a match */ found = true; break; } else if(ac < 0) { /* gone past position of this bond */ break; } else if(!b) { int idx; ac = BondCompare(bond + a, I->Bond + I->NBond - 1); if(ac > 0) /* bond is after all bonds in list, so don't even bother searching */ break; /* next, try to jump to an appropriate position in the list */ idx = ((a * I->NBond) / nBond) - 1; if((idx > 0) && (b != idx) && (idx < I->NBond)) { ac = BondCompare(bond + a, I->Bond + idx); if(ac > 0) b = idx; } } b++; /* no match yet, keep looking */ } } if(found) { index[a] = b; /* existing bond... */ nb = b + 1; } else { /* this is a new bond, save index and increment */ index[a] = I->NBond + c; c++; } } } /* first, reassign atom info for matched atoms */ if(c) { /* allocate additional space */ nBd = I->NBond + c; if(!I->Bond) I->Bond = pymol::vla(1); CHECKOK(ok, I->Bond); if (ok) VLACheck(I->Bond, BondType, nBd); CHECKOK(ok, I->Bond); for(a = 0; a < nBond; a++) { /* copy the new bonds */ a2 = index[a]; if(a2 >= I->NBond) { I->Bond[a2] = bond[a]; } } I->NBond = nBd; } FreeP(index); } } if(invalidate) { if(oldNAtom) { if(oldNAtom == I->NAtom) { if(oldNBond != I->NBond) { I->invalidate(cRepAll, cRepInvBonds, -1); } } else { I->invalidate(cRepAll, cRepInvAtoms, -1); } } } return ok; } /*========================================================================*/ /** * @param state Object state or -2 for current state * @return nullptr if there is no CoordSet for the given state */ CoordSet* ObjectMolecule::getCoordSet(int state) { return static_cast(getObjectState(state)); } const CoordSet* ObjectMolecule::getCoordSet(int state) const { return static_cast(getObjectState(state)); } /*========================================================================*/ void ObjectMoleculeTransformTTTf(ObjectMolecule * I, float *ttt, int frame) { int b; CoordSet *cs; for(b = 0; b < I->NCSet; b++) { if((frame < 0) || (frame == b)) { cs = I->CSet[b]; if(cs) { cs->invalidateRep(cRepAll, cRepInvCoord); MatrixTransformTTTfN3f(cs->NIndex, cs->Coord.data(), ttt, cs->Coord.data()); CoordSetRecordTxfApplied(cs, ttt, false); } } } } /*========================================================================*/ bool ObjectMoleculeSeleOp(ObjectMolecule * I, int sele, ObjectMoleculeOpRec * op) { float *coord; int a, b, s; int c, t_i; int a1 = 0, ind; float rms; float v1[3], v2, *vv1, *vv2, *vt, *vt1, *vt2; int hit_flag = false; int ok = true; int cnt; int skip_flag; int match_flag = false; int offset; int priority; int use_matrices = false; CoordSet *cs; AtomInfoType *ai, *ai0; PyMOLGlobals *G = I->G; #ifndef _PYMOL_NOPY PyObject* expr_co = nullptr; int compileType = Py_single_input; #endif #ifdef _WEBGL #endif PRINTFD(G, FB_ObjectMolecule) " %s-DEBUG: sele %d op->code %d\n", __func__, sele, op->code ENDFD; if(sele >= 0) { const char *errstr = "Alter"; /* always run on entry */ switch (op->code) { case OMOP_LABL: errstr = "Label"; if (op->i2 != cExecutiveLabelEvalOn){ break; } #ifndef _PYMOL_NOPY compileType = Py_eval_input; #endif case OMOP_ALTR: case OMOP_AlterState: // assume blocked interpreter if (op->s1 && op->s1[0]){ #ifndef _PYMOL_NOPY expr_co = Py_CompileString(op->s1, "", compileType); if(expr_co == nullptr) { ok = ErrMessage(G, errstr, "failed to compile expression"); } #else #ifndef _WEBGL ok = ErrMessage(G, errstr, "failed to compile expression"); #endif #endif } /* PBlockAndUnlockAPI() is not safe. * what if "v" is invalidated by another thread? */ break; } switch (op->code) { case OMOP_ReferenceStore: case OMOP_ReferenceRecall: case OMOP_ReferenceValidate: case OMOP_ReferenceSwap: for(b = 0; b < I->NCSet; b++) { if(I->CSet[b]) { if((b == op->i1) || (op->i1 < 0)) { int inv_flag = false; cs = I->CSet[b]; if(cs && CoordSetValidateRefPos(cs)) { for(a = 0; a < I->NAtom; a++) { s = I->AtomInfo[a].selEntry; if(SelectorIsMember(G, s, sele)) { ind = cs->atmToIdx(a); if(ind >= 0) { float *v = cs->coordPtr(ind); RefPosType *rp = cs->RefPos + ind; switch (op->code) { case OMOP_ReferenceStore: copy3f(v, rp->coord); rp->specified = true; break; case OMOP_ReferenceRecall: if(rp->specified) { copy3f(rp->coord, v); inv_flag = true; } break; case OMOP_ReferenceValidate: if(!rp->specified) { copy3f(v, rp->coord); rp->specified = true; } break; case OMOP_ReferenceSwap: if(rp->specified) { copy3f(rp->coord, v1); copy3f(v, rp->coord); copy3f(v1, v); inv_flag = true; } break; } op->i2++; } } } } if(inv_flag && cs) { cs->invalidateRep(cRepAll, cRepInvRep); } } } } break; case OMOP_AddHydrogens: if (ok) { ok &= ObjectMoleculeAddSeleHydrogensRefactored(I, sele, op->i1); } break; case OMOP_FixHydrogens: if (ok) ok &= ObjectMoleculeFixSeleHydrogens(I, sele, -1); /* state? */ break; case OMOP_RevalenceFromSource: case OMOP_RevalenceByGuessing: ai = I->AtomInfo.data(); for(a = 0; a < I->NAtom; a++) { if(SelectorIsMember(G, ai->selEntry, sele)) { hit_flag = true; break; } ai++; } break; case OMOP_PrepareFromTemplate: ai0 = op->ai; /* template atom */ for(a = 0; a < I->NAtom; a++) { s = I->AtomInfo[a].selEntry; if(SelectorIsMember(G, s, sele)) { ai = I->AtomInfo + a; if(op->i1 != 3) { ai->hetatm = ai0->hetatm; ai->flags = ai0->flags; LexAssign(G, ai->chain, ai0->chain); strcpy(ai->alt, ai0->alt); LexAssign(G, ai->segi, ai0->segi); } if(op->i1 == 1) { /* mode 1, merge residue information */ ai->inscode = ai0->inscode; ai->resv = ai0->resv; LexAssign(G, ai->resn, ai0->resn); } AtomInfoAssignColors(G, ai); if(op->i3) { if((ai->elem[0] == ai0->elem[0]) && (ai->elem[1] == ai0->elem[1])) ai->color = ai0->color; else if(ai->protons == cAN_C) { ai->color = op->i4; } } ai->visRep = ai0->visRep; ai->id = -1; ai->rank = -1; op->i2++; } } break; case OMOP_Sort: for(a = 0; ok && a < I->NAtom; a++) { s = I->AtomInfo[a].selEntry; if(SelectorIsMember(G, s, sele)) { if (ok) ok &= ObjectMoleculeSort(I); break; } } break; case OMOP_SetAtomicSetting: ai = I->AtomInfo.data(); for(a = 0; a < I->NAtom; a++) { if(SelectorIsMember(G, ai->selEntry, sele)) { int uid = AtomInfoCheckUniqueID(G, ai); ai->has_setting = true; if (SettingUniqueSetTypedValue(G, uid, op->i1, op->i2, op->ii1)) op->i4++; } ai++; } break; case OMOP_Pop: for(a = 0; a < I->NAtom; a++) { s = I->AtomInfo[a].selEntry; if(SelectorIsMember(G, s, sele)) { if(SelectorMoveMember(G, s, sele, op->i1)) { op->i2--; op->i3++; } if(!op->i2) break; } } break; case OMOP_AVRT: /* average vertex coordinate */ case OMOP_StateVRT: /* state vertex coordinate */ { int op_i2 = op->i2; int obj_TTTFlag = I->TTTFlag; int b_end = I->NCSet; if (op->code == OMOP_StateVRT && op->i1 < b_end) { b_end = op->i1 + 1; } if(op_i2) { use_matrices = SettingGet_i(I->G, I->Setting.get(), nullptr, cSetting_matrix_mode); if(use_matrices<0) use_matrices = 0; } for(a = 0; a < I->NAtom; a++) { s = I->AtomInfo[a].selEntry; if(!(priority = SelectorIsMember(G, s, sele))) continue; cnt = 0; // all states for AVRT, one state for StateVRT (don't use // StateIterator which depends on settings) for(b = op->i1; b < b_end; ++b) { if(!(cs = I->CSet[b])) continue; if((a1 = cs->atmToIdx(a)) == -1) continue; if(!cnt) { VLACheck(op->vv1, float, (op->nvv1 * 3) + 2); } cnt++; vv2 = cs->coordPtr(a1); if(op_i2) { /* do we want transformed coordinates? */ if(use_matrices) { if(!cs->Matrix.empty()) { /* state transformation */ transform44d3f(cs->Matrix.data(), vv2, v1); vv2 = v1; } } if(obj_TTTFlag) { transformTTT44f3f(I->TTT, vv2, v1); vv2 = v1; } } // sum up coordinates over states vv1 = op->vv1 + (op->nvv1 * 3); add3f(vv1, vv2, vv1); } // number of summed up coordinates (states) for this atom VLACheck(op->vc1, int, op->nvv1); op->vc1[op->nvv1] = cnt; // ordered_selections if(op->vp1) { VLACheck(op->vp1, int, op->nvv1); op->vp1[op->nvv1] = priority; } // atom pointer VLA if(op->ai1VLA) { VLACheck(op->ai1VLA, AtomInfoType *, op->nvv1); op->ai1VLA[op->nvv1] = I->AtomInfo + a; I->AtomInfo[a].temp1 = a; /* KLUDGE ALERT!!! storing atom index in the temp1 field... */ } // number of atoms in selection (incl. the ones with no coordinates) op->nvv1++; } } break; case OMOP_SFIT: /* state fitting within a single object */ vt = pymol::malloc(3 * op->nvv2); /* temporary (matching) target vertex pointers */ cnt = 0; for(a = 0; a < I->NAtom; a++) { s = I->AtomInfo[a].selEntry; if(SelectorIsMember(G, s, sele)) { cnt++; break; } } if(cnt) { /* only perform action for selected object */ for(b = 0; b < I->NCSet; b++) { rms = -1.0; vt1 = vt; /* reset target vertex pointers */ vt2 = op->vv2; t_i = 0; /* original target vertex index */ if(I->CSet[b] && (b != op->i2)) { op->nvv1 = 0; for(a = 0; a < I->NAtom; a++) { s = I->AtomInfo[a].selEntry; if(SelectorIsMember(G, s, sele)) { a1 = I->CSet[b]->atmToIdx(a); if(a1 >= 0) { match_flag = false; while(t_i < op->nvv2) { if(op->i1VLA[t_i] == a) { /* same atom? */ match_flag = true; break; } if(op->i1VLA[t_i] < a) { /* catch up? */ t_i++; vt2 += 3; } else break; } if(match_flag) { VLACheck(op->vv1, float, (op->nvv1 * 3) + 2); vv2 = I->CSet[b]->coordPtr(a1); vv1 = op->vv1 + (op->nvv1 * 3); *(vv1++) = *(vv2++); *(vv1++) = *(vv2++); *(vv1++) = *(vv2++); *(vt1++) = *(vt2); *(vt1++) = *(vt2 + 1); *(vt1++) = *(vt2 + 2); op->nvv1++; } } } } if(op->nvv1 != op->nvv2) { PRINTFB(G, FB_Executive, FB_Warnings) "Executive-Warning: Missing atoms in state %d (%d instead of %d).\n", b + 1, op->nvv1, op->nvv2 ENDFB(G); } if(op->nvv1) { if(op->i1 != 0) /* fitting flag */ rms = MatrixFitRMSTTTf(G, op->nvv1, op->vv1, vt, nullptr, op->ttt); else rms = MatrixGetRMS(G, op->nvv1, op->vv1, vt, nullptr); if(op->i1 == 2) { ObjectMoleculeTransformTTTf(I, op->ttt, b); if(op->i3) { const float divisor = (float) op->i3; const float premult = (float) op->i3 - 1.0F; /* mix flag is set, so average the prior target coordinates with these coordinates */ vt2 = op->vv2; t_i = 0; /* original target vertex index */ for(a = 0; a < I->NAtom; a++) { s = I->AtomInfo[a].selEntry; if(SelectorIsMember(G, s, sele)) { a1 = I->CSet[b]->atmToIdx(a); if(a1 >= 0) { match_flag = false; while(t_i < op->nvv2) { if(op->i1VLA[t_i] == a) { /* same atom? */ match_flag = true; break; } if(op->i1VLA[t_i] < a) { /* catch up? */ t_i++; vt2 += 3; } else break; } if(match_flag) { vv2 = I->CSet[b]->coordPtr(a1); *(vt2) = ((premult * (*vt2)) + *(vv2++)) / divisor; *(vt2 + 1) = ((premult * (*(vt2 + 1))) + *(vv2++)) / divisor; *(vt2 + 2) = ((premult * (*(vt2 + 2))) + *(vv2++)) / divisor; } } } } } } } else { PRINTFB(G, FB_Executive, FB_Warnings) "Executive-Warning: No matches found for state %d.\n", b + 1 ENDFB(G); } } VLACheck(op->f1VLA, float, b); op->f1VLA[b] = rms; } VLASize(op->f1VLA, float, I->NCSet); /* NOTE this action is object-specific! */ } FreeP(vt); break; case OMOP_OnOff: for(a = 0; a < I->NAtom; a++) { s = I->AtomInfo[a].selEntry; if(SelectorIsMember(G, s, sele)) { hit_flag = true; break; } } break; case OMOP_SaveUndo: /* save undo */ for(a = 0; a < I->NAtom; a++) { s = I->AtomInfo[a].selEntry; if(SelectorIsMember(G, s, sele)) { hit_flag = true; break; } } break; case OMOP_IdentifyObjects: /* identify atoms */ for(a = 0; a < I->NAtom; a++) { s = I->AtomInfo[a].selEntry; if(SelectorIsMember(G, s, sele)) { VLACheck(op->i1VLA, int, op->i1); op->i1VLA[op->i1] = I->AtomInfo[a].id; if (op->obj1VLA != nullptr) { VLACheck(op->obj1VLA, ObjectMolecule*, op->i1); op->obj1VLA[op->i1] = I; } op->i1++; } } break; case OMOP_Index: /* identify atoms */ for(a = 0; a < I->NAtom; a++) { s = I->AtomInfo[a].selEntry; if(SelectorIsMember(G, s, sele)) { VLACheck(op->i1VLA, int, op->i1); op->i1VLA[op->i1] = a; /* NOTE: need to incr by 1 before python */ VLACheck(op->obj1VLA, ObjectMolecule *, op->i1); op->obj1VLA[op->i1] = I; op->i1++; } } break; case OMOP_GetObjects: /* identify atoms */ for(a = 0; a < I->NAtom; a++) { s = I->AtomInfo[a].selEntry; if(SelectorIsMember(G, s, sele)) { VLACheck(op->obj1VLA, ObjectMolecule *, op->i1); op->obj1VLA[op->i1] = I; op->i1++; break; } } break; case OMOP_CountAtoms: /* count atoms in object, in selection */ ai = I->AtomInfo.data(); for(a = 0; a < I->NAtom; a++) { s = ai->selEntry; if(SelectorIsMember(G, s, sele)) op->i1++; ai++; } break; case OMOP_PhiPsi: ai = I->AtomInfo.data(); for(a = 0; a < I->NAtom; a++) { s = ai->selEntry; if(SelectorIsMember(G, s, sele)) { VLACheck(op->i1VLA, int, op->i1); op->i1VLA[op->i1] = a; VLACheck(op->obj1VLA, ObjectMolecule *, op->i1); op->obj1VLA[op->i1] = I; VLACheck(op->f1VLA, float, op->i1); VLACheck(op->f2VLA, float, op->i1); if(ObjectMoleculeGetPhiPsi (I, a, op->f1VLA + op->i1, op->f2VLA + op->i1, op->i2)) op->i1++; } ai++; } break; case OMOP_Cartoon: /* adjust cartoon type */ ai = I->AtomInfo.data(); for(a = 0; a < I->NAtom; a++) { s = ai->selEntry; if(SelectorIsMember(G, s, sele)) { if (ai->cartoon!=op->i1){ op->i3++; } ai->cartoon = op->i1; op->i2++; } ai++; } break; case OMOP_Protect: /* protect atoms from movement */ ai = I->AtomInfo.data(); for(a = 0; a < I->NAtom; a++) { s = ai->selEntry; if(SelectorIsMember(G, s, sele)) { ai->protekted = op->i1; op->i2++; } ai++; } break; case OMOP_Mask: /* protect atoms from selection */ ai = I->AtomInfo.data(); for(a = 0; a < I->NAtom; a++) { s = ai->selEntry; if(SelectorIsMember(G, s, sele)) { ai->masked = op->i1; op->i2++; } ai++; } break; case OMOP_SetB: /* set B-value */ ai = I->AtomInfo.data(); for(a = 0; a < I->NAtom; a++) { s = ai->selEntry; if(SelectorIsMember(G, s, sele)) { ai->b = op->f1; op->i2++; } ai++; } break; case OMOP_Remove: /* flag atoms for deletion */ ai = I->AtomInfo.data(); for(a = 0; a < I->NAtom; a++) { ai->deleteFlag = false; s = ai->selEntry; if(SelectorIsMember(G, s, sele)) { ai->deleteFlag = true; op->i1++; } ai++; } break; case OMOP_GetChains: ai = I->AtomInfo.data(); for(a = 0; a < I->NAtom; a++) { s = ai->selEntry; if(SelectorIsMember(G, s, sele)) { // pointer hack ((std::set *) (void*) op->ii1)->insert(ai->chain); op->i1++; } ai++; } break; case OMOP_Spectrum: ai = I->AtomInfo.data(); ai0 = nullptr; for(a = 0; a < I->NAtom; a++) { s = ai->selEntry; if(SelectorIsMember(G, s, sele)) { skip_flag = false; if(op->i4 && ai0) /* byres and we've done a residue */ if(AtomInfoSameResidue(G, ai, ai0)) skip_flag = true; if(!skip_flag) { c = (int) (0.49999 + op->i1 * (op->ff1[op->i3] - op->f1) / op->f2); if(c < 0) c = 0; if(c >= op->i1) c = op->i1 - 1; ai->color = op->ii1[c]; /* printf("%8.3 %8.3\n",ai->partial_charge, */ if(op->i4) { /* byres */ offset = -1; while((a + offset) >= 0) { ai0 = I->AtomInfo + a + offset; if(AtomInfoSameResidue(G, ai, ai0)) { ai0->color = op->ii1[c]; hit_flag = true; } else break; offset--; } offset = 1; while((a + offset) < I->NAtom) { ai0 = I->AtomInfo + a + offset; if(AtomInfoSameResidue(G, ai, ai0)) { ai0->color = op->ii1[c]; hit_flag = true; } else break; offset++; } } ai0 = ai; } op->i3++; } ai++; } break; case OMOP_SingleStateVertices: /* same as OMOP_VERT for a single state */ ai = I->AtomInfo.data(); if(op->cs1 < I->NCSet) { if(I->CSet[op->cs1]) { b = op->cs1; for(a = 0; a < I->NAtom; a++) { s = ai->selEntry; if(SelectorIsMember(G, s, sele)) { op->i1++; a1 = I->CSet[b]->atmToIdx(a); if(a1 >= 0) { VLACheck(op->vv1, float, (op->nvv1 * 3) + 2); vv2 = I->CSet[b]->coordPtr(a1); vv1 = op->vv1 + (op->nvv1 * 3); *(vv1++) = *(vv2++); *(vv1++) = *(vv2++); *(vv1++) = *(vv2++); op->nvv1++; } } ai++; } } } break; case OMOP_CSetIdxGetAndFlag: ai = I->AtomInfo.data(); for(a = 0; a < I->NAtom; a++) { s = ai->selEntry; if(SelectorIsMember(G, s, sele)) { for(b = op->cs1; b <= op->cs2; b++) { offset = b - op->cs1; if(b < I->NCSet) { if(I->CSet[b]) { a1 = I->CSet[b]->atmToIdx(a); if(a1 >= 0) { op->ii1[op->i1 * offset + op->i2] = 1; /* presence flag */ vv1 = op->vv1 + 3 * (op->i1 * offset + op->i2); /* atom-based offset */ vv2 = I->CSet[b]->coordPtr(a1); *(vv1++) = *(vv2++); *(vv1++) = *(vv2++); *(vv1++) = *(vv2++); op->nvv1++; } } } } op->i2++; /* atom index field for atoms within selection... */ } ai++; } break; case OMOP_CSetIdxSetFlagged: ai = I->AtomInfo.data(); hit_flag = false; for(a = 0; a < I->NAtom; a++) { s = ai->selEntry; if(SelectorIsMember(G, s, sele)) { for(b = op->cs1; b <= op->cs2; b++) { offset = b - op->cs1; if(b < I->NCSet) { if(I->CSet[b]) { a1 = I->CSet[b]->atmToIdx(a); if(a1 >= 0) { if(op->ii1[op->i1 * offset + op->i2]) { /* copy flag */ vv1 = op->vv1 + 3 * (op->i1 * offset + op->i2); /* atom-based offset */ vv2 = I->CSet[b]->coordPtr(a1); *(vv2++) = *(vv1++); *(vv2++) = *(vv1++); *(vv2++) = *(vv1++); op->nvv1++; hit_flag = true; } } } } } op->i2++; /* atom index field for atoms within selection... */ } ai++; } break; case OMOP_SUMC: /* performance optimized to speed center & zoom actions */ { /* given a selection, sum up all the coordinates (for centering) */ float *op_v1 = op->v1; int op_i1 = op->i1; int op_i2 = op->i2; int obj_TTTFlag = I->TTTFlag; int i_NCSet = I->NCSet; int i_NAtom = I->NAtom; int i_DiscreteFlag = I->DiscreteFlag; CoordSet* const* i_CSet = I->CSet.data(); if(op_i2) { use_matrices = SettingGet_i(I->G, I->Setting.get(), nullptr, cSetting_matrix_mode); if(use_matrices<0) use_matrices = 0; } ai = I->AtomInfo.data(); for(a = 0; a < i_NAtom; a++) { s = ai->selEntry; /* for each atom, if this current atom is in the selection */ if(SelectorIsMember(G, s, sele)) { /* loop over all atoms; regardless of state */ for(b = 0; b < i_NCSet; b++) { if(i_DiscreteFlag) { if((cs = I->DiscreteCSet[a])) a1 = I->DiscreteAtmToIdx[a]; } else { if((cs = i_CSet[b])) a1 = cs->AtmToIdx[a]; } /* if valid coordinate set and atom info for this atom */ if(cs && (a1 >= 0)) { coord = cs->coordPtr(a1); if(op_i2) { /* do we want transformed coordinates? */ if(use_matrices) { if(!cs->Matrix.empty()) { /* state transformation */ transform44d3f(cs->Matrix.data(), coord, v1); coord = v1; } } if(obj_TTTFlag) { transformTTT44f3f(I->TTT, coord, v1); coord = v1; } } /* op_v1 += coord */ add3f(op_v1, coord, op_v1); /* count += 1 */ op_i1++; } if(i_DiscreteFlag) break; } } /* next atom */ ai++; } op->i1 = op_i1; } break; case OMOP_MNMX: /* performance optimized to speed center & zoom actions */ { float *op_v1 = op->v1; float *op_v2 = op->v2; int op_i1 = op->i1; int op_i2 = op->i2; int obj_TTTFlag = I->TTTFlag; int i_NCSet = I->NCSet; int i_NAtom = I->NAtom; int i_DiscreteFlag = I->DiscreteFlag; CoordSet* const* i_CSet = I->CSet.data(); if(op_i2) { use_matrices = SettingGet_i(I->G, I->Setting.get(), nullptr, cSetting_matrix_mode); if(use_matrices<0) use_matrices = 0; } ai = I->AtomInfo.data(); for(a = 0; a < i_NAtom; a++) { s = ai->selEntry; if(SelectorIsMember(G, s, sele)) { for(b = 0; b < i_NCSet; b++) { if(i_DiscreteFlag) { if((cs = I->DiscreteCSet[a])) a1 = I->DiscreteAtmToIdx[a]; } else { if((cs = i_CSet[b])) a1 = cs->AtmToIdx[a]; } if(cs && (a1 >= 0)) { coord = cs->coordPtr(a1); if(op_i2) { /* do we want transformed coordinates? */ if(use_matrices) { if(!cs->Matrix.empty()) { /* state transformation */ transform44d3f(cs->Matrix.data(), coord, v1); coord = v1; } } if(obj_TTTFlag) { transformTTT44f3f(I->TTT, coord, v1); coord = v1; } } if(op_i1) { if(op_v1[0] > coord[0]) op_v1[0] = coord[0]; if(op_v1[1] > coord[1]) op_v1[1] = coord[1]; if(op_v1[2] > coord[2]) op_v1[2] = coord[2]; if(op_v2[0] < coord[0]) op_v2[0] = coord[0]; if(op_v2[1] < coord[1]) op_v2[1] = coord[1]; if(op_v2[2] < coord[2]) op_v2[2] = coord[2]; } else { op_v1[0] = coord[0]; op_v1[1] = coord[1]; op_v1[2] = coord[2]; op_v2[0] = coord[0]; op_v2[1] = coord[1]; op_v2[2] = coord[2]; } op_i1++; } if(i_DiscreteFlag) break; } } ai++; } op->i1 = op_i1; } break; default: { int inv_flag; #ifdef _PYMOL_IP_EXTRAS int use_stereo = 0, use_text_type = 0; #endif switch (op->code) { case OMOP_INVA: /* set up an important optimization... */ for(b = 0; b < I->NCSet; b++) { cs = I->CSet[b]; if(cs) cs->objMolOpInvalidated = false; } break; } use_matrices = SettingGet_i(I->G, I->Setting.get(), nullptr, cSetting_matrix_mode); if(use_matrices<0) use_matrices = 0; ai = I->AtomInfo.data(); #ifdef _PYMOL_IP_EXTRAS // use stereo or text_type ? // only do this for "label2" command (better logic in WrapperObjectSubScript) if (op->code == OMOP_LABL && op->i2 == cExecutiveLabelEvalAlt) { use_stereo = PLabelExprUsesVariable(G, op->s1, "stereo"); use_text_type = PLabelExprUsesVariable(G, op->s1, "text_type"); } #ifdef NO_MMLIBS if (use_stereo) { PRINTFB(G, FB_ObjectMolecule, FB_Warnings) " NO_MMLIBS-Warning: stereochemistry not supported in this PyMOL build.\n" ENDFB(G); } if (use_text_type) { PRINTFB(G, FB_ObjectMolecule, FB_Warnings) " NO_MMLIBS-Warning: automatic 'text_type' assignment not supported in this PyMOL build.\n" ENDFB(G); } #endif #endif for(a = 0; a < I->NAtom; a++) { switch (op->code) { case OMOP_Flag: ai->flags &= op->i2; /* clear flag using mask */ op->i4++; /* no break here is intentional! */ case OMOP_FlagSet: case OMOP_FlagClear: case OMOP_COLR: /* normal atom based loops */ case OMOP_VISI: case OMOP_CheckVis: case OMOP_TTTF: case OMOP_ALTR: case OMOP_LABL: case OMOP_AlterState: s = ai->selEntry; if(SelectorIsMember(G, s, sele)) { switch (op->code) { case OMOP_Flag: case OMOP_FlagSet: ai->flags |= op->i1; /* set flag */ op->i3++; break; case OMOP_FlagClear: ai->flags &= op->i2; /* clear flag */ op->i3++; break; case OMOP_VISI: switch (op->i2) { case cVis_HIDE: ai->visRep &= ~(op->i1); I->visRep &= ~(op->i1); // cell break; case cVis_SHOW: ai->visRep |= op->i1 & cRepsAtomMask; I->visRep |= op->i1 & cRepsObjectMask; // cell break; case cVis_AS: ai->visRep = op->i1 & cRepsAtomMask; I->visRep = op->i1 & cRepsObjectMask; // cell break; } break; case OMOP_CheckVis: if((ai->visRep & op->i1)) { op->i2 = true; } break; case OMOP_COLR: if(op->i1 == cColorAtomic) ai->color = AtomInfoGetColor(G, ai); else ai->color = op->i1; hit_flag = true; op->i2++; break; case OMOP_TTTF: hit_flag = true; break; case OMOP_LABL: if(ok) { if(!op->s1[0]) { if(ai->label) { op->i1--; /* negative if unlabelling */ LexAssign(G, ai->label, 0); } ai->visRep &= ~cRepLabelBit; hit_flag = true; } else { switch (op->i2) { case cExecutiveLabelEvalOn: { /* python label expression evaluation */ CoordSet *cs = nullptr; if(I->DiscreteFlag && I->DiscreteCSet) { cs = I->DiscreteCSet[a]; } else if (I->NCSet == 1){ cs = I->CSet[0]; } #ifndef _PYMOL_NOPY if(PLabelAtom(I->G, I, cs, expr_co, a)) { if (ai->label){ op->i1++; /* only if the string has been set, report labelled */ } ai->visRep |= cRepLabelBit; hit_flag = true; } else { ok = false; } #else ok = false; #endif } break; case cExecutiveLabelEvalAlt: { if(PLabelAtomAlt(I->G, &I->AtomInfo[a], I->Name, op->s1, a)) { if (ai->label){ op->i1++; /* only if the string has been set, report labelled */ } ai->visRep |= cRepLabelBit; hit_flag = true; } else { ok = false; } } break; case cExecutiveLabelEvalOff: { /* simple string label text */ AtomInfoType *ai = I->AtomInfo + a; LexDec(G, ai->label); ai->label = LexIdx(G, op->s1); } break; } } } break; case OMOP_ALTR: if(ok) { CoordSet *cs = nullptr; if(I->DiscreteFlag && I->DiscreteCSet) { cs = I->DiscreteCSet[a]; } else if (I->NCSet == 1){ cs = I->CSet[0]; } #ifndef _PYMOL_NOPY if(PAlterAtom (I->G, I, cs, expr_co, op->i2, a, op->py_ob1)) op->i1++; else #endif #ifdef _WEBGL #endif ok = false; } break; case OMOP_AlterState: if(ok) { if(op->i2 < I->NCSet) { cs = I->CSet[op->i2]; if(cs) { a1 = cs->atmToIdx(a); if(a1 >= 0) { #ifndef _PYMOL_NOPY if(PAlterAtomState(I->G, expr_co, op->i3, I, cs, a, a1, op->i2, op->py_ob1)) { op->i1++; hit_flag = true; } else #endif #ifdef _WEBGL #endif ok = false; } } } } break; } break; } break; /* coord-set based properties, iterating only a single coordinate set */ case OMOP_CSetMinMax: case OMOP_CSetCameraMinMax: case OMOP_CSetMaxDistToPt: case OMOP_CSetSumSqDistToPt: case OMOP_CSetSumVertices: case OMOP_CSetMoment: cs = nullptr; if((op->cs1 >= 0) && (op->cs1 < I->NCSet)) { cs = I->CSet[op->cs1]; } else if(op->include_static_singletons) { if((I->NCSet == 1) && (SettingGet_b(G, nullptr, I->Setting.get(), cSetting_static_singletons))) { cs = I->CSet[0]; /*treat static singletons as present in each state */ } } if(cs) { s = ai->selEntry; if(SelectorIsMember(G, s, sele)) { switch (op->code) { case OMOP_CSetSumVertices: a1 = cs->atmToIdx(a); if(a1 >= 0) { coord = cs->coordPtr(a1); if(op->i2) { /* do we want transformed coordinates? */ if(use_matrices) { if(!cs->Matrix.empty()) { /* state transformation */ transform44d3f(cs->Matrix.data(), coord, v1); coord = v1; } } if(I->TTTFlag) { transformTTT44f3f(I->TTT, coord, v1); coord = v1; } } add3f(op->v1, coord, op->v1); op->i1++; } break; case OMOP_CSetMinMax: a1 = cs->atmToIdx(a); if(a1 >= 0) { coord = cs->coordPtr(a1); if(op->i2) { /* do we want transformed coordinates? */ if(use_matrices) { if(!cs->Matrix.empty()) { /* state transformation */ transform44d3f(cs->Matrix.data(), coord, v1); coord = v1; } } if(I->TTTFlag) { transformTTT44f3f(I->TTT, coord, v1); coord = v1; } } if(op->i1) { for(c = 0; c < 3; c++) { if(*(op->v1 + c) > *(coord + c)) *(op->v1 + c) = *(coord + c); if(*(op->v2 + c) < *(coord + c)) *(op->v2 + c) = *(coord + c); } } else { for(c = 0; c < 3; c++) { *(op->v1 + c) = *(coord + c); *(op->v2 + c) = *(coord + c); } } op->i1++; } break; case OMOP_CSetCameraMinMax: a1 = cs->atmToIdx(a); if(a1 >= 0) { coord = cs->coordPtr(a1); if(op->i2) { /* do we want transformed coordinates? */ if(use_matrices) { if(!cs->Matrix.empty()) { /* state transformation */ transform44d3f(cs->Matrix.data(), coord, v1); coord = v1; } } if(I->TTTFlag) { transformTTT44f3f(I->TTT, coord, v1); coord = v1; } } MatrixTransformC44fAs33f3f(op->mat1, coord, v1); /* convert to view-space */ coord = v1; if(op->i1) { for(c = 0; c < 3; c++) { if(*(op->v1 + c) > *(coord + c)) *(op->v1 + c) = *(coord + c); if(*(op->v2 + c) < *(coord + c)) *(op->v2 + c) = *(coord + c); } } else { for(c = 0; c < 3; c++) { *(op->v1 + c) = *(coord + c); *(op->v2 + c) = *(coord + c); } } op->i1++; } break; case OMOP_CSetSumSqDistToPt: a1 = cs->atmToIdx(a); if(a1 >= 0) { float dist; coord = cs->coordPtr(a1); if(op->i2) { /* do we want transformed coordinates? */ if(use_matrices) { if(!cs->Matrix.empty()) { /* state transformation */ transform44d3f(cs->Matrix.data(), coord, v1); coord = v1; } } if(I->TTTFlag) { transformTTT44f3f(I->TTT, coord, v1); coord = v1; } } dist = (float) diff3f(op->v1, coord); op->d1 += dist * dist; op->i1++; } break; case OMOP_CSetMaxDistToPt: a1 = cs->atmToIdx(a); if(a1 >= 0) { float dist; coord = cs->coordPtr(a1); if(op->i2) { /* do we want transformed coordinates? */ if(use_matrices) { if(!cs->Matrix.empty()) { /* state transformation */ transform44d3f(cs->Matrix.data(), coord, v1); coord = v1; } } if(I->TTTFlag) { transformTTT44f3f(I->TTT, coord, v1); coord = v1; } } dist = (float) diff3f(op->v1, coord); if(dist > op->f1) op->f1 = dist; op->i1++; } break; case OMOP_CSetMoment: a1 = cs->atmToIdx(a); if(a1 >= 0) { subtract3f(cs->coordPtr(a1), op->v1, v1); v2 = v1[0] * v1[0] + v1[1] * v1[1] + v1[2] * v1[2]; op->d[0][0] += v2 - v1[0] * v1[0]; op->d[0][1] += -v1[0] * v1[1]; op->d[0][2] += -v1[0] * v1[2]; op->d[1][0] += -v1[1] * v1[0]; op->d[1][1] += v2 - v1[1] * v1[1]; op->d[1][2] += -v1[1] * v1[2]; op->d[2][0] += -v1[2] * v1[0]; op->d[2][1] += -v1[2] * v1[1]; op->d[2][2] += v2 - v1[2] * v1[2]; } break; } } } break; default: /* coord-set based properties, iterating as all coordsets within atoms */ for(b = 0; b < I->NCSet; b++) { if(I->DiscreteFlag) { cs = I->DiscreteCSet[a]; } else { cs = I->CSet[b]; } if(cs) { s = ai->selEntry; inv_flag = false; if(SelectorIsMember(G, s, sele)) { switch (op->code) { case OMOP_CameraMinMax: a1 = cs->atmToIdx(a); if(a1 >= 0) { coord = cs->coordPtr(a1); if(op->i2) { /* do we want transformed coordinates? */ if(use_matrices) { if(!cs->Matrix.empty()) { /* state transformation */ transform44d3f(cs->Matrix.data(), coord, v1); coord = v1; } } if(I->TTTFlag) { transformTTT44f3f(I->TTT, coord, v1); coord = v1; } } MatrixTransformC44fAs33f3f(op->mat1, coord, v1); /* convert to view-space */ coord = v1; if(op->i1) { for(c = 0; c < 3; c++) { if(*(op->v1 + c) > *(coord + c)) *(op->v1 + c) = *(coord + c); if(*(op->v2 + c) < *(coord + c)) *(op->v2 + c) = *(coord + c); } } else { for(c = 0; c < 3; c++) { *(op->v1 + c) = *(coord + c); *(op->v2 + c) = *(coord + c); } } op->i1++; } break; case OMOP_MaxDistToPt: a1 = cs->atmToIdx(a); if(a1 >= 0) { float dist; coord = cs->coordPtr(a1); if(op->i2) { /* do we want transformed coordinates? */ if(use_matrices) { if(!cs->Matrix.empty()) { /* state transformation */ transform44d3f(cs->Matrix.data(), coord, v1); coord = v1; } } if(I->TTTFlag) { transformTTT44f3f(I->TTT, coord, v1); coord = v1; } } dist = (float) diff3f(coord, op->v1); if(dist > op->f1) op->f1 = dist; op->i1++; } break; case OMOP_INVA: if(!cs->objMolOpInvalidated) { /* performance optimization: avoid repeatedly calling invalidate on the same coord sets */ a1 = cs->atmToIdx(a); if(a1 >= 0) /* selection touches this coordinate set */ inv_flag = true; /* so set the invalidation flag */ } break; case OMOP_VERT: /* get the atom index whether it's discrete or not */ a1 = cs->atmToIdx(a); if(a1 >= 0) { /* if a1 is a valid atom index, then copy it's xyz coordinates * into vv1; increment the counter, nvv1 */ VLACheck(op->vv1, float, (op->nvv1 * 3) + 2); vv2 = cs->coordPtr(a1); vv1 = op->vv1 + (op->nvv1 * 3); *(vv1++) = *(vv2++); *(vv1++) = *(vv2++); *(vv1++) = *(vv2++); op->nvv1++; } break; case OMOP_SVRT: /* gives us only vertices for a specific coordinate set */ if(b == op->i1) { a1 = cs->atmToIdx(a); if(a1 >= 0) { VLACheck(op->vv1, float, (op->nvv1 * 3) + 2); VLACheck(op->i1VLA, int, op->nvv1); op->i1VLA[op->nvv1] = a; /* save atom index for later comparisons */ vv2 = cs->coordPtr(a1); vv1 = op->vv1 + (op->nvv1 * 3); *(vv1++) = *(vv2++); *(vv1++) = *(vv2++); *(vv1++) = *(vv2++); op->nvv1++; } } break; case OMOP_MOME: /* Moment of inertia tensor - unweighted - assumes v1 is center of molecule */ a1 = cs->atmToIdx(a); if(a1 >= 0) { subtract3f(cs->coordPtr(a1), op->v1, v1); v2 = v1[0] * v1[0] + v1[1] * v1[1] + v1[2] * v1[2]; op->d[0][0] += v2 - v1[0] * v1[0]; op->d[0][1] += -v1[0] * v1[1]; op->d[0][2] += -v1[0] * v1[2]; op->d[1][0] += -v1[1] * v1[0]; op->d[1][1] += v2 - v1[1] * v1[1]; op->d[1][2] += -v1[1] * v1[2]; op->d[2][0] += -v1[2] * v1[0]; op->d[2][1] += -v1[2] * v1[1]; op->d[2][2] += v2 - v1[2] * v1[2]; } break; } } switch (op->code) { /* full coord-set based */ case OMOP_INVA: /* shouldn't this be calling the object invalidation routine instead? */ if(inv_flag) { cs->objMolOpInvalidated = true; for (auto d = cRep_t(0); d < cRepCnt; ++d) { if ((1 << d) & op->i1) { cs->invalidateRep(d, cRepInv_t(op->i2)); } } } break; } if(I->DiscreteFlag) { /* don't iterate every coordinate set for discrete objects! */ break; } } } /* end coordset section */ break; } ai++; } } /* case: default */ break; } if(hit_flag) { switch (op->code) { case OMOP_COLR: ExecutiveUpdateColorDepends(I->G, I); break; case OMOP_TTTF: ObjectMoleculeTransformTTTf(I, op->ttt, -1); break; case OMOP_LABL: I->invalidate(cRepLabel, cRepInvText, -1); break; case OMOP_AlterState: /* overly coarse - doing all states, could do just 1 */ if(!op->i3) { /* not read_only? */ I->invalidate(cRepAll, cRepInvRep, -1); SceneChanged(G); } break; case OMOP_CSetIdxSetFlagged: I->invalidate(cRepAll, cRepInvRep, -1); SceneChanged(G); break; case OMOP_SaveUndo: op->i2 = true; ObjectMoleculeSaveUndo(I, op->i1, false); break; case OMOP_OnOff: ExecutiveSetObjVisib(G, I->Name, op->i1, false); break; case OMOP_RevalenceFromSource: if(ObjectMoleculeXferValences(I, op->i1, op->i2, op->i3, op->obj3, op->i4, op->i5, op->i6)) { ObjectMoleculeVerifyChemistry(I, op->i3); I->invalidate(cRepAll, cRepInvBonds, op->i3); } break; case OMOP_RevalenceByGuessing: { int *flag1 = pymol::calloc(I->NAtom); int *flag2 = pymol::calloc(I->NAtom); if(flag1 && flag2) { int a; int *f1 = flag1; int *f2 = flag2; const AtomInfoType *ai = I->AtomInfo.data(); for(a = 0; a < I->NAtom; a++) { *(f1++) = SelectorIsMember(G, ai->selEntry, op->i1); *(f2++) = SelectorIsMember(G, ai->selEntry, op->i2); ai++; } { int target_state = op->i3; if(target_state < 0) target_state = 0; /* TO DO */ ObjectMoleculeGuessValences(I, target_state, flag1, flag2, op->i4); ObjectMoleculeVerifyChemistry(I, target_state); I->invalidate(cRepAll, cRepInvBonds, target_state); } FreeP(flag1); FreeP(flag2); } } break; } } /* always run on exit... */ switch (op->code) { case OMOP_LABL: if (op->i2 != cExecutiveLabelEvalOn){ break; } case OMOP_ALTR: case OMOP_AlterState: #ifndef _PYMOL_NOPY Py_XDECREF(expr_co); #endif break; } /* */ } return ok; } /*========================================================================*/ void ObjectMoleculeGetAtomSele(const ObjectMolecule * I, int index, char *buffer) { PyMOLGlobals * G = I->G; assert(index < I->NAtom); auto* ai = I->AtomInfo + index; char inscode_str[2] = { ai->inscode, '\0' }; snprintf(buffer, OrthoLineLength, "/%s/%s/%s/%s`%d%s/%s`%s", I->Name, LexStr(G, ai->segi), LexStr(G, ai->chain), LexStr(G, ai->resn), ai->resv, inscode_str, LexStr(G, ai->name), ai->alt); } /** * Get Atom selection string * @param I target Object Molecule * @param index atom index of I */ std::string ObjectMoleculeGetAtomSele(const ObjectMolecule * I, int index) { PyMOLGlobals * G = I->G; assert(index < I->NAtom); auto* ai = I->AtomInfo + index; char inscode_str[2] = { ai->inscode, '\0' }; std::string buffer = pymol::string_format("/%s/%s/%s/%s`%d%s/%s`%s", I->Name, LexStr(G, ai->segi), LexStr(G, ai->chain), LexStr(G, ai->resn), ai->resv, inscode_str, LexStr(G, ai->name), ai->alt); return buffer; } /*========================================================================*/ /** * Get Atom selection string * @param I target Object Molecule * @param index atom index of I */ std::string ObjectMolecule::describeElement(int index) const { auto I = this; auto buffer = ObjectMoleculeGetAtomSele(I, index); if(!I->AtomInfo[index].alt[0]) { // don't include the trailing backtick buffer.pop_back(); } return buffer; } /*========================================================================*/ std::string ObjectMoleculeGetAtomSeleLog(const ObjectMolecule* I, int index, int quote) { OrthoLineType buffer; ObjectMoleculeGetAtomSeleLog(I, index, buffer, quote); return buffer; } void ObjectMoleculeGetAtomSeleLog(const ObjectMolecule * I, int index, char *buffer, int quote) { char *p = quote ? buffer + 1 : buffer; if(SettingGetGlobal_b(I->G, cSetting_robust_logs)) { ObjectMoleculeGetAtomSele(I, index, p); } else { sprintf(p, "(%s`%d)", I->Name, index + 1); } if (quote) { int len = strlen(p); buffer[0] = buffer[len + 1] = '"'; buffer[len + 2] = 0; } } std::string ObjectMoleculeGetAtomSeleFast(const ObjectMolecule* I, int index) { auto G = I->G; auto* ai = I->AtomInfo + index; char inscodestr[2] = {ai->inscode, 0}; return pymol::string_format("(/'%s'/'%s'/'%s'/'%s'`%d%s/'%s'`'%s')", I->Name, LexStr(G, ai->segi), LexStr(G, ai->chain), LexStr(G, ai->resn), ai->resv, inscodestr, ai->name, ai->alt); } /*========================================================================*/ int ObjectMolecule::getNFrame() const { return NCSet; } struct _CCoordSetUpdateThreadInfo { CoordSet *cs; int a; }; void CoordSetUpdateThread(CCoordSetUpdateThreadInfo * T) { if(T->cs) { T->cs->update(T->a); } } #ifndef _PYMOL_NOPY static void ObjMolCoordSetUpdateSpawn(PyMOLGlobals * G, CCoordSetUpdateThreadInfo * Thread, int n_thread, int n_total) { if(n_total == 1) { CoordSetUpdateThread(Thread); } else if(n_total) { int blocked; PyObject *info_list; int a, n = 0; blocked = PAutoBlock(G); PRINTFB(G, FB_Scene, FB_Blather) " Scene: updating coordinate sets with %d threads...\n", n_thread ENDFB(G); info_list = PyList_New(n_total); for(a = 0; a < n_total; a++) { PyList_SetItem(info_list, a, PyCapsule_New(Thread + a, nullptr, nullptr)); n++; } PXDecRef(PYOBJECT_CALLMETHOD (G->P_inst->cmd, "_coordset_update_spawn", "Oi", info_list, n_thread)); Py_DECREF(info_list); PAutoUnblock(G, blocked); } } #endif /*========================================================================*/ void ObjectMolecule::update() { auto I = this; int a; /*, ok; */ OrthoBusyPrime(G); /* if the cached representation is invalid, reset state */ if(!I->RepVisCacheValid) { /* note which representations are active */ /* for each atom in each coordset, blank out the representation cache */ if(I->NCSet > 1) { const AtomInfoType *ai = I->AtomInfo.data(); I->RepVisCache = 0; for(a = 0; a < I->NAtom; a++) { I->RepVisCache |= ai->visRep; ai++; } } else { I->RepVisCache = cRepBitmask; /* if only one coordinate set, then * there's no benefit to pre-filtering * the representations... */ } I->RepVisCacheValid = true; } { /* determine the start/stop states */ int start = 0; int stop = I->NCSet; /* set start and stop given an object */ ObjectAdjustStateRebuildRange(I, &start, &stop); if((I->NCSet == 1) && (SettingGet_b(G, I->Setting.get(), nullptr, cSetting_static_singletons))) { start = 0; stop = 1; } if(stop > I->NCSet) stop = I->NCSet; /* single and multithreaded coord set updates */ { #ifndef _PYMOL_NOPY int n_thread = SettingGetGlobal_i(G, cSetting_max_threads); int multithread = SettingGetGlobal_i(G, cSetting_async_builds); if(multithread && (n_thread) && (stop - start) > 1) { int cnt = 0; /* must precalculate to avoid race-condition since this isn't mutexed yet and neighbors are needed by cartoons */ this->getNeighborArray(); for(a = start; a < stop; a++) if((aNCSet) && I->CSet[a]) cnt++; { CCoordSetUpdateThreadInfo *thread_info = pymol::malloc(cnt); if(thread_info) { cnt = 0; for(a = start; a < stop; a++) { if((aNCSet) && I->CSet[a]) { thread_info[cnt].cs = I->CSet[a]; thread_info[cnt].a = a; cnt++; } } ObjMolCoordSetUpdateSpawn(G, thread_info, n_thread, cnt); FreeP(thread_info); } } } else #endif { /* single thread */ for(a = start; a < stop; a++) { if((aNCSet) && I->CSet[a] && (!G->Interrupt)) { /* status bar */ OrthoBusySlow(G, a, I->NCSet); PRINTFB(G, FB_ObjectMolecule, FB_Blather) " ObjectMolecule-DEBUG: updating representations for state %d of \"%s\".\n", a + 1, I->Name ENDFB(G); I->CSet[a]->update(a); } } } } } /* end block */ PRINTFD(G, FB_ObjectMolecule) " ObjectMolecule: updates complete for object %s.\n", I->Name ENDFD; } /*========================================================================*/ void ObjectMolecule::invalidate(cRep_t rep, cRepInv_t level, int state) { auto I = this; int a; PRINTFD(I->G, FB_ObjectMolecule) " %s: entered. rep: %d level: %d\n", __func__, rep, level ENDFD; auto const level_actual = level; // Remove the "purge" bit level = static_cast(level & ~cRepInvPurgeMask); if(level >= cRepInvVisib) { I->RepVisCacheValid = false; } if (level >= cRepInvBondsNoNonbonded) { if (level < cRepInvBonds) { level = cRepInvBonds; } else { ObjectMoleculeUpdateNonbonded(I); } } if(level >= cRepInvBonds) { this->Neighbor.reset(); if(I->Sculpt) { DeleteP(I->Sculpt); } if(level >= cRepInvAtoms) { SelectorUpdateObjectSele(I->G, I); } } PRINTFD(I->G, FB_ObjectMolecule) " %s: invalidating representations...\n", __func__ ENDFD; if ( level >= cRepInvColor ) { /* after label, this gets called, so we shouldn't invalidate types b/c PYMOL-317 not sure if that is the exact level we should cut this off at 1/28/13 BB: changed from >cRepInvText to >=cRepInvColor for invalidating surface colors during gadget invalidation */ int start = 0; int stop = I->NCSet; if(state >= 0) { start = state; stop = state + 1; } if(stop > I->NCSet) stop = I->NCSet; for(a = start; a < stop; a++) { CoordSet *cset = 0; cset = I->CSet[a]; if(cset) { cset->invalidateRep(rep, level_actual); } } } PRINTFD(I->G, FB_ObjectMolecule) " %s: leaving...\n", __func__ ENDFD; } void ObjectMoleculeInvalidateAtomType(ObjectMolecule *I, int state){ CoordSet *cset = 0; int ai, atm; AtomInfoType *at; cset = I->CSet[state]; if (state < 0){ for (ai=0; ai < I->NAtom; ai++){ at = &I->AtomInfo[ai]; at->textType = 0; } } else { for (ai=0; ai < cset->NIndex; ai++){ atm = cset->IdxToAtm[ai]; if (atm>=0){ at = &I->AtomInfo[ai]; at->textType = 0; } } } } /*========================================================================*/ int ObjectMoleculeMoveAtom(ObjectMolecule * I, int state, int index, const float *v, int mode, int log) { int result = 0; PyMOLGlobals *G = I->G; CoordSet *cs; if(!(I->AtomInfo[index].protekted == cAtomProtected_explicit)) { if(state < 0) state = 0; if(I->NCSet == 1) state = 0; state = state % I->NCSet; if((!I->CSet[state]) && (SettingGet_b(G, I->Setting.get(), nullptr, cSetting_all_states))) state = 0; cs = I->CSet[state]; if(cs) { result = CoordSetMoveAtom(I->CSet[state], index, v, mode); cs->invalidateRep(cRepAll, cRepInvCoord); ExecutiveUpdateCoordDepends(G, I); } } if(log) { OrthoLineType line, buffer; if(SettingGetGlobal_i(G, cSetting_logging)) { ObjectMoleculeGetAtomSele(I, index, buffer); sprintf(line, "cmd.translate_atom(\"%s\",%15.9f,%15.9f,%15.9f,%d,%d,%d)\n", buffer, v[0], v[1], v[2], state + 1, mode, 0); PLog(G, line, cPLog_no_flush); } } return (result); } /*========================================================================*/ int ObjectMoleculeMoveAtomLabel(ObjectMolecule * I, int state, int index, float *v, int log, float *diff) { int result = 0; CoordSet *cs; if(!(I->AtomInfo[index].protekted == cAtomProtected_explicit)) { if(state < 0) state = 0; if(I->NCSet == 1) state = 0; state = state % I->NCSet; if((!I->CSet[state]) && (SettingGet_b(I->G, I->Setting.get(), nullptr, cSetting_all_states))) state = 0; cs = I->CSet[state]; if(cs) { result = CoordSetMoveAtomLabel(I->CSet[state], index, v, diff); cs->invalidateRep(cRepLabel, cRepInvCoord); } } return (result); } /*========================================================================*/ int ObjectMoleculeInitBondPath(ObjectMolecule * I, ObjectMoleculeBPRec * bp) { int a; bp->dist = pymol::malloc(I->NAtom); bp->list = pymol::malloc(I->NAtom); for(a = 0; a < I->NAtom; a++) bp->dist[a] = -1; bp->n_atom = 0; return 1; } /*========================================================================*/ int ObjectMoleculePurgeBondPath(ObjectMolecule * I, ObjectMoleculeBPRec * bp) { FreeP(bp->dist); FreeP(bp->list); return 1; } /*========================================================================*/ int ObjectMoleculeGetBondPaths(ObjectMolecule * I, int atom, int max, ObjectMoleculeBPRec * bp) { /* returns list of bond counts from atom to all others dist and list must be vla array pointers or nullptr */ int b_cnt = 0; /* reinitialize dist array (if we've done at least one pass) */ for (int a = 0; a < bp->n_atom; a++) bp->dist[bp->list[a]] = -1; bp->n_atom = 0; bp->dist[atom] = 0; bp->list[bp->n_atom] = atom; bp->n_atom++; int cur = 0; while(1) { b_cnt++; if(b_cnt > max) break; unsigned n_cur = bp->n_atom - cur; /* iterate through all current atoms */ if(!n_cur) break; while(n_cur--) { int const a1 = bp->list[cur++]; for (auto const& neighbor : AtomNeighbors(I, a1)) { auto const a2 = neighbor.atm; if(bp->dist[a2] < 0) { /* for each atom not yet sampled... */ bp->dist[a2] = b_cnt; bp->list[bp->n_atom] = a2; bp->n_atom++; } } } } return (bp->n_atom); } /*========================================================================*/ int ***ObjectMoleculeGetBondPrint(ObjectMolecule * I, int max_bond, int max_type, int *dim) { int a, b, i, c; int at1, at2; int ***result = nullptr; ObjectMoleculeBPRec bp; dim[0] = max_type + 1; dim[1] = max_type + 1; dim[2] = max_bond + 1; result = (int ***) UtilArrayCalloc((unsigned int *) dim, 3, sizeof(int)); ObjectMoleculeInitBondPath(I, &bp); for(a = 0; a < I->NAtom; a++) { at1 = I->AtomInfo[a].customType; if((at1 >= 0) && (at1 <= max_type)) { ObjectMoleculeGetBondPaths(I, a, max_bond, &bp); for(b = 0; b < bp.n_atom; b++) { i = bp.list[b]; at2 = I->AtomInfo[i].customType; if((at2 >= 0) && (at2 <= max_type)) { c = bp.dist[i]; result[at1][at2][c]++; } } } } ObjectMoleculePurgeBondPath(I, &bp); return (result); } /*========================================================================*/ float ObjectMoleculeGetAvgHBondVector(ObjectMolecule * I, int atom, int state, float *v, float *incoming) /* computes average / optima hydrogen bonding vector for an atom */ { float result = 0.0; int vec_cnt = 0; float v_atom[3], v_neigh[3], v_diff[3], v_acc[3] = { 0.0, 0.0, 0.0 }; int sp2_flag = false; int order; auto const* cs = I->getCoordSet(state); if(cs) { if (CoordSetGetAtomVertex(cs, atom, v_atom)) { /* atom exists in this C-set */ for (auto const& neighbor : AtomNeighbors(I, atom)) { auto const a2 = neighbor.atm; order = I->Bond[neighbor.bond].order; if((order == 2) || (order == 4)) { sp2_flag = true; } const auto& ai = I->AtomInfo[atom]; bool isH = I->AtomInfo[a2].protons == cAN_H; if (!isH || (ai.protons == cAN_O && isH)) { /* ignore hydrogens unless water*/ if (CoordSetGetAtomVertex(cs, a2, v_neigh)) { subtract3f(v_atom, v_neigh, v_diff); normalize3f(v_diff); add3f(v_diff, v_acc, v_acc); vec_cnt++; } } } if(vec_cnt) { result = (float) length3f(v_acc); result = result / vec_cnt; normalize23f(v_acc, v); } else { copy3f(v_acc, v); } if(incoming && (vec_cnt == 1) && (fabs(dot_product3f(v, incoming)) < 0.99F)) { /* if we know where the donor is, and the acceptor can potentially rotate the lone pair, then we should optimally orient the acceptor, if possible */ AtomInfoType *ai = I->AtomInfo + atom; float v_perp[3]; float v_tmp1[3], v_tmp2[3]; if(((ai->protons == cAN_O) && (!sp2_flag)) || /* C-O-H */ ((ai->protons == cAN_N) && (sp2_flag))) { /* C=N-H */ remove_component3f(incoming, v, v_perp); normalize3f(v_perp); scale3f(v, 0.333644F, v_tmp1); scale3f(v_perp, 0.942699F, v_tmp2); add3f(v_tmp1, v_tmp2, v_tmp2); subtract3f(v, v_tmp2, v); normalize3f(v); } } } } return (result); } /*========================================================================*/ int ObjectMoleculeGetAtomVertex(const ObjectMolecule * I, int state, int index, float *v) { int result = 0; if(state < 0) state = SettingGet_i(I->G, nullptr, I->Setting.get(), cSetting_state) - 1; if(state < 0) state = SceneGetState(I->G); if(I->NCSet == 1) state = 0; /* static singletons always active here it seems */ state = state % I->NCSet; if((!I->CSet[state]) && (SettingGet_b(I->G, I->Setting.get(), nullptr, cSetting_all_states))) state = 0; if(I->CSet[state]) result = CoordSetGetAtomVertex(I->CSet[state], index, v); return (result); } /*========================================================================*/ int ObjectMoleculeGetAtomTxfVertex(const ObjectMolecule * I, int state, int index, float *v) { int result = 0; const CoordSet* cs = nullptr; if (I->DiscreteFlag){ cs = I->DiscreteCSet[index]; } if(state < 0){ state = SettingGet_i(I->G, nullptr, I->Setting.get(), cSetting_state) - 1; } if(state < 0) state = SceneGetState(I->G); if(I->NCSet == 1) state = 0; /* static singletons always active here it seems */ state = state % I->NCSet; { if (!cs) cs = I->CSet[state]; if((!cs) && (SettingGet_b(I->G, I->Setting.get(), nullptr, cSetting_all_states))) { state = 0; cs = I->CSet[state]; } if(cs) { result = CoordSetGetAtomTxfVertex(cs, index, v); } } return (result); } /*========================================================================*/ int ObjectMoleculeSetAtomVertex(ObjectMolecule * I, int state, int index, float *v) { int result = 0; if(state < 0) state = SettingGet_i(I->G, nullptr, I->Setting.get(), cSetting_state) - 1; if(state < 0) state = SceneGetState(I->G); if(I->NCSet == 1) state = 0; state = state % I->NCSet; if((!I->CSet[state]) && (SettingGet_b(I->G, I->Setting.get(), nullptr, cSetting_all_states))) state = 0; if(I->CSet[state]) result = CoordSetSetAtomVertex(I->CSet[state], index, v); return (result); } /*========================================================================*/ void ObjectMolecule::render(RenderInfo * info) { auto I = this; int state = info->state; const RenderPass pass = info->pass; CoordSet *cs; int pop_matrix = false; int use_matrices = SettingGet_i(I->G, I->Setting.get(), nullptr, cSetting_matrix_mode); if(use_matrices<0) use_matrices = 0; PRINTFD(I->G, FB_ObjectMolecule) " ObjectMolecule: rendering %s pass %d...\n", I->Name, static_cast(pass) ENDFD; ObjectPrepareContext(I, info); for(StateIterator iter(G, I->Setting.get(), state, I->NCSet); iter.next();) { cs = I->CSet[iter.state]; if(cs) { if(use_matrices) pop_matrix = ObjectStatePushAndApplyMatrix(cs, info); cs->render(info); if(pop_matrix) ObjectStatePopMatrix(cs, info); } } PRINTFD(I->G, FB_ObjectMolecule) " ObjectMolecule: rendering complete for object %s.\n", I->Name ENDFD; } /*========================================================================*/ void ObjectMoleculeDummyUpdate(ObjectMolecule * I, int mode) { switch (mode) { case cObjectMoleculeDummyOrigin: SceneOriginGet(I->G, I->CSet[0]->Coord.data()); break; case cObjectMoleculeDummyCenter: SceneGetCenter(I->G, I->CSet[0]->Coord.data()); break; } } /*========================================================================*/ ObjectMolecule *ObjectMoleculeDummyNew(PyMOLGlobals * G, int type) { auto I = new ObjectMolecule(G, false); I->AtomInfo.resize(I->NAtom = 1); auto* cset = new CoordSet(G); cset->Obj = I; cset->setNIndex(1); cset->enumIndices(); I->CSet.resize(I->NCSet = 1); I->CSet[0] = cset; return I; } /*========================================================================*/ ObjectMolecule::ObjectMolecule(PyMOLGlobals * G, int discreteFlag) : pymol::CObject(G) { auto I = this; int a; I->type = cObjectMolecule; I->CSet = pymol::vla(10); /* auto-zero */ I->DiscreteFlag = discreteFlag; if(I->DiscreteFlag) { /* discrete objects don't share atoms between states */ I->DiscreteAtmToIdx = pymol::vla(0); I->DiscreteCSet = pymol::vla(0); } else { I->DiscreteAtmToIdx = nullptr; I->DiscreteCSet = nullptr; } I->AtomInfo = pymol::vla(10); for(a = 0; a <= cUndoMask; a++) { I->UndoCoord[a] = nullptr; I->UndoState[a] = -1; } I->UndoIter = 0; } /*========================================================================*/ void ObjectMoleculeCopyNoAlloc(const ObjectMolecule* obj, ObjectMolecule* I) { PyMOLGlobals * G = const_cast(obj->G); int a; BondType *i0; const BondType *i1; (*I) = (*obj); I->Sculpt = nullptr; I->Setting.reset(SettingCopyAll(G, obj->Setting.get(), nullptr)); I->ViewElem = nullptr; I->gridSlotSelIndicatorsCGO = nullptr; for(a = 0; a <= cUndoMask; a++) I->UndoCoord[a] = nullptr; I->CSet = pymol::vla(I->NCSet); /* auto-zero */ for(a = 0; a < I->NCSet; a++) { I->CSet[a] = CoordSetCopy(obj->CSet[a]); if (I->CSet[a]) I->CSet[a]->Obj = I; } if(obj->CSTmpl) I->CSTmpl = CoordSetCopy(obj->CSTmpl); if (obj->DiscreteFlag){ int sz = VLAGetSize(obj->DiscreteAtmToIdx); I->DiscreteAtmToIdx = VLACopy2(obj->DiscreteAtmToIdx); I->DiscreteCSet = pymol::vla(sz); I->updateAtmToIdx(); } I->Bond = pymol::vla(I->NBond); i0 = I->Bond.data(); i1 = obj->Bond.data(); for(a = 0; a < I->NBond; a++) { AtomInfoBondCopy(G, i1++, i0++); } // unfortunately, the AtomInfoType is not copy-constructable yet // assert copy done correct if (I->AtomInfo.size() != obj->AtomInfo.size()) { throw "AtomInfo copy failed"; } AtomInfoType *a0 = I->AtomInfo.data(); const AtomInfoType* a1 = obj->AtomInfo.data(); memset(a0, 0, sizeof(AtomInfoType) * I->NAtom); for(a = 0; a < I->NAtom; a++) AtomInfoCopy(G, a1++, a0++); } ObjectMolecule *ObjectMoleculeCopy(const ObjectMolecule * obj) { PyMOLGlobals * G = const_cast(obj->G); auto I = new ObjectMolecule(G, obj->DiscreteFlag); ObjectMoleculeCopyNoAlloc(obj, I); return (I); } /*========================================================================*/ /** * Set the order of coordinate sets with an index array */ int ObjectMoleculeSetStateOrder(ObjectMolecule * I, int * order, int len) { int a; CoordSet ** csets = VLAlloc(CoordSet *, I->NCSet); ok_assert(1, len == I->NCSet); // invalidate I->invalidate(cRepAll, cRepInvAll, -1); // new coord set array for(a = 0; a < I->NCSet; a++) { int i = order[a]; ok_assert(1, 0 <= i && i < I->NCSet); csets[a] = I->CSet[i]; } VLAFreeP(I->CSet); I->CSet = pymol::vla_take_ownership(csets); return true; ok_except1: ErrMessage(I->G, "ObjectMoleculeSetStateOrder", "failed"); VLAFreeP(csets); return false; } /*========================================================================*/ pymol::Result<> ObjectMoleculeDeleteStates(ObjectMolecule* I, const std::vector& states) { auto& CSet = I->CSet; // first pass, check for invalid states for (auto state : states) { if (state < 0 || state >= I->NCSet) { auto msg = pymol::string_format("Invalid state index: %d", state); I->G->Feedback->addColored(msg.c_str(), FB_Errors); return {}; } } // second pass, delete states for (auto it = states.rbegin(); it != states.rend(); ++it) { int state = *it; DeleteP(CSet[state]); CSet.erase(state, 1); } I->NCSet -= states.size(); CSet.resize(I->NCSet); // Update Rep States for (int i = 0; i < I->NCSet; ++i) { if (CSet[i]) { auto& cset = *CSet[i]; for (int r = 0; r < cRepCnt; ++r) { if (cset.Rep[r] && cset.Rep[r]->context.state != 0) { cset.Rep[r]->context.state = i; } } } } return {}; } /*========================================================================*/ ObjectMolecule::~ObjectMolecule() { auto I = this; int a; SelectorPurgeObjectMembers(I->G, I); for(a = 0; a < I->NCSet; a++){ if(I->CSet[a]) { delete I->CSet[a]; I->CSet[a] = nullptr; } } VLAFreeP(I->DiscreteAtmToIdx); VLAFreeP(I->DiscreteCSet); VLAFreeP(I->CSet); I->m_ciffile.reset(); // free data { int nAtom = I->NAtom; AtomInfoType *ai = I->AtomInfo.data(); for(a = 0; a < nAtom; a++) { AtomInfoPurge(I->G, ai); ai++; } VLAFreeP(I->AtomInfo); } { int nBond = I->NBond; BondType *bi = I->Bond.data(); for(a = 0; a < nBond; a++) { AtomInfoPurgeBond(I->G, bi); bi++; } VLAFreeP(I->Bond); } for(a = 0; a <= cUndoMask; a++) FreeP(I->UndoCoord[a]); if(I->Sculpt) DeleteP(I->Sculpt); delete I->CSTmpl; } /*========================================================================*/ ObjectMolecule *ObjectMoleculeReadPDBStr(PyMOLGlobals * G, ObjectMolecule * I, const char *PDBStr, int state, int discrete, char *pdb_name, const char **next_pdb, PDBInfoRec * pdb_info, int quiet, int *model_number) { CoordSet *cset = nullptr; pymol::vla atInfo; int ok = true; int isNew = true; unsigned int nAtom = 0; const char *start, *restart = nullptr; int repeatFlag = true; int successCnt = 0; unsigned int aic_mask = cAIC_PDBMask; SegIdent segi_override = ""; /* saved segi for corrupted NMR pdb files */ start = PDBStr; while(repeatFlag) { repeatFlag = false; if(!I) isNew = true; else isNew = false; if(ok) { if(isNew) { I = (ObjectMolecule *) new ObjectMolecule(G, discrete); CHECKOK(ok, I); if (ok) std::swap(atInfo, I->AtomInfo); isNew = true; } else { atInfo = pymol::vla(10); CHECKOK(ok, atInfo); isNew = false; } if(ok && isNew) { I->Color = AtomInfoUpdateAutoColor(G); if (pdb_info->variant == PDB_VARIANT_VDB || pdb_info->variant == PDB_VARIANT_PQR) { // pqr files have no chain identifier by default // vdb files have same chain identifiers in all symmetry copies SettingSet(cSetting_retain_order, 1, I); } } if (ok) cset = ObjectMoleculePDBStr2CoordSet(G, start, &atInfo, &restart, segi_override, pdb_name, next_pdb, pdb_info, quiet, model_number); CHECKOK(ok, cset); if (ok){ nAtom = cset->NIndex; } } if(ok && pdb_name && (*next_pdb)) { /* problematic scenario? */ } /* include coordinate set */ if(ok) { if(I->DiscreteFlag && atInfo) { unsigned int a; int fp1 = state + 1; AtomInfoType *ai = atInfo.data(); for(a = 0; a < nAtom; a++) { (ai++)->discrete_state = fp1; } } cset->Obj = I; cset->enumIndices(); cset->invalidateRep(cRepAll, cRepInvRep); if(isNew) { std::swap(I->AtomInfo, atInfo); } else { ok &= ObjectMoleculeMerge(I, std::move(atInfo), cset, true, aic_mask, true); /* NOTE: will release atInfo */ } if(isNew) I->NAtom = nAtom; if(state < 0) state = I->NCSet; if(*model_number > 0) { if(SettingGetGlobal_b(G, cSetting_pdb_honor_model_number)) state = *model_number - 1; } VLACheck(I->CSet, CoordSet *, state); CHECKOK(ok, I->CSet); if(ok){ if(I->NCSet <= state) I->NCSet = state + 1; delete I->CSet[state]; I->CSet[state] = cset; } if(ok && isNew) ok &= ObjectMoleculeConnect(I, cset); if(ok && cset->Symmetry) { I->Symmetry.reset(new CSymmetry(*cset->Symmetry)); } if (I->Symmetry) { /* check scale records */ if(pdb_info && pdb_info->scale.flag[0] && pdb_info->scale.flag[1] && pdb_info->scale.flag[2]) { float *sca = pdb_info->scale.matrix; sca[15] = 1.0F; CoordSetInsureOrthogonal(G, cset, sca, &I->Symmetry->Crystal, quiet); } } SceneCountFrames(G); if (ok) ok &= ObjectMoleculeExtendIndices(I, state); if (ok) ok &= ObjectMoleculeSort(I); if (ok){ ObjectMoleculeUpdateIDNumbers(I); ObjectMoleculeUpdateNonbonded(I); ObjectMoleculeAutoDisableAtomNameWildcard(I); } if(SettingGetGlobal_b(G, cSetting_pdb_hetatm_guess_valences)) { ObjectMoleculeGuessValences(I, state, nullptr, nullptr, false); } successCnt++; if(!quiet) { if(successCnt > 1) { if(successCnt == 2) { PRINTFB(G, FB_ObjectMolecule, FB_Actions) " %s: read MODEL %d\n", __func__, 1 ENDFB(G); } PRINTFB(G, FB_ObjectMolecule, FB_Actions) " %s: read MODEL %d\n", __func__, successCnt ENDFB(G); } } } if(restart) { repeatFlag = true; start = restart; state = state + 1; } } if (!ok && isNew){ DeleteP(I); } return (I); } /*========================================================================*/ static CoordSet *ObjectMoleculeMMDStr2CoordSet(PyMOLGlobals * G, const char *buffer, AtomInfoType ** atInfoPtr, const char **restart) { const char *p; int nAtom, nBond; int a, c, bPart, bOrder; float *coord = nullptr; CoordSet *cset = nullptr; AtomInfoType *atInfo = nullptr, *ai; char cc[MAXLINELEN]; WordType title; float *f; BondType *ii, *bond = nullptr; int ok = true; int auto_show = RepGetAutoShowMask(G); p = buffer; nAtom = 0; if(atInfoPtr) atInfo = *atInfoPtr; if(ok) { p = ncopy(cc, p, 6); // could this be too small for large molecules with #atoms > 999999 ? if(sscanf(cc, "%d", &nAtom) != 1) ok = ErrMessage(G, "ReadMMDFile", "bad atom count"); } if(ok) { coord = VLAlloc(float, 3 * nAtom); if(atInfo) VLACheck(atInfo, AtomInfoType, nAtom); } if(!atInfo) { ErrFatal(G, "MMDStr2CoordSet", "need atom information record!"); /* failsafe for old version.. */ } nBond = 0; if(ok) { bond = VLACalloc(BondType, 6 * nAtom); } p = ParseWordCopy(title, p, sizeof(WordType) - 1); UtilCleanStr(title); p = nextline(p); /* read coordinates and atom names */ if(ok) { f = coord; ii = bond; for(a = 0; a < nAtom; a++) { ai = atInfo + a; ai->id = a + 1; ai->rank = a; if(ok) { p = ncopy(cc, p, 4); if(sscanf(cc, "%d", &ai->customType) != 1) ok = ErrMessage(G, "ReadMMDFile", "bad atom type"); } if(ok) { if(ai->customType <= 14) strcpy(ai->elem, "C"); else if(ai->customType <= 23) strcpy(ai->elem, "O"); else if(ai->customType <= 40) strcpy(ai->elem, "N"); else if(ai->customType <= 48) strcpy(ai->elem, "H"); else if(ai->customType <= 52) strcpy(ai->elem, "S"); else if(ai->customType <= 53) strcpy(ai->elem, "P"); else if(ai->customType <= 55) strcpy(ai->elem, "B"); else if(ai->customType <= 56) strcpy(ai->elem, "F"); else if(ai->customType <= 57) strcpy(ai->elem, "Cl"); else if(ai->customType <= 58) strcpy(ai->elem, "Br"); else if(ai->customType <= 59) strcpy(ai->elem, "I"); else if(ai->customType <= 60) strcpy(ai->elem, "Si"); else if(ai->customType <= 61) strcpy(ai->elem, "Du"); else if(ai->customType <= 62) strcpy(ai->elem, "Z0"); else if(ai->customType <= 63) strcpy(ai->elem, "Lp"); else ai->elem[0] = 0; /* else strcpy(ai->elem,"?"); WLD 090305 -- guess instead. */ } for(c = 0; c < 6; c++) { if(ok) { p = ncopy(cc, p, 8); if(sscanf(cc, "%d%d", &bPart, &bOrder) != 2) ok = ErrMessage(G, "ReadMMDFile", "bad bond record"); else { if(bPart && bOrder && (a < (bPart - 1))) { nBond++; ii->index[0] = a; ii->index[1] = bPart - 1; ii->order = bOrder; ii++; } } } } if(ok) { p = ncopy(cc, p, 12); if(sscanf(cc, "%f", f++) != 1) ok = ErrMessage(G, "ReadMMDFile", "bad coordinate"); } if(ok) { p = ncopy(cc, p, 12); if(sscanf(cc, "%f", f++) != 1) ok = ErrMessage(G, "ReadMMDFile", "bad coordinate"); } if(ok) { p = ncopy(cc, p, 12); if(sscanf(cc, "%f", f++) != 1) ok = ErrMessage(G, "ReadMMDFile", "bad coordinate"); } if(ok) { p = nskip(p, 1); p = ncopy(cc, p, 5); ai->setResi(cc); // single-letter code p = nskip(p, 1); // chain p = ncopy(cc, p, 1); LexAssign(G, ai->chain, cc); } if(ok) { p = nskip(p, 4); p = ncopy(cc, p, 9); if(sscanf(cc, "%f", &ai->partialCharge) != 1) ok = ErrMessage(G, "ReadMMDFile", "bad charge"); } if(ok) { p = nskip(p, 10); p = ncopy(cc, p, 3); UtilCleanStr(cc); LexAssign(G, ai->resn, cc); ai->hetatm = true; } ai->segi = 0; ai->alt[0] = 0; if(ok) { p = nskip(p, 2); p = ntrim(cc, p, 4); if(!cc[0]) { sprintf(cc, "%s%02d", ai->elem, a + 1); } ai->name = LexIdx(G, cc); ai->visRep = auto_show; } if(ok) { AtomInfoAssignParameters(G, ai); AtomInfoAssignColors(G, ai); } if(!ok) break; p = nextline(p); } } if(ok) VLASize(bond, BondType, nBond); if(ok) { cset = CoordSetNew(G); cset->NIndex = nAtom; cset->Coord = pymol::vla_take_ownership(coord); cset->NTmpBond = nBond; cset->TmpBond = pymol::vla_take_ownership(bond); strcpy(cset->Name, title); } else { VLAFreeP(bond); VLAFreeP(coord); } if(atInfoPtr) *atInfoPtr = atInfo; *restart = *p ? p : nullptr; return (cset); } #ifdef _PYMOL_IP_EXTRAS #endif CPythonVal* ObjectMoleculeGetProperty( ObjectMolecule* I, const char* propname, int state, int quiet) { PyMOLGlobals* G = I->G; for (StateIterator iter(G, I->Setting.get(), state, I->NCSet); iter.next();) { if (I->CSet[iter.state]) return PropertyGetPyObject(G, I->CSet[iter.state], propname); } return nullptr; } int ObjectMoleculeSetProperty(ObjectMolecule* I, const char* propname, CPythonVal* value, const PropertyType& proptype, int state, int quiet) { int ok = true; PyMOLGlobals* G = I->G; for (StateIterator iter(G, I->Setting.get(), state, I->NCSet); iter.next();) { if (I->CSet[iter.state]) ok &= PropertySet(G, I->CSet[iter.state], propname, value, proptype); } return (ok); } void AtomInfoSettingGenerateSideEffects(PyMOLGlobals * G, ObjectMolecule *obj, int index, int id){ switch(index){ case cSetting_label_position: case cSetting_label_placement_offset: case cSetting_label_screen_point: case cSetting_label_relative_mode: obj->invalidate(cRepLabel, cRepInvCoord, -1); } } static int AtomInfoInOrder(PyMOLGlobals * G, const AtomInfoType * atom, int atom1, int atom2) { return (AtomInfoCompare(G, atom + atom1, atom + atom2) <= 0); } static int AtomInfoInOrderIgnoreHet(PyMOLGlobals * G, const AtomInfoType * atom, int atom1, int atom2) { return (AtomInfoCompareIgnoreHet(G, atom + atom1, atom + atom2) <= 0); } static int AtomInfoInOrigOrder(PyMOLGlobals * G, const AtomInfoType * atom, int atom1, int atom2) { if(atom[atom1].rank == atom[atom2].rank) return (AtomInfoCompare(G, atom + atom1, atom + atom2) <= 0); return (atom[atom1].rank < atom[atom2].rank); } int *AtomInfoGetSortedIndex(PyMOLGlobals * G, const ObjectMolecule* obj, const AtomInfoType* rec, int n, int **outdex) { int *index; int a; const CSetting* setting = nullptr; ok_assert(1, index = pymol::malloc(n + 1)); ok_assert(1, (*outdex) = pymol::malloc(n + 1)); if(obj && obj->DiscreteFlag) { for(a = 0; a < n; a++) index[a] = a; } else { if(obj) setting = obj->Setting.get(); UtilSortIndexGlobals(G, n, rec, index, (UtilOrderFnGlobals *) ( SettingGet_b(G, setting, nullptr, cSetting_retain_order) ? AtomInfoInOrigOrder : SettingGet_b(G, setting, nullptr, cSetting_pdb_hetatm_sort) ? AtomInfoInOrder : AtomInfoInOrderIgnoreHet)); } for(a = 0; a < n; a++) (*outdex)[index[a]] = a; return index; ok_except1: FreeP(index); return nullptr; } /** * Set the size of the DiscreteAtmToIdx and DiscreteCSet VLAs and pad * them with -1/NULL if necessary. */ bool ObjectMolecule::setNDiscrete(int natom) { int n = VLAGetSize(DiscreteAtmToIdx); if (n == natom) return true; VLASize(DiscreteAtmToIdx, int, natom); VLASize(DiscreteCSet, CoordSet*, natom); if (!DiscreteAtmToIdx || !DiscreteCSet) return false; for (int i = n; i < natom; ++i) { DiscreteAtmToIdx[i] = -1; DiscreteCSet[i] = nullptr; } return true; } /** * Update the AtmToIdx or DiscreteAtmToIdx/DiscreteCSet VLAs from the * IdxToAtm arrays */ bool ObjectMolecule::updateAtmToIdx() { if (DiscreteFlag) { ok_assert(1, setNDiscrete(NAtom)); } for (int i = -1; i < NCSet; ++i) { CoordSet * cset = (i < 0) ? CSTmpl : CSet[i]; if (!cset) continue; if (!DiscreteFlag) { cset->updateNonDiscreteAtmToIdx(NAtom); } else { for (int idx = 0; idx < cset->NIndex; ++idx) { int atm = cset->IdxToAtm[idx]; assert(atm < NAtom); DiscreteAtmToIdx[atm] = idx; DiscreteCSet[atm] = cset; AtomInfo[atm].discrete_state = i + 1; } } } return true; ok_except1: return false; } /** * Check if this atom has coordinates in any state */ bool ObjectMolecule::atomHasAnyCoordinates(size_t atm) const { for (size_t i = 0; i < NCSet; ++i) { auto cset = CSet[i]; if (cset && cset->atmToIdx(atm) != -1) { return true; } } return false; } pymol::CObject* ObjectMolecule::clone() const { return ObjectMoleculeCopy(this); } AtomNeighbors::AtomNeighbors(const ObjectMolecule* I, int atm) { auto* Neighbor = I->getNeighborArray(); m_neighbor = Neighbor + Neighbor[atm]; }