source: nanovis/trunk/FlowCmd.cpp @ 4643

Last change on this file since 4643 was 4353, checked in by ldelgass, 10 years ago

Use avconv instead of ffmpeg if found (Debian uses the libav fork and ffmpeg
is removed in e.g. Ubuntu 14.04).

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