Exemplo n.º 1
0
/// return true if it's time to print a status update message
int wkf_msg_timer_timeout(wkfmsgtimer *mt) {
  double elapsed = wkf_timer_timenow(mt->timer);
  if (elapsed > mt->updatetime) {
    // reset the clock and return true that our timer expired
    wkf_timer_start(mt->timer);
    return 1;
  } else if (elapsed < 0) {
    // time went backwards, best reset our clock!
    wkf_timer_start(mt->timer);
  }
  return 0;
}
Exemplo n.º 2
0
int QuickSurf::calc_surf(AtomSel *atomSel, DrawMolecule *mol,
                         const float *atompos, const float *atomradii,
                         int quality, float radscale, float gridspacing,
                         float isoval, const int *colidx, const float *cmap,
                         VMDDisplayList *cmdList) {
    wkf_timer_start(timer);
    int colorperatom = (colidx != NULL && cmap != NULL);
    int usebeads=0;

    // clean up any existing CPU arrays before going any further...
    if (voltexmap != NULL)
        free(voltexmap);
    voltexmap = NULL;

    ResizeArray<float> beadpos(64 + (3 * atomSel->selected) / 20);
    ResizeArray<float> beadradii(64 + (3 * atomSel->selected) / 20);
    ResizeArray<float> beadcolors(64 + (3 * atomSel->selected) / 20);

    if (getenv("VMDQUICKSURFBEADS")) {
        usebeads=1;
#if !defined(ARCH_BLUEWATERS)
        printf("QuickSurf using residue beads representation...\n");
#endif
    }

    int numbeads = 0;
    if (usebeads) {
        int i, resid, numres;

        // draw a bead for each residue
        numres = mol->residueList.num();
        for (resid=0; resid<numres; resid++) {
            float com[3] = {0.0, 0.0, 0.0};
            const ResizeArray<int> &atoms = mol->residueList[resid]->atoms;
            int numatoms = atoms.num();
            int oncount = 0;

            // find COM for residue
            for (i=0; i<numatoms; i++) {
                int idx = atoms[i];
                if (atomSel->on[idx]) {
                    oncount++;
                    vec_add(com, com, atompos + 3*idx);
                }
            }

            if (oncount < 1)
                continue; // exit if there weren't any atoms

            vec_scale(com, 1.0f / (float) oncount, com);

            // find radius of bounding sphere and save last atom index for color
            int atomcolorindex=0; // initialize, to please compilers
            float boundradsq = 0.0f;
            for (i=0; i<numatoms; i++) {
                int idx = atoms[i];
                if (atomSel->on[idx]) {
                    float tmpdist[3];
                    atomcolorindex = idx;
                    vec_sub(tmpdist, com, atompos + 3*idx);
                    float distsq = dot_prod(tmpdist, tmpdist);
                    if (distsq > boundradsq) {
                        boundradsq = distsq;
                    }
                }
            }
            beadpos.append(com[0]);
            beadpos.append(com[1]);
            beadpos.append(com[2]);

            beadradii.append(sqrtf(boundradsq) + 1.0f);

            if (colorperatom) {
                const float *cp = &cmap[colidx[atomcolorindex] * 3];
                beadcolors.append(cp[0]);
                beadcolors.append(cp[1]);
                beadcolors.append(cp[2]);
            }

            // XXX still need to add pick points...
        }

        numbeads = beadpos.num() / 3;
    }

    // initialize class variables
    isovalue=isoval;

    // If no volumetric texture will be computed we will use the cmap
    // parameter to pass in the solid color to be applied to all vertices
    vec_copy(solidcolor, cmap);

    // compute min/max atom radius, build list of selected atom radii,
    // and compute bounding box for the selected atoms
    float minx, miny, minz, maxx, maxy, maxz;
    float minrad, maxrad;
    int i;
    if (usebeads) {
        minx = maxx = beadpos[0];
        miny = maxy = beadpos[1];
        minz = maxz = beadpos[2];
        minrad = maxrad = beadradii[0];
        for (i=0; i<numbeads; i++) {
            int ind = i * 3;
            float tmpx = beadpos[ind  ];
            float tmpy = beadpos[ind+1];
            float tmpz = beadpos[ind+2];

            minx = (tmpx < minx) ? tmpx : minx;
            maxx = (tmpx > maxx) ? tmpx : maxx;

            miny = (tmpy < miny) ? tmpy : miny;
            maxy = (tmpy > maxy) ? tmpy : maxy;

            minz = (tmpz < minz) ? tmpz : minz;
            maxz = (tmpz > maxz) ? tmpz : maxz;

            // we always have to compute the rmin/rmax for beads
            // since these radii are defined on-the-fly
            float r = beadradii[i];
            minrad = (r < minrad) ? r : minrad;
            maxrad = (r > maxrad) ? r : maxrad;
        }
    } else {
        minx = maxx = atompos[atomSel->firstsel*3  ];
        miny = maxy = atompos[atomSel->firstsel*3+1];
        minz = maxz = atompos[atomSel->firstsel*3+2];

        // Query min/max atom radii for the entire molecule
        mol->get_radii_minmax(minrad, maxrad);

        // We only compute rmin/rmax for the actual group of selected atoms if
        // (rmax/rmin > 2.5) for the whole molecule, otherwise it's a small
        // enough range that we don't care since it won't hurt our performance.
        if (minrad <= 0.001 || maxrad/minrad > 2.5) {
            minrad = maxrad = atomradii[atomSel->firstsel];
            for (i=atomSel->firstsel; i<=atomSel->lastsel; i++) {
                if (atomSel->on[i]) {
                    int ind = i * 3;
                    float tmpx = atompos[ind  ];
                    float tmpy = atompos[ind+1];
                    float tmpz = atompos[ind+2];

                    minx = (tmpx < minx) ? tmpx : minx;
                    maxx = (tmpx > maxx) ? tmpx : maxx;

                    miny = (tmpy < miny) ? tmpy : miny;
                    maxy = (tmpy > maxy) ? tmpy : maxy;

                    minz = (tmpz < minz) ? tmpz : minz;
                    maxz = (tmpz > maxz) ? tmpz : maxz;

                    float r = atomradii[i];
                    minrad = (r < minrad) ? r : minrad;
                    maxrad = (r > maxrad) ? r : maxrad;
                }
            }
        } else {
            for (i=atomSel->firstsel; i<=atomSel->lastsel; i++) {
                if (atomSel->on[i]) {
                    int ind = i * 3;
                    float tmpx = atompos[ind  ];
                    float tmpy = atompos[ind+1];
                    float tmpz = atompos[ind+2];

                    minx = (tmpx < minx) ? tmpx : minx;
                    maxx = (tmpx > maxx) ? tmpx : maxx;

                    miny = (tmpy < miny) ? tmpy : miny;
                    maxy = (tmpy > maxy) ? tmpy : maxy;

                    minz = (tmpz < minz) ? tmpz : minz;
                    maxz = (tmpz > maxz) ? tmpz : maxz;
                }
            }
        }
    }

    float mincoord[3], maxcoord[3];
    mincoord[0] = minx;
    mincoord[1] = miny;
    mincoord[2] = minz;
    maxcoord[0] = maxx;
    maxcoord[1] = maxy;
    maxcoord[2] = maxz;

    // crude estimate of the grid padding we require to prevent the
    // resulting isosurface from being clipped
    float gridpadding = radscale * maxrad * 1.70f;
    float padrad = gridpadding;
    padrad = 0.65f * sqrtf(4.0f/3.0f*((float) VMD_PI)*padrad*padrad*padrad);
    gridpadding = MAX(gridpadding, padrad);

    // Handle coarse-grained structures and whole-cell models
    // XXX The switch at 4.0A from an assumed all-atom scale structure to
    //     CG or cell models is a simple heuristic at a somewhat arbitrary
    //     threshold value.
    //     For all-atom models the units shown in the GUI are in Angstroms
    //     and are absolute, but for CG or cell models the units in the GUI
    //     are relative to the atom with the minimum radius.
    //     This code doesn't do anything to handle structures with a minrad
    //     of zero, where perhaps only one particle has an unset radius.
    if (minrad > 4.0f) {
        gridspacing *= minrad;
    }

#if !defined(ARCH_BLUEWATERS)
    printf("QuickSurf: R*%.1f, I=%.1f, H=%.1f Pad: %.1f minR: %.1f maxR: %.1f)\n",
           radscale, isovalue, gridspacing, gridpadding, minrad, maxrad);
#endif

    mincoord[0] -= gridpadding;
    mincoord[1] -= gridpadding;
    mincoord[2] -= gridpadding;
    maxcoord[0] += gridpadding;
    maxcoord[1] += gridpadding;
    maxcoord[2] += gridpadding;

    // compute the real grid dimensions from the selected atoms
    xaxis[0] = maxcoord[0]-mincoord[0];
    yaxis[1] = maxcoord[1]-mincoord[1];
    zaxis[2] = maxcoord[2]-mincoord[2];
    numvoxels[0] = (int) ceil(xaxis[0] / gridspacing);
    numvoxels[1] = (int) ceil(yaxis[1] / gridspacing);
    numvoxels[2] = (int) ceil(zaxis[2] / gridspacing);

    // recalc the grid dimensions from rounded/padded voxel counts
    xaxis[0] = (numvoxels[0]-1) * gridspacing;
    yaxis[1] = (numvoxels[1]-1) * gridspacing;
    zaxis[2] = (numvoxels[2]-1) * gridspacing;
    maxcoord[0] = mincoord[0] + xaxis[0];
    maxcoord[1] = mincoord[1] + yaxis[1];
    maxcoord[2] = mincoord[2] + zaxis[2];

#if !defined(ARCH_BLUEWATERS)
    printf("  GridSZ: (%4d %4d %4d)  BBox: (%.1f %.1f %.1f)->(%.1f %.1f %.1f)\n",
           numvoxels[0], numvoxels[1], numvoxels[2],
           mincoord[0], mincoord[1], mincoord[2],
           maxcoord[0], maxcoord[1], maxcoord[2]);
#endif

    vec_copy(origin, mincoord);

    // build compacted lists of bead coordinates, radii, and colors
    float *xyzr = NULL;
    float *colors = NULL;
    if (usebeads) {
        int ind =0;
        int ind4=0;
        xyzr = (float *) malloc(numbeads * sizeof(float) * 4);
        if (colorperatom) {
            colors = (float *) malloc(numbeads * sizeof(float) * 4);

            // build compacted lists of bead coordinates, radii, and colors
            for (i=0; i<numbeads; i++) {
                const float *fp = &beadpos[0] + ind;
                xyzr[ind4    ] = fp[0]-origin[0];
                xyzr[ind4 + 1] = fp[1]-origin[1];
                xyzr[ind4 + 2] = fp[2]-origin[2];
                xyzr[ind4 + 3] = beadradii[i];

                const float *cp = &beadcolors[0] + ind;
                colors[ind4    ] = cp[0];
                colors[ind4 + 1] = cp[1];
                colors[ind4 + 2] = cp[2];
                colors[ind4 + 3] = 1.0f;
                ind4 += 4;
                ind += 3;
            }
        } else {
            // build compacted lists of bead coordinates and radii only
            for (i=0; i<numbeads; i++) {
                const float *fp = &beadpos[0] + ind;
                xyzr[ind4    ] = fp[0]-origin[0];
                xyzr[ind4 + 1] = fp[1]-origin[1];
                xyzr[ind4 + 2] = fp[2]-origin[2];
                xyzr[ind4 + 3] = beadradii[i];
                ind4 += 4;
                ind += 3;
            }
        }
    } else {
        int ind = atomSel->firstsel * 3;
        int ind4=0;
        xyzr = (float *) malloc(atomSel->selected * sizeof(float) * 4);
        if (colorperatom) {
            colors = (float *) malloc(atomSel->selected * sizeof(float) * 4);

            // build compacted lists of atom coordinates, radii, and colors
            for (i=atomSel->firstsel; i <= atomSel->lastsel; i++) {
                if (atomSel->on[i]) {
                    const float *fp = atompos + ind;
                    xyzr[ind4    ] = fp[0]-origin[0];
                    xyzr[ind4 + 1] = fp[1]-origin[1];
                    xyzr[ind4 + 2] = fp[2]-origin[2];
                    xyzr[ind4 + 3] = atomradii[i];

                    const float *cp = &cmap[colidx[i] * 3];
                    colors[ind4    ] = cp[0];
                    colors[ind4 + 1] = cp[1];
                    colors[ind4 + 2] = cp[2];
                    colors[ind4 + 3] = 1.0f;
                    ind4 += 4;
                }
                ind += 3;
            }
        } else {
            // build compacted lists of atom coordinates and radii only
            for (i=atomSel->firstsel; i <= atomSel->lastsel; i++) {
                if (atomSel->on[i]) {
                    const float *fp = atompos + ind;
                    xyzr[ind4    ] = fp[0]-origin[0];
                    xyzr[ind4 + 1] = fp[1]-origin[1];
                    xyzr[ind4 + 2] = fp[2]-origin[2];
                    xyzr[ind4 + 3] = atomradii[i];
                    ind4 += 4;
                }
                ind += 3;
            }
        }
    }

    // set gaussian window size based on user-specified quality parameter
    float gausslim = 2.0f;
    switch (quality) {
    case 3:
        gausslim = 4.0f;
        break; // max quality

    case 2:
        gausslim = 3.0f;
        break; // high quality

    case 1:
        gausslim = 2.5f;
        break; // medium quality

    case 0:
    default:
        gausslim = 2.0f; // low quality
        break;
    }

    pretime = wkf_timer_timenow(timer);

#if defined(VMDCUDA)
    if (!getenv("VMDNOCUDA")) {
        // compute both density map and floating point color texture map
        int pcount = (usebeads) ? numbeads : atomSel->selected;
        int rc = cudaqs->calc_surf(pcount, &xyzr[0],
                                   (colorperatom) ? &colors[0] : &cmap[0],
                                   colorperatom, origin, numvoxels, maxrad,
                                   radscale, gridspacing, isovalue, gausslim,
                                   cmdList);

        if (rc == 0) {
            free(xyzr);
            if (colors)
                free(colors);

            voltime = wkf_timer_timenow(timer);
            return 0;
        }
    }
#endif

#if !defined(ARCH_BLUEWATERS)
    printf("  Computing density map grid on CPUs ");
#endif

    long volsz = numvoxels[0] * numvoxels[1] * numvoxels[2];
    volmap = new float[volsz];
    if (colidx != NULL && cmap != NULL) {
        voltexmap = (float*) calloc(1, 3 * sizeof(float) * numvoxels[0] * numvoxels[1] * numvoxels[2]);
    }

    fflush(stdout);
    memset(volmap, 0, sizeof(float) * volsz);
    if ((volsz * atomSel->selected) > 20000000) {
        vmd_gaussdensity_threaded(atomSel->selected, &xyzr[0],
                                  (voltexmap!=NULL) ? &colors[0] : NULL,
                                  volmap, voltexmap, numvoxels, radscale,
                                  gridspacing, isovalue, gausslim);
    } else {
        vmd_gaussdensity_opt(atomSel->selected, &xyzr[0],
                             (voltexmap!=NULL) ? &colors[0] : NULL,
                             volmap, voltexmap,
                             numvoxels, radscale, gridspacing, isovalue, gausslim);
    }

    free(xyzr);
    if (colors)
        free(colors);

    voltime = wkf_timer_timenow(timer);

    // draw the surface
    draw_trimesh(cmdList);

#if !defined(ARCH_BLUEWATERS)
    printf(" Done.\n");
#endif
    return 0;
}
Exemplo n.º 3
0
void DrawMolItem::draw_orbital(int density, int wavefnctype, int wavefncspin, 
                               int wavefncexcitation, int orbid, 
                               float isovalue, 
                               int drawbox, int style, 
                               float gridspacing, int stepsize, int thickness) {
  if (!mol->numframes() || gridspacing <= 0.0f)
    return; 

  // only recalculate the orbital grid if necessary
  int regenorbital=0;
  if (density != orbgridisdensity ||
      wavefnctype != waveftype ||
      wavefncspin != wavefspin ||
      wavefncexcitation != wavefexcitation ||
      orbid != gridorbid ||
      gridspacing != orbgridspacing ||
      orbvol == NULL || 
      needRegenerate & MOL_REGEN ||
      needRegenerate & SEL_REGEN) {
    regenorbital=1;
  }

  double motime=0, voltime=0, gradtime=0;
  wkf_timerhandle timer = wkf_timer_create();
  wkf_timer_start(timer);

  if (regenorbital) {
    // XXX this needs to be fixed so that things like the
    //     draw multiple frames feature will work correctly for Orbitals
    int frame = mol->frame(); // draw currently active frame
    const Timestep *ts = mol->get_frame(frame);

    if (!ts->qm_timestep || !mol->qm_data || 
        !mol->qm_data->num_basis || orbid < 1) {
      wkf_timer_destroy(timer);
      return;
    }

    // Find the  timestep independent wavefunction ID tag
    // by comparing type, spin, and excitation with the
    // signatures of existing wavefunctions.
    int waveid = mol->qm_data->find_wavef_id_from_gui_specs(
                  wavefnctype, wavefncspin, wavefncexcitation);

    // Translate the wavefunction ID into the index the
    // wavefunction has in this timestep
    int iwave = ts->qm_timestep->get_wavef_index(waveid);

    if (iwave<0 || 
        !ts->qm_timestep->get_wavecoeffs(iwave) ||
        !ts->qm_timestep->get_num_orbitals(iwave) ||
        orbid > ts->qm_timestep->get_num_orbitals(iwave)) {
      wkf_timer_destroy(timer);
      return;
    }

    // Get the orbital index for this timestep from the orbital ID.
    int orbindex = ts->qm_timestep->get_orbital_index_from_id(iwave, orbid);

    // Build an Orbital object and prepare to calculate a grid
    Orbital *orbital = mol->qm_data->create_orbital(iwave, orbindex, 
                                                    ts->pos, ts->qm_timestep);

    // Set the bounding box of the atom coordinates as the grid dimensions
    orbital->set_grid_to_bbox(ts->pos, 3.0, gridspacing);

    // XXX needs more testing, can get stuck for certain orbitals
#if 0
    // XXX for GPU, we need to only optimize to a stepsize of 4 or more, as
    //     otherwise doing this actually slows us down rather than speeding up
    //     orbital.find_optimal_grid(0.01, 4, 8);
    // 
    // optimize: minstep 2, maxstep 8, threshold 0.01
    orbital->find_optimal_grid(0.01, 2, 8);
#endif

    // Calculate the molecular orbital
    orbital->calculate_mo(mol, density);

    motime = wkf_timer_timenow(timer);

    // query orbital grid origin, dimensions, and axes 
    const int *numvoxels = orbital->get_numvoxels();
    const float *origin = orbital->get_origin();

    float xaxis[3], yaxis[3], zaxis[3];
    orbital->get_grid_axes(xaxis, yaxis, zaxis);

    // build a VolumetricData object for rendering
    char dataname[64];
    sprintf(dataname, "molecular orbital %i", orbid);

    // update attributes of cached orbital grid
    orbgridisdensity = density;
    waveftype = wavefnctype;
    wavefspin = wavefncspin;
    wavefexcitation = wavefncexcitation;
    gridorbid = orbid;
    orbgridspacing = gridspacing;
    delete orbvol;
    orbvol = new VolumetricData(dataname, origin, 
                                xaxis, yaxis, zaxis,
                                numvoxels[0], numvoxels[1], numvoxels[2],
                                orbital->get_grid_data());
    delete orbital;

    voltime = wkf_timer_timenow(timer);

    orbvol->compute_volume_gradient(); // calc gradients: smooth vertex normals

    gradtime = wkf_timer_timenow(timer);
  } // regen the orbital grid...


  // draw the newly created VolumetricData object 
  sprintf(commentBuffer, "MoleculeID: %d ReprID: %d Beginning Orbital",
          mol->id(), repNumber);
  cmdCommentX.putdata(commentBuffer, cmdList);

  if (drawbox > 0) {
    // don't texture the box if color by volume is active
    if (atomColor->method() == AtomColor::VOLUME) {
      append(DVOLTEXOFF);
    }
    // wireframe only?  or solid?
    if (style > 0 || drawbox == 2) {
      draw_volume_box_lines(orbvol);
    } else {
      draw_volume_box_solid(orbvol);
    }
    if (atomColor->method() == AtomColor::VOLUME) {
      append(DVOLTEXON);
    }
  }

  if ((drawbox == 2) || (drawbox == 0)) {
    switch (style) {
      case 3:
        // shaded points isosurface looping over X-axis, 1 point per voxel
        draw_volume_isosurface_lit_points(orbvol, isovalue, stepsize, thickness);
        break;

      case 2:
        // points isosurface looping over X-axis, max of 1 point per voxel
        draw_volume_isosurface_points(orbvol, isovalue, stepsize, thickness);
        break;

      case 1:
        // lines implementation, max of 18 line per voxel (3-per triangle)
        draw_volume_isosurface_lines(orbvol, isovalue, stepsize, thickness);
        break;

      case 0:
      default:
        // trimesh polygonalized surface, max of 6 triangles per voxel
        draw_volume_isosurface_trimesh(orbvol, isovalue, stepsize);
        break;
    }
  }

  if (regenorbital) {
    double surftime = wkf_timer_timenow(timer);
    if (surftime > 5) {
      char strmsg[1024];
      sprintf(strmsg, "Total MO rep time: %.3f [MO: %.3f vol: %.3f grad: %.3f surf: %.2f]",
              surftime, motime, voltime - motime, gradtime - motime, surftime - gradtime);

      msgInfo << strmsg << sendmsg;
    }
  }

  wkf_timer_destroy(timer);
}
Exemplo n.º 4
0
// Extract the isosurface from the QuickSurf density map
int QuickSurf::get_trimesh(int &numverts,
                           float *&v3fv, float *&n3fv, float *&c3fv,
                           int &numfacets, int *&fiv) {
#if !defined(ARCH_BLUEWATERS)
    printf("Running marching cubes on CPU...\n");
#endif

    VolumetricData *surfvol; ///< Container used to generate isosurface on the CPU
    surfvol = new VolumetricData("molecular surface",
                                 origin, xaxis, yaxis, zaxis,
                                 numvoxels[0], numvoxels[1], numvoxels[2],
                                 volmap);

    // XXX we should calculate the volume gradient only for those
    //     vertices we extract, since for this rep any changes to settings
    //     will require recomputation of the entire volume
    surfvol->compute_volume_gradient(); // calc gradients: smooth vertex normals
    gradtime = wkf_timer_timenow(timer);

    // trimesh polygonalized surface, max of 6 triangles per voxel
    const int stepsize = 1;
    s.clear();                              // initialize isosurface data
    s.compute(surfvol, isovalue, stepsize); // compute the isosurface

    mctime = wkf_timer_timenow(timer);

    s.vertexfusion(surfvol, 9, 9);          // identify and eliminate duplicated vertices
    s.normalize();                          // normalize interpolated gradient/surface normals

    if (s.numtriangles > 0) {
        if (voltexmap != NULL) {
            // assign per-vertex colors by a 3-D texture map
            s.set_color_voltex_rgb3fv(voltexmap);
        } else {
            // use a single color for the entire mesh
            s.set_color_rgb3fv(solidcolor);
        }
    }

    numverts = s.v.num() / 3;
    v3fv=&s.v[0];
    n3fv=&s.n[0];
    c3fv=&s.c[0];

    numfacets = s.numtriangles;
    fiv=&s.f[0];

    delete surfvol;

    mcverttime = wkf_timer_timenow(timer);
    reptime = mcverttime;

#if !defined(ARCH_BLUEWATERS)
    char strmsg[1024];
    sprintf(strmsg, "QuickSurf: %.3f [pre:%.3f vol:%.3f gr:%.3f mc:%.2f mcv:%.3f]",
            reptime, pretime, voltime-pretime, gradtime-voltime,
            mctime-gradtime, mcverttime-mctime);

    msgInfo << strmsg << sendmsg;
#endif

    return 0;
}
Exemplo n.º 5
0
CoorData::CoorDataState CoorPluginData::next(Molecule *m) {
  if (!plugin) 
    return DONE;

  if (is_input) {
    if (recentFrame < 0) {
      recentFrame = 0;
      while (recentFrame < begFrame) {
        plugin->skip(m);
        recentFrame++;
      }
    } else {
      for (int i=1; i<frameSkip; i++) 
        plugin->skip(m);
      recentFrame += frameSkip;
    }
    if (endFrame < 0 || recentFrame <= endFrame) {
      Timestep *ts = plugin->next(m); 
      if (ts) {
        m->append_frame(ts);
        totalframes++;
        return NOTDONE;
      }
    }
  } else if (m->numframes() > 0) {  // output
    if (recentFrame < 0)
      recentFrame = begFrame;
    else
      recentFrame += frameSkip;

    // get next frame, and write to file
    if ((endFrame < 0 || recentFrame <= endFrame) 
        && m->numframes() > recentFrame) {
      Timestep *ts = m->get_frame(recentFrame);
      if (ts) {
        if (!plugin->write_timestep(ts, selection)) {
          totalframes++;
          return NOTDONE;
        } else {
          msgErr << "write_timestep returned nonzero" << sendmsg;
        }
      }      
    }
  }

  if (tm != NULL && totalframes > 0) {
    double iotime = wkf_timer_timenow(tm);
    // emit I/O stats if requested, or it took more than 3 seconds
    if ((getenv("VMDTSTIMER") != NULL) || (iotime > 3.0)) {
      float framerate = (float) (((double) totalframes) / iotime);
      char tmbuf[1024], frameratebuf[1024];
      sprintf(tmbuf, "%.1f", iotime);
      if (framerate > 10)
        sprintf(frameratebuf, "%.1f", framerate);
      else
        sprintf(frameratebuf, "%.2f", framerate);
      msgInfo << "Coordinate I/O rate " 
              << frameratebuf << " frames/sec, "
              << (int) (((totalframes * kbytesperframe) / 1024.0) / iotime) 
              << " MB/sec, "
              << tmbuf << " sec" << sendmsg;
    }
  }

  // we're done; close file and stop reading/writing
  delete plugin;
  plugin = NULL;
  return DONE;
}