mirror of
https://github.com/schrodinger/pymol-open-source.git
synced 2026-06-03 19:54:24 +08:00
4124 lines
123 KiB
C++
4124 lines
123 KiB
C++
|
|
/*
|
|
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 <utility>
|
|
|
|
#ifdef PYMOL_OPENMP
|
|
#include <omp.h>
|
|
#endif
|
|
|
|
#include"Version.h"
|
|
#include"os_python.h"
|
|
|
|
#include"os_predef.h"
|
|
#include"os_std.h"
|
|
#include"os_gl.h"
|
|
|
|
#include"Base.h"
|
|
#include"Parse.h"
|
|
#include"Err.h"
|
|
#include"Vector.h"
|
|
#include"PConv.h"
|
|
#include"ObjectMolecule.h"
|
|
#include"Feedback.h"
|
|
#include"Util.h"
|
|
#include"Util2.h"
|
|
#include"AtomInfo.h"
|
|
#include"Selector.h"
|
|
#include"SymOpPConv.h"
|
|
#include"ObjectDist.h"
|
|
#include"Executive.h"
|
|
#include"P.h"
|
|
#include"ObjectCGO.h"
|
|
#include"Scene.h"
|
|
#include "Lex.h"
|
|
|
|
#include"AtomInfoHistory.h"
|
|
#include"BondTypeHistory.h"
|
|
|
|
#include"Property.h"
|
|
|
|
#include "pymol/zstring_view.h"
|
|
|
|
#include <functional>
|
|
#include <iostream>
|
|
#include <map>
|
|
|
|
#define ntrim ParseNTrim
|
|
#define nextline ParseNextLine
|
|
#define ncopy ParseNCopy
|
|
#define nskip ParseNSkip
|
|
|
|
#define cResvMask 0x7FFF
|
|
|
|
#define cMaxOther 6
|
|
|
|
#define strstartswith p_strstartswith
|
|
|
|
static int strstartswithword(const char * s, const char * word) {
|
|
while(*word)
|
|
if(*s++ != *word++)
|
|
return false;
|
|
switch(*s) {
|
|
case ' ':
|
|
case '\t':
|
|
case '\r':
|
|
case '\n':
|
|
case '\0':
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void AddOrthoOutputIfMatchesTags(PyMOLGlobals * G, int n_tags,
|
|
int nAtom, const char* const* tag_start, const char *p, char *cc, int quiet)
|
|
{
|
|
if(n_tags && !quiet && !(nAtom > 0 && strstartswith(p, "HEADER"))) {
|
|
// HEADER is the mark for a new object, this is a new object, and
|
|
// gets processed on the next pass, when nAtom=0
|
|
int tc = 0;
|
|
for(; tc < n_tags; tc++) {
|
|
if(!strstartswithword(p, tag_start[tc]))
|
|
continue;
|
|
ParseNTrimRight(cc, p, MAXLINELEN - 1);
|
|
OrthoAddOutput(G, cc);
|
|
OrthoNewLine(G, nullptr, true);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
typedef struct {
|
|
int n_cyclic_arom, cyclic_arom[cMaxOther];
|
|
int n_arom, arom[cMaxOther];
|
|
int n_high_val, high_val[cMaxOther];
|
|
int n_cyclic, cyclic[cMaxOther];
|
|
int n_planer, planer[cMaxOther];
|
|
int n_rest, rest[cMaxOther];
|
|
int score;
|
|
} OtherRec;
|
|
|
|
static int populate_other(OtherRec * other, int at,
|
|
const AtomInfoType* ai,
|
|
const BondType* bd,
|
|
const ObjectMolecule* obj)
|
|
{
|
|
int five_cycle = false;
|
|
int six_cycle = false;
|
|
|
|
{
|
|
// don't get bogged down with structures that have unreasonable connectivity
|
|
const int ESCAPE_MAX = 500;
|
|
int escape_count = ESCAPE_MAX;
|
|
|
|
auto const mem0_atm = bd->index[0];
|
|
auto const mem1_atm = bd->index[1];
|
|
|
|
for (auto const& mem2 : AtomNeighbors(obj, mem1_atm)) {
|
|
if (mem2.atm == mem0_atm) {
|
|
continue;
|
|
}
|
|
|
|
for (auto const& mem3 : AtomNeighbors(obj, mem2.atm)) {
|
|
if (mem3.atm == mem1_atm) {
|
|
continue;
|
|
}
|
|
|
|
for (auto const& mem4 : AtomNeighbors(obj, mem3.atm)) {
|
|
if (mem4.atm == mem2.atm || mem4.atm == mem1_atm ||
|
|
mem4.atm == mem0_atm) {
|
|
continue;
|
|
}
|
|
|
|
for (auto const& mem5 : AtomNeighbors(obj, mem4.atm)) {
|
|
if (!(escape_count--)) {
|
|
goto escape;
|
|
}
|
|
|
|
if (mem5.atm == mem3.atm || mem5.atm == mem2.atm ||
|
|
mem5.atm == mem1_atm) {
|
|
continue;
|
|
}
|
|
|
|
if (mem5.atm == mem0_atm) { /* five-cycle */
|
|
five_cycle = true;
|
|
|
|
if (six_cycle) {
|
|
goto escape;
|
|
}
|
|
}
|
|
|
|
for (auto const& mem6 : AtomNeighbors(obj, mem5.atm)) {
|
|
if (mem6.atm == mem4.atm || mem6.atm == mem3.atm ||
|
|
mem6.atm == mem2.atm || mem6.atm == mem1_atm) {
|
|
continue;
|
|
}
|
|
|
|
if (mem6.atm == mem0_atm) { /* six-cycle */
|
|
six_cycle = true;
|
|
|
|
if (five_cycle) {
|
|
goto escape;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
escape:
|
|
|
|
if(bd->order == 4) { /* aromatic */
|
|
if((five_cycle || six_cycle) && (other->n_cyclic_arom < cMaxOther)) {
|
|
other->cyclic_arom[other->n_cyclic_arom++] = at;
|
|
if(five_cycle && six_cycle)
|
|
other->score += 34;
|
|
else if(five_cycle)
|
|
other->score += 33;
|
|
else
|
|
other->score += 32;
|
|
return 1;
|
|
} else if(other->n_arom < cMaxOther) {
|
|
other->arom[other->n_arom++] = at;
|
|
other->score += 64;
|
|
return 1;
|
|
}
|
|
}
|
|
if(bd->order > 1) {
|
|
if(other->n_high_val < cMaxOther) {
|
|
other->high_val[other->n_high_val++] = at;
|
|
other->score += 16;
|
|
return 1;
|
|
}
|
|
}
|
|
if(five_cycle || six_cycle) {
|
|
if(other->n_cyclic < cMaxOther) {
|
|
other->cyclic[other->n_cyclic++] = at;
|
|
other->score += 8;
|
|
return 1;
|
|
}
|
|
}
|
|
if(ai->geom == cAtomInfoPlanar) {
|
|
if(other->n_planer < cMaxOther) {
|
|
other->planer[other->n_planer++] = at;
|
|
other->score += 4;
|
|
return 1;
|
|
}
|
|
}
|
|
if(other->n_rest < cMaxOther) {
|
|
other->rest[other->n_rest++] = at;
|
|
other->score += 1;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int append_index(int *result, int offset, int a1, int a2, int score, int ar_count)
|
|
{
|
|
int c;
|
|
c = result[a1];
|
|
while(c < offset) {
|
|
if(result[c] == a2) { /* already entered */
|
|
if(result[c + 1] < score) {
|
|
result[c + 1] = score;
|
|
result[c + 2] = ar_count;
|
|
}
|
|
return offset;
|
|
}
|
|
c += 3;
|
|
}
|
|
result[offset++] = a2;
|
|
result[offset++] = score;
|
|
result[offset++] = ar_count;
|
|
return offset;
|
|
}
|
|
|
|
int ObjectMoleculeAddPseudoatom(ObjectMolecule * I, int sele_index, const char *name,
|
|
const char *resn, const char *resi, const char *chain,
|
|
const char *segi, const char *elem, float vdw,
|
|
int hetatm, float b, float q, const char *label,
|
|
const float *pos, int color, int state, int mode, int quiet)
|
|
{
|
|
PyMOLGlobals *G = I->G;
|
|
int start_state = 0, stop_state = 0;
|
|
int extant_only = false;
|
|
int ai_merged = false;
|
|
float pos_array[3] = { 0.0F, 0.0F, 0.0F };
|
|
int ok = true;
|
|
|
|
pymol::vla<AtomInfoType> atInfo(1);
|
|
AtomInfoType* ai = atInfo.data();
|
|
|
|
// FIXME this should be cStateCurrent
|
|
if (state == cStateAll) {
|
|
state = I->getCurrentState();
|
|
}
|
|
|
|
if(state >= 0) { /* specific state */
|
|
start_state = state;
|
|
stop_state = state + 1;
|
|
} else { /* all states */
|
|
if(sele_index >= 0) {
|
|
start_state = 0;
|
|
stop_state = SelectorCountStates(G, sele_index);
|
|
|
|
// Here, -3 does not mean cSelectorUpdateTableEffectiveStates. It means
|
|
// all states present in `sele_index` AND `I`. State != -3 means all
|
|
// states in `sele_index`, so it would create new states in `I` if they
|
|
// don't exist yet.
|
|
if(state == -3)
|
|
extant_only = true;
|
|
} else {
|
|
start_state = 0;
|
|
stop_state = ExecutiveCountStates(G, cKeywordAll);
|
|
if(stop_state < 1)
|
|
stop_state = 1;
|
|
}
|
|
}
|
|
{
|
|
/* match existing properties of the old atom */
|
|
ai->setResi(resi);
|
|
ai->hetatm = hetatm;
|
|
ai->geom = cAtomInfoNone;
|
|
ai->q = q;
|
|
ai->b = b;
|
|
ai->chain = LexIdx(G, chain);
|
|
ai->segi = LexIdx(G, segi);
|
|
ai->resn = LexIdx(G, resn);
|
|
ai->name = LexIdx(G, name);
|
|
strcpy(ai->elem, elem);
|
|
ai->id = -1;
|
|
ai->rank = -1;
|
|
if(vdw >= 0.0F)
|
|
ai->vdw = vdw;
|
|
else
|
|
ai->vdw = 1.0F;
|
|
if(label[0]) {
|
|
ai->label = LexIdx(G, label);
|
|
ai->visRep = cRepLabelBit;
|
|
} else {
|
|
ai->visRep = RepGetAutoShowMask(G);
|
|
}
|
|
|
|
ai->flags |= cAtomFlag_inorganic; // suppress auto_show_classified
|
|
|
|
if(color < 0) {
|
|
AtomInfoAssignColors(I->G, ai);
|
|
if((ai->elem[0] == 'C') && (ai->elem[1] == 0))
|
|
/* carbons are always colored according to the object color */
|
|
ai->color = I->Color;
|
|
} else {
|
|
ai->color = color;
|
|
}
|
|
AtomInfoAssignParameters(I->G, ai);
|
|
AtomInfoUniquefyNames(I->G, I->AtomInfo, I->NAtom, ai, nullptr, 1);
|
|
if(!quiet) {
|
|
PRINTFB(G, FB_ObjectMolecule, FB_Actions)
|
|
" ObjMol: created %s/%s/%s/%s`%d%c/%s\n",
|
|
I->Name, LexStr(G, ai->segi), LexStr(G, ai->chain),
|
|
LexStr(G, ai->resn), ai->resv, ai->getInscode(true),
|
|
LexStr(G, ai->name) ENDFB(G);
|
|
}
|
|
}
|
|
|
|
CoordSet *cset = CoordSetNew(G);
|
|
cset->NIndex = 1;
|
|
cset->Coord = pymol::vla<float>(3);
|
|
cset->Obj = I;
|
|
cset->enumIndices();
|
|
|
|
for(state = start_state; state < stop_state; state++) {
|
|
|
|
|
|
if((extant_only && (state < I->NCSet) && I->CSet[state]) || !extant_only) {
|
|
|
|
if(sele_index >= 0) {
|
|
ObjectMoleculeOpRec op;
|
|
ObjectMoleculeOpRecInit(&op);
|
|
op.code = OMOP_CSetSumVertices;
|
|
op.cs1 = state;
|
|
|
|
ExecutiveObjMolSeleOp(I->G, sele_index, &op);
|
|
|
|
if(op.i1) {
|
|
float factor = 1.0F / op.i1;
|
|
scale3f(op.v1, factor, pos_array);
|
|
pos = pos_array;
|
|
|
|
if(vdw < 0.0F) {
|
|
switch (mode) {
|
|
case 1:
|
|
ObjectMoleculeOpRecInit(&op);
|
|
op.code = OMOP_CSetMaxDistToPt;
|
|
copy3f(pos_array, op.v1);
|
|
op.cs1 = state;
|
|
ExecutiveObjMolSeleOp(I->G, sele_index, &op);
|
|
vdw = op.f1;
|
|
break;
|
|
case 2:
|
|
ObjectMoleculeOpRecInit(&op);
|
|
op.code = OMOP_CSetSumSqDistToPt;
|
|
copy3f(pos_array, op.v1);
|
|
op.cs1 = state;
|
|
ExecutiveObjMolSeleOp(I->G, sele_index, &op);
|
|
vdw = sqrt1f(op.d1 / op.i1);
|
|
break;
|
|
case 0:
|
|
default:
|
|
vdw = 0.5F;
|
|
break;
|
|
}
|
|
if(vdw >= 0.0F)
|
|
ai->vdw = vdw; /* NOTE: only uses vdw from first state selection... */
|
|
}
|
|
} else {
|
|
pos = nullptr; /* skip this state */
|
|
}
|
|
} else if(!pos) {
|
|
SceneGetCenter(I->G, pos_array);
|
|
pos = pos_array;
|
|
}
|
|
|
|
if(pos) { /* only add coordinate to state if we have position for it */
|
|
|
|
float *coord = cset->Coord.data();
|
|
|
|
copy3f(pos, coord);
|
|
|
|
if(!ai_merged) {
|
|
if (ok)
|
|
ok &= ObjectMoleculeMerge(I, std::move(atInfo), cset, false, cAIC_AllMask, true); /* NOTE: will release atInfo */
|
|
if (ok)
|
|
ok &= ObjectMoleculeExtendIndices(I, -1);
|
|
ai_merged = true;
|
|
}
|
|
if(state >= I->NCSet) {
|
|
VLACheck(I->CSet, CoordSet *, state);
|
|
I->NCSet = state + 1;
|
|
}
|
|
if(!I->CSet[state]) {
|
|
/* new coordinate set */
|
|
I->CSet[state] = CoordSetCopy(cset);
|
|
} else {
|
|
/* merge coordinate set */
|
|
if (ok)
|
|
ok &= CoordSetMerge(I, I->CSet[state], cset);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
delete cset;
|
|
|
|
if(ai_merged) {
|
|
if (ok)
|
|
ok &= ObjectMoleculeSort(I);
|
|
ObjectMoleculeUpdateIDNumbers(I);
|
|
ObjectMoleculeUpdateNonbonded(I);
|
|
I->invalidate(cRepAll, cRepInvAtoms, -1);
|
|
} else {
|
|
VLAFreeP(atInfo);
|
|
}
|
|
return (ok);
|
|
}
|
|
|
|
int *ObjectMoleculeGetPrioritizedOtherIndexList(ObjectMolecule * I, CoordSet * cs)
|
|
{
|
|
int a, b;
|
|
int b1, b2, a1, a2, a3;
|
|
OtherRec *o;
|
|
OtherRec *other = pymol::calloc<OtherRec>(cs->NIndex);
|
|
int *result = nullptr;
|
|
int offset;
|
|
int n_alloc = 0;
|
|
const BondType *bd;
|
|
int ok = true;
|
|
|
|
CHECKOK(ok, other);
|
|
|
|
bd = I->Bond;
|
|
for(a = 0; ok && a < I->NBond; a++) {
|
|
b1 = bd->index[0];
|
|
b2 = bd->index[1];
|
|
a1 = cs->atmToIdx(b1);
|
|
a2 = cs->atmToIdx(b2);
|
|
if((a1 >= 0) && (a2 >= 0)) {
|
|
n_alloc += populate_other(other + a1, a2, I->AtomInfo + b2, bd, I);
|
|
n_alloc += populate_other(other + a2, a1, I->AtomInfo + b1, bd, I);
|
|
}
|
|
bd++;
|
|
ok &= !I->G->Interrupt;
|
|
}
|
|
if (ok){
|
|
n_alloc = 3 * (n_alloc + cs->NIndex);
|
|
o = other;
|
|
result = pymol::malloc<int>(n_alloc);
|
|
CHECKOK(ok, result);
|
|
}
|
|
if (ok){
|
|
for(a = 0; a < cs->NIndex; a++) {
|
|
result[a] = -1;
|
|
}
|
|
offset = cs->NIndex;
|
|
bd = I->Bond;
|
|
}
|
|
for(a = 0; ok && a < I->NBond; a++) {
|
|
b1 = bd->index[0];
|
|
b2 = bd->index[1];
|
|
a1 = cs->atmToIdx(b1);
|
|
a2 = cs->atmToIdx(b2);
|
|
if((a1 >= 0) && (a2 >= 0)) {
|
|
if(result[a1] < 0) {
|
|
o = other + a1;
|
|
result[a1] = offset;
|
|
for(b = 0; b < o->n_cyclic_arom; b++) {
|
|
a3 = o->cyclic_arom[b];
|
|
offset = append_index(result, offset, a1, a3, 128 + other[a3].score, 1);
|
|
}
|
|
for(b = 0; b < o->n_arom; b++) {
|
|
a3 = o->arom[b];
|
|
offset = append_index(result, offset, a1, a3, 64 + other[a3].score, 1);
|
|
}
|
|
for(b = 0; b < o->n_high_val; b++) {
|
|
a3 = o->high_val[b];
|
|
offset = append_index(result, offset, a1, a3, 16 + other[a3].score, 0);
|
|
}
|
|
for(b = 0; b < o->n_cyclic; b++) {
|
|
a3 = o->cyclic[b];
|
|
offset = append_index(result, offset, a1, a3, 8 + other[a3].score, 0);
|
|
}
|
|
for(b = 0; b < o->n_planer; b++) {
|
|
a3 = o->planer[b];
|
|
offset = append_index(result, offset, a1, a3, 2 + other[a3].score, 0);
|
|
}
|
|
for(b = 0; b < o->n_rest; b++) {
|
|
a3 = o->rest[b];
|
|
offset = append_index(result, offset, a1, a3, 1 + other[a3].score, 0);
|
|
}
|
|
result[offset++] = -1;
|
|
}
|
|
|
|
if(result[a2] < 0) {
|
|
o = other + a2;
|
|
result[a2] = offset;
|
|
for(b = 0; b < o->n_cyclic_arom; b++) {
|
|
a3 = o->cyclic_arom[b];
|
|
offset = append_index(result, offset, a2, a3, 128 + other[a3].score, 1);
|
|
}
|
|
for(b = 0; b < o->n_arom; b++) {
|
|
a3 = o->arom[b];
|
|
offset = append_index(result, offset, a2, a3, 64 + other[a3].score, 1);
|
|
}
|
|
for(b = 0; b < o->n_high_val; b++) {
|
|
a3 = o->high_val[b];
|
|
offset = append_index(result, offset, a2, a3, 16 + other[a3].score, 0);
|
|
}
|
|
for(b = 0; b < o->n_cyclic; b++) {
|
|
a3 = o->cyclic[b];
|
|
offset = append_index(result, offset, a2, a3, 8 + other[a3].score, 0);
|
|
}
|
|
for(b = 0; b < o->n_planer; b++) {
|
|
a3 = o->planer[b];
|
|
offset = append_index(result, offset, a2, a3, 2 + other[a3].score, 0);
|
|
}
|
|
for(b = 0; b < o->n_rest; b++) {
|
|
a3 = o->rest[b];
|
|
offset = append_index(result, offset, a2, a3, 1 + other[a3].score, 0);
|
|
}
|
|
result[offset++] = -1;
|
|
}
|
|
|
|
}
|
|
bd++;
|
|
ok &= !I->G->Interrupt;
|
|
}
|
|
FreeP(other);
|
|
return result;
|
|
}
|
|
|
|
int ObjectMoleculeGetNearestBlendedColor(ObjectMolecule * I, const float *point,
|
|
float cutoff, int state, float *dist,
|
|
float *color, int sub_vdw)
|
|
{
|
|
int result = -1;
|
|
float tot_weight = 0.0F;
|
|
float cutoff2 = cutoff * cutoff;
|
|
float nearest = -1.0F;
|
|
|
|
color[0] = 0.0F;
|
|
color[1] = 0.0F;
|
|
color[2] = 0.0F;
|
|
|
|
assert(state != -1 /* all states */);
|
|
auto* cs = I->getCoordSet(state);
|
|
|
|
{
|
|
if(cs) {
|
|
MapType *map;
|
|
CoordSetUpdateCoord2IdxMap(cs, cutoff);
|
|
if(sub_vdw) {
|
|
cutoff -= MAX_VDW;
|
|
cutoff2 = cutoff * cutoff;
|
|
}
|
|
nearest = cutoff2;
|
|
if((map = cs->Coord2Idx)) {
|
|
int a, b, c, d, e, f, j;
|
|
float test;
|
|
float *v;
|
|
MapLocus(map, point, &a, &b, &c);
|
|
for(d = a - 1; d <= a + 1; d++)
|
|
for(e = b - 1; e <= b + 1; e++)
|
|
for(f = c - 1; f <= c + 1; f++) {
|
|
j = *(MapFirst(map, d, e, f));
|
|
while(j >= 0) {
|
|
v = cs->coordPtr(j);
|
|
test = diffsq3f(v, point);
|
|
if(sub_vdw) {
|
|
test = sqrt1f(test);
|
|
test -= I->AtomInfo[cs->IdxToAtm[j]].vdw;
|
|
if(test < 0.0F)
|
|
test = 0.0F;
|
|
test = test * test;
|
|
}
|
|
if(test < cutoff2) {
|
|
float weight = cutoff - sqrt1f(test);
|
|
const float *at_col = ColorGet(I->G, I->AtomInfo[cs->IdxToAtm[j]].color);
|
|
color[0] += at_col[0] * weight;
|
|
color[1] += at_col[1] * weight;
|
|
color[2] += at_col[2] * weight;
|
|
tot_weight += weight;
|
|
}
|
|
if(test <= nearest) {
|
|
result = j;
|
|
nearest = test;
|
|
}
|
|
j = MapNext(map, j);
|
|
}
|
|
}
|
|
} else {
|
|
int j;
|
|
float test;
|
|
const float* v = cs->Coord.data();
|
|
for(j = 0; j < cs->NIndex; j++) {
|
|
test = diffsq3f(v, point);
|
|
if(sub_vdw) {
|
|
test = sqrt1f(test);
|
|
test -= I->AtomInfo[cs->IdxToAtm[j]].vdw;
|
|
if(test < 0.0F)
|
|
test = 0.0F;
|
|
test = test * test;
|
|
}
|
|
if(test < cutoff2) {
|
|
float weight = cutoff - sqrt1f(test);
|
|
const float *at_col = ColorGet(I->G, I->AtomInfo[cs->IdxToAtm[j]].color);
|
|
color[0] += at_col[0] * weight;
|
|
color[1] += at_col[1] * weight;
|
|
color[2] += at_col[2] * weight;
|
|
tot_weight += weight;
|
|
}
|
|
if(test <= nearest) {
|
|
result = j;
|
|
nearest = test;
|
|
}
|
|
v += 3;
|
|
}
|
|
}
|
|
if(result >= 0)
|
|
result = cs->IdxToAtm[result];
|
|
}
|
|
}
|
|
if(dist) {
|
|
if(result >= 0) {
|
|
*dist = sqrt1f(nearest);
|
|
if(tot_weight > 0.0F) {
|
|
color[0] /= tot_weight;
|
|
color[1] /= tot_weight;
|
|
color[2] /= tot_weight;
|
|
}
|
|
} else {
|
|
*dist = -1.0F;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int ObjectMoleculeGetNearestAtomIndex(ObjectMolecule * I, const float *point, float cutoff,
|
|
int state, float *dist)
|
|
{
|
|
int result = -1;
|
|
float nearest = -1.0F;
|
|
|
|
assert(state != -1 /* all states */);
|
|
auto* cs = I->getCoordSet(state);
|
|
|
|
{
|
|
if(cs) {
|
|
MapType *map;
|
|
CoordSetUpdateCoord2IdxMap(cs, cutoff);
|
|
nearest = cutoff * cutoff;
|
|
if((map = cs->Coord2Idx)) {
|
|
int a, b, c, d, e, f, j;
|
|
float test;
|
|
float *v;
|
|
MapLocus(map, point, &a, &b, &c);
|
|
for(d = a - 1; d <= a + 1; d++)
|
|
for(e = b - 1; e <= b + 1; e++)
|
|
for(f = c - 1; f <= c + 1; f++) {
|
|
j = *(MapFirst(map, d, e, f));
|
|
while(j >= 0) {
|
|
v = cs->coordPtr(j);
|
|
test = diffsq3f(v, point);
|
|
if(test <= nearest) {
|
|
result = j;
|
|
nearest = test;
|
|
}
|
|
j = MapNext(map, j);
|
|
}
|
|
}
|
|
} else {
|
|
int j;
|
|
float test;
|
|
const float* v = cs->Coord.data();
|
|
for(j = 0; j < cs->NIndex; j++) {
|
|
test = diffsq3f(v, point);
|
|
if(test <= nearest) {
|
|
result = j;
|
|
nearest = test;
|
|
}
|
|
v += 3;
|
|
}
|
|
}
|
|
if(result >= 0)
|
|
result = cs->IdxToAtm[result];
|
|
}
|
|
}
|
|
if(dist) {
|
|
if(result >= 0) {
|
|
*dist = sqrt1f(nearest);
|
|
} else {
|
|
*dist = -1.0F;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int ObjectMoleculeGetPrioritizedOther(const int *other, int a1, int a2, int *double_sided)
|
|
{
|
|
int a3 = -1;
|
|
int lvl = -1, ck, ck_lvl;
|
|
int offset;
|
|
int ar_count = 0;
|
|
|
|
a3 = -1;
|
|
lvl = -1;
|
|
if(a1 >= 0) {
|
|
offset = other[a1];
|
|
if(offset >= 0) {
|
|
while(1) {
|
|
ck = other[offset];
|
|
if(ck != a2) {
|
|
if(ck >= 0) {
|
|
ck_lvl = other[offset + 1];
|
|
if(ck_lvl > lvl) {
|
|
a3 = ck;
|
|
lvl = ck_lvl;
|
|
}
|
|
ar_count += other[offset + 2];
|
|
} else
|
|
break;
|
|
}
|
|
offset += 3;
|
|
}
|
|
}
|
|
}
|
|
if(a2 >= 0) {
|
|
offset = other[a2];
|
|
if(offset >= 0) {
|
|
while(1) {
|
|
ck = other[offset];
|
|
if(ck != a1) {
|
|
if(ck >= 0) {
|
|
ck_lvl = other[offset + 1];
|
|
if(ck_lvl > lvl) {
|
|
a3 = ck;
|
|
lvl = ck_lvl;
|
|
}
|
|
ar_count += other[offset + 2];
|
|
} else
|
|
break;
|
|
}
|
|
offset += 3;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(double_sided) {
|
|
if(ar_count == 4)
|
|
*double_sided = true;
|
|
else
|
|
*double_sided = false;
|
|
}
|
|
return a3;
|
|
}
|
|
|
|
/* Check if atom is bonded to an atom with given name
|
|
*
|
|
* param same_res:
|
|
* 0 = must not be in same residue
|
|
* 1 = must be in same residue
|
|
* -1 = don't check residue
|
|
*/
|
|
int ObjectMoleculeIsAtomBondedToName(ObjectMolecule * obj, int a0, const char *name, int same_res)
|
|
{
|
|
PyMOLGlobals * G = obj->G;
|
|
AtomInfoType const* const ai0 = obj->AtomInfo.data() + a0;
|
|
|
|
if(a0 >= 0) {
|
|
for (auto const& neighbor : AtomNeighbors(obj, a0)) {
|
|
auto const* const ai2 = obj->AtomInfo.data() + neighbor.atm;
|
|
if(WordMatchExact(G, LexStr(G, ai2->name), name, true) &&
|
|
(same_res < 0 || (same_res == AtomInfoSameResidue(G, ai0, ai2))))
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int ObjectMoleculeAreAtomsBonded2(ObjectMolecule * obj0, int a0, ObjectMolecule * obj1,
|
|
int a1)
|
|
{
|
|
if (obj0 == obj1 && a0 >= 0) {
|
|
assert(a1 >= 0);
|
|
for (auto const& neighbor : AtomNeighbors(obj0, a0)) {
|
|
if (a1 == neighbor.atm)
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ObjectMoleculeIsAtomBondedToSele(
|
|
const ObjectMolecule* I, int atm, SelectorID_t sele)
|
|
{
|
|
if (atm < I->NAtom) {
|
|
for (auto const& neighbor : AtomNeighbors(I, atm)) {
|
|
if (SelectorIsMember(I->G, I->AtomInfo[neighbor.atm].selEntry, sele)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Based on PDB nomenclature (resn, name), do:
|
|
*
|
|
* 1) If `ai1` or `ai2` is a known charged PDB atom, assign `formalCharge`
|
|
* and set `chemFlag` to false
|
|
*
|
|
* 2) If `ai1` and `ai2` are connected by a double bond, set (*bond_order) = 2
|
|
*/
|
|
static void assign_pdb_known_residue(PyMOLGlobals * G, AtomInfoType * ai1,
|
|
AtomInfoType * ai2, int *bond_order)
|
|
{
|
|
int order = *(bond_order);
|
|
auto const name1 = pymol::zstring_view(LexStr(G, ai1->name));
|
|
auto const name2 = pymol::zstring_view(LexStr(G, ai2->name));
|
|
auto const resn1 = pymol::zstring_view(LexStr(G, ai1->resn));
|
|
|
|
/* nasty high-speed hack to get bond valences and formal charges
|
|
for standard residues */
|
|
if (((name1 == "C" && name2 == "O") || (name2 == "C" && name1 == "O")) &&
|
|
AtomInfoKnownProteinResName(resn1.c_str())) {
|
|
order = 2;
|
|
} else if(name2 == "C" && name1 == "OXT") {
|
|
ai1->formalCharge = -1;
|
|
ai1->chemFlag = false;
|
|
} else if(name1 == "C" && name2 == "OXT") {
|
|
ai2->formalCharge = -1;
|
|
ai2->chemFlag = false;
|
|
} else {
|
|
switch (resn1[0]) {
|
|
case 'A':
|
|
switch (resn1[1]) {
|
|
case 'R':
|
|
switch (resn1[2]) {
|
|
case 'G': /* ARG... */
|
|
switch (resn1[3]) {
|
|
case 0:
|
|
case 'P': /* ARG, ARGP */
|
|
if (name1 == "NH1") {
|
|
ai1->formalCharge = 1;
|
|
ai1->chemFlag = false;
|
|
} else if (name2 == "NH1") {
|
|
ai2->formalCharge = 1;
|
|
ai2->chemFlag = false;
|
|
}
|
|
// PYMOL-5019: If we're forcing NH1 to be formal charge 1, the other
|
|
// atom must have formal charge 0.
|
|
if (name1 == "NH2") {
|
|
ai1->formalCharge = 0;
|
|
ai1->chemFlag = false;
|
|
} else if (name2 == "NH2") {
|
|
ai2->formalCharge = 0;
|
|
ai2->chemFlag = false;
|
|
}
|
|
break;
|
|
}
|
|
if((name1 == "CZ" && name2 == "NH1") ||
|
|
(name2 == "CZ" && name1 == "NH1"))
|
|
order = 2;
|
|
// PYMOL-5019: If we're forcing NH1 to be double bounded, the other
|
|
// bond must display as single.
|
|
if((name1 == "CZ" && name2 == "NH2") ||
|
|
(name2 == "CZ" && name1 == "NH2"))
|
|
order = 1;
|
|
break;
|
|
}
|
|
break;
|
|
case 'S':
|
|
switch (resn1[2]) {
|
|
case 'P': /* ASP... */
|
|
switch (resn1[3]) {
|
|
case 0:
|
|
case 'M': /* ASP, ASPM minus assumption */
|
|
if (name1 == "OD2") {
|
|
ai1->formalCharge = -1;
|
|
ai1->chemFlag = false;
|
|
} else if (name2 == "OD2") {
|
|
ai2->formalCharge = -1;
|
|
ai2->chemFlag = false;
|
|
}
|
|
break;
|
|
}
|
|
if((name1 == "CG" && name2 == "OD1") ||
|
|
(name2 == "CG" && name1 == "OD1"))
|
|
order = 2;
|
|
break;
|
|
case 'N': /* ASN */
|
|
if((name1 == "CG" && name2 == "OD1") ||
|
|
(name2 == "CG" && name1 == "OD1"))
|
|
order = 2;
|
|
break;
|
|
}
|
|
break;
|
|
case 0:
|
|
if((name1 == "O2P" || name1 == "OP2")) {
|
|
ai1->formalCharge = -1;
|
|
ai1->chemFlag = false;
|
|
} else if((name2 == "O2P" || name2 == "OP2")) {
|
|
ai2->formalCharge = -1;
|
|
ai2->chemFlag = false;
|
|
}
|
|
if((name1 == "C8" && name2 == "N7") ||
|
|
(name2 == "C8" && name1 == "N7"))
|
|
order = 2;
|
|
else if((name1 == "C4" && name2 == "C5") ||
|
|
(name2 == "C4" && name1 == "C5"))
|
|
order = 2;
|
|
|
|
else if((name1 == "C6" && name2 == "N1") ||
|
|
(name2 == "C6" && name1 == "N1"))
|
|
order = 2;
|
|
else if((name1 == "C2" && name2 == "N3") ||
|
|
(name2 == "C2" && name1 == "N3"))
|
|
order = 2;
|
|
else
|
|
if ((name1 == "P" && (name2 == "O1P" || name2 == "OP1")) ||
|
|
(name2 == "P" && (name1 == "O1P" || name1 == "OP1")))
|
|
order = 2;
|
|
break;
|
|
}
|
|
break;
|
|
case 'C':
|
|
if(resn1[1] == 0) {
|
|
if((name1 == "O2P" || name1 == "OP2")) {
|
|
ai1->formalCharge = -1;
|
|
ai1->chemFlag = false;
|
|
} else if((name2 == "O2P" || name2 == "OP2")) {
|
|
ai2->formalCharge = -1;
|
|
ai2->chemFlag = false;
|
|
}
|
|
if((name1 == "C2" && name2 == "O2") ||
|
|
(name2 == "C2" && name1 == "O2"))
|
|
order = 2;
|
|
else if((name1 == "C4" && name2 == "N3") ||
|
|
(name2 == "C4" && name1 == "N3"))
|
|
order = 2;
|
|
|
|
else if((name1 == "C5" && name2 == "C6") ||
|
|
(name2 == "C5" && name1 == "C6"))
|
|
order = 2;
|
|
else
|
|
if ((name1 == "P" && (name2 == "O1P" || name2 == "OP1")) ||
|
|
(name2 == "P" && (name1 == "O1P" || name1 == "OP1")))
|
|
order = 2;
|
|
}
|
|
break;
|
|
case 'D': /* deoxy nucleic acids */
|
|
switch (resn1[1]) {
|
|
case 'A':
|
|
if(resn1[2] == 0) {
|
|
if((name1 == "O2P" || name1 == "OP2")) {
|
|
ai1->formalCharge = -1;
|
|
ai1->chemFlag = false;
|
|
} else if((name2 == "O2P" || name2 == "OP2")) {
|
|
ai2->formalCharge = -1;
|
|
ai2->chemFlag = false;
|
|
}
|
|
if((name1 == "C8" && name2 == "N7") ||
|
|
(name2 == "C8" && name1 == "N7"))
|
|
order = 2;
|
|
else if((name1 == "C4" && name2 == "C5") ||
|
|
(name2 == "C4" && name1 == "C5"))
|
|
order = 2;
|
|
|
|
else if((name1 == "C6" && name2 == "N1") ||
|
|
(name2 == "C6" && name1 == "N1"))
|
|
order = 2;
|
|
else if((name1 == "C2" && name2 == "N3") ||
|
|
(name2 == "C2" && name1 == "N3"))
|
|
order = 2;
|
|
else
|
|
if ((name1 == "P" && (name2 == "O1P" || name2 == "OP1")) ||
|
|
(name2 == "P" && (name1 == "O1P" || name1 == "OP1")))
|
|
order = 2;
|
|
}
|
|
break;
|
|
case 'C':
|
|
if(resn1[2] == 0) {
|
|
if((name1 == "O2P" || name1 == "OP2")) {
|
|
ai1->formalCharge = -1;
|
|
ai1->chemFlag = false;
|
|
} else if((name2 == "O2P" || name2 == "OP2")) {
|
|
ai2->formalCharge = -1;
|
|
ai2->chemFlag = false;
|
|
}
|
|
if((name1 == "C2" && name2 == "O2") ||
|
|
(name2 == "C2" && name1 == "O2"))
|
|
order = 2;
|
|
else if((name1 == "C4" && name2 == "N3") ||
|
|
(name2 == "C4" && name1 == "N3"))
|
|
order = 2;
|
|
|
|
else if((name1 == "C5" && name2 == "C6") ||
|
|
(name2 == "C5" && name1 == "C6"))
|
|
order = 2;
|
|
else
|
|
if ((name1 == "P" && (name2 == "O1P" || name2 == "OP1")) ||
|
|
(name2 == "P" && (name1 == "O1P" || name1 == "OP1")))
|
|
order = 2;
|
|
}
|
|
break;
|
|
case 'T':
|
|
if(resn1[2] == 0) {
|
|
if((name1 == "O2P" || name1 == "OP2"))
|
|
ai1->formalCharge = -1;
|
|
else if((name2 == "O2P" || name2 == "OP2"))
|
|
ai2->formalCharge = -1;
|
|
|
|
if((name1 == "C2" && name2 == "O2") ||
|
|
(name2 == "C2" && name1 == "O2"))
|
|
order = 2;
|
|
else if((name1 == "C4" && name2 == "O4") ||
|
|
(name2 == "C4" && name1 == "O4"))
|
|
order = 2;
|
|
|
|
else if((name1 == "C5" && name2 == "C6") ||
|
|
(name2 == "C5" && name1 == "C6"))
|
|
order = 2;
|
|
else
|
|
if ((name1 == "P" && (name2 == "O1P" || name2 == "OP1")) ||
|
|
(name2 == "P" && (name1 == "O1P" || name1 == "OP1")))
|
|
order = 2;
|
|
}
|
|
break;
|
|
case 'G':
|
|
if(resn1[2] == 0) {
|
|
if((name1 == "O2P" || name1 == "OP2")) {
|
|
ai1->formalCharge = -1;
|
|
ai1->chemFlag = false;
|
|
} else if((name2 == "O2P" || name2 == "OP2")) {
|
|
ai2->formalCharge = -1;
|
|
ai2->chemFlag = false;
|
|
}
|
|
if((name1 == "C6" && name2 == "O6") ||
|
|
(name2 == "C6" && name1 == "O6"))
|
|
order = 2;
|
|
else if((name1 == "C2" && name2 == "N3") ||
|
|
(name2 == "C2" && name1 == "N3"))
|
|
order = 2;
|
|
else if((name1 == "C8" && name2 == "N7") ||
|
|
(name2 == "C8" && name1 == "N7"))
|
|
order = 2;
|
|
else if((name1 == "C4" && name2 == "C5") ||
|
|
(name2 == "C4" && name1 == "C5"))
|
|
order = 2;
|
|
else
|
|
if ((name1 == "P" && (name2 == "O1P" || name2 == "OP1")) ||
|
|
(name2 == "P" && (name1 == "O1P" || name1 == "OP1")))
|
|
order = 2;
|
|
}
|
|
break;
|
|
case 'U':
|
|
if(resn1[2] == 0) {
|
|
if((name1 == "O2P" || name1 == "OP2")) {
|
|
ai1->formalCharge = -1;
|
|
ai1->chemFlag = false;
|
|
} else if((name2 == "O2P" || name2 == "OP2")) {
|
|
ai2->formalCharge = -1;
|
|
ai2->chemFlag = false;
|
|
}
|
|
|
|
if((name1 == "C2" && name2 == "O2") ||
|
|
(name2 == "C2" && name1 == "O2"))
|
|
order = 2;
|
|
else if((name1 == "C4" && name2 == "O4") ||
|
|
(name2 == "C4" && name1 == "O4"))
|
|
order = 2;
|
|
|
|
else if((name1 == "C5" && name2 == "C6") ||
|
|
(name2 == "C5" && name1 == "C6"))
|
|
order = 2;
|
|
else
|
|
if ((name1 == "P" && (name2 == "O1P" || name2 == "OP1")) ||
|
|
(name2 == "P" && (name1 == "O1P" || name1 == "OP1")))
|
|
order = 2;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
case 'G':
|
|
switch (resn1[1]) {
|
|
case 'L':
|
|
switch (resn1[2]) {
|
|
case 'U': /* GLU missing GLUN, GLUH, GLH handling */
|
|
switch (resn1[3]) {
|
|
case 0:
|
|
case 'M': /* minus */
|
|
if (name1 == "OE2") {
|
|
ai1->formalCharge = -1;
|
|
ai1->chemFlag = false;
|
|
} else if (name2 == "OE2") {
|
|
ai2->formalCharge = -1;
|
|
ai2->chemFlag = false;
|
|
}
|
|
break;
|
|
}
|
|
if((name1 == "CD" && name2 == "OE1") ||
|
|
(name2 == "CD" && name1 == "OE1"))
|
|
order = 2;
|
|
break;
|
|
case 'N': /* GLN or GLU */
|
|
if((name1 == "CD" && name2 == "OE1") ||
|
|
(name2 == "CD" && name1 == "OE1"))
|
|
order = 2;
|
|
break;
|
|
}
|
|
break;
|
|
case 0:
|
|
if((name1 == "O2P" || name1 == "OP2")) {
|
|
ai1->formalCharge = -1;
|
|
ai1->chemFlag = false;
|
|
} else if((name2 == "O2P" || name2 == "OP2")) {
|
|
ai2->formalCharge = -1;
|
|
ai2->chemFlag = false;
|
|
}
|
|
|
|
if((name1 == "C6" && name2 == "O6") ||
|
|
(name2 == "C6" && name1 == "O6"))
|
|
order = 2;
|
|
else if((name1 == "C2" && name2 == "N3") ||
|
|
(name2 == "C2" && name1 == "N3"))
|
|
order = 2;
|
|
else if((name1 == "C8" && name2 == "N7") ||
|
|
(name2 == "C8" && name1 == "N7"))
|
|
order = 2;
|
|
else if((name1 == "C4" && name2 == "C5") ||
|
|
(name2 == "C4" && name1 == "C5"))
|
|
order = 2;
|
|
else
|
|
if((name1 == "P"
|
|
&& (name2 == "O1P" || name2 == "OP1"))
|
|
|| (name2 == "P"
|
|
&& (name1 == "O1P" || name1 == "OP1")))
|
|
order = 2;
|
|
break;
|
|
}
|
|
break;
|
|
case 'H':
|
|
switch (resn1[1]) {
|
|
case 'I':
|
|
switch (resn1[2]) {
|
|
case 'P':
|
|
if (name1 == "ND1") {
|
|
ai1->formalCharge = 1;
|
|
ai1->chemFlag = false;
|
|
} else if (name2 == "ND1") {
|
|
ai2->formalCharge = 1;
|
|
ai2->chemFlag = false;
|
|
}
|
|
if((name1 == "CG" && name2 == "CD2") ||
|
|
(name2 == "CG" && name1 == "CD2"))
|
|
order = 2;
|
|
else if((name1 == "CE1" && name2 == "ND1") ||
|
|
(name2 == "CE1" && name1 == "ND1"))
|
|
order = 2;
|
|
break;
|
|
case 'S':
|
|
switch (resn1[3]) {
|
|
case 'A': /* HISA Gromacs */
|
|
case 'D': /* HISD Quanta */
|
|
if((name1 == "CG" && name2 == "CD2") ||
|
|
(name2 == "CG" && name1 == "CD2"))
|
|
order = 2;
|
|
else if((name1 == "CE1" && name2 == "NE2") ||
|
|
(name2 == "CE1" && name1 == "NE2"))
|
|
order = 2;
|
|
break;
|
|
case 0: /* plain HIS */
|
|
case 'B': /* HISB Gromacs */
|
|
case 'E': /* HISE Quanta */
|
|
if((name1 == "CG" && name2 == "CD2") ||
|
|
(name2 == "CG" && name1 == "CD2"))
|
|
order = 2;
|
|
else if((name1 == "CE1" && name2 == "ND1") ||
|
|
(name2 == "CE1" && name1 == "ND1"))
|
|
order = 2;
|
|
break;
|
|
case 'H': /* HISH Gromacs */
|
|
case 'P': /* HISP Quanta */
|
|
if (name1 == "ND1") {
|
|
ai1->formalCharge = 1;
|
|
ai1->chemFlag = false;
|
|
} else if (name2 == "ND1") {
|
|
ai2->formalCharge = 1;
|
|
ai2->chemFlag = false;
|
|
}
|
|
if((name1 == "CG" && name2 == "CD2") ||
|
|
(name2 == "CG" && name1 == "CD2"))
|
|
order = 2;
|
|
else if((name1 == "CE1" && name2 == "ND1") ||
|
|
(name2 == "CE1" && name1 == "ND1"))
|
|
order = 2;
|
|
break;
|
|
}
|
|
break;
|
|
case 'E': /* HIE */
|
|
if((name1 == "CG" && name2 == "CD2") ||
|
|
(name2 == "CG" && name1 == "CD2"))
|
|
order = 2;
|
|
else if((name1 == "CE1" && name2 == "ND1") ||
|
|
(name2 == "CE1" && name1 == "ND1"))
|
|
order = 2;
|
|
break;
|
|
case 'D': /* HID */
|
|
if((name1 == "CG" && name2 == "CD2") ||
|
|
(name2 == "CG" && name1 == "CD2"))
|
|
order = 2;
|
|
else if((name1 == "CE1" && name2 == "NE2") ||
|
|
(name2 == "CE1" && name1 == "NE2"))
|
|
order = 2;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
case 'I':
|
|
if(resn1[1] == 0) {
|
|
if((name1 == "O2P" || name1 == "OP2")) {
|
|
ai1->formalCharge = -1;
|
|
ai1->chemFlag = false;
|
|
} else if((name2 == "O2P" || name2 == "OP2")) {
|
|
ai2->formalCharge = -1;
|
|
ai2->chemFlag = false;
|
|
}
|
|
if((name1 == "C8" && name2 == "N7") ||
|
|
(name2 == "C8" && name1 == "N7"))
|
|
order = 2;
|
|
else if((name1 == "C4" && name2 == "C5") ||
|
|
(name2 == "C4" && name1 == "C5"))
|
|
order = 2;
|
|
|
|
else if((name1 == "C6" && name2 == "N1") ||
|
|
(name2 == "C6" && name1 == "N1"))
|
|
order = 2;
|
|
else if((name1 == "C2" && name2 == "N3") ||
|
|
(name2 == "C2" && name1 == "N3"))
|
|
order = 2;
|
|
else
|
|
if((name1 == "P"
|
|
&& (name2 == "O1P" || name2 == "OP1"))
|
|
|| (name2 == "P"
|
|
&& (name1 == "O1P" || name1 == "OP1")))
|
|
order = 2;
|
|
}
|
|
break;
|
|
case 'P':
|
|
switch (resn1[1]) {
|
|
case 'H': /* PHE */
|
|
if(resn1[2] == 'E') {
|
|
if((name1 == "CG" && name2 == "CD1") ||
|
|
(name2 == "CG" && name1 == "CD1"))
|
|
order = 2;
|
|
else if((name1 == "CZ" && name2 == "CE1") ||
|
|
(name2 == "CZ" && name1 == "CE1"))
|
|
order = 2;
|
|
|
|
else if((name1 == "CE2" && name2 == "CD2") ||
|
|
(name2 == "CE2" && name1 == "CD2"))
|
|
order = 2;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case 'L':
|
|
switch (resn1[1]) {
|
|
case 'Y':
|
|
switch (resn1[2]) {
|
|
case 'S': /* LYS. */
|
|
switch (resn1[3]) {
|
|
case 0:
|
|
case 'P': /* LYS, LYSP */
|
|
if (name1 == "NZ") {
|
|
ai1->formalCharge = 1;
|
|
ai1->chemFlag = false;
|
|
} else if (name2 == "NZ") {
|
|
ai2->formalCharge = 1;
|
|
ai2->chemFlag = false;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
case 'T':
|
|
switch (resn1[1]) {
|
|
case 'Y': /* TYR */
|
|
if(resn1[2] == 'R') {
|
|
if((name1 == "CG" && name2 == "CD1") ||
|
|
(name2 == "CG" && name1 == "CD1"))
|
|
order = 2;
|
|
else if((name1 == "CZ" && name2 == "CE1") ||
|
|
(name2 == "CZ" && name1 == "CE1"))
|
|
order = 2;
|
|
|
|
else if((name1 == "CE2" && name2 == "CD2") ||
|
|
(name2 == "CE2" && name1 == "CD2"))
|
|
order = 2;
|
|
break;
|
|
}
|
|
break;
|
|
case 'R':
|
|
if(resn1[2] == 'P') {
|
|
if((name1 == "CG" && name2 == "CD1") ||
|
|
(name2 == "CG" && name1 == "CD1"))
|
|
order = 2;
|
|
else if((name1 == "CZ3" && name2 == "CE3") ||
|
|
(name2 == "CZ3" && name1 == "CE3"))
|
|
order = 2;
|
|
else if((name1 == "CZ2" && name2 == "CH2") ||
|
|
(name2 == "CZ2" && name1 == "CH2"))
|
|
order = 2;
|
|
else if((name1 == "CE2" && name2 == "CD2") ||
|
|
(name2 == "CE2" && name1 == "CD2"))
|
|
order = 2;
|
|
break;
|
|
}
|
|
break;
|
|
case 0:
|
|
if((name1 == "O2P" || name1 == "OP2")) {
|
|
ai1->formalCharge = -1;
|
|
ai1->chemFlag = false;
|
|
} else if((name2 == "O2P" || name2 == "OP2")) {
|
|
ai2->formalCharge = -1;
|
|
ai2->chemFlag = false;
|
|
}
|
|
|
|
if((name1 == "C2" && name2 == "O2") ||
|
|
(name2 == "C2" && name1 == "O2"))
|
|
order = 2;
|
|
else if((name1 == "C4" && name2 == "O4") ||
|
|
(name2 == "C4" && name1 == "O4"))
|
|
order = 2;
|
|
|
|
else if((name1 == "C5" && name2 == "C6") ||
|
|
(name2 == "C5" && name1 == "C6"))
|
|
order = 2;
|
|
else
|
|
if ((name1 == "P" && (name2 == "O1P" || name2 == "OP1")) ||
|
|
(name2 == "P" && (name1 == "O1P" || name1 == "OP1")))
|
|
order = 2;
|
|
break;
|
|
}
|
|
break;
|
|
case 'U':
|
|
if(resn1[1] == 0) {
|
|
if((name1 == "O2P" || name1 == "OP2")) {
|
|
ai1->formalCharge = -1;
|
|
ai1->chemFlag = false;
|
|
} else if((name2 == "O2P" || name2 == "OP2")) {
|
|
ai2->formalCharge = -1;
|
|
ai2->chemFlag = false;
|
|
}
|
|
if((name1 == "C2" && name2 == "O2") ||
|
|
(name2 == "C2" && name1 == "O2"))
|
|
order = 2;
|
|
else if((name1 == "C4" && name2 == "O4") ||
|
|
(name2 == "C4" && name1 == "O4"))
|
|
order = 2;
|
|
|
|
else if((name1 == "C5" && name2 == "C6") ||
|
|
(name2 == "C5" && name1 == "C6"))
|
|
order = 2;
|
|
else
|
|
if ((name1 == "P" && (name2 == "O1P" || name2 == "OP1")) ||
|
|
(name2 == "P" && (name1 == "O1P" || name1 == "OP1")))
|
|
order = 2;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
*(bond_order) = order;
|
|
}
|
|
|
|
void ObjectMoleculeFixChemistry(ObjectMolecule * I, int sele1, int sele2, int invalidate)
|
|
{
|
|
int b;
|
|
int flag = false;
|
|
int s1, s2;
|
|
AtomInfoType *ai1, *ai2;
|
|
int order;
|
|
BondType* bond = I->Bond.data();
|
|
for(b = 0; b < I->NBond; b++) {
|
|
flag = false;
|
|
ai1 = I->AtomInfo + bond->index[0];
|
|
ai2 = I->AtomInfo + bond->index[1];
|
|
s1 = ai1->selEntry;
|
|
s2 = ai2->selEntry;
|
|
|
|
if((SelectorIsMember(I->G, s1, sele1) &&
|
|
SelectorIsMember(I->G, s2, sele2)) ||
|
|
(SelectorIsMember(I->G, s2, sele1) && SelectorIsMember(I->G, s1, sele2))) {
|
|
order = -1;
|
|
if(strlen(LexStr(I->G, ai1->resn)) < 4) { /* Standard disconnected PDB residue */
|
|
if(AtomInfoSameResidue(I->G, ai1, ai2)) {
|
|
assign_pdb_known_residue(I->G, ai1, ai2, &order);
|
|
}
|
|
}
|
|
if(order > 0) {
|
|
bond->order = order;
|
|
ai1->chemFlag = false;
|
|
ai2->chemFlag = false;
|
|
flag = true;
|
|
} else if(invalidate) {
|
|
ai1->chemFlag = false;
|
|
ai2->chemFlag = false;
|
|
flag = true;
|
|
}
|
|
}
|
|
bond++;
|
|
}
|
|
if(flag) {
|
|
I->invalidate(cRepAll, cRepInvAll, -1);
|
|
SceneChanged(I->G);
|
|
}
|
|
}
|
|
|
|
void ObjMolPairwiseInit(ObjMolPairwise * pairwise)
|
|
{
|
|
UtilZeroMem((char *) pairwise, sizeof(ObjMolPairwise));
|
|
pairwise->trg_vla = VLAlloc(int, 10);
|
|
pairwise->mbl_vla = VLAlloc(int, 10);
|
|
}
|
|
|
|
void ObjMolPairwisePurge(ObjMolPairwise * pairwise)
|
|
{
|
|
VLAFreeP(pairwise->trg_vla);
|
|
VLAFreeP(pairwise->mbl_vla);
|
|
}
|
|
|
|
int ObjectMoleculeConvertIDsToIndices(ObjectMolecule * I, int *id, int n_id)
|
|
{
|
|
/* return true if all IDs are unique, false if otherwise */
|
|
|
|
int min_id, max_id, range, *lookup = nullptr;
|
|
int unique = true;
|
|
|
|
/* this routine only works if IDs cover a reasonable range --
|
|
should rewrite using a hash table */
|
|
|
|
if(I->NAtom) {
|
|
|
|
/* determine range */
|
|
|
|
{
|
|
int a, cur_id;
|
|
cur_id = I->AtomInfo[0].id;
|
|
min_id = cur_id;
|
|
max_id = cur_id;
|
|
for(a = 1; a < I->NAtom; a++) {
|
|
cur_id = I->AtomInfo[a].id;
|
|
if(min_id > cur_id)
|
|
min_id = cur_id;
|
|
if(max_id < cur_id)
|
|
max_id = cur_id;
|
|
}
|
|
}
|
|
|
|
/* create cross-reference table */
|
|
|
|
{
|
|
int a, offset;
|
|
|
|
range = max_id - min_id + 1;
|
|
lookup = pymol::calloc<int>(range);
|
|
for(a = 0; a < I->NAtom; a++) {
|
|
offset = I->AtomInfo[a].id - min_id;
|
|
if(!lookup[offset])
|
|
lookup[offset] = a + 1;
|
|
else
|
|
unique = false;
|
|
}
|
|
}
|
|
|
|
/* iterate through IDs and replace with indices or -1 */
|
|
|
|
{
|
|
int i, offset, lkup;
|
|
|
|
for(i = 0; i < n_id; i++) {
|
|
offset = id[i] - min_id;
|
|
if((offset >= 0) && (offset < range)) {
|
|
lkup = lookup[offset];
|
|
if(lkup > 0) {
|
|
id[i] = lkup - 1;
|
|
} else {
|
|
id[i] = -1; /* negative means no match */
|
|
}
|
|
} else
|
|
id[i] = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
FreeP(lookup);
|
|
return unique;
|
|
|
|
}
|
|
|
|
static const char *check_next_pdb_object(const char *p, int skip_to_next)
|
|
{
|
|
const char *start = p;
|
|
while(*p) {
|
|
if(strstartswith(p, "HEADER")) {
|
|
if(skip_to_next)
|
|
return p;
|
|
return start;
|
|
} else if(strstartswith(p, "ATOM ") || strstartswith(p, "HETATM")) {
|
|
return start;
|
|
} else if(skip_to_next && strcmp("END", p) == 0) {
|
|
/* if we pass over the END of the current PDB file, then reset start */
|
|
start = p;
|
|
}
|
|
p = nextline(p);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static int get_multi_object_status(const char *p)
|
|
{ /* expensive -- only call
|
|
this if there is no
|
|
other way to determine
|
|
this information */
|
|
int seen_header = 0;
|
|
while(*p) {
|
|
if(strstartswith(p, "HEADER")) {
|
|
if(seen_header) {
|
|
return 1;
|
|
} else {
|
|
seen_header = true;
|
|
}
|
|
}
|
|
p = nextline(p);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* If any atom in I->AtomInfo contains the wildcard character (from
|
|
* "atom_name_wildcard" or "wildcard" setting), then set the object-level
|
|
* "atom_name_wildcard" setting to " " (disables wildcard matching).
|
|
*/
|
|
int ObjectMoleculeAutoDisableAtomNameWildcard(ObjectMolecule * I)
|
|
{
|
|
PyMOLGlobals *G = I->G;
|
|
char wildcard = 0;
|
|
int found_wildcard = false;
|
|
|
|
{
|
|
const char *tmp = SettingGet_s(G, nullptr, I->Setting.get(), cSetting_atom_name_wildcard);
|
|
if(tmp && tmp[0]) {
|
|
wildcard = *tmp;
|
|
} else {
|
|
tmp = SettingGet_s(G, nullptr, I->Setting.get(), cSetting_wildcard);
|
|
if(tmp) {
|
|
wildcard = *tmp;
|
|
}
|
|
}
|
|
if(wildcard == 32)
|
|
wildcard = 0;
|
|
|
|
}
|
|
|
|
if(wildcard) {
|
|
int a;
|
|
const char *p;
|
|
char ch;
|
|
const AtomInfoType *ai = I->AtomInfo;
|
|
|
|
for(a = 0; a < I->NAtom; a++) {
|
|
p = LexStr(G, ai->name);
|
|
while((ch = *(p++))) {
|
|
if(ch == wildcard) {
|
|
found_wildcard = true;
|
|
break;
|
|
}
|
|
}
|
|
ai++;
|
|
}
|
|
if(found_wildcard) {
|
|
ExecutiveSetObjSettingFromString(G, cSetting_atom_name_wildcard, " ",
|
|
I, -1, true, true);
|
|
}
|
|
}
|
|
return found_wildcard;
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
#define PDB_MAX_TAGS 64
|
|
|
|
static void ObjectMoleculePDBStr2CoordSetPASS1(PyMOLGlobals * G, int *ok,
|
|
const char **restart_model, const char *p, int n_tags, const char* const* tag_start,
|
|
int *nAtom, char cc[], int quiet, int *bogus_name_alignment,
|
|
int *ssFlag, const char **next_pdb, PDBInfoRec *info, int only_read_one_model,
|
|
int *ignore_conect, int *bondFlag, int *have_bond_order) {
|
|
int seen_end_of_atoms = false;
|
|
*restart_model = nullptr;
|
|
while(*ok && *p) {
|
|
AddOrthoOutputIfMatchesTags(G, n_tags, *nAtom, tag_start, p, cc, quiet);
|
|
if((strstartswith(p, "ATOM ") ||
|
|
strstartswith(p, "HETATM")) && (!*restart_model)) {
|
|
if(!seen_end_of_atoms)
|
|
(*nAtom)++;
|
|
if(*bogus_name_alignment) {
|
|
ncopy(cc, nskip(p, 12), 4); /* copy the atom field */
|
|
if((cc[0] == 32) && (cc[1] != 32)) { /* check to see if indentation was followed correctly, 32 - space */
|
|
*bogus_name_alignment = false;
|
|
}
|
|
}
|
|
} else if(strstartswith(p, "HELIX ")){
|
|
*ssFlag = true;
|
|
}else if(strstartswith(p, "SHEET ")){
|
|
*ssFlag = true;
|
|
}else if(strstartswith(p, "HEADER")) {
|
|
if(*nAtom > 0) { /* if we've already found atom records, then this must be a new pdb */
|
|
(*next_pdb) = p;
|
|
break;
|
|
}
|
|
} else if(strstartswith(p, "REMARK")) {
|
|
// char * start = p; // USED TO SAVE REMARKS (TODO)
|
|
do {
|
|
if(info && strncmp(" GENERATED BY TRJCONV", p + 6, 24) == 0)
|
|
info->ignore_header_names = true;
|
|
p = nextline(p);
|
|
AddOrthoOutputIfMatchesTags(G, n_tags, *nAtom, tag_start, p, cc, quiet);
|
|
} while(strstartswith(p, "REMARK"));
|
|
// REMARKS are string from start to p, but not saved currently (TODO)
|
|
continue;
|
|
} else if(strstartswith(p, "ENDMDL") && (!*restart_model)) {
|
|
*restart_model = nextline(p);
|
|
seen_end_of_atoms = true;
|
|
if(only_read_one_model)
|
|
break;
|
|
} else if(strstartswith(p, "END")) { /* stop parsing after END */
|
|
ntrim(cc, p, 6);
|
|
if(strcmp("END", cc) == 0) { /* END */
|
|
seen_end_of_atoms = true;
|
|
if(next_pdb) {
|
|
p = nextline(p);
|
|
ncopy(cc, p, 6);
|
|
if(strcmp("HEADER", cc) == 0) {
|
|
(*next_pdb) = p; /* found another PDB file after this one... */
|
|
} else if(strcmp("ENDMDL", cc) == 0) {
|
|
seen_end_of_atoms = false;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
} else if(strstartswith(p, "CONECT")) {
|
|
if(!*ignore_conect)
|
|
*bondFlag = true;
|
|
} else if(strstartswith(p, "USER") && (!*restart_model)) {
|
|
}
|
|
p = nextline(p);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Datastructure for efficient array-based secondary structure lookup.
|
|
*/
|
|
struct SSHash {
|
|
int n_ss; // number of ss_list items
|
|
int* ss[256]; // one array for each chain identifier
|
|
SSEntry *ss_list; // VLA
|
|
};
|
|
|
|
static void sshash_free(SSHash *hash) {
|
|
int a;
|
|
if(!hash)
|
|
return;
|
|
for(a = 0; a <= 255; a++)
|
|
FreeP(hash->ss[a]);
|
|
VLAFreeP(hash->ss_list);
|
|
FreeP(hash);
|
|
}
|
|
|
|
static SSHash * sshash_new() {
|
|
SSHash *hash = pymol::calloc<SSHash>(1);
|
|
ok_assert(1, hash);
|
|
hash->n_ss = 1;
|
|
hash->ss_list = VLAlloc(SSEntry, 50);
|
|
ok_assert(1, hash->ss_list);
|
|
return hash;
|
|
ok_except1:
|
|
sshash_free(hash);
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
* Insert a secondary structure record into the hash table.
|
|
*/
|
|
static int sshash_register_rec(SSHash * hash,
|
|
unsigned char ss_chain1, int ss_resv1, char ss_inscode1,
|
|
unsigned char ss_chain2, int ss_resv2, char ss_inscode2,
|
|
char SSCode) {
|
|
/* pretty confusing how this works... the following efficient (i.e. array-based)
|
|
secondary structure lookup even when there are multiple insertion codes
|
|
and where there may be multiple SS records for the residue using different
|
|
insertion codes */
|
|
|
|
unsigned char chain;
|
|
int ss_found = false, ssi = 0, a, b, index;
|
|
SSEntry *sst;
|
|
|
|
// up to two iterations:
|
|
// 1) assume chain1==chain2
|
|
// 2) chains are not the same (undefined in PDB spec?)
|
|
for (a = 0, chain = ss_chain1; a < 2; a++, chain = ss_chain2) {
|
|
// allocate new array for chain if necc.
|
|
if(!hash->ss[chain]) {
|
|
ok_assert(1, hash->ss[chain] = pymol::calloc<int>(cResvMask + 1));
|
|
}
|
|
|
|
sst = nullptr;
|
|
// iterate over all residues indicated
|
|
for(b = ss_resv1; b <= ss_resv2; b++) {
|
|
index = b & cResvMask;
|
|
|
|
if(hash->ss[chain][index]) {
|
|
// make a unique copy in the event of multiple entries for one resv
|
|
sst = nullptr;
|
|
}
|
|
|
|
if(!sst) {
|
|
VLACheck(hash->ss_list, SSEntry, hash->n_ss);
|
|
ok_assert(1, hash->ss_list);
|
|
ssi = (hash->n_ss)++;
|
|
sst = hash->ss_list + ssi;
|
|
sst->resv1 = ss_resv1;
|
|
sst->resv2 = ss_resv2;
|
|
sst->chain1 = ss_chain1;
|
|
sst->chain2 = ss_chain2;
|
|
sst->type = SSCode;
|
|
sst->inscode1 = ss_inscode1;
|
|
sst->inscode2 = ss_inscode2;
|
|
ss_found = true;
|
|
}
|
|
sst->next = hash->ss[chain][index];
|
|
hash->ss[chain][index] = ssi;
|
|
if(sst->next)
|
|
sst = nullptr; /* force another unique copy */
|
|
}
|
|
}
|
|
return ss_found;
|
|
ok_except1:
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Assign ai->ssType
|
|
*/
|
|
static void sshash_lookup(SSHash *hash, AtomInfoType *ai, unsigned char ss_chain1) {
|
|
int index, ssi;
|
|
SSEntry *sst = nullptr;
|
|
|
|
index = ai->resv & cResvMask;
|
|
if(hash->ss[ss_chain1]) {
|
|
ssi = hash->ss[ss_chain1][index];
|
|
while(ssi) {
|
|
sst = hash->ss_list + ssi;
|
|
/* contains shared entry, or unique linked list for each residue */
|
|
if( ai->resv >= sst->resv1
|
|
&& ai->resv <= sst->resv2
|
|
&& (ai->resv != sst->resv1 || ai->inscode >= sst->inscode1)
|
|
&& (ai->resv != sst->resv2 || ai->inscode <= sst->inscode2))
|
|
{
|
|
ai->ssType[0] = sst->type;
|
|
return;
|
|
}
|
|
ssi = sst->next;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* PQR atom line parsing
|
|
*
|
|
* Try to parse columns white space delimited (10 columns with optional
|
|
* chain, 9 without).
|
|
*
|
|
* Where PQR files come from:
|
|
*
|
|
* pdb2pqr -> writes PDB-like fixed colums. APBS will fail to read those files
|
|
* if columns are too wide.
|
|
*
|
|
* pdb2pqr --whitespace -> puts extra whitespace, starting at column 2, but
|
|
* not between chain and resi! PyMOL <= 2.1 fails to read those files.
|
|
*
|
|
* APBS Tools Plugin by Michael Lerner adds extra whitespace before negative
|
|
* coordinates (assuming -100.0 is the most likely source of error).
|
|
*
|
|
* @param[in,out] p Pointer to parse from, points after the ATOM field.
|
|
* Will move the pointer to the end of the line if
|
|
* parsing was successful.
|
|
* @param[out] ai Atom to populate with data
|
|
* @param[out] coord Coordinates to populate
|
|
* @return true on success, false otherwise.
|
|
*/
|
|
static bool parse_pqr_atom_line(PyMOLGlobals * G,
|
|
const char * &p,
|
|
AtomInfoType * ai,
|
|
float * coord)
|
|
{
|
|
auto p_eol = nskip(p, 999); // end of line pointer
|
|
std::string cc(p, p_eol); // line (starting after ATOM field)
|
|
|
|
// whitespace splitting
|
|
auto columns = strsplit(cc);
|
|
|
|
// insert chain column if missing
|
|
if (columns.size() == 9) {
|
|
columns.insert(columns.begin() + 3, "");
|
|
|
|
// check for concatenated chain + resi
|
|
if (columns[4].size() > 4 && !isdigit(columns[4][0])) {
|
|
columns[3] = columns[4].substr(0, 1);
|
|
columns[4] = columns[4].substr(1);
|
|
}
|
|
}
|
|
|
|
// for validation: if number parsing consumes the entire string, then dummy
|
|
// will never be populated (sscanf(...) == 1, not 2)
|
|
char dummy[2];
|
|
|
|
// validate numeric fields and populate atom info and coordinates
|
|
if (columns.size() == 10 &&
|
|
sscanf(columns[0].c_str(), "%d%1s", &ai->id, dummy) == 1 &&
|
|
sscanf(columns[5].c_str(), "%f%1s", coord + 0, dummy) == 1 &&
|
|
sscanf(columns[6].c_str(), "%f%1s", coord + 1, dummy) == 1 &&
|
|
sscanf(columns[7].c_str(), "%f%1s", coord + 2, dummy) == 1 &&
|
|
sscanf(columns[8].c_str(), "%f%1s", &ai->partialCharge, dummy) == 1 &&
|
|
sscanf(columns[9].c_str(), "%f%1s", &ai->elec_radius, dummy) == 1) {
|
|
LexAssign(G, ai->name, columns[1].c_str());
|
|
LexAssign(G, ai->resn, columns[2].c_str());
|
|
LexAssign(G, ai->chain, columns[3].c_str());
|
|
ai->setResi(columns[4].c_str());
|
|
|
|
// move parser to next line
|
|
p = p_eol;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
CoordSet *ObjectMoleculePDBStr2CoordSet(PyMOLGlobals * G,
|
|
const char *buffer,
|
|
AtomInfoType ** atInfoPtr,
|
|
const char **restart_model,
|
|
char *segi_override,
|
|
char *pdb_name,
|
|
const char **next_pdb,
|
|
PDBInfoRec * info, int quiet, int *model_number)
|
|
{
|
|
|
|
const char *p;
|
|
int nAtom;
|
|
int a;
|
|
float *coord = nullptr;
|
|
CoordSet *cset = nullptr;
|
|
AtomInfoType *atInfo = nullptr, *ai;
|
|
int AFlag;
|
|
char SSCode;
|
|
int atomCount;
|
|
int bondFlag = false;
|
|
BondType *bond = nullptr, *ii1, *ii2;
|
|
int *idx;
|
|
int nBond = 0;
|
|
int b1, b2, nReal, maxAt;
|
|
std::unique_ptr<CSymmetry> symmetry;
|
|
int auto_show = RepGetAutoShowMask(G);
|
|
int reformat_names = SettingGetGlobal_i(G, cSetting_pdb_reformat_names_mode);
|
|
int truncate_resn = SettingGetGlobal_b(G, cSetting_pdb_truncate_residue_name);
|
|
const char *tags_in = SettingGetGlobal_s(G, cSetting_pdb_echo_tags), *tag_start[PDB_MAX_TAGS];
|
|
int n_tags = 0;
|
|
int foundNextModelFlag = false;
|
|
int ssFlag = false;
|
|
int ss_resv1 = 0, ss_resv2 = 0;
|
|
char ss_inscode1 = '\0', ss_inscode2 = '\0';
|
|
unsigned char ss_chain1 = 0, ss_chain2 = 0;
|
|
SSHash *ss_hash = nullptr;
|
|
char cc[MAXLINELEN], tags[MAXLINELEN];
|
|
int ignore_pdb_segi = 0;
|
|
int ss_valid, ss_found = false;
|
|
int only_read_one_model = false;
|
|
int ignore_conect = SettingGetGlobal_b(G, cSetting_pdb_ignore_conect);
|
|
int have_bond_order = false;
|
|
int seen_model, in_model = false;
|
|
int is_end_of_object = false;
|
|
int literal_names = SettingGetGlobal_b(G, cSetting_pdb_literal_names);
|
|
int bogus_name_alignment = true;
|
|
AtomName literal_name = "";
|
|
int ok = true;
|
|
lexidx_t segi_override_idx = LexIdx(G, segi_override);
|
|
|
|
if(tags_in && (!quiet) && (!*restart_model)) {
|
|
char *p = tags;
|
|
strcpy(tags, tags_in);
|
|
|
|
while(*p) {
|
|
while(*p == ' ') /* skip spaces */
|
|
p++;
|
|
if(*p) {
|
|
tag_start[n_tags] = p;
|
|
n_tags++;
|
|
while(*p) {
|
|
if(*p != ',') {
|
|
if(*p == ' ')
|
|
*p = 0;
|
|
p++;
|
|
} else
|
|
break;
|
|
}
|
|
if(*p) { /* terminate tag */
|
|
*p = 0;
|
|
p++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(literal_names)
|
|
reformat_names = 0;
|
|
|
|
ignore_pdb_segi = SettingGetGlobal_b(G, cSetting_ignore_pdb_segi);
|
|
|
|
p = buffer;
|
|
nAtom = 0;
|
|
if(atInfoPtr)
|
|
atInfo = *atInfoPtr;
|
|
|
|
if(!atInfo)
|
|
ErrFatal(G, "PDBStr2CoordSet", "need atom information record!"); /* failsafe for old version.. */
|
|
|
|
if(buffer == *restart_model)
|
|
only_read_one_model = true;
|
|
else if(info && (info->multiplex > 0)) {
|
|
only_read_one_model = true;
|
|
if(!info->multi_object_status) { /* figure out if this is a multi-object (multi-HEADER) pdb file */
|
|
info->multi_object_status = get_multi_object_status(p);
|
|
}
|
|
}
|
|
/* PASS 1 */
|
|
ObjectMoleculePDBStr2CoordSetPASS1(G, &ok, restart_model, p, n_tags,
|
|
tag_start, &nAtom, cc, quiet, &bogus_name_alignment, &ssFlag, next_pdb,
|
|
info, only_read_one_model, &ignore_conect, &bondFlag, &have_bond_order);
|
|
/* INPUT USED:
|
|
only_read_one_model - only reads one pdb model if set
|
|
n_tags, tag_start - tags defined to report in log
|
|
cc - just temporary char * buffer that is used, no input/output
|
|
OUTPUT USED:
|
|
bogus_name_alignment - whether all ATOM/HETATM has correct names in PDB (12-16, starting with space
|
|
ssFlag - if PDB has HELIX and/or SHEET records
|
|
both INPUT/OUTPUT:
|
|
nAtom - returns the number of atoms read in, also uses it to detect multiple PDBs
|
|
ignore_conect - do not set bondFlag if set
|
|
|
|
END PASS 1 */
|
|
|
|
*restart_model = nullptr;
|
|
if (ok){
|
|
coord = VLAlloc(float, 3 * nAtom);
|
|
CHECKOK(ok, coord);
|
|
}
|
|
|
|
if(ok && atInfo){
|
|
VLACheck(atInfo, AtomInfoType, nAtom);
|
|
CHECKOK(ok, atInfo);
|
|
if (ok)
|
|
*atInfoPtr = atInfo;
|
|
}
|
|
if(ok && bondFlag) {
|
|
nBond = 0;
|
|
bond = VLACalloc(BondType, 6 * nAtom);
|
|
CHECKOK(ok, bond);
|
|
}
|
|
p = buffer;
|
|
PRINTFB(G, FB_ObjectMolecule, FB_Blather)
|
|
" ObjectMoleculeReadPDB: Found %i atoms...\n", nAtom ENDFB(G);
|
|
|
|
if(ok && ssFlag) {
|
|
ss_hash = sshash_new();
|
|
}
|
|
|
|
a = 0; /* WATCHOUT */
|
|
atomCount = 0;
|
|
|
|
/* PASS 2 */
|
|
seen_model = false;
|
|
|
|
while(ok && *p) {
|
|
/* printf("%c%c%c%c%c%c\n",p[0],p[1],p[2],p[3],p[4],p[5]); */
|
|
AFlag = false;
|
|
SSCode = 0;
|
|
if(strstartswith(p, "ATOM "))
|
|
AFlag = 1;
|
|
else if(strstartswith(p, "HETATM"))
|
|
AFlag = 2;
|
|
else if(strstartswith(p, "HEADER")) {
|
|
|
|
if(pdb_name) {
|
|
if(atomCount > 0) {
|
|
/* have we already read atoms? then this is probably a new PDB file */
|
|
(*next_pdb) = p; /* found another PDB file after this one... */
|
|
break;
|
|
} else if((!info) || (!info->ignore_header_names)) {
|
|
/* if this isn't an MD trajectory... */
|
|
const char *pp;
|
|
pp = nskip(p, 62); /* is there a four-letter PDB code? */
|
|
pp = ntrim(pdb_name, pp, 4);
|
|
if(pdb_name[0] == 0) { /* if not, is there a plain name (for MERCK!) */
|
|
p = nskip(p, 6);
|
|
p = ntrim(cc, p, 44);
|
|
UtilNCopy(pdb_name, cc, WordLength);
|
|
} else {
|
|
p = pp;
|
|
}
|
|
}
|
|
}
|
|
} else if(strstartswith(p, "MODEL ")) {
|
|
if(model_number) {
|
|
int tmp;
|
|
p = nskip(p, 10);
|
|
p = ncopy(cc, p, 5);
|
|
if(sscanf(cc, "%d", &tmp) == 1)
|
|
*model_number = tmp;
|
|
}
|
|
seen_model = true;
|
|
in_model = true;
|
|
} else if(strstartswith(p, "HELIX ")) {
|
|
ss_valid = true;
|
|
|
|
p = nskip(p, 19);
|
|
ss_chain1 = (*p);
|
|
p = nskip(p, 2);
|
|
p = ncopy(cc, p, 4);
|
|
if(!sscanf(cc, "%d", &ss_resv1))
|
|
ss_valid = false;
|
|
ss_inscode1 = makeInscode(*p);
|
|
|
|
p = nskip(p, 6);
|
|
ss_chain2 = (*p);
|
|
p = nskip(p, 2);
|
|
p = ncopy(cc, p, 4);
|
|
|
|
if(!sscanf(cc, "%d", &ss_resv2))
|
|
ss_valid = false;
|
|
ss_inscode2 = makeInscode(*p);
|
|
|
|
if(ss_valid) {
|
|
PRINTFB(G, FB_ObjectMolecule, FB_Blather)
|
|
" ObjectMolecule: read HELIX %c %d%.1s %c %d%.1s\n",
|
|
ss_chain1, ss_resv1, &ss_inscode1, ss_chain2, ss_resv2, &ss_inscode2 ENDFB(G);
|
|
SSCode = 'H';
|
|
}
|
|
|
|
if(ss_chain1 == ' ')
|
|
ss_chain1 = 0;
|
|
if(ss_chain2 == ' ')
|
|
ss_chain2 = 0;
|
|
|
|
} else if(strstartswith(p, "SHEET ")) {
|
|
ss_valid = true;
|
|
p = nskip(p, 21);
|
|
ss_chain1 = (*p);
|
|
p = nskip(p, 1);
|
|
p = ncopy(cc, p, 4);
|
|
if(!sscanf(cc, "%d", &ss_resv1))
|
|
ss_valid = false;
|
|
ss_inscode1 = makeInscode(*p);
|
|
p = nskip(p, 6);
|
|
ss_chain2 = (*p);
|
|
p = nskip(p, 1);
|
|
p = ncopy(cc, p, 4);
|
|
if(!sscanf(cc, "%d", &ss_resv2))
|
|
ss_valid = false;
|
|
ss_inscode2 = makeInscode(*p);
|
|
|
|
if(ss_valid) {
|
|
PRINTFB(G, FB_ObjectMolecule, FB_Blather)
|
|
" ObjectMolecule: read SHEET %c %d%.1s %c %d%.1s\n",
|
|
ss_chain1, ss_resv1, &ss_inscode1, ss_chain2, ss_resv2, &ss_inscode2 ENDFB(G);
|
|
SSCode = 'S';
|
|
}
|
|
|
|
if(ss_chain1 == ' ')
|
|
ss_chain1 = 0;
|
|
if(ss_chain2 == ' ')
|
|
ss_chain2 = 0;
|
|
|
|
} else if(strstartswith(p, "ENDMDL")) {
|
|
if(*restart_model)
|
|
in_model = false;
|
|
else {
|
|
*restart_model = nextline(p);
|
|
in_model = false;
|
|
if(only_read_one_model) {
|
|
const char *pp;
|
|
pp = nextline(p);
|
|
if(strstartswith(pp, "END")) { /* END we're going to be starting a new object... */
|
|
(*next_pdb) = check_next_pdb_object(nextline(pp), true);
|
|
if(info && (info->multiplex == 0)) {
|
|
/* multiplex == 0: FORCED multimodel behavior with concatenated PDB files */
|
|
(*restart_model) = (*next_pdb);
|
|
(*next_pdb) = nullptr;
|
|
foundNextModelFlag = true;
|
|
info->multi_object_status = -1;
|
|
} else {
|
|
is_end_of_object = true;
|
|
}
|
|
} else if(strstartswith(pp, "MODEL")) { /* not a new object...just a new state (model) */
|
|
if(info && (info->multiplex > 0)) { /* end object if we're multiplexing */
|
|
(*next_pdb) = check_next_pdb_object(pp, true);
|
|
(*restart_model) = nullptr;
|
|
} else
|
|
is_end_of_object = false;
|
|
} else {
|
|
if(pp[0] > 32) /* more content follows... */
|
|
(*next_pdb) = check_next_pdb_object(pp, true);
|
|
else
|
|
(*next_pdb) = nullptr; /* at end of file */
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
} else if(strstartswith(p, "END")) {
|
|
ntrim(cc, p, 6);
|
|
if((strcmp("END", cc) == 0) && (!in_model)) { /* stop parsing here... */
|
|
const char *pp;
|
|
pp = nextline(p); /* ...unless we're in MODEL or next line is itself ENDMDL */
|
|
p = ncopy(cc, p, 6);
|
|
if(!((cc[0] == 'E') && (cc[1] == 'N') && (cc[2] == 'D') && (cc[3] == 'M') && (cc[4] == 'D') && (cc[5] == 'L'))) { /* NOTE: this test seems unnecessary given strcmp above... */
|
|
if(!*next_pdb) {
|
|
(*next_pdb) = check_next_pdb_object(pp, false);
|
|
}
|
|
if((*next_pdb) && info && (!info->multiplex) && !(*restart_model)) {
|
|
/* multiplex == 0: FORCED multimodel behavior with concatenated PDB files */
|
|
(*restart_model) = (*next_pdb);
|
|
(*next_pdb) = nullptr;
|
|
foundNextModelFlag = true;
|
|
info->multi_object_status = -1;
|
|
is_end_of_object = false;
|
|
break;
|
|
}
|
|
if(*next_pdb) { /* we've found another object... */
|
|
if(*restart_model)
|
|
is_end_of_object = false; /* however, if we're parsing multi-models, then we're not yet at the end */
|
|
else
|
|
is_end_of_object = true;
|
|
break;
|
|
} else if(!seen_model)
|
|
break;
|
|
}
|
|
}
|
|
} else if(strstartswith(p, "CRYST1") && (!*restart_model)) {
|
|
if(!symmetry){
|
|
symmetry.reset(new CSymmetry(G));
|
|
CHECKOK(ok, symmetry);
|
|
}
|
|
if(symmetry) {
|
|
int symFlag = true;
|
|
PRINTFB(G, FB_ObjectMolecule, FB_Blather)
|
|
" PDBStrToCoordSet: Attempting to read symmetry information\n" ENDFB(G);
|
|
p = nskip(p, 6);
|
|
symFlag = true;
|
|
float cellparams[3];
|
|
p = ncopy(cc, p, 9);
|
|
if(sscanf(cc, "%f", cellparams + 0) != 1)
|
|
symFlag = false;
|
|
p = ncopy(cc, p, 9);
|
|
if(sscanf(cc, "%f", cellparams + 1) != 1)
|
|
symFlag = false;
|
|
p = ncopy(cc, p, 9);
|
|
if(sscanf(cc, "%f", cellparams + 2) != 1)
|
|
symFlag = false;
|
|
|
|
symmetry->Crystal.setDims(cellparams);
|
|
|
|
p = ncopy(cc, p, 7);
|
|
if(sscanf(cc, "%f", cellparams + 0) != 1)
|
|
symFlag = false;
|
|
p = ncopy(cc, p, 7);
|
|
if(sscanf(cc, "%f", cellparams + 1) != 1)
|
|
symFlag = false;
|
|
p = ncopy(cc, p, 7);
|
|
if(sscanf(cc, "%f", cellparams + 2) != 1)
|
|
symFlag = false;
|
|
|
|
symmetry->Crystal.setAngles(cellparams);
|
|
|
|
p = nskip(p, 1);
|
|
p = ncopy(cc, p, 11);
|
|
UtilCleanStr(cc);
|
|
symmetry->setSpaceGroup(cc);
|
|
p = ncopy(cc, p, 3);
|
|
if(sscanf(cc, "%d", &symmetry->PDBZValue) != 1)
|
|
symmetry->PDBZValue = 1;
|
|
if(!symFlag) {
|
|
ErrMessage(G, "PDBStrToCoordSet", "Error reading CRYST1 record\n");
|
|
symmetry.reset();
|
|
}
|
|
}
|
|
} else if(strstartswith(p, "SCALE") && (!*restart_model) && info) { /* SCALEn */
|
|
switch (p[5]) {
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
{
|
|
int flag = (p[5] - '1');
|
|
int offset = flag * 4;
|
|
int scale_flag = true;
|
|
p = nskip(p, 10);
|
|
p = ncopy(cc, p, 10);
|
|
if(sscanf(cc, "%f", &info->scale.matrix[offset]) != 1)
|
|
scale_flag = false;
|
|
p = ncopy(cc, p, 10);
|
|
if(sscanf(cc, "%f", &info->scale.matrix[offset + 1]) != 1)
|
|
scale_flag = false;
|
|
p = ncopy(cc, p, 10);
|
|
if(sscanf(cc, "%f", &info->scale.matrix[offset + 2]) != 1)
|
|
scale_flag = false;
|
|
p = nskip(p, 5);
|
|
p = ncopy(cc, p, 10);
|
|
if(sscanf(cc, "%f", &info->scale.matrix[offset + 3]) != 1)
|
|
scale_flag = false;
|
|
if(scale_flag)
|
|
info->scale.flag[flag] = true;
|
|
PRINTFB(G, FB_ObjectMolecule, FB_Blather)
|
|
" PDBStrToCoordSet: SCALE%d %8.4f %8.4f %8.4f %8.4f\n", flag + 1,
|
|
info->scale.matrix[offset],
|
|
info->scale.matrix[offset + 1],
|
|
info->scale.matrix[offset + 2], info->scale.matrix[offset + 3]
|
|
ENDFB(G);
|
|
}
|
|
break;
|
|
}
|
|
} else if(strstartswith(p, "CONECT") &&
|
|
bondFlag && (!ignore_conect) && ((!*restart_model) || (!in_model))) {
|
|
p = nskip(p, 6);
|
|
p = ncopy(cc, p, 5);
|
|
if(sscanf(cc, "%d", &b1) == 1)
|
|
while(1) {
|
|
p = ncopy(cc, p, 5);
|
|
if(sscanf(cc, "%d", &b2) != 1)
|
|
break;
|
|
else {
|
|
if((b1 >= 0) && (b2 >= 0) && (b1 != b2)) { /* IDs must be positive and distinct */
|
|
VLACheck(bond, BondType, nBond);
|
|
CHECKOK(ok, bond);
|
|
if (ok){
|
|
if(b1 <= b2) {
|
|
bond[nBond].index[0] = b1; /* temporarily store the atom indexes */
|
|
bond[nBond].index[1] = b2;
|
|
bond[nBond].order = 1;
|
|
} else {
|
|
bond[nBond].index[0] = b2;
|
|
bond[nBond].index[1] = b1;
|
|
bond[nBond].order = 1;
|
|
}
|
|
nBond++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else if(strstartswith(p, "USER") && (!*restart_model)) {
|
|
} else if(strstartswith(p, "ANISOU") && (!*restart_model) && (atomCount)) {
|
|
ai = atInfo + atomCount - 1;
|
|
|
|
/* TODO: check atom identifier match */
|
|
|
|
{
|
|
int dummy;
|
|
p = nskip(p, 6);
|
|
p = ncopy(cc, p, 5);
|
|
if(!sscanf(cc, "%d", &dummy))
|
|
dummy = 0;
|
|
if(dummy == ai->id) { /* ATOM ID must match */
|
|
int dummy;
|
|
float * anisou = ai->get_anisou();
|
|
p = nskip(p, 17);
|
|
for (int i = 0; i < 6; ++i) {
|
|
p = ncopy(cc, p, 7);
|
|
if(sscanf(cc, "%d", &dummy))
|
|
anisou[i] = dummy / 10000.0F;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* END KEYWORDS */
|
|
|
|
/* Secondary structure records */
|
|
|
|
if(ok && SSCode) {
|
|
ss_found = sshash_register_rec(ss_hash,
|
|
ss_chain1, ss_resv1, ss_inscode1,
|
|
ss_chain2, ss_resv2, ss_inscode2, SSCode);
|
|
}
|
|
/* Atom records */
|
|
|
|
if(ok && AFlag && (!*restart_model)) {
|
|
ai = atInfo + atomCount;
|
|
p = nskip(p, 6);
|
|
|
|
ai->rank = atomCount;
|
|
|
|
if(info && info->is_pqr_file()) {
|
|
if (parse_pqr_atom_line(G, p, ai, coord + a)) {
|
|
goto pqr_done;
|
|
}
|
|
}
|
|
|
|
p = ncopy(cc, p, 5);
|
|
if(!sscanf(cc, "%d", &ai->id))
|
|
ai->id = 0;
|
|
|
|
p = nskip(p, 1); /* to 12 */
|
|
p = ncopy(literal_name, p, 4);
|
|
if(literal_names) {
|
|
LexAssign(G, ai->name, literal_name);
|
|
} else {
|
|
ParseNTrim(cc, literal_name, 4);
|
|
LexAssign(G, ai->name, cc);
|
|
}
|
|
|
|
p = ncopy(cc, p, 1);
|
|
if(*cc == 32)
|
|
ai->alt[0] = 0;
|
|
else {
|
|
ai->alt[0] = *cc;
|
|
ai->alt[1] = 0;
|
|
}
|
|
|
|
p = ntrim(cc, p, 4); /* now allowing for 4-letter residues */
|
|
if (truncate_resn) /* unless specifically disabled */
|
|
cc[3] = 0;
|
|
|
|
LexAssign(G, ai->resn, cc);
|
|
|
|
if(ai->name) {
|
|
const char * ai_name = LexStr(G, ai->name);
|
|
int name_len = strlen(ai_name);
|
|
char name[5];
|
|
switch (reformat_names) {
|
|
case 1: /* pdb compliant: HH12 becomes 2HH1, etc. */
|
|
if(name_len > 3) {
|
|
if((ai_name[0] >= 'A') && ((ai_name[0] <= 'Z')) &&
|
|
isdigit(ai_name[3])) {
|
|
if(!(((ai_name[1] >= 'a') && (ai_name[1] <= 'z')) ||
|
|
((ai_name[0] == 'C') && (ai_name[1] == 'L')) || /* try to be smart about */
|
|
((ai_name[0] == 'B') && (ai_name[1] == 'R')) || /* distinguishing common atoms */
|
|
((ai_name[0] == 'C') && (ai_name[1] == 'A')) || /* in all-caps from typical */
|
|
((ai_name[0] == 'F') && (ai_name[1] == 'E')) || /* nonatomic abbreviations */
|
|
((ai_name[0] == 'C') && (ai_name[1] == 'U')) ||
|
|
((ai_name[0] == 'N') && (ai_name[1] == 'A')) ||
|
|
((ai_name[0] == 'N') && (ai_name[1] == 'I')) ||
|
|
((ai_name[0] == 'M') && (ai_name[1] == 'G')) ||
|
|
((ai_name[0] == 'M') && (ai_name[1] == 'N')) ||
|
|
((ai_name[0] == 'H') && (ai_name[1] == 'G')) ||
|
|
((ai_name[0] == 'S') && (ai_name[1] == 'E')) ||
|
|
((ai_name[0] == 'S') && (ai_name[1] == 'I')) ||
|
|
((ai_name[0] == 'Z') && (ai_name[1] == 'N'))
|
|
)) {
|
|
strncpy(name + 1, ai_name, 3);
|
|
name[0] = ai_name[3];
|
|
name[4] = 0;
|
|
LexAssign(G, ai->name, name);
|
|
}
|
|
}
|
|
} else if(name_len == 3) {
|
|
if((ai_name[0] == 'H') &&
|
|
(ai_name[1] >= 'A') && ((ai_name[1] <= 'Z')) &&
|
|
isdigit(ai_name[2])) {
|
|
AtomInfoGetPDB3LetHydroName(G, LexStr(G, ai->resn), ai_name, name);
|
|
LexAssign(G, ai->name, (name[0] == ' ') ? (name + 1) : name);
|
|
}
|
|
}
|
|
break;
|
|
case 2: /* amber compliant: 2HH1 becomes HH12 */
|
|
case 3: /* pdb compliant, but use IUPAC within PyMOL */
|
|
if(ai_name[0]) {
|
|
if(isdigit(ai_name[0]) && ai_name[1] && (!isdigit(ai_name[1]))) {
|
|
if (1 < name_len && name_len < 5) {
|
|
strcpy(name, ai_name + 1);
|
|
name[name_len - 1] = ai_name[0];
|
|
name[name_len] = 0;
|
|
LexAssign(G, ai->name, name);
|
|
}
|
|
break;
|
|
default: /* AS IS */
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case 4: /* simply read trim and write back out with 3-letter names starting from the
|
|
second column, and four-letter names starting in the first */
|
|
ntrim(cc, ai_name, 4);
|
|
LexAssign(G, ai->name, cc);
|
|
break;
|
|
}
|
|
}
|
|
|
|
p = ncopy(cc, p, 1);
|
|
if (ai->chain){
|
|
LexDec(G, ai->chain);
|
|
}
|
|
if(*cc == ' ') {
|
|
ss_chain1 = 0;
|
|
ai->chain = 0;
|
|
} else {
|
|
ss_chain1 = *cc;
|
|
ai->chain = LexIdx(G, cc);
|
|
}
|
|
|
|
p = ncopy(cc, p, 4);
|
|
if(!sscanf(cc, "%d", &ai->resv))
|
|
ai->resv = 0;
|
|
ai->setInscode(*p);
|
|
p = nskip(p, 1);
|
|
|
|
if(ssFlag) { /* get secondary structure information (if avail) */
|
|
sshash_lookup(ss_hash, ai, ss_chain1);
|
|
} else {
|
|
ai->cartoon = cCartoon_tube;
|
|
}
|
|
|
|
{
|
|
p = nskip(p, 3);
|
|
p = ncopy(cc, p, 8);
|
|
sscanf(cc, "%f", coord + a);
|
|
p = ncopy(cc, p, 8);
|
|
sscanf(cc, "%f", coord + (a + 1));
|
|
p = ncopy(cc, p, 8);
|
|
sscanf(cc, "%f", coord + (a + 2));
|
|
}
|
|
|
|
if((!info) || (!info->is_pqr_file())) { /* standard PDB file */
|
|
p = ncopy(cc, p, 6);
|
|
if(!sscanf(cc, "%f", &ai->q))
|
|
ai->q = 1.0;
|
|
|
|
p = ncopy(cc, p, 6);
|
|
if(!sscanf(cc, "%f", &ai->b))
|
|
ai->b = 0.0;
|
|
|
|
if (info->variant == PDB_VARIANT_PDBQT) {
|
|
ignore_pdb_segi = true;
|
|
p = nskip(p, 4);
|
|
p = ncopy(cc, p, 6);
|
|
if(!sscanf(cc, "%f", &ai->partialCharge))
|
|
ai->partialCharge = 0.0;
|
|
|
|
// type is 78-79 in pdbqt, 77-78 in pdb
|
|
p = nskip(p, 1);
|
|
} else {
|
|
p = nskip(p, 6);
|
|
p = ncopy(cc, p, 4);
|
|
}
|
|
|
|
if(!ignore_pdb_segi) {
|
|
if(!segi_override_idx) {
|
|
if(cc[3] == '1' && atomCount && strncmp(p, "0000", 4) == 0) {
|
|
/* atom ID overflow? (nonstandard use...)... */
|
|
LexAssign(G, segi_override_idx, (ai - 1)->segi);
|
|
LexAssign(G, ai->segi, (ai - 1)->segi);
|
|
} else {
|
|
UtilCleanStr(cc);
|
|
LexAssign(G, ai->segi, cc);
|
|
}
|
|
} else {
|
|
LexAssign(G, ai->segi, segi_override_idx);
|
|
}
|
|
} else {
|
|
LexAssign(G, ai->segi, 0);
|
|
}
|
|
|
|
p = ncopy(cc, p, 2);
|
|
if(!sscanf(cc, "%s", ai->elem))
|
|
ai->elem[0] = 0;
|
|
else if(!((((ai->elem[0] >= 'a') && (ai->elem[0] <= 'z')) || /* don't get confused by PDB misuse */
|
|
((ai->elem[0] >= 'A') && (ai->elem[0] <= 'Z'))) &&
|
|
(((ai->elem[1] == 0) ||
|
|
((ai->elem[1] >= 'a') && (ai->elem[1] <= 'z')) ||
|
|
((ai->elem[1] >= 'A') && (ai->elem[1] <= 'Z'))))))
|
|
ai->elem[0] = 0;
|
|
else if (info->variant == PDB_VARIANT_PDBQT) {
|
|
if (strcmp(ai->elem, "A") == 0) {
|
|
// aromatic carbon
|
|
ai->elem[0] = 'C';
|
|
} else if (isupper(ai->elem[1])) {
|
|
// h-bond donor or acceptor
|
|
ai->elem[1] = 0;
|
|
}
|
|
}
|
|
|
|
if(!ai->elem[0]) {
|
|
if(((literal_name[0] == ' ') || ((literal_name[0] >= '0') && (literal_name[0] <= '9'))) && (literal_name[1] >= 'A') && (literal_name[1] <= 'Z')) { /* infer element from name column */
|
|
ai->elem[0] = literal_name[1];
|
|
ai->elem[1] = 0;
|
|
} else if(((literal_name[0] >= 'A') && (literal_name[0] <= 'Z')) && (((literal_name[1] >= 'A') && (literal_name[1] <= 'Z')) || ((literal_name[1] >= 'a') && (literal_name[1] <= 'z')))) { /* infer element from name column */
|
|
ai->elem[0] = literal_name[0];
|
|
ai->elem[2] = 0;
|
|
if((literal_name[1] >= 'A') && (literal_name[1] <= 'Z')) { /* second letter is capitalized */
|
|
if(bogus_name_alignment) {
|
|
/* if other atom names aren't properly aligned */
|
|
ai->elem[1] = 0; /* kill 2nd letter */
|
|
} else if(literal_name[0] == 'H') {
|
|
/* or if this is an ultra-bogus PDB with inconsistent
|
|
indendentation, and this is likely a hydrogen */
|
|
ai->elem[1] = 0; /* kill 2nd letter */
|
|
} else {
|
|
ai->elem[1] = tolower(literal_name[1]);
|
|
}
|
|
} else
|
|
ai->elem[1] = literal_name[1];
|
|
}
|
|
}
|
|
|
|
p = ncopy(cc, p, 2);
|
|
if((cc[1] == '-') || (cc[1] == '+')) {
|
|
/* only read formal charge when sign is present */
|
|
char ctmp = cc[0];
|
|
cc[0] = cc[1];
|
|
cc[1] = ctmp;
|
|
if(!sscanf(cc, "%hhi", &ai->formalCharge))
|
|
ai->formalCharge = 0;
|
|
}
|
|
|
|
/* end normal PDB */
|
|
} else if(info && info->is_pqr_file()) {
|
|
p = ParseWordNumberCopy(cc, p, MAXLINELEN - 1);
|
|
if(!sscanf(cc, "%f", &ai->partialCharge))
|
|
ai->partialCharge = 0.0F;
|
|
|
|
p = ParseWordNumberCopy(cc, p, MAXLINELEN - 1);
|
|
if(sscanf(cc, "%f", &ai->elec_radius) != 1)
|
|
ai->elec_radius = 0.0F;
|
|
}
|
|
|
|
pqr_done:
|
|
|
|
ai->visRep = auto_show;
|
|
|
|
if(AFlag == 1)
|
|
ai->hetatm = 0;
|
|
else {
|
|
ai->hetatm = 1;
|
|
ai->flags = cAtomFlag_ignore;
|
|
}
|
|
|
|
AtomInfoAssignParameters(G, ai);
|
|
AtomInfoAssignColors(G, ai);
|
|
|
|
PRINTFD(G, FB_ObjectMolecule)
|
|
"%s %s %d%c %s %8.3f %8.3f %8.3f %6.2f %6.2f %s\n",
|
|
LexStr(G, ai->name), LexStr(G, ai->resn), ai->resv, ai->getInscode(true), LexStr(G, ai->chain),
|
|
*(coord + a), *(coord + a + 1), *(coord + a + 2), ai->b, ai->q, LexStr(G, ai->segi) ENDFD;
|
|
|
|
if(atomCount < nAtom) { /* safety */
|
|
a += 3;
|
|
atomCount++;
|
|
}
|
|
}
|
|
p = nextline(p);
|
|
}
|
|
|
|
/* END PASS 2 */
|
|
|
|
if(ok && bondFlag) {
|
|
UtilSortInPlace(G, bond, nBond, sizeof(BondType), (UtilOrderFn *) BondInOrder);
|
|
if(nBond) {
|
|
if(!have_bond_order) { /* handle PDB bond-order kludge */
|
|
ii1 = bond;
|
|
ii2 = bond + 1;
|
|
nReal = 1;
|
|
ii1->order = 1;
|
|
a = nBond - 1;
|
|
while(a) {
|
|
if((ii1->index[0] == ii2->index[0]) && (ii1->index[1] == ii2->index[1])) {
|
|
ii1->order++; /* count dup */
|
|
} else {
|
|
ii1++; /* non-dup, make copy */
|
|
ii1->index[0] = ii2->index[0];
|
|
ii1->index[1] = ii2->index[1];
|
|
ii1->order = ii2->order;
|
|
nReal++;
|
|
}
|
|
ii2++;
|
|
a--;
|
|
}
|
|
nBond = nReal;
|
|
}
|
|
/* now, find atoms we're looking for */
|
|
|
|
/* determine ranges */
|
|
maxAt = nAtom;
|
|
ii1 = bond;
|
|
for(a = 0; a < nBond; a++) {
|
|
if(ii1->index[0] > maxAt)
|
|
maxAt = ii1->index[0];
|
|
if(ii1->index[1] > maxAt)
|
|
maxAt = ii1->index[1];
|
|
ii1++;
|
|
}
|
|
for(a = 0; a < nAtom; a++)
|
|
if(maxAt < atInfo[a].id)
|
|
maxAt = atInfo[a].id;
|
|
/* build index */
|
|
maxAt++;
|
|
idx = pymol::malloc<int>(maxAt + 1);
|
|
CHECKOK(ok, idx);
|
|
if (ok){
|
|
for(a = 0; a < maxAt; a++) {
|
|
idx[a] = -1;
|
|
}
|
|
for(a = 0; a < nAtom; a++)
|
|
idx[atInfo[a].id] = a;
|
|
|
|
/* convert indices to bonds */
|
|
ii1 = bond;
|
|
ii2 = bond;
|
|
nReal = 0;
|
|
}
|
|
if (ok) {
|
|
int unbond_cations = SettingGetGlobal_i(G, cSetting_pdb_unbond_cations);
|
|
int flag;
|
|
|
|
for(a = 0; a < nBond; a++) {
|
|
|
|
if((ii1->index[0] >= 0) && ((ii1->index[1]) >= 0)) {
|
|
if((idx[ii1->index[0]] >= 0) && (idx[ii1->index[1]] >= 0)) { /* in case PDB file has bad bonds */
|
|
ii2->index[0] = idx[ii1->index[0]];
|
|
ii2->index[1] = idx[ii1->index[1]];
|
|
ii2->order = ii1->order;
|
|
if((ii2->index[0] >= 0) && (ii2->index[1] >= 0)) {
|
|
|
|
if(!have_bond_order) { /* handle PDB bond order kludge */
|
|
if(ii1->order <= 2)
|
|
ii2->order = 1;
|
|
else if(ii1->order <= 4)
|
|
ii2->order = 2;
|
|
else if(ii1->order <= 6)
|
|
ii2->order = 3;
|
|
else
|
|
ii2->order = 4;
|
|
}
|
|
flag = true;
|
|
if(unbond_cations) {
|
|
if(AtomInfoIsFreeCation(G, atInfo + ii2->index[0]))
|
|
flag = false;
|
|
else if(AtomInfoIsFreeCation(G, atInfo + ii2->index[1]))
|
|
flag = false;
|
|
}
|
|
if(flag) {
|
|
atInfo[ii2->index[0]].bonded = true;
|
|
atInfo[ii2->index[1]].bonded = true;
|
|
nReal++;
|
|
ii2++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ii1++;
|
|
}
|
|
}
|
|
nBond = nReal;
|
|
FreeP(idx);
|
|
}
|
|
}
|
|
if(ss_found && !quiet) {
|
|
PRINTFB(G, FB_ObjectMolecule, FB_Details)
|
|
" ObjectMolecule: Read secondary structure assignments.\n" ENDFB(G);
|
|
}
|
|
if(symmetry && !quiet && (!only_read_one_model)) {
|
|
PRINTFB(G, FB_ObjectMolecule, FB_Details)
|
|
" ObjectMolecule: Read crystal symmetry information.\n" ENDFB(G);
|
|
}
|
|
PRINTFB(G, FB_ObjectMolecule, FB_Blather)
|
|
" PDBStr2CoordSet: Read %d bonds from CONECT records (%p).\n", nBond,
|
|
(void *) bond ENDFB(G);
|
|
|
|
if (ok){
|
|
cset = CoordSetNew(G);
|
|
CHECKOK(ok, cset);
|
|
|
|
cset->NIndex = nAtom;
|
|
cset->Coord = pymol::vla_take_ownership(coord);
|
|
cset->TmpBond = pymol::vla_take_ownership(bond);
|
|
cset->NTmpBond = nBond;
|
|
cset->Symmetry = std::move(symmetry);
|
|
if(atInfoPtr)
|
|
*atInfoPtr = atInfo;
|
|
|
|
if((*restart_model) && (!foundNextModelFlag)) {
|
|
/* if plan on need to reading another model into this object,
|
|
make sure there is another model to read... */
|
|
p = *restart_model;
|
|
while(*p) {
|
|
if(strstartswith(p, "HEADER")) {
|
|
/* seeing HEADER means we're off the end of the existing file */
|
|
break;
|
|
} else if(strstartswith(p, "MODEL ") ||
|
|
strstartswith(p, "ENDMDL")) {
|
|
foundNextModelFlag = true;
|
|
break;
|
|
}
|
|
p = nextline(p);
|
|
}
|
|
if(!foundNextModelFlag) {
|
|
*restart_model = nullptr;
|
|
}
|
|
}
|
|
}
|
|
if (!ok){
|
|
if (cset){
|
|
delete cset;
|
|
cset = nullptr;
|
|
} else {
|
|
VLAFreeP(coord);
|
|
VLAFreeP(bond);
|
|
}
|
|
}
|
|
sshash_free(ss_hash);
|
|
|
|
if (ok){
|
|
if(!seen_model)
|
|
*model_number = 1;
|
|
|
|
if((*restart_model) && (*next_pdb)) { /* if we're mixing multistate objects and
|
|
trajectories, then enforce sanity by
|
|
reading the models first... */
|
|
if(is_end_of_object)
|
|
(*restart_model) = nullptr;
|
|
else if((*next_pdb) < (*restart_model))
|
|
(*next_pdb) = nullptr;
|
|
}
|
|
}
|
|
|
|
LexDec(G, segi_override_idx);
|
|
return (cset);
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
|
|
void ObjectMoleculeInitHBondCriteria(PyMOLGlobals * G, HBondCriteria * hbc)
|
|
{
|
|
hbc->maxAngle = SettingGet_f(G, nullptr, nullptr, cSetting_h_bond_max_angle);
|
|
hbc->maxDistAtMaxAngle = SettingGet_f(G, nullptr, nullptr, cSetting_h_bond_cutoff_edge);
|
|
hbc->maxDistAtZero = SettingGet_f(G, nullptr, nullptr, cSetting_h_bond_cutoff_center);
|
|
hbc->power_a = SettingGet_f(G, nullptr, nullptr, cSetting_h_bond_power_a);
|
|
hbc->power_b = SettingGet_f(G, nullptr, nullptr, cSetting_h_bond_power_b);
|
|
hbc->cone_dangle =
|
|
(float) cos(PI * 0.5 * SettingGet_f(G, nullptr, nullptr, cSetting_h_bond_cone) / 180.0F);
|
|
if(hbc->maxDistAtMaxAngle != 0.0F) {
|
|
hbc->factor_a = (float) (0.5 / pow(hbc->maxAngle, hbc->power_a));
|
|
hbc->factor_b = (float) (0.5 / pow(hbc->maxAngle, hbc->power_b));
|
|
}
|
|
}
|
|
|
|
static int ObjectMoleculeTestHBond(float *donToAcc, float *donToH, float *hToAcc,
|
|
float *accPlane, HBondCriteria * hbc)
|
|
{
|
|
float nDonToAcc[3], nDonToH[3], nAccPlane[3], nHToAcc[3];
|
|
double angle;
|
|
double cutoff;
|
|
double curve;
|
|
double dist;
|
|
float dangle;
|
|
|
|
/* A ~~ H - D */
|
|
|
|
normalize23f(donToAcc, nDonToAcc);
|
|
normalize23f(hToAcc, nHToAcc);
|
|
if(accPlane) { /* remember, plane need not exist if it's water... */
|
|
normalize23f(accPlane, nAccPlane);
|
|
if(dot_product3f(nHToAcc, nAccPlane) > (-hbc->cone_dangle)) /* don't allow H behind Acceptor plane */
|
|
return 0;
|
|
}
|
|
|
|
normalize23f(donToH, nDonToH);
|
|
normalize23f(donToAcc, nDonToAcc);
|
|
|
|
dangle = dot_product3f(nDonToH, nDonToAcc);
|
|
if((dangle < 1.0F) && (dangle > 0.0F))
|
|
angle = 180.0 * acos((double) dangle) / PI;
|
|
else if(dangle > 0.0F)
|
|
angle = 0.0;
|
|
else
|
|
angle = 90.0;
|
|
|
|
if(angle > hbc->maxAngle)
|
|
return 0;
|
|
|
|
/* interpolate cutoff based on ADH angle */
|
|
|
|
if(hbc->maxDistAtMaxAngle != 0.0F) {
|
|
curve = (pow(angle, (double) hbc->power_a) * hbc->factor_a +
|
|
pow(angle, (double) hbc->power_b) * hbc->factor_b);
|
|
|
|
cutoff = (hbc->maxDistAtMaxAngle * curve) + (hbc->maxDistAtZero * (1.0 - curve));
|
|
} else {
|
|
cutoff = hbc->maxDistAtZero;
|
|
}
|
|
|
|
/*
|
|
printf("angle %8.3f curve %8.3f %8.3f %8.3f %8.3f\n",angle,
|
|
curve,cutoff,hbc->maxDistAtMaxAngle,hbc->maxDistAtZero);
|
|
*/
|
|
|
|
dist = length3f(donToAcc);
|
|
|
|
if(dist > cutoff)
|
|
return 0;
|
|
else
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
|
|
static int ObjectMoleculeFindBestDonorH(ObjectMolecule * I,
|
|
int atom,
|
|
int state, float *dir, float *best,
|
|
AtomInfoType **h_real)
|
|
{
|
|
int result = 0;
|
|
CoordSet *cs;
|
|
float cand[3], cand_dir[3];
|
|
float best_dot = 0.0F, cand_dot;
|
|
|
|
if((state >= 0) && (state < I->NCSet) && (cs = I->CSet[state]) && (atom < I->NAtom)) {
|
|
|
|
auto idx = cs->atmToIdx(atom);
|
|
|
|
if(idx >= 0) {
|
|
|
|
const float* orig = cs->coordPtr(idx);
|
|
|
|
/* do we need to add any new hydrogens? */
|
|
|
|
auto const neighbors = AtomNeighbors(I, atom);
|
|
int const nn = neighbors.size();
|
|
|
|
if((nn < I->AtomInfo[atom].valence) || I->AtomInfo[atom].hb_donor) { /* is there an implicit hydrogen? */
|
|
if (CoordSetFindOpenValenceVector(cs, atom, best, dir)) {
|
|
result = true;
|
|
best_dot = dot_product3f(best, dir);
|
|
add3f(orig, best, best);
|
|
if(h_real)
|
|
*h_real = nullptr;
|
|
}
|
|
}
|
|
/* iterate through real hydrogens looking for best match
|
|
with desired direction */
|
|
|
|
/* look for an attached non-hydrogen as a base */
|
|
for (int i = 0; i < nn; ++i) {
|
|
int const a1 = neighbors[i].atm;
|
|
if(I->AtomInfo[a1].protons == 1) { /* hydrogen */
|
|
if(ObjectMoleculeGetAtomVertex(I, state, a1, cand)) { /* present */
|
|
|
|
subtract3f(cand, orig, cand_dir);
|
|
normalize3f(cand_dir);
|
|
cand_dot = dot_product3f(cand_dir, dir);
|
|
if(result) { /* improved */
|
|
if((best_dot < cand_dot) || (h_real && !*h_real)) {
|
|
best_dot = cand_dot;
|
|
copy3f(cand, best);
|
|
if(h_real)
|
|
*h_real = I->AtomInfo + a1;
|
|
}
|
|
} else { /* first */
|
|
result = true;
|
|
copy3f(cand, best);
|
|
best_dot = cand_dot;
|
|
if(h_real)
|
|
*h_real = I->AtomInfo + a1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
|
|
int ObjectMoleculeGetCheckHBond(AtomInfoType **h_real,
|
|
float *h_crd_ret,
|
|
ObjectMolecule * don_obj,
|
|
int don_atom,
|
|
int don_state,
|
|
ObjectMolecule * acc_obj,
|
|
int acc_atom,
|
|
int acc_state,
|
|
HBondCriteria * hbc)
|
|
{
|
|
int result = 0;
|
|
const CoordSet *csD, *csA;
|
|
float donToAcc[3];
|
|
float donToH[3];
|
|
float bestH[3];
|
|
float hToAcc[3];
|
|
float accPlane[3], *vAccPlane = nullptr;
|
|
|
|
/* first, check for existence of coordinate sets */
|
|
|
|
if((don_state >= 0) &&
|
|
(don_state < don_obj->NCSet) &&
|
|
(csD = don_obj->CSet[don_state]) &&
|
|
(acc_state >= 0) &&
|
|
(acc_state < acc_obj->NCSet) &&
|
|
(csA = acc_obj->CSet[acc_state]) &&
|
|
(don_atom < don_obj->NAtom) && (acc_atom < acc_obj->NAtom)) {
|
|
|
|
/* now check for coordinates of these actual atoms */
|
|
|
|
auto idxD = csD->atmToIdx(don_atom);
|
|
auto idxA = csA->atmToIdx(acc_atom);
|
|
|
|
if((idxA >= 0) && (idxD >= 0)) {
|
|
|
|
/* now get local geometries, including
|
|
real or virtual hydrogen atom positions */
|
|
|
|
const float* vDon = csD->coordPtr(idxD);
|
|
const float* vAcc = csA->coordPtr(idxA);
|
|
|
|
subtract3f(vAcc, vDon, donToAcc);
|
|
|
|
if(ObjectMoleculeFindBestDonorH(don_obj,
|
|
don_atom, don_state, donToAcc, bestH, h_real)) {
|
|
|
|
subtract3f(bestH, vDon, donToH);
|
|
subtract3f(vAcc, bestH, hToAcc);
|
|
|
|
if(ObjectMoleculeGetAvgHBondVector(acc_obj, acc_atom,
|
|
acc_state, accPlane, hToAcc) > 0.1) {
|
|
vAccPlane = &accPlane[0];
|
|
}
|
|
result = ObjectMoleculeTestHBond(donToAcc, donToH, hToAcc, vAccPlane, hbc);
|
|
if(result && h_crd_ret && h_real && *h_real)
|
|
copy3f(bestH, h_crd_ret);
|
|
}
|
|
}
|
|
}
|
|
|
|
return (result);
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
|
|
float ObjectMoleculeGetMaxVDW(ObjectMolecule * I)
|
|
{
|
|
float max_vdw = 0.0F;
|
|
int a;
|
|
const AtomInfoType *ai;
|
|
if(I->NAtom) {
|
|
ai = I->AtomInfo;
|
|
for(a = 0; a < I->NAtom; a++) {
|
|
if(max_vdw < ai->vdw)
|
|
max_vdw = ai->vdw;
|
|
ai++;
|
|
}
|
|
}
|
|
return (max_vdw);
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
static PyObject *ObjectMoleculeCSetAsPyList(ObjectMolecule * I)
|
|
{
|
|
PyObject *result = nullptr;
|
|
int a;
|
|
result = PyList_New(I->NCSet);
|
|
for(a = 0; a < I->NCSet; a++) {
|
|
if(I->CSet[a]) {
|
|
PyList_SetItem(result, a, CoordSetAsPyList(I->CSet[a]));
|
|
} else {
|
|
PyList_SetItem(result, a, PConvAutoNone(Py_None));
|
|
}
|
|
}
|
|
return (PConvAutoNone(result));
|
|
}
|
|
|
|
|
|
/*static PyObject *ObjectMoleculeDiscreteCSetAsPyList(ObjectMolecule *I)
|
|
{
|
|
PyObject *result = nullptr;
|
|
return(PConvAutoNone(result));
|
|
}*/
|
|
static int ObjectMoleculeCSetFromPyList(ObjectMolecule * I, PyObject * list)
|
|
{
|
|
int ok = true;
|
|
int a;
|
|
if(ok)
|
|
ok = PyList_Check(list);
|
|
if(ok) {
|
|
VLACheck(I->CSet, CoordSet *, I->NCSet);
|
|
for(a = 0; a < I->NCSet; a++) {
|
|
if(ok)
|
|
ok = CoordSetFromPyList(I->G, PyList_GetItem(list, a), &I->CSet[a]);
|
|
PRINTFB(I->G, FB_ObjectMolecule, FB_Debugging)
|
|
" %s: ok %d after CoordSet %d\n", __func__, ok, a ENDFB(I->G);
|
|
|
|
if(ok)
|
|
if(I->CSet[a]) /* WLD 030205 */
|
|
I->CSet[a]->Obj = I;
|
|
}
|
|
}
|
|
return (ok);
|
|
}
|
|
|
|
static PyObject *ObjectMoleculeBondAsPyList(ObjectMolecule * I)
|
|
{
|
|
PyObject *result = nullptr;
|
|
PyObject *bond_list;
|
|
const BondType *bond;
|
|
int a;
|
|
|
|
#ifndef PICKLETOOLS
|
|
PyMOLGlobals *G = I->G;
|
|
int pse_export_version = SettingGetGlobal_f(I->G, cSetting_pse_export_version) * 1000;
|
|
|
|
if (SettingGetGlobal_b(G, cSetting_pse_binary_dump) && (!pse_export_version || pse_export_version >= 1765)){
|
|
/* For the pse_binary_dump, save entire Bond array to a binary string array
|
|
*/
|
|
|
|
// supported versions
|
|
auto version = (!pse_export_version || pse_export_version >= 1810) ?
|
|
181 : (pse_export_version >= 1770) ? 177 : 176;
|
|
|
|
auto blob = Copy_To_BondType_Version(version, I->Bond.data(), I->NBond);
|
|
auto blobsize = VLAGetByteSize(blob);
|
|
|
|
result = PyList_New(2);
|
|
PyList_SetItem(result, 0, PyInt_FromLong(version));
|
|
PyList_SetItem(result, 1, PyBytes_FromStringAndSize(reinterpret_cast<const char*>(blob), blobsize));
|
|
|
|
VLAFreeP(blob);
|
|
|
|
return result;
|
|
}
|
|
#endif
|
|
result = PyList_New(I->NBond);
|
|
bond = I->Bond;
|
|
for(a = 0; a < I->NBond; a++) {
|
|
size_t const list_size = bond->hasSymOp() ? 8 : 7;
|
|
bond_list = PyList_New(list_size);
|
|
PyList_SetItem(bond_list, 0, PyInt_FromLong(bond->index[0]));
|
|
PyList_SetItem(bond_list, 1, PyInt_FromLong(bond->index[1]));
|
|
PyList_SetItem(bond_list, 2, PyInt_FromLong(bond->order));
|
|
PyList_SetItem(bond_list, 3, PyInt_FromLong(-1)); // id
|
|
PyList_SetItem(bond_list, 4, PyInt_FromLong(0)); // stereo
|
|
PyList_SetItem(bond_list, 5, PyInt_FromLong(bond->unique_id));
|
|
PyList_SetItem(bond_list, 6, PyInt_FromLong(bond->has_setting));
|
|
if (list_size > 7) {
|
|
PyList_SetItem(bond_list, 7, PConvToPyObject(bond->symop_2));
|
|
}
|
|
PyList_SetItem(result, a, bond_list);
|
|
bond++;
|
|
}
|
|
|
|
return (PConvAutoNone(result));
|
|
}
|
|
|
|
static int ObjectMoleculeBondFromPyList(ObjectMolecule * I, PyObject * list)
|
|
{
|
|
PyMOLGlobals *G = I->G;
|
|
int ok = true;
|
|
int a;
|
|
int stereo, ll = 0;
|
|
PyObject *bond_list = nullptr;
|
|
BondType *bond;
|
|
|
|
if(ok)
|
|
ok = PyList_Check(list);
|
|
if(ok)
|
|
ll = PyList_Size(list);
|
|
|
|
bool pse_binary_dump = false;
|
|
|
|
if (ll >= 2) {
|
|
// checking if from pse_binary_dump
|
|
// pse_binary_dump saves 2 values: bondInfo_version, BondType binary
|
|
CPythonVal *val1 = CPythonVal_PyList_GetItem(G, list, 1);
|
|
pse_binary_dump = PyBytes_Check(val1);
|
|
CPythonVal_Free(val1);
|
|
}
|
|
if (pse_binary_dump){
|
|
CPythonVal *verobj = CPythonVal_PyList_GetItem(G, list, 0);
|
|
int bondInfo_version;
|
|
ok = PConvPyIntToInt(verobj, &bondInfo_version);
|
|
|
|
CPythonVal *strobj = CPythonVal_PyList_GetItem(G, list, 1);
|
|
auto strval = PyBytes_AsSomeString(strobj);
|
|
|
|
if(ok)
|
|
ok = bool((I->Bond = pymol::vla<BondType>(I->NBond)));
|
|
|
|
Copy_Into_BondType_From_Version(strval.data(), bondInfo_version, I->Bond.data(), I->NBond);
|
|
|
|
CPythonVal_Free(verobj);
|
|
CPythonVal_Free(strobj);
|
|
} else {
|
|
if(ok)
|
|
ok = bool((I->Bond = pymol::vla<BondType>(I->NBond)));
|
|
bond = I->Bond.data();
|
|
for(a = 0; a < I->NBond; a++) {
|
|
bond_list = nullptr;
|
|
if(ok)
|
|
bond_list = PyList_GetItem(list, a);
|
|
if(ok)
|
|
ok = PyList_Check(bond_list);
|
|
if(ok)
|
|
ll = PyList_Size(bond_list);
|
|
if(ok)
|
|
ok = PConvPyIntToInt(PyList_GetItem(bond_list, 0), &bond->index[0]);
|
|
if(ok)
|
|
ok = PConvPyIntToInt(PyList_GetItem(bond_list, 1), &bond->index[1]);
|
|
if(ok)
|
|
if((ok = CPythonVal_PConvPyIntToInt_From_List(I->G, bond_list, 2, &stereo)))
|
|
bond->order = stereo;
|
|
if(ok && (ll > 5)) {
|
|
int has_setting;
|
|
if(ok)
|
|
ok = PConvPyIntToInt(PyList_GetItem(bond_list, 5), &bond->unique_id);
|
|
if(ok)
|
|
ok = PConvPyIntToInt(PyList_GetItem(bond_list, 6), &has_setting);
|
|
if(ok)
|
|
bond->has_setting = (short int) has_setting;
|
|
if(ok && bond->unique_id) { /* reserve existing IDs */
|
|
bond->unique_id = SettingUniqueConvertOldSessionID(G, bond->unique_id);
|
|
}
|
|
}
|
|
if (ll > 7) {
|
|
PConvFromPyListItem(G, bond_list, 7, bond->symop_2);
|
|
}
|
|
bond++;
|
|
CPythonVal_Free(bond_list);
|
|
}
|
|
}
|
|
PRINTFB(G, FB_ObjectMolecule, FB_Debugging)
|
|
" %s: ok %d after restore\n", __func__, ok ENDFB(G);
|
|
|
|
return (ok);
|
|
}
|
|
|
|
/**
|
|
* Extract an atom property "column" as a Python list.
|
|
*
|
|
* As an optimization, trailing None values are removed, so the returned list
|
|
* may be shorter than the atom array or even be empty.
|
|
*
|
|
* @param mol Object molecule which provides the atom array
|
|
* @param func Function which extracts a property from an atom
|
|
* @return List of properties
|
|
*/
|
|
static PyObject* AtomColumnAsPyList(const ObjectMolecule& mol,
|
|
std::function<PyObject*(const AtomInfoType&)> func)
|
|
{
|
|
auto list = PyList_New(mol.NAtom);
|
|
PyObject* prev = Py_None;
|
|
int pruned_size = 0;
|
|
|
|
for (int i = 0; i < mol.NAtom; ++i) {
|
|
PyObject* value = func(mol.AtomInfo[i]);
|
|
|
|
#ifndef PICKLETOOLS
|
|
// Simple optimization: Repeated property lists will reference the same
|
|
// Python object
|
|
if (prev != value && PyObject_RichCompareBool(prev, value, Py_EQ)) {
|
|
Py_INCREF(prev);
|
|
Py_DECREF(value);
|
|
value = prev;
|
|
} else {
|
|
prev = value;
|
|
}
|
|
|
|
if (value != Py_None) {
|
|
pruned_size = i + 1;
|
|
}
|
|
#endif
|
|
|
|
PyList_SetItem(list, i, value);
|
|
}
|
|
|
|
#ifndef PICKLETOOLS
|
|
assert(pruned_size <= mol.NAtom);
|
|
|
|
// Simple optimization: Prune "None" tail
|
|
PyList_SetSlice(list, pruned_size, mol.NAtom, nullptr);
|
|
|
|
assert(PyList_Size(list) == pruned_size);
|
|
#endif
|
|
|
|
return list;
|
|
}
|
|
|
|
/**
|
|
* Restore an atom property "column" from a Python list.
|
|
* @param mol Object molecule to update atoms in
|
|
* @param list Property list (may be shorter than atom array)
|
|
* @param func Function which sets a property for an atom
|
|
*/
|
|
static void AtomColumnFromPyList(ObjectMolecule& mol, PyObject* list,
|
|
std::function<void(AtomInfoType&, PyObject*)> func)
|
|
{
|
|
if (!list || !PyList_Check(list)) {
|
|
return;
|
|
}
|
|
|
|
int size = PyList_Size(list);
|
|
assert(size <= mol.NAtom);
|
|
|
|
for (int i = 0; i < size; ++i) {
|
|
func(mol.AtomInfo[i], PyList_GetItem(list, i));
|
|
}
|
|
}
|
|
|
|
static PyObject *ObjectMoleculeAtomAsPyList(ObjectMolecule * I)
|
|
{
|
|
PyMOLGlobals *G = I->G;
|
|
PyObject *result = nullptr;
|
|
const AtomInfoType *ai;
|
|
int a;
|
|
#ifndef PICKLETOOLS
|
|
int pse_export_version = SettingGetGlobal_f(I->G, cSetting_pse_export_version) * 1000;
|
|
|
|
if (SettingGetGlobal_b(G, cSetting_pse_binary_dump) && (!pse_export_version || pse_export_version >= 1765)){
|
|
/* For the pse_binary_dump, record all strings in lex and
|
|
write them into separate binary string
|
|
*/
|
|
AtomInfoTypeConverter converter(G, I->NAtom);
|
|
std::set<lexidx_t> lexIDs;
|
|
int totalstlen = 0;
|
|
ai = I->AtomInfo.data();
|
|
for(a = 0; a < I->NAtom; a++) {
|
|
if (ai->textType) lexIDs.insert(ai->textType);
|
|
if (ai->chain) lexIDs.insert(ai->chain);
|
|
if (ai->label) lexIDs.insert(ai->label);
|
|
if (ai->custom) lexIDs.insert(ai->custom);
|
|
if (ai->segi) lexIDs.insert(ai->segi);
|
|
if (ai->resn) lexIDs.insert(ai->resn);
|
|
if (ai->name) lexIDs.insert(ai->name);
|
|
++ai;
|
|
}
|
|
for (const auto& lexID : lexIDs) { // need to calculate totalstlen so we can allocate
|
|
const char *lexstr = LexStr(G, lexID);
|
|
int lexlen = strlen(lexstr);
|
|
totalstlen += lexlen + 1;
|
|
}
|
|
int strinfolen = totalstlen + sizeof(int) * (lexIDs.size() + 1);
|
|
void *strinfo = pymol::malloc<unsigned char>(strinfolen);
|
|
int *strval = (int*)strinfo;
|
|
*(strval++) = lexIDs.size(); // first write number of strings into binary data string
|
|
char *strpl = (char*)((char*)strinfo + (1 + lexIDs.size()) * sizeof(int));
|
|
/* write map of lex ids and strings into binary data string as an array of ids
|
|
and null-terminated strings */
|
|
for (const auto& lexID : lexIDs) {
|
|
*(strval++) = converter.to_lexidx_int(lexID);
|
|
const char *strptr = LexStr(G, lexID);
|
|
strcpy(strpl, strptr);
|
|
strpl += strlen(strptr) + 1;
|
|
}
|
|
|
|
auto version = AtomInfoVERSION;
|
|
if (pse_export_version && pse_export_version < 1810) {
|
|
if (pse_export_version < 1770) {
|
|
version = 176;
|
|
} else {
|
|
version = 177;
|
|
}
|
|
}
|
|
|
|
auto blob = converter.allocCopy(version, I->AtomInfo);
|
|
auto blobsize = VLAGetByteSize(blob);
|
|
|
|
// PyMOL versions up to 2.3.5 can only restore list size 3
|
|
size_t result_size = 3;
|
|
|
|
// Atom properties (not binary)
|
|
PyObject* prop_list = nullptr;
|
|
if (pse_export_version > 2399) {
|
|
prop_list = AtomColumnAsPyList(*I, [G](const AtomInfoType& atom) {
|
|
return PConvAutoNone(
|
|
atom.prop_id ? PropertyAsPyList(G, atom.prop_id, true) : nullptr);
|
|
});
|
|
|
|
result_size = 4;
|
|
}
|
|
|
|
result = PyList_New(result_size);
|
|
PyList_SetItem(result, 0, PyInt_FromLong(version));
|
|
PyList_SetItem(result, 1, PyBytes_FromStringAndSize(reinterpret_cast<const char*>(blob), blobsize));
|
|
PyList_SetItem(result, 2, PyBytes_FromStringAndSize(reinterpret_cast<const char*>(strinfo), strinfolen));
|
|
|
|
if (result_size > 3) {
|
|
PyList_SetItem(result, 3, PConvAutoNone(prop_list));
|
|
}
|
|
|
|
VLAFreeP(blob);
|
|
FreeP(strinfo);
|
|
return result;
|
|
}
|
|
#endif
|
|
result = PyList_New(I->NAtom);
|
|
ai = I->AtomInfo;
|
|
for(a = 0; a < I->NAtom; a++) {
|
|
PyList_SetItem(result, a, AtomInfoAsPyList(I->G, ai));
|
|
ai++;
|
|
}
|
|
return (PConvAutoNone(result));
|
|
}
|
|
|
|
static int ObjectMoleculeAtomFromPyList(ObjectMolecule * I, PyObject * list)
|
|
{
|
|
PyMOLGlobals *G = I->G;
|
|
int ok = true;
|
|
int a, ll = 0;
|
|
AtomInfoType *ai;
|
|
|
|
if(ok)
|
|
ok = PyList_Check(list);
|
|
if (ok)
|
|
ll = PyList_Size(list);
|
|
|
|
bool pse_binary_dump = false;
|
|
|
|
if (ll >= 3) {
|
|
// checking if from pse_binary_dump
|
|
// pse_binary_dump saves 3 values: atomInfo_version, AtomInfo binary, and strings array
|
|
CPythonVal *val1 = CPythonVal_PyList_GetItem(G, list, 1);
|
|
CPythonVal *val2 = CPythonVal_PyList_GetItem(G, list, 2);
|
|
pse_binary_dump = PyBytes_Check(val1) && PyBytes_Check(val2);
|
|
CPythonVal_Free(val1);
|
|
CPythonVal_Free(val2);
|
|
}
|
|
if (pse_binary_dump){
|
|
CPythonVal *verobj = CPythonVal_PyList_GetItem(G, list, 0);
|
|
int atomInfo_version;
|
|
ok = PConvPyIntToInt(verobj, &atomInfo_version);
|
|
|
|
CPythonVal *strlookupobj = CPythonVal_PyList_GetItem(G, list, 2);
|
|
auto strval_1 = PyBytes_AsSomeString(strlookupobj);
|
|
int *strval = (int*)strval_1.data();
|
|
|
|
AtomInfoTypeConverter converter(G, I->NAtom);
|
|
|
|
auto& oldIDtoLexID = converter.lexidxmap;
|
|
int nstrings = *(strval++);
|
|
char *strpl = (char*)(strval + nstrings);
|
|
int strcnt = nstrings;
|
|
int stlen;
|
|
// populate oldIDtoLexID with nstrings from binary string data (3rd entry in list)
|
|
while (strcnt){
|
|
lexidx_t idx = LexIdx(G, strpl); // increments ref count, need to take into account
|
|
int oldidx = *(strval++);
|
|
oldIDtoLexID[oldidx] = idx;
|
|
stlen = strlen(strpl);
|
|
strpl += stlen + 1;
|
|
strcnt--;
|
|
}
|
|
|
|
CPythonVal *strobj = CPythonVal_PyList_GetItem(G, list, 1);
|
|
auto strval_2 = PyBytes_AsSomeString(strobj);
|
|
|
|
VLACheck(I->AtomInfo, AtomInfoType, I->NAtom + 1);
|
|
converter.copy(I->AtomInfo.data(), strval_2.data(), atomInfo_version);
|
|
|
|
// go through AtomInfo array, swap new strings, convert colors, convert settings
|
|
// (everything that AtomInfoFromPyList does except set properties, which are currently
|
|
// not saved for pse_binary_dump)
|
|
AtomInfoType *ai = I->AtomInfo.data();
|
|
for(a = 0; a < I->NAtom; ++a, ++ai) {
|
|
ai->color = ColorConvertOldSessionIndex(G, ai->color);
|
|
if (ai->unique_id){
|
|
ai->unique_id = SettingUniqueConvertOldSessionID(G, ai->unique_id);
|
|
}
|
|
}
|
|
// need to decrement since we call LexIdx() above on each
|
|
for (auto it = oldIDtoLexID.begin(); it != oldIDtoLexID.end(); ++it){
|
|
LexDec(G, it->second);
|
|
}
|
|
CPythonVal_Free(verobj);
|
|
CPythonVal_Free(strobj);
|
|
CPythonVal_Free(strlookupobj);
|
|
|
|
if (ll > 3) {
|
|
// Restore atom properties
|
|
AtomColumnFromPyList(*I, PyList_GetItem(list, 3), //
|
|
[G](AtomInfoType& atom, PyObject* value) {
|
|
assert(atom.prop_id == 0);
|
|
atom.prop_id = PropertyFromPyList(G, value);
|
|
});
|
|
}
|
|
|
|
} else {
|
|
// The old slow way of loading in AtomInfo, using python lists
|
|
if (ok)
|
|
VLACheck(I->AtomInfo, AtomInfoType, I->NAtom + 1);
|
|
CHECKOK(ok, I->AtomInfo);
|
|
ai = I->AtomInfo.data();
|
|
for(a = 0; ok && a < I->NAtom; a++) {
|
|
PyObject *val = PyList_GetItem(list, a);
|
|
ok &= AtomInfoFromPyList(I->G, ai, val);
|
|
ai++;
|
|
}
|
|
}
|
|
PRINTFB(I->G, FB_ObjectMolecule, FB_Debugging)
|
|
" %s: ok %d \n", __func__, ok ENDFB(I->G);
|
|
return (ok);
|
|
}
|
|
|
|
int ObjectMoleculeNewFromPyList(PyMOLGlobals * G, PyObject * list,
|
|
ObjectMolecule ** result)
|
|
{
|
|
int ok = true;
|
|
ObjectMolecule *I = nullptr;
|
|
int discrete_flag = 0;
|
|
(*result) = nullptr;
|
|
|
|
if(ok)
|
|
ok = PyList_Check(list);
|
|
/* TO SUPPORT BACKWARDS COMPATIBILITY...
|
|
Always check ll when adding new PyList_GetItem's */
|
|
if(ok)
|
|
ok = PConvPyIntToInt(PyList_GetItem(list, 8), &discrete_flag);
|
|
|
|
if (ok)
|
|
I = new ObjectMolecule(G, discrete_flag);
|
|
CHECKOK(ok, I);
|
|
|
|
if(ok){
|
|
PyObject *val = PyList_GetItem(list, 0);
|
|
ok = ObjectFromPyList(G, val, I);
|
|
}
|
|
if(ok)
|
|
ok = PConvPyIntToInt(PyList_GetItem(list, 1), &I->NCSet);
|
|
if(ok)
|
|
ok = PConvPyIntToInt(PyList_GetItem(list, 2), &I->NBond);
|
|
if(ok)
|
|
ok = PConvPyIntToInt(PyList_GetItem(list, 3), &I->NAtom);
|
|
if(ok)
|
|
ok = ObjectMoleculeCSetFromPyList(I, PyList_GetItem(list, 4));
|
|
if(ok){
|
|
ok = CoordSetFromPyList(G, PyList_GetItem(list, 5), &I->CSTmpl);
|
|
|
|
if(I->CSTmpl)
|
|
I->CSTmpl->Obj = I;
|
|
}
|
|
if(ok){
|
|
CPythonVal *val = CPythonVal_PyList_GetItem(G, list, 6);
|
|
ok = ObjectMoleculeBondFromPyList(I, val);
|
|
CPythonVal_Free(val);
|
|
}
|
|
if (!ok && I)
|
|
I->NBond = 0;
|
|
if(ok){
|
|
CPythonVal *val = CPythonVal_PyList_GetItem(G, list, 7);
|
|
ok = ObjectMoleculeAtomFromPyList(I, val);
|
|
CPythonVal_Free(val);
|
|
}
|
|
if (!ok && I)
|
|
I->NAtom = 0;
|
|
if(ok){
|
|
CPythonVal *val = CPythonVal_PyList_GetItem(G, list, 10);
|
|
I->Symmetry.reset(SymmetryNewFromPyList(G, val));
|
|
CPythonVal_Free(val);
|
|
}
|
|
/* 11 was CurCSet */
|
|
/* 12 was BondCounter */
|
|
if(ok)
|
|
ok = PConvPyIntToInt(PyList_GetItem(list, 13), &I->AtomCounter);
|
|
|
|
I->updateAtmToIdx();
|
|
|
|
if (ok)
|
|
I->invalidate(cRepAll, cRepInvAll, -1);
|
|
if(ok)
|
|
(*result) = I;
|
|
else {
|
|
/* cleanup */
|
|
if (I)
|
|
DeleteP(I);
|
|
(*result) = nullptr;
|
|
}
|
|
return (ok);
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
PyObject *ObjectMoleculeAsPyList(ObjectMolecule * I)
|
|
{
|
|
PyObject *result = nullptr;
|
|
|
|
/* first, dump the atoms */
|
|
|
|
result = PyList_New(16);
|
|
PyList_SetItem(result, 0, ObjectAsPyList(I));
|
|
PyList_SetItem(result, 1, PyInt_FromLong(I->NCSet));
|
|
PyList_SetItem(result, 2, PyInt_FromLong(I->NBond));
|
|
PyList_SetItem(result, 3, PyInt_FromLong(I->NAtom));
|
|
PyList_SetItem(result, 4, ObjectMoleculeCSetAsPyList(I));
|
|
PyList_SetItem(result, 5, CoordSetAsPyList(I->CSTmpl));
|
|
PyList_SetItem(result, 6, ObjectMoleculeBondAsPyList(I));
|
|
PyList_SetItem(result, 7, ObjectMoleculeAtomAsPyList(I));
|
|
PyList_SetItem(result, 8, PyInt_FromLong(I->DiscreteFlag));
|
|
PyList_SetItem(result, 9, PyInt_FromLong(I->DiscreteFlag ? I->NAtom : 0 /* NDiscrete */));
|
|
PyList_SetItem(result, 10, SymmetryAsPyList(I->Symmetry.get()));
|
|
PyList_SetItem(result, 11, PyInt_FromLong(0 /* CurCSet */));
|
|
PyList_SetItem(result, 12, PyInt_FromLong(-1 /* BondCounter */));
|
|
PyList_SetItem(result, 13, PyInt_FromLong(I->AtomCounter));
|
|
|
|
float pse_export_version = SettingGetGlobal_f(I->G, cSetting_pse_export_version);
|
|
|
|
if(I->DiscreteFlag
|
|
&& (pse_export_version || !SettingGetGlobal_b(I->G, cSetting_pse_binary_dump))
|
|
&& pse_export_version < 1.7699) {
|
|
int *dcs;
|
|
int a;
|
|
CoordSet *cs;
|
|
|
|
/* get coordinate set indices */
|
|
|
|
for(a = 0; a < I->NCSet; a++) {
|
|
cs = I->CSet[a];
|
|
if(cs) {
|
|
cs->tmp_index = a;
|
|
}
|
|
}
|
|
|
|
dcs = pymol::malloc<int>(I->NAtom);
|
|
|
|
for(a = 0; a < I->NAtom; a++) {
|
|
cs = I->DiscreteCSet[a];
|
|
if(cs)
|
|
dcs[a] = cs->tmp_index;
|
|
else
|
|
dcs[a] = -1;
|
|
}
|
|
|
|
PyList_SetItem(result, 14, PConvIntArrayToPyList(I->DiscreteAtmToIdx, I->NAtom));
|
|
PyList_SetItem(result, 15, PConvIntArrayToPyList(dcs, I->NAtom));
|
|
FreeP(dcs);
|
|
} else {
|
|
PyList_SetItem(result, 14, PConvAutoNone(nullptr));
|
|
PyList_SetItem(result, 15, PConvAutoNone(nullptr));
|
|
}
|
|
|
|
return (PConvAutoNone(result));
|
|
}
|
|
|
|
/*========================================================================*/
|
|
|
|
static
|
|
float connect_cutoff_adjustment(
|
|
const AtomInfoType * ai1,
|
|
const AtomInfoType * ai2)
|
|
{
|
|
if (ai1->isHydrogen() || ai2->isHydrogen())
|
|
return -0.2f;
|
|
|
|
if (ai1->protons == cAN_S || ai2->protons == cAN_S)
|
|
return 0.2f;
|
|
|
|
return 0.f;
|
|
}
|
|
|
|
/**
|
|
* True if two atoms should be bonded
|
|
*/
|
|
static
|
|
bool is_distance_bonded(
|
|
PyMOLGlobals * G,
|
|
const CoordSet * cs,
|
|
const AtomInfoType * ai1,
|
|
const AtomInfoType * ai2,
|
|
const float * v1,
|
|
const float * v2,
|
|
float cutoff,
|
|
int connect_mode,
|
|
int discrete_chains,
|
|
bool connect_bonded,
|
|
bool unbond_cations)
|
|
{
|
|
auto dst = (float) diff3f(v1, v2);
|
|
|
|
if (dst < R_SMALL4)
|
|
return false;
|
|
|
|
dst -= (ai1->vdw + ai2->vdw) / 2;
|
|
|
|
cutoff += connect_cutoff_adjustment(ai1, ai2);
|
|
|
|
if (dst > cutoff)
|
|
return false;
|
|
|
|
if (ai1->isHydrogen() && ai2->isHydrogen())
|
|
return false;
|
|
|
|
if (discrete_chains > 0 && ai1->chain != ai2->chain)
|
|
return false;
|
|
|
|
if (!connect_bonded && ai1->bonded && ai2->bonded)
|
|
return false;
|
|
|
|
bool water_flag = (
|
|
AtomInfoKnownWaterResName(G, LexStr(G, ai1->resn)) ||
|
|
AtomInfoKnownWaterResName(G, LexStr(G, ai2->resn)));
|
|
|
|
if (connect_mode != 3 &&
|
|
cs->TmpBond && /* connectivity information present in file */
|
|
ai1->hetatm &&
|
|
ai2->hetatm &&
|
|
!water_flag &&
|
|
!(AtomInfoKnownPolymerResName(LexStr(G, ai1->resn)) &&
|
|
AtomInfoKnownPolymerResName(LexStr(G, ai2->resn))))
|
|
return false;
|
|
|
|
// don't connect water atoms in different residues
|
|
if (water_flag && !AtomInfoSameResidue(G, ai1, ai2))
|
|
return false;
|
|
|
|
// don't connect atoms with different, non-NULL alternate conformations
|
|
if (ai1->alt[0] != ai2->alt[0] && ai1->alt[0] && ai2->alt[0])
|
|
return false;
|
|
|
|
// if either is a cation, unbond is user wants
|
|
if (unbond_cations &&
|
|
(AtomInfoIsFreeCation(G, ai1) ||
|
|
AtomInfoIsFreeCation(G, ai2)))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Do bonding of atoms in `I`, using distances and/or temporary bonds in `cs`.
|
|
*
|
|
* Incorporates `cs->TmpBond` unless `connect_mode` is 2.
|
|
* Incorporates `cs->TmpLinkBond`.
|
|
*
|
|
* @param I Molecule to modify
|
|
* @param cs Coordinates and temporary bonds to consider
|
|
* @param bondSearchMode If false and `connect_mode` != 2, do not search for new
|
|
* bonds (only use TmpBond/TmpLinkBond).
|
|
* @param connectModeOverride Overrides `connect_mode` setting if not -1
|
|
* @param pbc Use periodic boundary conditions (find symop bonds)
|
|
*
|
|
* `connect_mode` options:
|
|
* 0 = distance-based (excluding HETATM to HETATM) and CONECT records (default)
|
|
* 1 = CONECT records
|
|
* 2 = distance-based, ignores CONECT records
|
|
* 3 = distance-based (including HETATM to HETATM) and CONECT records
|
|
* 4 = same as `connect_mode` = 0 (special meaning during mmCIF loading)
|
|
*/
|
|
bool ObjectMoleculeConnect(ObjectMolecule* I, CoordSet* cs, bool bondSearchMode,
|
|
int connectModeOverride, bool pbc)
|
|
{
|
|
return ObjectMoleculeConnect(
|
|
I, I->NBond, I->Bond, cs, bondSearchMode, connectModeOverride, pbc);
|
|
}
|
|
|
|
/*========================================================================*/
|
|
bool ObjectMoleculeConnect(ObjectMolecule* I, int& nBond, pymol::vla<BondType>& bondvla,
|
|
struct CoordSet *cs, int bondSearchMode,
|
|
int connectModeOverride,
|
|
bool pbc)
|
|
{
|
|
PyMOLGlobals *G = I->G;
|
|
AtomInfoType* const ai = I->AtomInfo.data();
|
|
auto discrete_chains = SettingGet<int>(G, cSetting_pdb_discrete_chains);
|
|
auto const connect_bonded = SettingGet<bool>(G, cSetting_connect_bonded);
|
|
auto const unbond_cations = SettingGet<int>(G, cSetting_pdb_unbond_cations);
|
|
auto const cutoff_v = SettingGet<float>(G, cSetting_connect_cutoff);
|
|
auto const max_cutoff = cutoff_v + 0.2F; ///< Sulfur cutoff
|
|
auto connect_mode = (connectModeOverride >= 0)
|
|
? connectModeOverride
|
|
: SettingGet<int>(G, cSetting_connect_mode);
|
|
|
|
if (connect_mode == 2) {
|
|
// Force use of distance-based connectivity, ignoring that
|
|
// provided with file.
|
|
bondSearchMode = true;
|
|
cs->NTmpBond = 0;
|
|
VLAFreeP(cs->TmpBond);
|
|
} else if (connect_mode == 4) {
|
|
// mmCIF specific, fall back to default to get any bonds for PDB, XYZ, etc.
|
|
connect_mode = 0;
|
|
}
|
|
|
|
nBond = 0;
|
|
auto const maxBond = cs->NIndex * 8;
|
|
// Number of bonds is typically close to number of atoms
|
|
bondvla.reserve(cs->NIndex * 1.2);
|
|
p_return_val_if_fail(bondvla, false); // memory error
|
|
|
|
bool repeat = false;
|
|
switch (connect_mode) {
|
|
case 0: /* distance-based and explicit (not HETATM to HETATM) */
|
|
case 2: /* distance-based only */
|
|
case 3: /* distance-based and explicit (even HETATM to HETATM) */
|
|
repeat = bondSearchMode && cs->NIndex > 0;
|
|
}
|
|
|
|
// Distance-based bond location
|
|
while (repeat) {
|
|
repeat = false;
|
|
nBond = 0;
|
|
|
|
// For monitoring excessive numbers of bonds
|
|
int violations = 0;
|
|
auto const max_violations = cs->NIndex >> 3; // 12%
|
|
auto const cnt = std::make_unique<signed char[]>(size_t(cs->NIndex));
|
|
p_return_val_if_fail(cnt, false); // memory error
|
|
|
|
/* initialize each atom's (max) expected valence */
|
|
for (unsigned i = 0; i < cs->NIndex; ++i) {
|
|
auto valcnt = AtomInfoGetExpectedValence(G, ai + cs->IdxToAtm[i]);
|
|
cnt[i] = (valcnt < 0) ? 6 : valcnt;
|
|
}
|
|
|
|
// Search for symop bonds (periodic boundary conditions)?
|
|
int offset_begin = 0, offset_end = 1, symmat_end = 1;
|
|
if (pbc && cs->getSymmetry() && //
|
|
!cs->getSymmetry()->Crystal.isSuspicious()) {
|
|
offset_begin = -1;
|
|
offset_end = 2;
|
|
symmat_end = cs->getSymmetry()->getNSymMat();
|
|
assert(symmat_end > 0);
|
|
}
|
|
|
|
/* make a map of the local neighborhood in space */
|
|
auto const map = std::unique_ptr<MapType>(
|
|
new MapType(G, (max_cutoff + MAX_VDW) * (offset_begin ? -1 : 1), //
|
|
cs->Coord, cs->NIndex));
|
|
p_return_val_if_fail(map, false); // memory error
|
|
MapSetupExpress(map.get()); // Don't let MapEIter call this in omp parallel
|
|
|
|
/// Return false on error
|
|
auto const find_bonds_for_atom = [&](unsigned i, float const* v1,
|
|
pymol::SymOp const& symop) -> bool {
|
|
auto const a1 = cs->IdxToAtm[i];
|
|
auto* const ai1 = ai + a1;
|
|
|
|
for (const auto j : MapEIter(*map, v1)) {
|
|
if (i <= j && !symop)
|
|
continue;
|
|
|
|
/* position in space for atom 2 */
|
|
auto const* const v2 = cs->coordPtr(j);
|
|
auto const a2 = cs->IdxToAtm[j];
|
|
auto* const ai2 = ai + a2;
|
|
|
|
if (!is_distance_bonded(G, cs, ai1, ai2, v1, v2, cutoff_v, connect_mode,
|
|
discrete_chains, connect_bonded, unbond_cations))
|
|
continue;
|
|
|
|
/* we have a bond, now process it */
|
|
|
|
int order = 1;
|
|
if (!ai1->hetatm || ai1->resn == G->lex_const.MSE) {
|
|
if (AtomInfoSameResidue(I->G, ai1, ai2)) {
|
|
/* hookup standard disconnected PDB residue */
|
|
assign_pdb_known_residue(G, ai1, ai2, &order);
|
|
}
|
|
}
|
|
|
|
#ifdef PYMOL_OPENMP
|
|
#pragma omp critical
|
|
#endif
|
|
{
|
|
auto const bnd = bondvla.check(nBond++);
|
|
BondTypeInit2(
|
|
bnd, a2, a1, -order /* store tentative valence as negative */);
|
|
bnd->symop_2 = symop;
|
|
|
|
/* if we allow bonds between chains and it screws up
|
|
* the bonding, disallow inter-chain bonds */
|
|
if (discrete_chains < 0) {
|
|
/* decrement free valences, since we have a bond */
|
|
if (--cnt[i] == -2)
|
|
violations++;
|
|
if (--cnt[j] == -2)
|
|
violations++;
|
|
|
|
if (violations > max_violations) {
|
|
PRINTFB(G, FB_ObjectMolecule, FB_Blather)
|
|
" %s: Assuming chains are discrete...\n", __func__ ENDFB(G);
|
|
|
|
discrete_chains = 1;
|
|
repeat = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (repeat) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
bool break_all = false;
|
|
|
|
// Do bond search in parallel for every atom
|
|
#ifdef PYMOL_OPENMP
|
|
#pragma omp parallel for
|
|
#endif
|
|
for (int i = 0; i < cs->NIndex; ++i) {
|
|
float _v1_buf[3];
|
|
pymol::SymOp symop{};
|
|
for (symop.x = offset_begin; symop.x < offset_end; ++symop.x) {
|
|
for (symop.y = offset_begin; symop.y < offset_end; ++symop.y) {
|
|
for (symop.z = offset_begin; symop.z < offset_end; ++symop.z) {
|
|
for (symop.index = 0; symop.index != symmat_end; ++symop.index) {
|
|
auto const* const v1 = cs->coordPtrSym(i, symop, _v1_buf);
|
|
assert(v1);
|
|
|
|
if (break_all || !find_bonds_for_atom(i, v1, symop) ||
|
|
nBond > maxBond) {
|
|
goto break_all_find_bonds_for_atom;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
continue;
|
|
break_all_find_bonds_for_atom:
|
|
// can't "break" inside an omp parallel block
|
|
break_all = true;
|
|
}
|
|
|
|
PRINTFB(G, FB_ObjectMolecule, FB_Blather)
|
|
" %s: Found %d bonds.\n", __func__, nBond ENDFB(G);
|
|
}
|
|
|
|
/* if we have explicit connectivity, determine if we need to set check_conect_all */
|
|
if (cs->NTmpBond && cs->TmpBond) {
|
|
bool check_conect_all = false;
|
|
bool pdb_conect_all = false;
|
|
|
|
PRINTFB(G, FB_ObjectMolecule, FB_Blather)
|
|
" %s: incorporating explicit bonds. %d %d\n", __func__,
|
|
nBond, cs->NTmpBond ENDFB(G);
|
|
if((nBond == 0) && (cs->NTmpBond > 0) &&
|
|
bondSearchMode && (connect_mode == 0) && cs->NIndex) {
|
|
/* if no bonds were found, and we have explicit connectivity,
|
|
* try to determine if we need to set pdb_conect_mode */
|
|
for (unsigned i = 0; i < cs->NIndex; ++i) {
|
|
auto const& ai1 = ai[cs->IdxToAtm[i]];
|
|
if (ai1.bonded && !ai1.hetatm) {
|
|
/* apparent PDB ATOM record with explicit bonding... */
|
|
check_conect_all = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bondvla.check(nBond + cs->NTmpBond);
|
|
p_return_val_if_fail(bondvla, false); // memory error
|
|
|
|
auto* ii1 = bondvla.data() + nBond;
|
|
auto* ii2 = cs->TmpBond.data();
|
|
int n_atom = I->NAtom;
|
|
for (unsigned a = 0; a < cs->NTmpBond; ++a) {
|
|
auto const a1 = cs->IdxToAtm[ii2->index[0]];
|
|
auto const a2 = cs->IdxToAtm[ii2->index[1]];
|
|
if((a1 >= 0) && (a2 >= 0) && (a1 < n_atom) && (a2 < n_atom)) {
|
|
if(check_conect_all) {
|
|
if((!ai[a1].hetatm) && (!ai[a2].hetatm)) {
|
|
/* found bond between non-HETATMs -- so tell PyMOL to CONECT all ATOMs
|
|
* when writing out a PDB file */
|
|
pdb_conect_all = true;
|
|
}
|
|
}
|
|
ai[a1].bonded = true;
|
|
ai[a2].bonded = true;
|
|
*ii1 = std::move(*ii2);
|
|
ii1->index[0] = a1;
|
|
ii1->index[1] = a2;
|
|
ii1++;
|
|
ii2++;
|
|
nBond++;
|
|
}
|
|
}
|
|
|
|
VLAFreeP(cs->TmpBond);
|
|
cs->NTmpBond = 0;
|
|
|
|
if(pdb_conect_all) {
|
|
int dummy;
|
|
if(!SettingGetIfDefined_b(G, I->Setting.get(), cSetting_pdb_conect_all, &dummy)) {
|
|
{
|
|
auto handle = I->getSettingHandle(-1);
|
|
if(handle) {
|
|
SettingCheckHandle(G, *handle);
|
|
SettingSet_b(handle->get(), cSetting_pdb_conect_all, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Link b/t ligand and residue? */
|
|
if (cs->NTmpLinkBond && cs->TmpLinkBond) {
|
|
PRINTFB(G, FB_ObjectMolecule, FB_Blather)
|
|
"%s: incorporating linkage bonds. %d %d\n", __func__,
|
|
nBond, cs->NTmpLinkBond ENDFB(G);
|
|
|
|
bondvla.check(nBond + cs->NTmpLinkBond);
|
|
p_return_val_if_fail(bondvla, false); // memory error
|
|
|
|
auto* ii1 = bondvla.data() + nBond;
|
|
auto* ii2 = cs->TmpLinkBond.data();
|
|
for (unsigned a = 0; a < cs->NTmpLinkBond; ++a) {
|
|
auto const a1 = ii2->index[0]; /* first atom is in object */
|
|
auto const a2 = cs->IdxToAtm[ii2->index[1]]; /* second is in the cset */
|
|
ai[a1].bonded = true;
|
|
ai[a2].bonded = true;
|
|
(*ii1) = std::move(*ii2);
|
|
ii1->index[0] = a1;
|
|
ii1->index[1] = a2;
|
|
ii1++;
|
|
ii2++;
|
|
}
|
|
nBond += cs->NTmpLinkBond;
|
|
VLAFreeP(cs->TmpLinkBond);
|
|
cs->NTmpLinkBond = 0;
|
|
}
|
|
|
|
// Eliminate duplicates
|
|
// TODO do we expect any?
|
|
// TODO should we also check with swapped indices?
|
|
// TODO why not for discrete objects?
|
|
if (nBond > 1 && !I->DiscreteFlag) {
|
|
PRINTFD(G, FB_ObjectMolecule)
|
|
" %s: elminating duplicates with %d bonds...\n", __func__, nBond ENDFD;
|
|
|
|
UtilSortInPlace(
|
|
G, bondvla.data(), nBond, sizeof(BondType), (UtilOrderFn*) BondInOrder);
|
|
auto* ii1 = bondvla.data();
|
|
auto* ii2 = bondvla.data() + 1;
|
|
for (int a = nBond; --a;) {
|
|
if (BondCompare(ii2, ii1) != 0) {
|
|
if (++ii1 != ii2) {
|
|
*ii1 = std::move(*ii2);
|
|
}
|
|
} else if (ii2->order > 0 && ii1->order < 0) {
|
|
// use most certain valence
|
|
ii1->order = ii2->order;
|
|
}
|
|
ii2++;
|
|
}
|
|
nBond = ii1 - bondvla.data() + 1;
|
|
}
|
|
|
|
bondvla.resize(nBond);
|
|
|
|
// restore bond order positivity
|
|
for (auto bnd = bondvla.begin(), bnd_end = bnd + nBond; bnd != bnd_end;
|
|
++bnd) {
|
|
if (bnd->order < 0)
|
|
bnd->order = -bnd->order;
|
|
}
|
|
|
|
PRINTFD(G, FB_ObjectMolecule)
|
|
" %s: leaving with %d bonds...\n", __func__, nBond ENDFD;
|
|
return true;
|
|
}
|
|
|
|
void ObjectMoleculeConnectDiscrete(ObjectMolecule* I, int searchFlag,
|
|
int connectModeOverride, bool pbc)
|
|
{
|
|
for (int i = 0; i < I->NCSet; i++) {
|
|
if (!I->CSet[i]) {
|
|
continue;
|
|
}
|
|
|
|
int nbond = 0;
|
|
pymol::vla<BondType> bond;
|
|
|
|
ObjectMoleculeConnect(I, nbond, bond, I->CSet[i], searchFlag, connectModeOverride, pbc);
|
|
|
|
if (!bond) {
|
|
continue;
|
|
}
|
|
|
|
if (!I->Bond) {
|
|
I->Bond = std::move(bond);
|
|
} else {
|
|
I->Bond.check(I->NBond + nbond - 1);
|
|
std::copy_n(bond.data(), nbond, I->Bond.data() + I->NBond);
|
|
}
|
|
|
|
I->NBond += nbond;
|
|
}
|
|
}
|
|
|
|
/*========================================================================*/
|
|
/**
|
|
* Sort the ObjectMolecule::AtomInfo and ObjectMolecule::Bond arrays and adjust
|
|
* IdxToAtm/AtmToIdx in all coordiante sets.
|
|
*
|
|
* This function has no effect on discrete objects.
|
|
*/
|
|
int ObjectMoleculeSort(ObjectMolecule * I)
|
|
{ /* sorts atoms and bonds */
|
|
int *index;
|
|
int *outdex = nullptr;
|
|
int a, b;
|
|
int ok = true;
|
|
if(!I->DiscreteFlag) { /* currently, discrete objects are never sorted */
|
|
int already_in_order = true;
|
|
int i_NAtom = I->NAtom;
|
|
index = AtomInfoGetSortedIndex(I->G, I, I->AtomInfo, i_NAtom, &outdex);
|
|
CHECKOK(ok, index);
|
|
if (ok){
|
|
for(a = 0; a < i_NAtom; a++) {
|
|
if(index[a] != a) {
|
|
already_in_order = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if(ok && !already_in_order) { /* if we aren't already in perfect order */
|
|
|
|
for(a = 0; a < I->NBond; a++) { /* bonds */
|
|
I->Bond[a].index[0] = outdex[I->Bond[a].index[0]];
|
|
I->Bond[a].index[1] = outdex[I->Bond[a].index[1]];
|
|
}
|
|
|
|
for(a = -1; a < I->NCSet; a++) { /* coordinate set mapping */
|
|
auto* cs = (a < 0) ? I->CSTmpl : I->CSet[a];
|
|
|
|
if(cs) {
|
|
int cs_NIndex = cs->NIndex;
|
|
int *cs_IdxToAtm = cs->IdxToAtm.data();
|
|
for(b = 0; b < cs_NIndex; b++)
|
|
cs_IdxToAtm[b] = outdex[cs_IdxToAtm[b]];
|
|
}
|
|
}
|
|
|
|
I->updateAtmToIdx();
|
|
|
|
ExecutiveUniqueIDAtomDictInvalidate(I->G);
|
|
|
|
pymol::vla<AtomInfoType> atInfo(i_NAtom);
|
|
CHECKOK(ok, atInfo);
|
|
if (ok){
|
|
for(a = 0; a < i_NAtom; a++)
|
|
atInfo[a] = std::move(I->AtomInfo[index[a]]);
|
|
I->AtomInfo = std::move(atInfo);
|
|
}
|
|
}
|
|
AtomInfoFreeSortedIndexes(I->G, &index, &outdex);
|
|
if (ok){
|
|
UtilSortInPlace(I->G, I->Bond.data(), I->NBond, sizeof(BondType),
|
|
(UtilOrderFn *) BondInOrder);
|
|
/* sort...important! */
|
|
I->invalidate(cRepAll, cRepInvAtoms, -1); /* important */
|
|
}
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
int ObjectMoleculeSetGeometry(
|
|
PyMOLGlobals* G, ObjectMolecule* I, int sele, int geom, int valence)
|
|
{
|
|
int count = 0;
|
|
for (int a = 0; a < I->NAtom; ++a) {
|
|
auto s = I->AtomInfo[a].selEntry;
|
|
|
|
if(SelectorIsMember(G, s, sele)) {
|
|
auto& ai = I->AtomInfo[a];
|
|
ai.geom = geom;
|
|
ai.valence = valence;
|
|
ai.chemFlag = true;
|
|
count++;
|
|
break;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|