Main Page   Namespace List   Class Hierarchy   Alphabetical List   Compound List   File List   Namespace Members   Compound Members   File Members   Related Pages  

pdbplugin.c

Go to the documentation of this file.
00001 /***************************************************************************
00002  *cr
00003  *cr            (C) Copyright 1995-2016 The Board of Trustees of the
00004  *cr                        University of Illinois
00005  *cr                         All Rights Reserved
00006  *cr
00007  ***************************************************************************/
00008 
00009 /***************************************************************************
00010  * RCS INFORMATION:
00011  *
00012  *      $RCSfile: pdbplugin.c,v $
00013  *      $Author: johns $       $Locker:  $             $State: Exp $
00014  *      $Revision: 1.74 $       $Date: 2024/03/01 02:17:44 $
00015  *
00016  ***************************************************************************/
00017 
00018 /*
00019  * PDB file format specifications:
00020  *   http://www.rcsb.org/pdb/static.do?p=file_formats/pdb/index.html
00021  */
00022 
00023 #include "largefiles.h"   /* platform dependent 64-bit file I/O defines */
00024 
00025 #include "molfile_plugin.h"
00026 #include "readpdb.h"
00027 #include "periodic_table.h"
00028 #include <stdio.h>
00029 #include <stdlib.h>
00030 #include <string.h>
00031 
00032 /*
00033  * API functions start here
00034  */
00035 
00036 typedef struct {
00037   FILE *fd;
00038   int first_frame;
00039   int natoms;
00040   molfile_atom_t *atomlist;
00041   molfile_metadata_t *meta;
00042   int nconect;
00043   int nbonds, maxbnum;
00044   int *from, *to, *idxmap;
00045 } pdbdata;
00046 
00047 static void *open_pdb_read(const char *filepath, const char *filetype, 
00048     int *natoms) {
00049   FILE *fd;
00050   pdbdata *pdb;
00051   char pdbstr[PDB_BUFFER_LENGTH];
00052   int indx, nconect;
00053 
00054   fd = fopen(filepath, "r");
00055   if (!fd) 
00056     return NULL;
00057   pdb = (pdbdata *)malloc(sizeof(pdbdata));
00058   pdb->fd = fd;
00059   pdb->meta = (molfile_metadata_t *) malloc(sizeof(molfile_metadata_t));
00060   memset(pdb->meta, 0, sizeof(molfile_metadata_t));
00061 
00062   pdb->meta->remarklen = 0;
00063   pdb->meta->remarks = NULL;
00064 
00065   *natoms=0;
00066   nconect=0;
00067   do {
00068     indx = read_pdb_record(pdb->fd, pdbstr);
00069     if (indx == PDB_ATOM) {
00070       *natoms += 1;
00071     } else if (indx == PDB_CONECT) {
00072       nconect++;
00073     } else if (indx == PDB_HEADER) {
00074       get_pdb_header(pdbstr, pdb->meta->accession, pdb->meta->date, NULL);
00075       if (strlen(pdb->meta->accession) > 0) 
00076         strcpy(pdb->meta->database, "PDB");
00077     } else if (indx == PDB_REMARK || indx == PDB_CONECT || indx == PDB_UNKNOWN) {
00078       int len=strlen(pdbstr);
00079       int newlen = len + pdb->meta->remarklen;
00080 
00081       char *newstr=realloc(pdb->meta->remarks, newlen + 1);
00082       if (newstr != NULL) {
00083         pdb->meta->remarks = newstr;
00084         pdb->meta->remarks[pdb->meta->remarklen] = '\0';
00085         memcpy(pdb->meta->remarks + pdb->meta->remarklen, pdbstr, len);
00086         pdb->meta->remarks[newlen] = '\0';
00087         pdb->meta->remarklen = newlen;
00088       }
00089     }
00090  
00091   } while (indx != PDB_END && indx != PDB_EOF);
00092 
00093   /* If no atoms were found, this is probably not a PDB file! */
00094   if (!*natoms) {
00095     fprintf(stderr, "PDB file '%s' contains no atoms.\n", filepath);
00096     if (pdb->meta->remarks != NULL)
00097       free(pdb->meta->remarks);
00098     if (pdb->meta != NULL)
00099       free(pdb->meta);
00100     free(pdb);
00101     return NULL;
00102   }
00103 
00104   rewind(pdb->fd); /* if ok, rewind file and prepare to parse it for real */
00105   pdb->natoms = *natoms;
00106   pdb->nconect = nconect;
00107   pdb->nbonds = 0;
00108   pdb->maxbnum = 0;
00109   pdb->from = NULL;
00110   pdb->to = NULL;
00111   pdb->idxmap = NULL;
00112   pdb->atomlist = NULL;
00113 
00114 #if defined(VMDUSECONECTRECORDS)
00115   /* allocate atom index translation table if we have 99,999 atoms or less */
00116   /* and we have conect records to process                                 */
00117   if (pdb->natoms < 100000 && pdb->nconect > 0) {
00118     pdb->idxmap = (int *) malloc(100000 * sizeof(int));
00119     memset(pdb->idxmap, 0, 100000 * sizeof(int));
00120   }
00121 #endif
00122  
00123   return pdb; 
00124 }
00125 
00126 static int read_pdb_structure(void *mydata, int *optflags, 
00127     molfile_atom_t *atoms) { 
00128   pdbdata *pdb = (pdbdata *)mydata;
00129   molfile_atom_t *atom;
00130   char pdbrec[PDB_BUFFER_LENGTH];
00131   int i, rectype, atomserial, pteidx;
00132   char ridstr[8];
00133   char elementsymbol[3];
00134   int badptecount = 0;
00135   long fpos = ftell(pdb->fd);
00136 
00137   *optflags = MOLFILE_INSERTION | MOLFILE_OCCUPANCY | MOLFILE_BFACTOR |
00138               MOLFILE_ALTLOC | MOLFILE_ATOMICNUMBER | MOLFILE_BONDSSPECIAL;
00139 
00140   i = 0;
00141   do {
00142     rectype = read_pdb_record(pdb->fd, pdbrec);
00143     switch (rectype) {
00144     case PDB_ATOM:
00145       atom = atoms+i;
00146       get_pdb_fields(pdbrec, strlen(pdbrec), &atomserial, 
00147           atom->name, atom->resname, atom->chain, atom->segid, 
00148           ridstr, atom->insertion, atom->altloc, elementsymbol,
00149           NULL, NULL, NULL, &atom->occupancy, &atom->bfactor);
00150 
00151       if (pdb->idxmap != NULL && atomserial < 100000) {
00152         pdb->idxmap[atomserial] = i; /* record new serial number translation */ 
00153       }
00154  
00155       atom->resid = atoi(ridstr);
00156 
00157       /* determine atomic number from the element symbol */
00158       pteidx = get_pte_idx_from_string(elementsymbol);
00159       atom->atomicnumber = pteidx;
00160       if (pteidx != 0) {
00161         atom->mass = get_pte_mass(pteidx);
00162         atom->radius = get_pte_vdw_radius(pteidx);
00163       } else {
00164         badptecount++; /* unrecognized element */
00165       }
00166  
00167       strcpy(atom->type, atom->name);
00168       i++;
00169       break;
00170 
00171     case PDB_CONECT:
00172       /* only read CONECT records for structures where we know they can */
00173       /* be valid for all of the atoms in the structure                 */
00174       if (pdb->idxmap != NULL) {
00175         get_pdb_conect(pdbrec, pdb->natoms, pdb->idxmap, 
00176                        &pdb->maxbnum, &pdb->nbonds, &pdb->from, &pdb->to);
00177       }
00178       break;
00179 
00180     default:
00181       /* other record types are ignored in the structure callback */
00182       /* and are dealt with in the timestep callback or elsewhere */
00183       break;
00184     }
00185   } while (rectype != PDB_END && rectype != PDB_EOF);
00186 
00187   fseek(pdb->fd, fpos, SEEK_SET);
00188 
00189   /* if all atoms are recognized, set the mass and radius flags too,  */
00190   /* otherwise let VMD guess these for itself using it's own methods  */
00191   if (badptecount == 0) {
00192     *optflags |= MOLFILE_MASS | MOLFILE_RADIUS;
00193   }
00194 
00195   return MOLFILE_SUCCESS;
00196 }
00197 
00198 static int read_bonds(void *v, int *nbonds, int **fromptr, int **toptr, 
00199                       float ** bondorder,int **bondtype, 
00200                       int *nbondtypes, char ***bondtypename) {
00201   pdbdata *pdb = (pdbdata *)v;
00202   
00203   *nbonds = 0;
00204   *fromptr = NULL;
00205   *toptr = NULL;
00206   *bondorder = NULL; /* PDB files don't have bond order information */
00207   *bondtype = NULL;
00208   *nbondtypes = 0;
00209   *bondtypename = NULL;
00210 
00211 /* The newest plugin API allows us to return CONECT records as 
00212  * additional bonds above and beyond what the distance search returns.
00213  * Without that feature, we otherwise have to check completeness and
00214  * ignore them if they don't look to be fully specified for this molecule */
00215 #if !defined(MOLFILE_BONDSSPECIAL)
00216   if (pdb->natoms >= 100000) {
00217     printf("pdbplugin) Warning: more than 99,999 atoms, ignored CONECT records\n");
00218     return MOLFILE_SUCCESS;
00219   } else if (((float) pdb->nconect / (float) pdb->natoms) <= 0.85) {
00220     printf("pdbplugin) Warning: Probable incomplete bond structure specified,\n");
00221     printf("pdbplugin)          ignoring CONECT records\n");
00222     return MOLFILE_SUCCESS;
00223   } else if (pdb->nconect == 0) {
00224     return MOLFILE_SUCCESS;
00225   }
00226 #endif
00227 
00228   *nbonds = pdb->nbonds;
00229   *fromptr = pdb->from;
00230   *toptr = pdb->to;
00231 
00232   return MOLFILE_SUCCESS;
00233 }
00234 
00235 
00236 /* 
00237  * 
00238  */
00239 static int read_next_timestep(void *v, int natoms, molfile_timestep_t *ts) {
00240   pdbdata *pdb = (pdbdata *)v;
00241   char pdbstr[PDB_BUFFER_LENGTH];
00242   int indx, i;
00243   float *x, *y, *z;
00244   float occup, bfac;
00245   if (pdb->natoms == 0) 
00246     return MOLFILE_ERROR; /* EOF */
00247   if (ts) {
00248     x = ts->coords;
00249     y = x+1;
00250     z = x+2;
00251   } else {
00252     x = y = z = 0;
00253   } 
00254   i = 0;
00255   do {
00256     indx = read_pdb_record(pdb->fd, pdbstr);
00257     if((indx == PDB_END || indx == PDB_EOF) && (i < pdb->natoms)) {
00258       return MOLFILE_ERROR;
00259     } else if(indx == PDB_ATOM) {
00260       if(i++ >= pdb->natoms) {
00261         break;      
00262       }
00263       /* just get the coordinates, and store them */
00264       if (ts) {
00265         get_pdb_coordinates(pdbstr, x, y, z, &occup, &bfac);
00266         x += 3;
00267         y += 3;
00268         z += 3;
00269       } 
00270     } else if (indx == PDB_CRYST1) {
00271       if (ts) {
00272         get_pdb_cryst1(pdbstr, &ts->alpha, &ts->beta, &ts->gamma,
00273                                &ts->A, &ts->B, &ts->C);
00274       }
00275     }
00276   } while(!(indx == PDB_END || indx == PDB_EOF));
00277 
00278   return MOLFILE_SUCCESS;
00279 }
00280 
00281 static void close_pdb_read(void *v) { 
00282   pdbdata *pdb = (pdbdata *)v;
00283   if (pdb->fd != NULL)
00284     fclose(pdb->fd);
00285   if (pdb->idxmap != NULL)
00286     free(pdb->idxmap);
00287   if (pdb->meta->remarks != NULL)
00288     free(pdb->meta->remarks);
00289   if (pdb->meta != NULL) 
00290     free(pdb->meta);
00291   free(pdb);
00292 }
00293 
00294 static void *open_file_write(const char *path, const char *filetype, 
00295     int natoms) {
00296 
00297   FILE *fd;
00298   pdbdata *pdb;
00299   fd = fopen(path, "w");
00300   if (!fd) {
00301     fprintf(stderr, "Unable to open file %s for writing\n", path);
00302     return NULL;
00303   }
00304   pdb = (pdbdata *)malloc(sizeof(pdbdata));
00305   pdb->fd = fd;
00306   pdb->natoms = natoms; 
00307   pdb->atomlist = NULL;
00308   pdb->first_frame = 1;
00309   return pdb;
00310 }
00311  
00312 static int write_structure(void *v, int optflags, 
00313     const molfile_atom_t *atoms) {
00314 
00315   int i;
00316   pdbdata *pdb = (pdbdata *)v;
00317   int natoms = pdb->natoms;
00318   pdb->atomlist = (molfile_atom_t *)malloc(natoms*sizeof(molfile_atom_t));
00319   memcpy(pdb->atomlist, atoms, natoms*sizeof(molfile_atom_t));
00320 
00321   /* If occ, bfactor, and insertion aren't given, we assign defaultvalues. */
00322   if (!(optflags & MOLFILE_OCCUPANCY)) {
00323     for (i=0; i<natoms; i++) pdb->atomlist[i].occupancy = 0.0f;
00324   }
00325   if (!(optflags & MOLFILE_BFACTOR)) {
00326     for (i=0; i<natoms; i++) pdb->atomlist[i].bfactor= 0.0f;
00327   }
00328   if (!(optflags & MOLFILE_INSERTION)) {
00329     for (i=0; i<natoms; i++) {
00330       pdb->atomlist[i].insertion[0] =' ';
00331       pdb->atomlist[i].insertion[1] ='\0';
00332     }
00333   }
00334   if (!(optflags & MOLFILE_ALTLOC)) {
00335     for (i=0; i<natoms; i++) {
00336       pdb->atomlist[i].altloc[0]=' ';
00337       pdb->atomlist[i].altloc[1]='\0';
00338     }
00339   }
00340   if (!(optflags & MOLFILE_ATOMICNUMBER)) {
00341     for (i=0; i<natoms; i++) pdb->atomlist[i].atomicnumber = 0;
00342   }
00343 
00344   /* TODO: put bonds into CONECT records? */
00345   return MOLFILE_SUCCESS;
00346 }
00347 
00348 /* SEQRES records look like this:
00349 
00350 COLUMNS        DATA TYPE       FIELD         DEFINITION
00351 ---------------------------------------------------------------------------------
00352  1 -  6        Record name     "SEQRES"
00353 
00354  9 - 10        Integer         serNum        Serial number of the SEQRES record
00355                                              for the current chain.  Starts at 1
00356                                              and increments by one each line.
00357                                              Reset to 1 for each chain.
00358 
00359 12             Character       chainID       Chain identifier.  This may be any
00360                                              single legal character, including a
00361                                              blank which is used if there is
00362                                              only one chain.
00363 
00364 14 - 17        Integer         numRes        Number of residues in the chain.
00365                                              This value is repeated on every
00366                                              record.
00367 
00368 20 - 22        Residue name    resName       Residue name.
00369 
00370 24 - 26        Residue name    resName       Residue name.
00371 
00372 ... and so forth out to 68-70, for a total of 13 in each line (except possibly
00373 the last.
00374 
00375 source:
00376 http://www.rcsb.org/pdb/file_formats/pdb/pdbguide2.2/part_35.html
00377 */
00378 
00379 /*
00380  * However, we don't use them right now because of several issues that
00381  * can't presently be resolved satisfactorily in VMD:
00382 
00383 According to the RCSB, SEQRES records have to contain all residues, not
00384 just those in the structure, which means VMD will usually produce incorrect
00385 output and there's nothing we can do about it.  The RCSB actually specifies
00386 that all residues in the chain have to present in the SEQRES records, even
00387 if they're not in the structure.
00388   
00389 We can never know which residues to output.  Our current system of outputting   
00390 everything is just terrible when you have 20,000 waters in your system; we
00391 have to fix this immediately.  We could almost get away with making a hash
00392 table of the names of protein and nucleic acid residues and only write chains
00393 containing those residues.  However, there's this little snippet from the
00394 specification:
00395   
00396 * Heterogens which are integrated into the backbone of the chain are listed
00397   as being part of the chain and are included in the SEQRES records for
00398   that chain.
00399   
00400 That means that we can never know what might appear in the sequence unless we
00401 also read HET records and keep track of them in VMD as well.  We shouldn't 
00402 get people depending on such fallible SEQRES records.
00403   
00404 And of course, there's the fact that no other program that we know of besides   
00405 CE needs these SEQRES records.
00406 
00407  * Uncomment the write_seqres line in write_timestep to turn them back on.
00408  */
00409 
00410 
00411 #if 0
00412 static void write_seqres(FILE * fd, int natoms, const molfile_atom_t *atomlist) {
00413   int i=0;
00414   while (i < natoms) {
00415     int k, serNum;
00416     int j = i;
00417     int ires, nres = 1;
00418     int resid = atomlist[i].resid;
00419     /* Count up the number of residues in the chain */
00420     const char *chain = atomlist[i].chain;
00421     while (j < natoms && !strcmp(chain, atomlist[j].chain)) {
00422       if (resid != atomlist[j].resid) {
00423         nres++;
00424         resid = atomlist[j].resid;
00425       }
00426       j++;
00427     }
00428     /* There are nres residues in the chain, from atoms i to j. */
00429     serNum = 1;
00430     ires = 1;
00431     resid = atomlist[i].resid;
00432     fprintf(fd, "SEQRES  %2d %c %4d  ",  serNum, chain[0], nres);
00433     serNum = 2;
00434     fprintf(fd, "%3s ", atomlist[i].resname);
00435     for (k=i; k<j; k++) {
00436       if (resid != atomlist[k].resid) {
00437         resid = atomlist[k].resid;
00438         if (!(ires % 13)) {
00439           fprintf(fd, "\nSEQRES  %2d %c %4d  ",  serNum, chain[0], nres);
00440           serNum++;
00441         }
00442         fprintf(fd, "%3s ", atomlist[k].resname);
00443         ires++;
00444       }
00445     }
00446     i = j;
00447     fprintf(fd, "\n");
00448   }
00449 }
00450 #endif
00451 
00452 /*
00453 CRYST1 records look like this:
00454 The CRYST1 record presents the unit cell parameters, space group, and Z value. If the structure was not determined by crystallographic means, CRYST1 simply defines a unit cube. 
00455 
00456 
00457 Record Format 
00458 
00459 COLUMNS       DATA TYPE      FIELD         DEFINITION
00460 -------------------------------------------------------------
00461  1 -  6       Record name    "CRYST1"
00462 
00463  7 - 15       Real(9.3)      a             a (Angstroms).
00464 
00465 16 - 24       Real(9.3)      b             b (Angstroms).
00466 
00467 25 - 33       Real(9.3)      c             c (Angstroms).
00468 
00469 34 - 40       Real(7.2)      alpha         alpha (degrees).
00470 
00471 41 - 47       Real(7.2)      beta          beta (degrees).
00472 
00473 48 - 54       Real(7.2)      gamma         gamma (degrees).
00474 
00475 56 - 66       LString        sGroup        Space group.
00476 
00477 67 - 70       Integer        z             Z value.
00478 
00479 * If the coordinate entry describes a structure determined by a technique
00480 other than crystallography, CRYST1 contains a = b = c = 1.0, alpha =
00481 beta = gamma = 90 degrees, space group = P 1, and Z = 1.
00482 
00483 We will use "P 1" and "1" for space group and z value, as recommended, but
00484 we'll populate the other fields with the unit cell information we do have.
00485 
00486 */
00487   
00488 static void write_cryst1(FILE *fd, const molfile_timestep_t *ts) {
00489   fprintf(fd, "CRYST1%9.3f%9.3f%9.3f%7.2f%7.2f%7.2f P 1           1\n", 
00490     ts->A, ts->B, ts->C, ts->alpha, ts->beta, ts->gamma);
00491 }
00492 
00493 
00494 static int write_timestep(void *v, const molfile_timestep_t *ts) {
00495   pdbdata *pdb = (pdbdata *)v; 
00496   const molfile_atom_t *atom;
00497   const float *pos;
00498   int i;
00499   char elementsymbol[3];
00500 
00501   if (pdb->natoms == 0)
00502     return MOLFILE_SUCCESS;
00503 
00504   if (pdb->first_frame) {
00505     /* Turn off SEQRES writing for now; see comments above.
00506     write_seqres(pdb->fd, pdb->natoms, pdb->atomlist);
00507     */
00508     write_cryst1(pdb->fd, ts);
00509     pdb->first_frame = 0;
00510   }
00511   atom = pdb->atomlist;
00512   pos = ts->coords;
00513   for (i=0; i<pdb->natoms; i++) {
00514     /*
00515      * The 8.3 format for position, occupancy, and bfactor permits values 
00516      * only in the range of -999.9994 to 9999.9994 (so that they round
00517      * to the range [-999.999, 9999.999]).  If values fall outside of that
00518      * range, fail and emit an error message rather than generate a
00519      * misformatted PDB file.
00520      */
00521 #define PDBBAD(x) ((x) < -999.9994f || (x) > 9999.9994f)
00522     if (PDBBAD(pos[0]) || PDBBAD(pos[1]) || PDBBAD(pos[2]) ||
00523                 PDBBAD(atom->occupancy) || PDBBAD(atom->bfactor)) {
00524             fprintf(stderr, "PDB WRITE ERROR: Position, occupancy, or b-factor (beta) for atom %d\n", i);
00525       fprintf(stderr, "                 cannot be written in PDB format.\n");
00526       fprintf(stderr, "                 File will be truncated.\n");
00527       return MOLFILE_ERROR;
00528     }
00529 
00530     /* check the atomicnumber and format the atomic element symbol string */
00531     strcpy(elementsymbol, (atom->atomicnumber < 1) ? "  " : get_pte_label(atom->atomicnumber));
00532     elementsymbol[0] = toupper(elementsymbol[0]);
00533     elementsymbol[1] = toupper(elementsymbol[1]);
00534  
00535     if (!write_raw_pdb_record(pdb->fd,  
00536         "ATOM  ", i+1, atom->name, atom->resname, atom->resid, 
00537         atom->insertion, atom->altloc, elementsymbol,
00538         pos[0], pos[1], pos[2], 
00539         atom->occupancy, atom->bfactor, atom->chain, atom->segid)) {
00540       fprintf(stderr, 
00541           "PDB: Error encoutered writing atom %d; file may be incomplete.\n", 
00542           i+1);
00543       return MOLFILE_ERROR;
00544     }
00545     ++atom;
00546     pos += 3;
00547   }
00548   fprintf(pdb->fd, "END\n");
00549 
00550   return MOLFILE_SUCCESS;
00551 }
00552  
00553 static void close_file_write(void *v) {
00554   pdbdata *pdb = (pdbdata *)v; 
00555   fclose(pdb->fd);
00556   free(pdb->atomlist);
00557   free(pdb);
00558 }
00559 
00560 static int read_molecule_metadata(void *v, molfile_metadata_t **metadata) {
00561   pdbdata *pdb = (pdbdata *)v; 
00562   *metadata = pdb->meta;
00563   return MOLFILE_SUCCESS;
00564 }
00565 
00566 /*
00567  * Initialization stuff down here
00568  */
00569 
00570 static molfile_plugin_t plugin;
00571  
00572 VMDPLUGIN_API int VMDPLUGIN_init() {
00573   memset(&plugin, 0, sizeof(molfile_plugin_t));
00574   plugin.abiversion = vmdplugin_ABIVERSION;
00575   plugin.type = MOLFILE_PLUGIN_TYPE;
00576   plugin.name = "pdb";
00577   plugin.prettyname = "PDB";
00578   plugin.author = "Justin Gullingsrud, John Stone";
00579   plugin.majorv = 1;
00580   plugin.minorv = 17;
00581   plugin.is_reentrant = VMDPLUGIN_THREADSAFE;
00582   plugin.filename_extension = "pdb,ent";
00583   plugin.open_file_read = open_pdb_read;
00584   plugin.read_structure = read_pdb_structure;
00585   plugin.read_bonds = read_bonds;
00586   plugin.read_next_timestep = read_next_timestep;
00587   plugin.close_file_read = close_pdb_read;
00588   plugin.open_file_write = open_file_write;
00589   plugin.write_structure = write_structure;
00590   plugin.write_timestep = write_timestep;
00591   plugin.close_file_write = close_file_write;
00592   plugin.read_molecule_metadata = read_molecule_metadata;
00593   return VMDPLUGIN_SUCCESS;
00594 }
00595 
00596 VMDPLUGIN_API int VMDPLUGIN_register(void *v, vmdplugin_register_cb cb) {
00597   (*cb)(v, (vmdplugin_t *)&plugin);
00598   return VMDPLUGIN_SUCCESS;
00599 }
00600 
00601 VMDPLUGIN_API int VMDPLUGIN_fini() {
00602   return VMDPLUGIN_SUCCESS;
00603 }
00604 

Generated on Sun Dec 8 03:08:53 2024 for VMD Plugins (current) by doxygen1.2.14 written by Dimitri van Heesch, © 1997-2002