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

Last change on this file since 4893 was 4893, checked in by ldelgass, 7 years ago

merge r4056 from trunk

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