source: nanovis/branches/1.1/FlowCmd.cpp @ 4923

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

Merge serveral changes from trunk. Does not include threading, world space
changes, etc.

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