source: nanovis/trunk/FlowCmd.cpp @ 4923

Last change on this file since 4923 was 4895, checked in by ldelgass, 5 years ago

Merge some fixes from release branch

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