source: nanovis/branches/1.2/FlowCmd.cpp @ 5722

Last change on this file since 5722 was 5588, checked in by ldelgass, 9 years ago

Merge support for VTK vector fields in nanovis to release branch

  • Property svn:eol-style set to native
File size: 39.8 KB
Line 
1/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 * Copyright (c) 2004-2013  HUBzero Foundation, LLC
4 *
5 */
6#include <assert.h>
7#define _OPEN_SYS
8#include <fcntl.h>
9#define _XOPEN_SOURCE_EXTENDED 1
10#include <sys/uio.h>
11#include <sys/stat.h>
12#include <stdlib.h>
13#include <stddef.h>
14#include <limits.h>
15#include <stdint.h>
16#include <unistd.h>
17#include <poll.h>
18
19#include <sstream>
20
21#include <tcl.h>
22
23#include <RpBuffer.h>
24
25#include <vrmath/Vector3f.h>
26
27#include "nvconf.h"
28
29#include "nanovisServer.h"
30#include "nanovis.h"
31#include "CmdProc.h"
32#include "Command.h"
33#include "PPMWriter.h"
34#ifdef USE_VTK
35#include "VtkDataSetReader.h"
36#else
37#include "VtkReader.h"
38#endif
39#include "FlowCmd.h"
40#include "FlowTypes.h"
41#include "Flow.h"
42#include "FlowBox.h"
43#include "FlowParticles.h"
44#include "Switch.h"
45#include "TransferFunction.h"
46#include "LIC.h"
47#include "Unirect.h"
48#include "VelocityArrowsSlice.h"
49#include "Volume.h"
50#include "Trace.h"
51
52using namespace nv;
53using namespace vrmath;
54
55static SwitchParseProc AxisSwitchProc;
56static SwitchCustom axisSwitch = {
57    AxisSwitchProc, NULL, 0,
58};
59
60static SwitchParseProc ColorSwitchProc;
61static SwitchCustom colorSwitch = {
62    ColorSwitchProc, NULL, 0,
63};
64
65static SwitchParseProc PointSwitchProc;
66static SwitchCustom pointSwitch = {
67    PointSwitchProc, NULL, 0,
68};
69
70static SwitchParseProc PositionSwitchProc;
71static SwitchCustom positionSwitch = {
72    PositionSwitchProc, NULL, 0,
73};
74
75static SwitchParseProc TransferFunctionSwitchProc;
76static SwitchCustom transferFunctionSwitch = {
77    TransferFunctionSwitchProc, NULL, 0,
78};
79
80SwitchSpec Flow::_switches[] = {
81    {SWITCH_FLOAT, "-ambient", "value",
82     offsetof(FlowValues, ambient), 0},
83    {SWITCH_BOOLEAN, "-arrows", "boolean",
84     offsetof(FlowValues, showArrows), 0},
85    {SWITCH_CUSTOM, "-axis", "axis",
86     offsetof(FlowValues, slicePos.axis), 0, 0, &axisSwitch},
87    {SWITCH_FLOAT, "-diffuse", "value",
88     offsetof(FlowValues, diffuse), 0},
89    {SWITCH_BOOLEAN, "-hide", "boolean",
90     offsetof(FlowValues, isHidden), 0},
91    {SWITCH_BOOLEAN, "-light2side", "boolean",
92     offsetof(FlowValues, twoSidedLighting), 0},
93    {SWITCH_FLOAT, "-opacity", "value",
94     offsetof(FlowValues, opacity), 0},
95    {SWITCH_BOOLEAN, "-outline", "boolean",
96     offsetof(FlowValues, showOutline), 0},
97    {SWITCH_CUSTOM, "-position", "number",
98     offsetof(FlowValues, slicePos), 0, 0, &positionSwitch},
99    {SWITCH_BOOLEAN, "-slice", "boolean",
100     offsetof(FlowValues, sliceVisible), 0},
101    {SWITCH_FLOAT, "-specularExp", "value",
102     offsetof(FlowValues, specularExp), 0},
103    {SWITCH_FLOAT, "-specularLevel", "value",
104     offsetof(FlowValues, specular), 0},
105    {SWITCH_CUSTOM, "-transferfunction", "name",
106     offsetof(FlowValues, transferFunction), 0, 0, &transferFunctionSwitch},
107    {SWITCH_BOOLEAN, "-volume", "boolean",
108     offsetof(FlowValues, showVolume), 0},
109    {SWITCH_END}
110};
111
112SwitchSpec FlowParticles::_switches[] = {
113    {SWITCH_CUSTOM, "-axis", "axis",
114     offsetof(FlowParticlesValues, position.axis), 0, 0, &axisSwitch},
115    {SWITCH_CUSTOM, "-color", "{r g b a}",
116     offsetof(FlowParticlesValues, color), 0, 0,  &colorSwitch},
117    {SWITCH_BOOLEAN, "-hide", "boolean",
118     offsetof(FlowParticlesValues, isHidden), 0},
119    {SWITCH_CUSTOM, "-position", "number",
120     offsetof(FlowParticlesValues, position), 0, 0, &positionSwitch},
121    {SWITCH_FLOAT, "-size", "float",
122     offsetof(FlowParticlesValues, particleSize), 0},
123    {SWITCH_END}
124};
125
126SwitchSpec FlowBox::_switches[] = {
127    {SWITCH_CUSTOM, "-color", "{r g b a}",
128     offsetof(FlowBoxValues, color), 0, 0,  &colorSwitch},
129    {SWITCH_CUSTOM, "-corner1", "{x y z}",
130     offsetof(FlowBoxValues, corner1), 0, 0, &pointSwitch},
131    {SWITCH_CUSTOM, "-corner2", "{x y z}",
132     offsetof(FlowBoxValues, corner2), 0, 0, &pointSwitch},
133    {SWITCH_BOOLEAN, "-hide", "boolean",
134     offsetof(FlowBoxValues, isHidden), 0},
135    {SWITCH_FLOAT, "-linewidth", "number",
136     offsetof(FlowBoxValues, lineWidth), 0},
137    {SWITCH_END}
138};
139
140/**
141 * $flow data follows nbytes nComponents
142 */
143static int
144FlowDataFollowsOp(ClientData clientData, Tcl_Interp *interp, int objc,
145                  Tcl_Obj *const *objv)
146{
147    TRACE("Enter");
148
149    int nBytes;
150    if (Tcl_GetIntFromObj(interp, objv[3], &nBytes) != TCL_OK) {
151        ERROR("Bad nBytes \"%s\"", Tcl_GetString(objv[3]));
152        return TCL_ERROR;
153    }
154    if (nBytes <= 0) {
155        Tcl_AppendResult(interp, "bad # bytes request \"",
156                         Tcl_GetString(objv[3]), "\" for \"data follows\"", (char *)NULL);
157        ERROR("Bad nbytes %d", nBytes);
158        return TCL_ERROR;
159    }
160    int nComponents;
161    if (Tcl_GetIntFromObj(interp, objv[4], &nComponents) != TCL_OK) {
162        ERROR("Bad # of components \"%s\"", Tcl_GetString(objv[4]));
163        return TCL_ERROR;
164    }
165    if (nComponents <= 0) {
166        Tcl_AppendResult(interp, "bad # of components request \"",
167                         Tcl_GetString(objv[4]), "\" for \"data follows\"", (char *)NULL);
168        ERROR("Bad # of components %d", nComponents);
169        return TCL_ERROR;
170    }
171    Rappture::Buffer buf(nBytes);
172    TRACE("Flow data loading bytes: %d components: %d", nBytes, nComponents);
173    if (GetDataStream(interp, buf, nBytes) != TCL_OK) {
174        return TCL_ERROR;
175    }
176    char *bytes = (char *)buf.bytes();
177    size_t length = buf.size();
178
179    Unirect3d *unirect = NULL;
180    Volume *volume = NULL;
181
182    Flow *flow = (Flow *)clientData;
183    if ((length > 4) && (strncmp(bytes, "<DX>", 4) == 0)) {
184#ifdef USE_DX_READER
185        unirect = new Unirect3d(nComponents);
186        if (!unirect->importDx(nComponents, length - 4, bytes + 4)) {
187            Tcl_AppendResult(interp, "Failed to load DX file", (char *)NULL);
188            delete unirect;
189            return TCL_ERROR;
190        }
191#else
192        Tcl_AppendResult(interp, "Loading DX files is not supported by this server", (char*)NULL);
193        return TCL_ERROR;
194#endif
195    } else if ((length > 10) && (strncmp(bytes, "unirect3d ", 10) == 0)) {
196        unirect = new Unirect3d(nComponents);
197        if (unirect->parseBuffer(interp, bytes, length) != TCL_OK) {
198            delete unirect;
199            return TCL_ERROR;
200        }
201    } else if ((length > 10) && (strncmp(bytes, "unirect2d ", 10) == 0)) {
202        Unirect2d *u2dPtr = new Unirect2d(nComponents);
203        if (u2dPtr->parseBuffer(interp, bytes, length) != TCL_OK) {
204            delete u2dPtr;
205            return TCL_ERROR;
206        }
207        unirect = new Unirect3d(nComponents);
208        unirect->convert(u2dPtr);
209        delete u2dPtr;
210    } else if ((length > 14) && (strncmp(bytes, "# vtk DataFile", 14) == 0)) {
211        TRACE("VTK loading...");
212#ifdef USE_VTK
213        volume = load_vtk_volume_stream(flow->name(), bytes, length);
214#else
215        std::stringstream fdata;
216        fdata.write(bytes, length);
217        volume = load_vtk_volume_stream(flow->name(), fdata);
218#endif
219        if (volume == NULL) {
220            Tcl_AppendResult(interp, "Failed to load VTK file", (char*)NULL);
221            return TCL_ERROR;
222        }
223    } else {
224#ifdef USE_DX_READER
225        TRACE("header is %.14s", buf.bytes());
226        unirect = new Unirect3d(nComponents);
227        if (!unirect->importDx(nComponents, length, bytes)) {
228            Tcl_AppendResult(interp, "Failed to load DX file", (char *)NULL);
229            delete unirect;
230            return TCL_ERROR;
231        }
232#else
233        Tcl_AppendResult(interp, "Loading DX files is not supported by this server", (char*)NULL);
234        return TCL_ERROR;
235#endif
236    }
237    if (unirect != NULL && unirect->nValues() == 0) {
238        delete unirect;
239        Tcl_AppendResult(interp, "no data found in stream", (char *)NULL);
240        return TCL_ERROR;
241    }
242    if (unirect != NULL) {
243        TRACE("nx = %d ny = %d nz = %d",
244              unirect->xNum(), unirect->yNum(), unirect->zNum());
245        TRACE("x0 = %g y0 = %g z0 = %g",
246              unirect->xMin(), unirect->yMin(), unirect->zMin());
247        TRACE("lx = %g ly = %g lz = %g",
248              unirect->xMax() - unirect->xMin(),
249              unirect->yMax() - unirect->yMin(),
250              unirect->zMax() - unirect->zMin());
251        TRACE("dx = %g dy = %g dz = %g",
252              unirect->xNum() > 1 ? (unirect->xMax() - unirect->xMin())/(unirect->xNum()-1) : 0,
253              unirect->yNum() > 1 ? (unirect->yMax() - unirect->yMin())/(unirect->yNum()-1) : 0,
254              unirect->zNum() > 1 ? (unirect->zMax() - unirect->zMin())/(unirect->zNum()-1) : 0);
255        TRACE("magMin = %g magMax = %g",
256              unirect->magMin(), unirect->magMax());
257
258        flow->data(unirect);
259    } else {
260        flow->data(volume);
261    }
262    double range[2];
263    flow->getVectorRange(range);
264    {
265        char info[1024];
266        int length =
267            sprintf(info, "nv>data tag %s min %g max %g\n",
268                    flow->name(), range[0], range[1]);
269#ifdef USE_THREADS
270        queueResponse(info, (size_t)length, Response::VOLATILE);
271#else
272        if (SocketWrite(info, (size_t)length) < 0) {
273            return TCL_ERROR;
274        }
275#endif
276    }
277    Flow::updatePending = true;
278    NanoVis::eventuallyRedraw();
279    return TCL_OK;
280}
281
282static CmdSpec flowDataOps[] = {
283    {"follows", 1, FlowDataFollowsOp, 5, 5, "size nComponents",},
284};
285static int nFlowDataOps = NumCmdSpecs(flowDataOps);
286
287static int
288FlowDataOp(ClientData clientData, Tcl_Interp *interp, int objc,
289           Tcl_Obj *const *objv)
290{
291    Tcl_ObjCmdProc *proc;
292
293    proc = GetOpFromObj(interp, nFlowDataOps, flowDataOps,
294                        CMDSPEC_ARG2, objc, objv, 0);
295    if (proc == NULL) {
296        return TCL_ERROR;
297    }
298    return (*proc) (clientData, interp, objc, objv);
299}
300
301/**
302 * \brief Convert a Tcl_Obj representing the label of a child node into its
303 * integer node id.
304 *
305 * \param clientData Flag indicating if the node is considered before or
306 * after the insertion position.
307 * \param interp Interpreter to send results back to
308 * \param switchName Not used
309 * \param objPtr String representation
310 * \param record Structure record
311 * \param offset Not used
312 * \param flags Not used
313 *
314 * \return The return value is a standard Tcl result.
315 */
316static int
317AxisSwitchProc(ClientData clientData, Tcl_Interp *interp,
318               const char *switchName, Tcl_Obj *objPtr,
319               char *record, int offset, int flags)
320{
321    const char *string = Tcl_GetString(objPtr);
322    if (string[1] == '\0') {
323        FlowSliceAxis *axisPtr = (FlowSliceAxis *)(record + offset);
324        char c;
325        c = tolower((unsigned char)string[0]);
326        if (c == 'x') {
327            *axisPtr = AXIS_X;
328            return TCL_OK;
329        } else if (c == 'y') {
330            *axisPtr = AXIS_Y;
331            return TCL_OK;
332        } else if (c == 'z') {
333            *axisPtr = AXIS_Z;
334            return TCL_OK;
335        }
336        /*FALLTHRU*/
337    }
338    Tcl_AppendResult(interp, "bad axis \"", string,
339                     "\": should be x, y, or z", (char*)NULL);
340    return TCL_ERROR;
341}
342
343/**
344 * \brief Convert a Tcl_Obj representing the label of a list of four color
345 * components in to a RGBA color value.
346 *
347 * \param clientData Flag indicating if the node is considered before or
348 * after the insertion position.
349 * \param interp Interpreter to send results back to
350 * \param switchName Not used
351 * \param objPtr String representation
352 * \param record Structure record
353 * \param offset Not used
354 * \param flags Not used
355 *
356 * \return The return value is a standard Tcl result.
357 */
358static int
359ColorSwitchProc(ClientData clientData, Tcl_Interp *interp,
360                const char *switchName, Tcl_Obj *objPtr,
361                char *record, int offset, int flags)
362{
363    Tcl_Obj **objv;
364    int objc;
365    FlowColor *color = (FlowColor *)(record + offset);
366
367    if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) {
368        return TCL_ERROR;
369    }
370    if ((objc < 3) || (objc > 4)) {
371        Tcl_AppendResult(interp, "wrong # of elements in color definition",
372                         (char *)NULL);
373        return TCL_ERROR;
374    }
375    float values[4];
376    int i;
377    values[3] = 1.0f;
378    for (i = 0; i < objc; i++) {
379        float value;
380
381        if (GetFloatFromObj(interp, objv[i], &value) != TCL_OK) {
382            return TCL_ERROR;
383        }
384        if ((value < 0.0) || (value > 1.0)) {
385            Tcl_AppendResult(interp, "bad component value in \"",
386                             Tcl_GetString(objPtr), "\": color values must be [0..1]",
387                             (char *)NULL);
388            return TCL_ERROR;
389        }
390        values[i] = value;
391    }
392    color->r = values[0];
393    color->g = values[1];
394    color->b = values[2];
395    color->a = values[3];
396    return TCL_OK;
397}
398
399/**
400 * \brief Convert a Tcl_Obj representing the a 3-D coordinate into a point.
401 *
402 * \param clientData Flag indicating if the node is considered before or
403 * after the insertion position.
404 * \param interp Interpreter to send results back to
405 * \param switchName Not used
406 * \param objPtr String representation
407 * \param record Structure record
408 * \param offset Not used
409 * \param flags Not used
410 *
411 * \return The return value is a standard Tcl result.
412 */
413static int
414PointSwitchProc(ClientData clientData, Tcl_Interp *interp,
415                const char *switchName, Tcl_Obj *objPtr,
416                char *record, int offset, int flags)
417{
418    FlowPoint *point = (FlowPoint *)(record + offset);
419    int objc;
420    Tcl_Obj **objv;
421
422    if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) {
423        return TCL_ERROR;
424    }
425    if (objc != 3) {
426        Tcl_AppendResult(interp, "wrong # of elements for box coordinates: "
427                         " should be \"x y z\"", (char *)NULL);
428        return TCL_ERROR;
429    }
430    float values[3];
431    int i;
432    for (i = 0; i < objc; i++) {
433        float value;
434
435        if (GetFloatFromObj(interp, objv[i], &value) != TCL_OK) {
436            return TCL_ERROR;
437        }
438        values[i] = value;
439    }
440    point->x = values[0];
441    point->y = values[1];
442    point->z = values[2];
443    return TCL_OK;
444}
445
446/**
447 * \brief Convert a Tcl_Obj representing the a 3-D coordinate into a point.
448 *
449 * \param clientData Flag indicating if the node is considered before or
450 * after the insertion position.
451 * \param interp Interpreter to send results back to
452 * \param switchName Not used
453 * \param objPtr String representation
454 * \param record Structure record
455 * \param offset Not used
456 * \param flags Not used
457 *
458 * \return The return value is a standard Tcl result.
459 */
460static int
461PositionSwitchProc(ClientData clientData, Tcl_Interp *interp,
462                   const char *switchName, Tcl_Obj *objPtr,
463                   char *record, int offset, int flags)
464{
465    FlowPosition *posPtr = (FlowPosition *)(record + offset);
466    const char *string;
467    char *p;
468
469    string = Tcl_GetString(objPtr);
470    p = strrchr((char *)string, '%');
471    if (p == NULL) {
472        double value;
473
474        if (Tcl_GetDoubleFromObj(interp, objPtr, &value) != TCL_OK) {
475            return TCL_ERROR;
476        }
477        posPtr->value = value;
478        posPtr->flags = ABSPOS;
479    } else {
480        double value;
481
482        *p = '\0';
483        if (Tcl_GetDouble(interp, string, &value) != TCL_OK) {
484            return TCL_ERROR;
485        }
486        posPtr->value = value * 0.01;
487        posPtr->flags = RELPOS;
488    }
489    return TCL_OK;
490}
491
492/**
493 * \brief Convert a Tcl_Obj representing the transfer function into a
494 *  TransferFunction pointer. 
495 *
496 * The transfer function must have been previously defined.
497 *
498 * \param clientData Flag indicating if the node is considered before or
499 * after the insertion position.
500 * \param interp Interpreter to send results back to
501 * \param switchName Not used
502 * \param objPtr String representation
503 * \param record Structure record
504 * \param offset Not used
505 * \param flags Not used
506 *
507 * \return The return value is a standard Tcl result.
508 */
509static int
510TransferFunctionSwitchProc(ClientData clientData, Tcl_Interp *interp,
511                           const char *switchName, Tcl_Obj *objPtr,
512                           char *record, int offset, int flags)
513{
514    TransferFunction **funcPtrPtr = (TransferFunction **)(record + offset);
515    TransferFunction *tf = NanoVis::getTransferFunction(Tcl_GetString(objPtr));
516    if (tf == NULL) {
517        Tcl_AppendResult(interp, "transfer function \"", Tcl_GetString(objPtr),
518                         "\" is not defined", (char*)NULL);
519        return TCL_ERROR;
520    }
521    *funcPtrPtr = tf;
522    return TCL_OK;
523}
524
525static int
526FlowConfigureOp(ClientData clientData, Tcl_Interp *interp, int objc,
527                Tcl_Obj *const *objv)
528{
529    Flow *flow = (Flow *)clientData;
530
531    if (flow->parseSwitches(interp, objc - 2, objv + 2) != TCL_OK) {
532        return TCL_ERROR;
533    }
534    if (flow->configure()) {
535        Flow::updatePending = true;
536    }
537    NanoVis::eventuallyRedraw();
538    return TCL_OK;
539}
540
541static int
542FlowParticlesAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
543                   Tcl_Obj *const *objv)
544{
545    Flow *flow = (Flow *)clientData;
546    const char *particlesName = Tcl_GetString(objv[3]);
547    FlowParticles *particles = flow->createParticles(particlesName);
548    if (particles == NULL) {
549        Tcl_AppendResult(interp, "Flow particle injection plane \"",
550                         particlesName,
551                         "\" already exists or could not be created",
552                         (char*)NULL);
553        return TCL_ERROR;
554    }
555    if (particles->parseSwitches(interp, objc - 4, objv + 4) != TCL_OK) {
556        flow->deleteParticles(particlesName);
557        return TCL_ERROR;
558    }
559    particles->configure();
560    Flow::updatePending = true;
561    NanoVis::eventuallyRedraw();
562    Tcl_SetObjResult(interp, objv[3]);
563    return TCL_OK;
564}
565
566static int
567FlowParticlesConfigureOp(ClientData clientData, Tcl_Interp *interp, int objc,
568                         Tcl_Obj *const *objv)
569{
570    Flow *flow = (Flow *)clientData;
571    const char *particlesName = Tcl_GetString(objv[3]);
572    FlowParticles *particles = flow->getParticles(particlesName);
573    if (particles == NULL) {
574        Tcl_AppendResult(interp, "Flow particle injection plane \"",
575                         particlesName, "\" not found",
576                         (char*)NULL);
577        return TCL_ERROR;
578    }
579    if (particles->parseSwitches(interp, objc - 4, objv + 4) != TCL_OK) {
580        return TCL_ERROR;
581    }
582    if (particles->configure()) {
583        Flow::updatePending = true;
584    }
585    NanoVis::eventuallyRedraw();
586    return TCL_OK;
587}
588
589static int
590FlowParticlesDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
591                      Tcl_Obj *const *objv)
592{
593    Flow *flow = (Flow *)clientData;
594    for (int i = 3; i < objc; i++) {
595        flow->deleteParticles(Tcl_GetString(objv[i]));
596    }
597    NanoVis::eventuallyRedraw();
598    return TCL_OK;
599}
600
601static int
602FlowParticlesNamesOp(ClientData clientData, Tcl_Interp *interp, int objc,
603                     Tcl_Obj *const *objv)
604{
605    Flow *flow = (Flow *)clientData;
606    Tcl_Obj *listObjPtr;
607    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
608    std::vector<std::string> names;
609    flow->getParticlesNames(names);
610    for (std::vector<std::string>::iterator itr = names.begin();
611         itr != names.end(); ++itr) {
612        Tcl_Obj *objPtr = Tcl_NewStringObj(itr->c_str(), -1);
613        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
614    }
615    Tcl_SetObjResult(interp, listObjPtr);
616    return TCL_OK;
617}
618
619static CmdSpec flowParticlesOps[] = {
620    {"add",        1, FlowParticlesAddOp,        4, 0, "name ?switches?",},
621    {"configure",  1, FlowParticlesConfigureOp,  4, 0, "name ?switches?",},
622    {"delete",     1, FlowParticlesDeleteOp,     4, 0, "name ?name...?"},
623    {"names",      1, FlowParticlesNamesOp,      3, 3, ""},
624};
625
626static int nFlowParticlesOps = NumCmdSpecs(flowParticlesOps);
627
628/**
629 * \brief This procedure is invoked to process commands on behalf of the flow
630 * object.
631 *
632 * Side effects: See the user documentation.
633 *
634 * $flow particles oper $name
635 *
636 * \return A standard Tcl result.
637 */
638static int
639FlowParticlesOp(ClientData clientData, Tcl_Interp *interp, int objc,
640                Tcl_Obj *const *objv)
641{
642    Tcl_ObjCmdProc *proc;
643    proc = GetOpFromObj(interp, nFlowParticlesOps, flowParticlesOps,
644                        CMDSPEC_ARG2, objc, objv, 0);
645    if (proc == NULL) {
646        return TCL_ERROR;
647    }
648    Flow *flow = (Flow *)clientData;
649    Tcl_Preserve(flow);
650    int result;
651    result = (*proc) (clientData, interp, objc, objv);
652    Tcl_Release(flow);
653    return result;
654}
655
656static int
657FlowBoxAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
658             Tcl_Obj *const *objv)
659{
660    Flow *flow = (Flow *)clientData;
661    const char *boxName = Tcl_GetString(objv[3]);
662    FlowBox *box = flow->createBox(boxName);
663    if (box == NULL) {
664        Tcl_AppendResult(interp, "Flow box \"", boxName,
665                         "\" already exists or could not be created",
666                         (char*)NULL);
667        return TCL_ERROR;
668    }
669    if (box->parseSwitches(interp, objc - 4, objv + 4) != TCL_OK) {
670        flow->deleteBox(boxName);
671        return TCL_ERROR;
672    }
673    NanoVis::eventuallyRedraw();
674    Tcl_SetObjResult(interp, objv[3]);
675    return TCL_OK;
676}
677
678static int
679FlowBoxConfigureOp(ClientData clientData, Tcl_Interp *interp, int objc,
680                   Tcl_Obj *const *objv)
681{
682    Flow *flow = (Flow *)clientData;
683    const char *boxName = Tcl_GetString(objv[3]);
684    FlowBox *box = flow->getBox(boxName);
685    if (box == NULL) {
686        Tcl_AppendResult(interp, "Flow box \"", boxName, "\" not found",
687                         (char*)NULL);
688        return TCL_ERROR;
689    }
690    if (box->parseSwitches(interp, objc - 4, objv + 4) != TCL_OK) {
691        return TCL_ERROR;
692    }
693    NanoVis::eventuallyRedraw();
694    return TCL_OK;
695}
696
697static int
698FlowBoxDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
699                Tcl_Obj *const *objv)
700{
701    Flow *flow = (Flow *)clientData;
702    for (int i = 3; i < objc; i++) {
703        flow->deleteBox(Tcl_GetString(objv[i]));
704    }
705    NanoVis::eventuallyRedraw();
706    return TCL_OK;
707}
708
709static int
710FlowBoxNamesOp(ClientData clientData, Tcl_Interp *interp, int objc,
711               Tcl_Obj *const *objv)
712{
713    Flow *flow = (Flow *)clientData;
714    Tcl_Obj *listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
715    std::vector<std::string> names;
716    flow->getBoxNames(names);
717    for (std::vector<std::string>::iterator itr = names.begin();
718         itr != names.end(); ++itr) {
719        Tcl_Obj *objPtr = Tcl_NewStringObj(itr->c_str(), -1);
720        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
721    }
722    Tcl_SetObjResult(interp, listObjPtr);
723    return TCL_OK;
724}
725
726static CmdSpec flowBoxOps[] = {
727    {"add",        1, FlowBoxAddOp,        4, 0, "name ?switches?",},
728    {"configure",  1, FlowBoxConfigureOp,  4, 0, "name ?switches?",},
729    {"delete",     1, FlowBoxDeleteOp,     4, 0, "name ?name...?"},
730    {"names",      1, FlowBoxNamesOp,      3, 3, ""},
731};
732
733static int nFlowBoxOps = NumCmdSpecs(flowBoxOps);
734
735/**
736 * \brief This procedure is invoked to process commands on behalf of the flow
737 *         object.
738 *
739 * Side effects:  See the user documentation.
740 *
741 * \return A standard Tcl result.
742 */
743static int
744FlowBoxOp(ClientData clientData, Tcl_Interp *interp, int objc,
745          Tcl_Obj *const *objv)
746{
747    Tcl_ObjCmdProc *proc;
748    proc = GetOpFromObj(interp, nFlowBoxOps, flowBoxOps,
749                        CMDSPEC_ARG2, objc, objv, 0);
750    if (proc == NULL) {
751        return TCL_ERROR;
752    }
753    Flow *flow = (Flow *)clientData;
754    Tcl_Preserve(flow);
755    int result;
756    result = (*proc) (clientData, interp, objc, objv);
757    Tcl_Release(flow);
758    return result;
759}
760
761/*
762 * CLIENT COMMAND:
763 *   $flow legend <width> <height>
764 *
765 * Clients use this to generate a legend image for the specified
766 * transfer function.  The legend image is a color gradient from 0
767 * to one, drawn in the given transfer function.  The resulting image
768 * is returned in the size <width> x <height>.
769 */
770static int
771FlowLegendOp(ClientData clientData, Tcl_Interp *interp, int objc,
772             Tcl_Obj *const *objv)
773{
774    Flow *flow = (Flow *)clientData;
775   
776    const char *string = Tcl_GetString(objv[1]);
777    TransferFunction *tf;
778    tf = flow->getTransferFunction();
779    if (tf == NULL) {
780        Tcl_AppendResult(interp, "unknown transfer function \"", string, "\"",
781                         (char*)NULL);
782        return TCL_ERROR;
783    }
784    const char *label;
785    label = Tcl_GetString(objv[0]);
786    int w, h;
787    if ((Tcl_GetIntFromObj(interp, objv[2], &w) != TCL_OK) ||
788        (Tcl_GetIntFromObj(interp, objv[3], &h) != TCL_OK)) {
789        return TCL_ERROR;
790    }
791    if (Flow::updatePending) {
792        NanoVis::setFlowRanges();
793    }
794    NanoVis::renderLegend(tf, Flow::magMin, Flow::magMax, w, h, label);
795    return TCL_OK;
796}
797
798static CmdSpec flowInstOps[] = {
799    {"box",         1, FlowBoxOp,        2, 0, "oper ?args?"},
800    {"configure",   1, FlowConfigureOp,  2, 0, "?switches?"},
801    {"data",        1, FlowDataOp,       2, 0, "oper ?args?"},
802    {"legend",      1, FlowLegendOp,     4, 4, "w h"},
803    {"particles",   1, FlowParticlesOp,  2, 0, "oper ?args?"}
804};
805static int nFlowInstOps = NumCmdSpecs(flowInstOps);
806
807/**
808 * \brief This procedure is invoked to process commands on behalf of the flow
809 * object.
810 *
811 * Side effects: See the user documentation.
812 *
813 * \return A standard Tcl result.
814 */
815int
816nv::FlowInstObjCmd(ClientData clientData, Tcl_Interp *interp, int objc,
817                   Tcl_Obj *const *objv)
818{
819    Tcl_ObjCmdProc *proc;
820    proc = GetOpFromObj(interp, nFlowInstOps, flowInstOps,
821                        CMDSPEC_ARG1, objc, objv, 0);
822    if (proc == NULL) {
823        return TCL_ERROR;
824    }
825    assert(CheckGL(AT));
826    Flow *flow = (Flow *)clientData;
827    Tcl_Preserve(flow);
828    int result = (*proc) (clientData, interp, objc, objv);
829    Tcl_Release(flow);
830    return result;
831}
832
833/**
834 * \brief Deletes the command associated with the flow
835 *
836 * This is called only when the command associated with the flow is destroyed.
837 */
838void
839nv::FlowInstDeleteProc(ClientData clientData)
840{
841    Flow *flow = (Flow *)clientData;
842    NanoVis::deleteFlow(flow->name());
843}
844
845static int
846FlowAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
847          Tcl_Obj *const *objv)
848{
849    const char *name = Tcl_GetString(objv[2]);
850    Tcl_CmdInfo cmdInfo;
851    if (Tcl_GetCommandInfo(interp, name, &cmdInfo)) {
852        Tcl_AppendResult(interp, "an another command \"", name,
853                         "\" already exists.", (char *)NULL);
854        return TCL_ERROR;
855    }
856    Flow *flow = NanoVis::createFlow(interp, name);
857    if (flow == NULL) {
858        Tcl_AppendResult(interp, "Flow \"", name, "\" already exists",
859                         (char*)NULL);
860        return TCL_ERROR;
861    }
862    if (flow->parseSwitches(interp, objc - 3, objv + 3) != TCL_OK) {
863        Tcl_DeleteCommand(interp, flow->name());
864        return TCL_ERROR;
865    }
866    Tcl_SetObjResult(interp, objv[2]);
867    NanoVis::eventuallyRedraw();
868    return TCL_OK;
869}
870
871static int
872FlowDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
873             Tcl_Obj *const *objv)
874{
875    for (int i = 2; i < objc; i++) {
876        Flow *flow = NanoVis::getFlow(Tcl_GetString(objv[i]));
877        if (flow != NULL) {
878            Tcl_DeleteCommandFromToken(interp, flow->getCommandToken());
879        }
880    }
881    Flow::updatePending = true;
882    NanoVis::eventuallyRedraw();
883    return TCL_OK;
884}
885
886static int
887FlowExistsOp(ClientData clientData, Tcl_Interp *interp, int objc,
888             Tcl_Obj *const *objv)
889{
890    bool value = false;
891    Flow *flow = NanoVis::getFlow(Tcl_GetString(objv[2]));
892    if (flow != NULL) {
893        value = true;
894    }
895    Tcl_SetBooleanObj(Tcl_GetObjResult(interp), (int)value);
896    return TCL_OK;
897}
898
899/**
900 * \brief flow goto number
901 */
902static int
903FlowGotoOp(ClientData clientData, Tcl_Interp *interp, int objc,
904           Tcl_Obj *const *objv)
905{
906    int nSteps;
907    if (Tcl_GetIntFromObj(interp, objv[2], &nSteps) != TCL_OK) {
908        return TCL_ERROR;
909    }
910    if ((nSteps < 0) || (nSteps > SHRT_MAX)) {
911        Tcl_AppendResult(interp, "flow goto: bad # of steps \"",
912                         Tcl_GetString(objv[2]), "\"", (char *)NULL);
913        return TCL_ERROR;
914    }
915    NanoVis::resetFlows();
916    if (Flow::updatePending) {
917        NanoVis::setFlowRanges();
918    }
919    for (int i = 0; i < nSteps; i++) {
920        NanoVis::licRenderer->convolve();
921        NanoVis::advectFlows();
922    }
923    NanoVis::eventuallyRedraw();
924    return TCL_OK;
925}
926
927static int
928FlowNamesOp(ClientData clientData, Tcl_Interp *interp, int objc,
929            Tcl_Obj *const *objv)
930{
931    Tcl_Obj *listObjPtr;
932    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
933    for (NanoVis::FlowHashmap::iterator itr = NanoVis::flowTable.begin();
934         itr != NanoVis::flowTable.end(); ++itr) {
935        Flow *flow = itr->second;
936        Tcl_Obj *objPtr = Tcl_NewStringObj(flow->name(), -1);
937        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
938    }
939    Tcl_SetObjResult(interp, listObjPtr);
940    return TCL_OK;
941}
942
943static int
944FlowNextOp(ClientData clientData, Tcl_Interp *interp, int objc,
945           Tcl_Obj *const *objv)
946{
947    assert(NanoVis::licRenderer != NULL);
948    if (Flow::updatePending) {
949        NanoVis::setFlowRanges();
950    }
951    NanoVis::licRenderer->convolve();
952    NanoVis::advectFlows();
953    NanoVis::eventuallyRedraw();
954    return TCL_OK;
955}
956
957static int
958FlowResetOp(ClientData clientData, Tcl_Interp *interp, int objc,
959            Tcl_Obj *const *objv)
960{
961    NanoVis::resetFlows();
962    return TCL_OK;
963}
964
965#if defined(HAVE_FFMPEG) || defined(HAVE_AVCONV)
966
967/**
968 * \brief Convert a Tcl_Obj representing the video format into its
969 * integer id.
970 *
971 * \param clientData Not used
972 * \param interp Interpreter to send results back to
973 * \param switchName Not used
974 * \param objPtr String representation
975 * \param record Structure record
976 * \param offset Not used
977 * \param flags Not used
978 *
979 * \return The return value is a standard Tcl result.
980 */
981static int
982VideoFormatSwitchProc(ClientData clientData, Tcl_Interp *interp,
983                      const char *switchName, Tcl_Obj *objPtr,
984                      char *record, int offset, int flags)
985{
986    Tcl_Obj **formatObjPtr = (Tcl_Obj **)(record + offset);
987    Tcl_Obj *fmtObjPtr;
988    const char *string;
989    char c;
990    int length;
991
992    string = Tcl_GetStringFromObj(objPtr, &length);
993    c = string[0];
994    if ((c == 'm') && (length > 1) &&
995        (strncmp(string, "mpeg", length) == 0)) {
996        fmtObjPtr =  Tcl_NewStringObj("mpeg1video", 10);
997    } else if ((c == 't') && (strncmp(string, "theora", length) == 0)) {
998        fmtObjPtr =  Tcl_NewStringObj("theora", 6);
999    } else if ((c == 'm') && (length > 1) &&
1000               (strncmp(string, "mov", length) == 0)) {
1001        fmtObjPtr =  Tcl_NewStringObj("mov", 3);
1002    } else {
1003        Tcl_AppendResult(interp, "bad video format \"", string,
1004                         "\": should be mpeg, theora, or mov", (char*)NULL);
1005        return TCL_ERROR;
1006    }
1007    if (*formatObjPtr != NULL) {
1008        Tcl_DecrRefCount(*formatObjPtr);
1009    }
1010    Tcl_IncrRefCount(fmtObjPtr);
1011    *formatObjPtr = fmtObjPtr;
1012    return TCL_OK;
1013}
1014
1015struct FlowVideoSwitches {
1016    float frameRate;         /**< Frame rate */
1017    int bitRate;             /**< Video bitrate */
1018    int width, height;       /**< Dimensions of video frame. */
1019    int numFrames;
1020    Tcl_Obj *formatObjPtr;
1021};
1022
1023static SwitchParseProc VideoFormatSwitchProc;
1024static SwitchCustom videoFormatSwitch = {
1025    VideoFormatSwitchProc, NULL, 0,
1026};
1027
1028static SwitchSpec flowVideoSwitches[] = {
1029    {SWITCH_INT, "-bitrate", "value",
1030     offsetof(FlowVideoSwitches, bitRate), 0},
1031    {SWITCH_CUSTOM, "-format", "string",
1032     offsetof(FlowVideoSwitches, formatObjPtr), 0, 0, &videoFormatSwitch},
1033    {SWITCH_FLOAT, "-framerate", "value",
1034     offsetof(FlowVideoSwitches, frameRate), 0},
1035    {SWITCH_INT, "-height", "integer",
1036     offsetof(FlowVideoSwitches, height), 0},
1037    {SWITCH_INT, "-numframes", "count",
1038     offsetof(FlowVideoSwitches, numFrames), 0},
1039    {SWITCH_INT, "-width", "integer",
1040     offsetof(FlowVideoSwitches, width), 0},
1041    {SWITCH_END}
1042};
1043
1044static bool
1045MakeImageFiles(char *tmpFileName,
1046               int width, int height, int numFrames,
1047               bool *cancelPtr)
1048{
1049    struct pollfd pollResults;
1050    pollResults.fd = g_fdIn;
1051    pollResults.events = POLLIN;
1052#define PENDING_TIMEOUT          10  /* milliseconds. */
1053    int timeout = PENDING_TIMEOUT;
1054
1055    int oldWidth, oldHeight;
1056    oldWidth = NanoVis::winWidth;
1057    oldHeight = NanoVis::winHeight;
1058
1059    if (width != oldWidth ||
1060        height != oldHeight) {
1061        // Resize to the requested size.
1062        NanoVis::resizeOffscreenBuffer(width, height);
1063    }
1064    NanoVis::resetFlows();
1065    *cancelPtr = false;
1066    bool result = true;
1067    size_t length = strlen(tmpFileName);
1068    for (int i = 1; i <= numFrames; i++) {
1069        if (((i & 0xF) == 0) && (poll(&pollResults, 1, timeout) > 0)) {
1070            /* If there's another command on stdin, that means the client is
1071             * trying to cancel this operation. */
1072            *cancelPtr = true;
1073            break;
1074        }
1075
1076        NanoVis::licRenderer->convolve();
1077        NanoVis::advectFlows();
1078
1079        int fboOrig;
1080        glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &fboOrig);
1081
1082        NanoVis::bindOffscreenBuffer();
1083        NanoVis::render();
1084        NanoVis::readScreen();
1085
1086        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboOrig);
1087
1088        sprintf(tmpFileName + length, "/image%d.ppm", i);
1089        result = nv::writePPMFile(tmpFileName, NanoVis::screenBuffer,
1090                                  width, height);
1091        if (!result) {
1092            break;
1093        }
1094    }
1095    if (width != oldWidth ||
1096        height != oldHeight) {
1097        NanoVis::resizeOffscreenBuffer(oldWidth, oldHeight);
1098    }
1099    tmpFileName[length] = '\0';
1100    NanoVis::resetFlows();
1101    return result;
1102}
1103
1104static int
1105MakeMovie(Tcl_Interp *interp, char *tmpFileName, const char *token,
1106          FlowVideoSwitches *switchesPtr)
1107{
1108    char cmd[BUFSIZ];
1109#ifdef HAVE_AVCONV
1110    /* Generate the movie from the frame images by exec-ing avconv */
1111    /* The avconv command is
1112     *   avconv -f image2 -i /var/tmp/xxxxx/image%d.ppm                 \
1113     *      -f outformat -b bitrate -r framerate /var/tmp/xxxxx/movie.mpeg
1114     */
1115#ifndef AVCONV
1116#  define AVCONV "/usr/bin/avconv"
1117#endif
1118    sprintf(cmd, "%s -f image2 -i %s/image%%d.ppm -f %s -b %d -r %f -",
1119            AVCONV, tmpFileName, Tcl_GetString(switchesPtr->formatObjPtr),
1120            switchesPtr->bitRate, switchesPtr->frameRate);
1121#else
1122    /* Generate the movie from the frame images by exec-ing ffmpeg */
1123    /* The ffmpeg command is
1124     *   ffmpeg -f image2 -i /var/tmp/xxxxx/image%d.ppm                 \
1125     *      -f outformat -b bitrate -r framerate /var/tmp/xxxxx/movie.mpeg
1126     */
1127#ifndef FFMPEG
1128#  define FFMPEG "/usr/bin/ffmpeg"
1129#endif
1130    sprintf(cmd, "%s -f image2 -i %s/image%%d.ppm -f %s -b %d -r %f -",
1131            FFMPEG, tmpFileName, Tcl_GetString(switchesPtr->formatObjPtr),
1132            switchesPtr->bitRate, switchesPtr->frameRate);
1133#endif
1134    TRACE("Enter: %s", cmd);
1135    FILE *f = popen(cmd, "r");
1136    if (f == NULL) {
1137        Tcl_AppendResult(interp, "can't run ffmpeg: ",
1138                         Tcl_PosixError(interp), (char *)NULL);
1139        return TCL_ERROR;
1140    }
1141    char *data = NULL;
1142    size_t total = 0;
1143    for (;;) {
1144        char buffer[BUFSIZ];
1145       
1146        ssize_t numRead = fread(buffer, sizeof(char), BUFSIZ, f);
1147        total += numRead;
1148        if (numRead == 0) {             // EOF
1149            break;
1150        }
1151        if (numRead < 0) {              // Error
1152            ERROR("Can't read movie data: %s",
1153                  Tcl_PosixError(interp));
1154            Tcl_AppendResult(interp, "can't read movie data: ",
1155                Tcl_PosixError(interp), (char *)NULL);
1156            return TCL_ERROR;
1157        }
1158        data = (char *)realloc(data, total);
1159        if (data == NULL) {
1160            ERROR("Can't append movie data to buffer %d bytes",
1161                  numRead);
1162            Tcl_AppendResult(interp, "can't append movie data to buffer",
1163                             (char *)NULL);
1164            return TCL_ERROR;
1165        }
1166        memcpy(data + (total - numRead), buffer, numRead);
1167    }
1168    if (total == 0) {
1169        ERROR("ffmpeg returned 0 bytes");
1170    }
1171    // Send zero length to client so it can deal with error
1172    sprintf(cmd,"nv>image -type movie -token \"%s\" -bytes %lu\n",
1173            token, total);
1174    // Memory for data payload is freed by this call
1175    nv::sendDataToClient(cmd, data, total);
1176    return TCL_OK;
1177}
1178
1179static int
1180FlowVideoOp(ClientData clientData, Tcl_Interp *interp, int objc,
1181            Tcl_Obj *const *objv)
1182{
1183    FlowVideoSwitches switches;
1184    const char *token;
1185
1186    token = Tcl_GetString(objv[2]);
1187    switches.frameRate = 25.0f;                // Default frame rate 25 fps
1188    switches.bitRate = 6000000;                // Default video bit rate.
1189    switches.width = NanoVis::winWidth;
1190    switches.height = NanoVis::winHeight;
1191    switches.numFrames = 100;
1192    switches.formatObjPtr = Tcl_NewStringObj("mpeg1video", 10);
1193    Tcl_IncrRefCount(switches.formatObjPtr);
1194    if (ParseSwitches(interp, flowVideoSwitches,
1195                objc - 3, objv + 3, &switches, SWITCH_DEFAULTS) < 0) {
1196        return TCL_ERROR;
1197    }
1198    if ((switches.width < 0) || (switches.width > SHRT_MAX) ||
1199        (switches.height < 0) || (switches.height > SHRT_MAX)) {
1200        Tcl_AppendResult(interp, "bad dimensions for video", (char *)NULL);
1201        return TCL_ERROR;
1202    }
1203    if ((switches.frameRate < 0.0f) || (switches.frameRate > 30.0f)) {
1204        Tcl_AppendResult(interp, "bad frame rate.", (char *)NULL);
1205        return TCL_ERROR;
1206    }
1207    if (switches.bitRate < 0) {
1208        Tcl_AppendResult(interp, "bad bit rate.", (char *)NULL);
1209        return TCL_ERROR;
1210    }
1211    if (NanoVis::licRenderer == NULL) {
1212        Tcl_AppendResult(interp, "no lic renderer.", (char *)NULL);
1213        return TCL_ERROR;
1214    }
1215    TRACE("FLOW started");
1216
1217    char *tmpFileName;
1218    char nameTemplate[200];
1219    strcpy(nameTemplate,"/var/tmp/flowXXXXXX");
1220    tmpFileName = mkdtemp(nameTemplate);
1221    int result = TCL_OK;
1222    if (tmpFileName == NULL) {
1223        Tcl_AppendResult(interp, "can't create temporary directory \"",
1224                         nameTemplate, "\" for frame image files: ",
1225                         Tcl_PosixError(interp), (char *)NULL);
1226        return TCL_ERROR;
1227    }
1228    size_t length = strlen(tmpFileName);
1229    bool canceled = false;
1230    if (MakeImageFiles(tmpFileName,
1231                       switches.width, switches.height, switches.numFrames,
1232                       &canceled)) {
1233        result = TCL_OK;
1234    } else {
1235        result = TCL_ERROR;
1236    }
1237    if ((result == TCL_OK) && (!canceled)) {
1238        result = MakeMovie(interp, tmpFileName, token, &switches);
1239    }
1240    for (int i = 1; i <= switches.numFrames; i++) {
1241        sprintf(tmpFileName + length, "/image%d.ppm", i);
1242        unlink(tmpFileName);
1243    }
1244    tmpFileName[length] = '\0';
1245    rmdir(tmpFileName);
1246    FreeSwitches(flowVideoSwitches, &switches, 0);
1247    return result;
1248}
1249#else
1250/**
1251 *  Not implemented
1252 */
1253static int
1254FlowVideoOp(ClientData clientData, Tcl_Interp *interp, int objc,
1255            Tcl_Obj *const *objv)
1256{
1257    return TCL_OK;
1258}
1259#endif /* HAVE_FFMPEG */
1260
1261static CmdSpec flowCmdOps[] = {
1262    {"add",      1, FlowAddOp,     3, 0, "name ?option value...?",},
1263    {"delete",   1, FlowDeleteOp,  3, 0, "name ?name...?",},
1264    {"exists",   1, FlowExistsOp,  3, 3, "name",},
1265    {"goto",     1, FlowGotoOp,    3, 3, "nSteps",},
1266    {"names",    1, FlowNamesOp,   2, 2, "",},
1267    {"next",     2, FlowNextOp,    2, 2, "",},
1268    {"reset",    1, FlowResetOp,   2, 2, "",},
1269    {"video",    1, FlowVideoOp,   3, 0, "token ?switches...?",},
1270};
1271static int nFlowCmdOps = NumCmdSpecs(flowCmdOps);
1272
1273static int
1274FlowCmdProc(ClientData clientData, Tcl_Interp *interp, int objc,
1275            Tcl_Obj *const *objv)
1276{
1277    Tcl_ObjCmdProc *proc;
1278
1279    proc = GetOpFromObj(interp, nFlowCmdOps, flowCmdOps,
1280                        CMDSPEC_ARG1, objc, objv, 0);
1281    if (proc == NULL) {
1282        return TCL_ERROR;
1283    }
1284    return (*proc) (clientData, interp, objc, objv);
1285}
1286
1287/**
1288 *\brief This procedure is invoked to initialize the "flow" command.
1289 *
1290 * Side effects:
1291 *    Creates the new command and adds a new entry into a global Tcl
1292 *    associative array.
1293 */
1294void
1295nv::FlowCmdInitProc(Tcl_Interp *interp, ClientData clientData)
1296{
1297    Tcl_CreateObjCommand(interp, "flow", FlowCmdProc, clientData, NULL);
1298}
Note: See TracBrowser for help on using the repository browser.