source: trunk/gui/scripts/nanovisviewer.tcl @ 1423

Last change on this file since 1423 was 1423, checked in by gah, 16 years ago

add camera method to update camera settings

File size: 65.7 KB
Line 
1
2# ----------------------------------------------------------------------
3#  COMPONENT: nanovisviewer - 3D volume rendering
4#
5#  This widget performs volume rendering on 3D scalar/vector datasets.
6#  It connects to the Nanovis server running on a rendering farm,
7#  transmits data, and displays the results.
8# ======================================================================
9#  AUTHOR:  Michael McLennan, Purdue University
10#  Copyright (c) 2004-2005  Purdue Research Foundation
11#
12#  See the file "license.terms" for information on usage and
13#  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
14# ======================================================================
15package require Itk
16package require BLT
17package require Img
18
19option add *NanovisViewer.width 4i widgetDefault
20option add *NanovisViewer*cursor crosshair widgetDefault
21option add *NanovisViewer.height 4i widgetDefault
22option add *NanovisViewer.foreground black widgetDefault
23option add *NanovisViewer.controlBackground gray widgetDefault
24option add *NanovisViewer.controlDarkBackground #999999 widgetDefault
25option add *NanovisViewer.plotBackground black widgetDefault
26option add *NanovisViewer.plotForeground white widgetDefault
27option add *NanovisViewer.plotOutline gray widgetDefault
28option add *NanovisViewer.font \
29    -*-helvetica-medium-r-normal-*-12-* widgetDefault
30
31# must use this name -- plugs into Rappture::resources::load
32proc NanovisViewer_init_resources {} {
33    Rappture::resources::register \
34        nanovis_server Rappture::NanovisViewer::SetServerList
35}
36
37itcl::class Rappture::NanovisViewer {
38    inherit Rappture::VisViewer
39
40    itk_option define -plotforeground plotForeground Foreground ""
41    itk_option define -plotbackground plotBackground Background ""
42    itk_option define -plotoutline plotOutline PlotOutline ""
43
44    constructor { hostlist args } {
45        Rappture::VisViewer::constructor $hostlist
46    } {
47        # defined below
48    }
49    destructor {
50        # defined below
51    }
52    public proc SetServerList { namelist } {
53        Rappture::VisViewer::SetServerList "nanovis" $namelist
54    }
55    public method add {dataobj {settings ""}}
56    public method get {args}
57    public method delete {args}
58    public method scale {args}
59    public method download {option args}
60    public method parameters {title args} {
61        # do nothing
62    }
63    public method limits { tf }
64    public method isconnected {}
65    public method updatetransferfuncs {}
66    public method rmdupmarker { m x }
67    public method overmarker { m x }
68    public method camera {option args}
69
70    protected method Connect {}
71    protected method Disconnect {}
72
73    protected method SendCmd {string}
74    protected method SendDataObjs {}
75    protected method SendTransferFuncs {}
76
77    protected method ReceiveImage { args }
78    protected method ReceiveLegend { ivol vmin vmax size }
79    protected method ReceiveData { args }
80
81    protected method Rebuild {}
82    protected method CurrentVolumeIds {{what -all}}
83    protected method Zoom {option}
84    protected method Pan {option x y}
85    protected method Rotate {option x y}
86    protected method Probe {option args}
87    protected method Marker {index option args}
88    protected method Slice {option args}
89    protected method SlicerTip {axis}
90
91    protected method State {comp}
92    protected method DoResize {}
93    protected method FixLegend {}
94    protected method FixSettings {what {value ""}}
95
96    # The following methods are only used by this class.
97    private method NameTransferFunc { ivol }
98    private method ComputeTransferFunc { tf }
99    private method AddIsoMarker { x y }
100    private method ParseMarkersOption { tf ivol markers }
101    private method ParseLevelsOption { tf ivol levels }
102    private method EventuallyResize { w h }
103    private method EventuallyResizeLegend { }
104
105    private method BuildCutplanesTab {}
106    private method BuildViewTab {}
107    private method BuildVolumeTab {}
108    private method BuildCameraTab {}
109    private method PanCamera {}
110
111    private variable outbuf_       ;# buffer for outgoing commands
112
113    private variable dlist_ ""     ;# list of data objects
114    private variable allDataObjs_
115    private variable id2style_     ;# maps id => style settings
116    private variable obj2ovride_   ;# maps dataobj => style override
117    private variable obj2id_       ;# maps dataobj => volume ID in server
118    private variable id2obj_       ;# maps dataobj => volume ID in server
119    private variable sendobjs_ ""  ;# list of data objs to send to server
120    private variable receiveIds_   ;# list of data objs to send to server
121    private variable obj2styles_   ;# maps id => style settings
122    private variable style2ids_    ;# maps id => style settings
123
124    private variable click_        ;# info used for _rotate operations
125    private variable limits_       ;# autoscale min/max for all axes
126    private variable view_         ;# view params for 3D view
127    private variable isomarkers_    ;# array of isosurface level values 0..1
128    private common   settings_
129    private variable activeTf_ ""  ;# The currently active transfer function.
130    # This
131    # indicates which isomarkers and transfer
132    # function to use when changing markers,
133    # opacity, or thickness.
134    #common _downloadPopup          ;# download options from popup
135    private common hardcopy_
136    private variable width_ 0
137    private variable height_ 0
138}
139
140itk::usual NanovisViewer {
141    keep -background -foreground -cursor -font
142    keep -plotbackground -plotforeground
143}
144
145# ----------------------------------------------------------------------
146# CONSTRUCTOR
147# ----------------------------------------------------------------------
148itcl::body Rappture::NanovisViewer::constructor {hostlist args} {
149
150    # Draw legend event
151    $_dispatcher register !legend
152    $_dispatcher dispatch $this !legend "[itcl::code $this FixLegend]; list"
153    # Send dataobjs event
154    $_dispatcher register !send_dataobjs
155    $_dispatcher dispatch $this !send_dataobjs \
156        "[itcl::code $this SendDataObjs]; list"
157    # Send transfer functions event
158    $_dispatcher register !send_transfunc
159    $_dispatcher dispatch $this !send_transfunc \
160        "[itcl::code $this SendTransferFuncs]; list"
161    # Rebuild event
162    $_dispatcher register !rebuild
163    $_dispatcher dispatch $this !rebuild "[itcl::code $this Rebuild]; list"
164
165    # Draw legend event
166    $_dispatcher register !resize
167    $_dispatcher dispatch $this !resize "[itcl::code $this DoResize]; list"
168
169    set outbuf_ ""
170
171    #
172    # Populate parser with commands handle incoming requests
173    #
174    $_parser alias image [itcl::code $this ReceiveImage]
175    $_parser alias legend [itcl::code $this ReceiveLegend]
176    $_parser alias data [itcl::code $this ReceiveData]
177    #$_parser alias print [itcl::code $this ReceivePrint]
178
179    # Initialize the view to some default parameters.
180    array set view_ {
181        theta   45
182        phi     45
183        psi     0
184        zoom    1.0
185        pan-x   0
186        pan-y   0
187    }
188    set obj2id_(count) 0
189    set id2obj_(count) 0
190    set limits_(vmin) 0.0
191    set limits_(vmax) 1.0
192
193    set settings_($this-theta) $view_(theta)
194    set settings_($this-phi)   $view_(phi)
195    set settings_($this-psi)   $view_(psi)
196    set settings_($this-pan-x) $view_(pan-x)
197    set settings_($this-pan-y) $view_(pan-y)
198    set settings_($this-zoom)  $view_(zoom)
199
200    set f [$itk_component(main) component controls]
201    itk_component add reset {
202        button $f.reset -borderwidth 1 -padx 1 -pady 1 \
203            -highlightthickness 0 \
204            -image [Rappture::icon reset-view] \
205            -command [itcl::code $this Zoom reset]
206    } {
207        usual
208        ignore -highlightthickness
209    }
210    pack $itk_component(reset) -side top -padx 2 -pady 2
211    Rappture::Tooltip::for $itk_component(reset) "Reset the view to the default zoom level"
212
213    itk_component add zoomin {
214        button $f.zin -borderwidth 1 -padx 1 -pady 1 \
215            -highlightthickness 0 \
216            -image [Rappture::icon zoom-in] \
217            -command [itcl::code $this Zoom in]
218    } {
219        usual
220        ignore -highlightthickness
221    }
222    pack $itk_component(zoomin) -side top -padx 2 -pady 2
223    Rappture::Tooltip::for $itk_component(zoomin) "Zoom in"
224
225    itk_component add zoomout {
226        button $f.zout -borderwidth 1 -padx 1 -pady 1 \
227            -highlightthickness 0 \
228            -image [Rappture::icon zoom-out] \
229            -command [itcl::code $this Zoom out]
230    } {
231        usual
232        ignore -highlightthickness
233    }
234    pack $itk_component(zoomout) -side top -padx 2 -pady 2
235    Rappture::Tooltip::for $itk_component(zoomout) "Zoom out"
236
237    #
238    # Volume toggle...
239    #
240    itk_component add volume {
241        label $f.volume -borderwidth 1 -relief sunken \
242            -image [Rappture::icon volume-on]
243    }
244    bind $itk_component(volume) <ButtonPress> \
245        [itcl::code $this Slice volume toggle]
246    Rappture::Tooltip::for $itk_component(volume) \
247        "Toggle the volume cloud on/off"
248    pack $itk_component(volume) -ipadx 1 -ipady 1 -padx 2 -pady 2
249
250    BuildViewTab
251    BuildVolumeTab
252    BuildCutplanesTab
253    BuildCameraTab
254
255    # Legend
256
257    set _image(legend) [image create photo]
258    itk_component add legend {
259        canvas $itk_component(plotarea).legend -height 50 -highlightthickness 0
260    } {
261        usual
262        ignore -highlightthickness
263        rename -background -plotbackground plotBackground Background
264    }
265    bind $itk_component(legend) <Configure> \
266        [itcl::code $this EventuallyResizeLegend]
267
268    # Hack around the Tk panewindow.  The problem is that the requested
269    # size of the 3d view isn't set until an image is retrieved from
270    # the server.  So the panewindow uses the tiny size.
271    set w [expr [winfo reqwidth $itk_component(hull)] - 80]
272    pack forget $itk_component(3dview)
273    blt::table $itk_component(plotarea) \
274        0,0 $itk_component(3dview) -fill both \
275        1,0 $itk_component(legend) -fill x
276    blt::table configure $itk_component(plotarea) r1 -resize none
277
278    # Bindings for rotation via mouse
279    bind $itk_component(3dview) <ButtonPress-1> \
280        [itcl::code $this Rotate click %x %y]
281    bind $itk_component(3dview) <B1-Motion> \
282        [itcl::code $this Rotate drag %x %y]
283    bind $itk_component(3dview) <ButtonRelease-1> \
284        [itcl::code $this Rotate release %x %y]
285    bind $itk_component(3dview) <Configure> \
286        [itcl::code $this EventuallyResize %w %h]
287
288    # Bindings for panning via mouse
289    bind $itk_component(3dview) <ButtonPress-2> \
290        [itcl::code $this Pan click %x %y]
291    bind $itk_component(3dview) <B2-Motion> \
292        [itcl::code $this Pan drag %x %y]
293    bind $itk_component(3dview) <ButtonRelease-2> \
294        [itcl::code $this Pan release %x %y]
295
296    # Bindings for panning via keyboard
297    bind $itk_component(3dview) <KeyPress-Left> \
298        [itcl::code $this Pan set -10 0]
299    bind $itk_component(3dview) <KeyPress-Right> \
300        [itcl::code $this Pan set 10 0]
301    bind $itk_component(3dview) <KeyPress-Up> \
302        [itcl::code $this Pan set 0 -10]
303    bind $itk_component(3dview) <KeyPress-Down> \
304        [itcl::code $this Pan set 0 10]
305    bind $itk_component(3dview) <Shift-KeyPress-Left> \
306        [itcl::code $this Pan set -2 0]
307    bind $itk_component(3dview) <Shift-KeyPress-Right> \
308        [itcl::code $this Pan set 2 0]
309    bind $itk_component(3dview) <Shift-KeyPress-Up> \
310        [itcl::code $this Pan set 0 -2]
311    bind $itk_component(3dview) <Shift-KeyPress-Down> \
312        [itcl::code $this Pan set 0 2]
313
314    # Bindings for zoom via keyboard
315    bind $itk_component(3dview) <KeyPress-Prior> \
316        [itcl::code $this Zoom out]
317    bind $itk_component(3dview) <KeyPress-Next> \
318        [itcl::code $this Zoom in]
319
320    bind $itk_component(3dview) <Enter> "focus $itk_component(3dview)"
321
322    if {[string equal "x11" [tk windowingsystem]]} {
323        # Bindings for zoom via mouse
324        bind $itk_component(3dview) <4> [itcl::code $this Zoom out]
325        bind $itk_component(3dview) <5> [itcl::code $this Zoom in]
326    }
327
328    set _image(download) [image create photo]
329
330    eval itk_initialize $args
331
332    Connect
333}
334
335# ----------------------------------------------------------------------
336# DESTRUCTOR
337# ----------------------------------------------------------------------
338itcl::body Rappture::NanovisViewer::destructor {} {
339    set sendobjs_ ""  ;# stop any send in progress
340    $_dispatcher cancel !rebuild
341    $_dispatcher cancel !send_dataobjs
342    $_dispatcher cancel !send_transfunc
343    image delete $_image(plot)
344    image delete $_image(legend)
345    image delete $_image(download)
346    array unset settings_ $this-*
347}
348
349# ----------------------------------------------------------------------
350# USAGE: add <dataobj> ?<settings>?
351#
352# Clients use this to add a data object to the plot.  The optional
353# <settings> are used to configure the plot.  Allowed settings are
354# -color, -brightness, -width, -linestyle, and -raise.
355# ----------------------------------------------------------------------
356itcl::body Rappture::NanovisViewer::add {dataobj {settings ""}} {
357    array set params {
358        -color auto
359        -width 1
360        -linestyle solid
361        -brightness 0
362        -raise 0
363        -description ""
364        -param ""
365    }
366    foreach {opt val} $settings {
367        if {![info exists params($opt)]} {
368            error "bad setting \"$opt\": should be [join [lsort [array names params]] {, }]"
369        }
370        set params($opt) $val
371    }
372    if {$params(-color) == "auto" || $params(-color) == "autoreset"} {
373        # can't handle -autocolors yet
374        set params(-color) black
375    }
376
377    set pos [lsearch -exact $dataobj $dlist_]
378    if {$pos < 0} {
379        lappend dlist_ $dataobj
380        set allDataObjs_($dataobj) 1
381        set obj2ovride_($dataobj-color) $params(-color)
382        set obj2ovride_($dataobj-width) $params(-width)
383        set obj2ovride_($dataobj-raise) $params(-raise)
384        $_dispatcher event -idle !rebuild
385    }
386}
387
388# ----------------------------------------------------------------------
389# USAGE: get ?-objects?
390# USAGE: get ?-image 3dview|legend?
391#
392# Clients use this to query the list of objects being plotted, in
393# order from bottom to top of this result.  The optional "-image"
394# flag can also request the internal images being shown.
395# ----------------------------------------------------------------------
396itcl::body Rappture::NanovisViewer::get {args} {
397    if {[llength $args] == 0} {
398        set args "-objects"
399    }
400
401    set op [lindex $args 0]
402    switch -- $op {
403      -objects {
404        # put the dataobj list in order according to -raise options
405        set dlist $dlist_
406        foreach obj $dlist {
407            if {[info exists obj2ovride_($obj-raise)] && $obj2ovride_($obj-raise)} {
408                set i [lsearch -exact $dlist $obj]
409                if {$i >= 0} {
410                    set dlist [lreplace $dlist $i $i]
411                    lappend dlist $obj
412                }
413            }
414        }
415        return $dlist
416      }
417      -image {
418        if {[llength $args] != 2} {
419            error "wrong # args: should be \"get -image 3dview|legend\""
420        }
421        switch -- [lindex $args end] {
422            3dview {
423                return $_image(plot)
424            }
425            legend {
426                return $_image(legend)
427            }
428            default {
429                error "bad image name \"[lindex $args end]\": should be 3dview or legend"
430            }
431        }
432      }
433      default {
434        error "bad option \"$op\": should be -objects or -image"
435      }
436    }
437}
438
439# ----------------------------------------------------------------------
440# USAGE: delete ?<dataobj1> <dataobj2> ...?
441#
442#       Clients use this to delete a dataobj from the plot.  If no dataobjs
443#       are specified, then all dataobjs are deleted.  No data objects are
444#       deleted.  They are only removed from the display list.
445#
446# ----------------------------------------------------------------------
447itcl::body Rappture::NanovisViewer::delete {args} {
448    if {[llength $args] == 0} {
449        set args $dlist_
450    }
451    # Delete all specified dataobjs
452    set changed 0
453    foreach dataobj $args {
454        set pos [lsearch -exact $dlist_ $dataobj]
455        if { $pos >= 0 } {
456            set dlist_ [lreplace $dlist_ $pos $pos]
457            foreach key [array names obj2ovride_ $dataobj-*] {
458                unset obj2ovride_($key)
459            }
460            set changed 1
461        }
462    }
463    # If anything changed, then rebuild the plot
464    if {$changed} {
465        $_dispatcher event -idle !rebuild
466    }
467}
468
469# ----------------------------------------------------------------------
470# USAGE: scale ?<data1> <data2> ...?
471#
472# Sets the default limits for the overall plot according to the
473# limits of the data for all of the given <data> objects.  This
474# accounts for all objects--even those not showing on the screen.
475# Because of this, the limits are appropriate for all objects as
476# the user scans through data in the ResultSet viewer.
477# ----------------------------------------------------------------------
478itcl::body Rappture::NanovisViewer::scale {args} {
479    foreach val {xmin xmax ymin ymax zmin zmax vmin vmax} {
480        set limits_($val) ""
481    }
482    foreach obj $args {
483        foreach axis {x y z v} {
484
485            foreach { min max } [$obj limits $axis] break
486
487            if {"" != $min && "" != $max} {
488                if {"" == $limits_(${axis}min)} {
489                    set limits_(${axis}min) $min
490                    set limits_(${axis}max) $max
491                } else {
492                    if {$min < $limits_(${axis}min)} {
493                        set limits_(${axis}min) $min
494                    }
495                    if {$max > $limits_(${axis}max)} {
496                        set limits_(${axis}max) $max
497                    }
498                }
499            }
500        }
501    }
502}
503
504# ----------------------------------------------------------------------
505# USAGE: download coming
506# USAGE: download controls <downloadCommand>
507# USAGE: download now
508#
509# Clients use this method to create a downloadable representation
510# of the plot.  Returns a list of the form {ext string}, where
511# "ext" is the file extension (indicating the type of data) and
512# "string" is the data itself.
513# ----------------------------------------------------------------------
514itcl::body Rappture::NanovisViewer::download {option args} {
515    switch $option {
516        coming {
517            if {[catch {blt::winop snap $itk_component(plotarea) $_image(download)}]} {
518                $_image(download) configure -width 1 -height 1
519                $_image(download) put #000000
520            }
521        }
522        controls {
523            # no controls for this download yet
524            return ""
525        }
526        now {
527            # Get the image data (as base64) and decode it back to binary.
528            # This is better than writing to temporary files.  When we switch
529            # to the BLT picture image it won't be necessary to decode the
530            # image data.
531            set bytes [$_image(plot) data -format "jpeg -quality 100"]
532            set bytes [Rappture::encoding::decode -as b64 $bytes]
533            return [list .jpg $bytes]
534        }
535        default {
536            error "bad option \"$option\": should be coming, controls, now"
537        }
538    }
539}
540
541# ----------------------------------------------------------------------
542# USAGE: Connect ?<host:port>,<host:port>...?
543#
544# Clients use this method to establish a connection to a new
545# server, or to reestablish a connection to the previous server.
546# Any existing connection is automatically closed.
547# ----------------------------------------------------------------------
548itcl::body Rappture::NanovisViewer::Connect {} {
549    set _hosts [GetServerList "nanovis"]
550    if { "" == $_hosts } {
551        return 0
552    }
553    set result [VisViewer::Connect $_hosts]
554    if { $result } {
555        set w [winfo width $itk_component(3dview)]
556        set h [winfo height $itk_component(3dview)]
557        EventuallyResize $w $h
558    }
559    return $result
560}
561
562#
563# isconnected --
564#
565#       Indicates if we are currently connected to the visualization server.
566#
567itcl::body Rappture::NanovisViewer::isconnected {} {
568    return [VisViewer::IsConnected]
569}
570
571#
572# Disconnect --
573#
574#       Clients use this method to disconnect from the current rendering
575#       server.
576#
577itcl::body Rappture::NanovisViewer::Disconnect {} {
578    VisViewer::Disconnect
579
580    # disconnected -- no more data sitting on server
581    set outbuf_ ""
582    catch {unset obj2id_}
583    array unset id2obj_
584    set obj2id_(count) 0
585    set id2obj_(count) 0
586    set sendobjs_ ""
587}
588
589#
590# SendCmd
591#
592#       Send commands off to the rendering server.  If we're currently
593#       sending data objects to the server, buffer the commands to be
594#       sent later.
595#
596itcl::body Rappture::NanovisViewer::SendCmd {string} {
597    if {[llength $sendobjs_] > 0} {
598        append outbuf_ $string "\n"
599    } else {
600        foreach line [split $string \n] {
601            SendEcho >>line $line
602        }
603        SendBytes $string
604    }
605}
606
607# ----------------------------------------------------------------------
608# USAGE: SendDataObjs
609#
610# Used internally to send a series of volume objects off to the
611# server.  Sends each object, a little at a time, with updates in
612# between so the interface doesn't lock up.
613# ----------------------------------------------------------------------
614itcl::body Rappture::NanovisViewer::SendDataObjs {} {
615    blt::busy hold $itk_component(hull); update idletasks
616    foreach dataobj $sendobjs_ {
617        foreach comp [$dataobj components] {
618            # send the data as one huge base64-encoded mess -- yuck!
619            set data [$dataobj values $comp]
620            set nbytes [string length $data]
621            if { ![SendBytes "volume data follows $nbytes"] } {
622                return
623            }
624            if { ![SendBytes $data] } {
625                return
626            }
627            set ivol $obj2id_(count)
628            incr obj2id_(count)
629
630            set id2obj_($ivol) [list $dataobj $comp]
631            set obj2id_($dataobj-$comp) $ivol
632            NameTransferFunc $ivol
633            set receiveIds_($ivol) 1
634        }
635    }
636    set sendobjs_ ""
637    blt::busy release $itk_component(hull)
638
639    # activate the proper volume
640    set first [lindex [get] 0]
641    if {"" != $first} {
642        set axis [$first hints updir]
643        if {"" != $axis} {
644            SendCmd "up $axis"
645        }
646        # The active transfer function is by default the first component of
647        # the first data object.  This assumes that the data is always
648        # successfully transferred.
649        set comp [lindex [$first components] 0]
650        set activeTf_ $id2style_($obj2id_($first-$comp))
651    }
652    foreach key [array names obj2id_ *-*] {
653        set state [string match $first-* $key]
654        set ivol $obj2id_($key)
655        SendCmd "volume state $state $ivol"
656    }
657
658    # sync the state of slicers
659    set vols [CurrentVolumeIds -cutplanes]
660    foreach axis {x y z} {
661        SendCmd "cutplane state [State ${axis}CutButton] $axis $vols"
662        set pos [expr {0.01*[$itk_component(${axis}CutScale) get]}]
663        SendCmd "cutplane position $pos $axis $vols"
664    }
665    SendCmd "volume data state [State volume] $vols"
666
667    if 0 {
668    # Add this when we fix grid for volumes
669    SendCmd "volume axis label x \"\""
670    SendCmd "volume axis label y \"\""
671    SendCmd "volume axis label z \"\""
672    SendCmd "grid axisname x X eV"
673    SendCmd "grid axisname y Y eV"
674    SendCmd "grid axisname z Z eV"
675    }
676    # if there are any commands in the buffer, send them now that we're done
677    SendBytes $outbuf_
678    set outbuf_ ""
679}
680
681# ----------------------------------------------------------------------
682# USAGE: SendTransferFuncs
683# ----------------------------------------------------------------------
684itcl::body Rappture::NanovisViewer::SendTransferFuncs {} {
685    if { $activeTf_ == "" } {
686        return
687    }
688    set tf $activeTf_
689    set first [lindex [get] 0]
690
691    # Insure that the global opacity and thickness settings (in the slider
692    # settings widgets) are used for the active transfer-function.  Update the
693    # values in the settings_ varible.
694    set value $settings_($this-opacity)
695    set opacity [expr { double($value) * 0.01 }]
696    set settings_($this-$tf-opacity) $opacity
697    set value $settings_($this-thickness)
698    # Scale values between 0.00001 and 0.01000
699    set thickness [expr {double($value) * 0.0001}]
700    set settings_($this-$tf-thickness) $thickness
701
702    if { ![info exists $obj2styles_($first)] } {
703        foreach tf $obj2styles_($first) {
704            ComputeTransferFunc $tf
705        }
706        $_dispatcher event -idle !legend
707    }
708}
709
710# ----------------------------------------------------------------------
711# USAGE: ReceiveImage -bytes <size> -type <type> -token <token>
712#
713# Invoked automatically whenever the "image" command comes in from
714# the rendering server.  Indicates that binary image data with the
715# specified <size> will follow.
716# ----------------------------------------------------------------------
717set counter 0
718itcl::body Rappture::NanovisViewer::ReceiveImage { args } {
719    if { ![isconnected] } {
720        return
721    }
722    global counter
723    incr counter
724    array set info {
725        -token "???"
726        -bytes 0
727        -type image
728    }
729    array set info $args
730    set bytes [ReceiveBytes $info(-bytes)]
731    ReceiveEcho <<line "<read $info(-bytes) bytes for [image width $_image(plot)]x[image height $_image(plot)] image>"
732    if { $info(-type) == "image" } {
733        $_image(plot) configure -data $bytes
734    } elseif { $info(type) == "print" } {
735        set tag $this-print-$info(-token)
736        set hardcopy_($tag) $bytes
737    }
738}
739
740#
741# ReceiveLegend --
742#
743#       The procedure is the response from the render server to each "legend"
744#       command.  The server sends back a "legend" command invoked our
745#       the slave interpreter.  The purpose is to collect data of the image
746#       representing the legend in the canvas.  In addition, the isomarkers
747#       of the active transfer function are displayed.
748#
749#       I don't know is this is the right place to display the isomarkers.
750#       I don't know all the different paths used to draw the plot. There's
751#       "Rebuild", "add", etc.
752#
753itcl::body Rappture::NanovisViewer::ReceiveLegend { tf vmin vmax size } {
754    if { ![isconnected] } {
755        return
756    }
757    set bytes [ReceiveBytes $size]
758    $_image(legend) configure -data $bytes
759    ReceiveEcho <<line "<read $size bytes for [image width $_image(legend)]x[image height $_image(legend)] legend>"
760
761    set c $itk_component(legend)
762    set w [winfo width $c]
763    set h [winfo height $c]
764    #foreach { dataobj comp } $id2obj_($ivol) break
765    set lx 10
766    set ly [expr {$h - 1}]
767    if {"" == [$c find withtag transfunc]} {
768        $c create image 10 10 -anchor nw \
769            -image $_image(legend) -tags transfunc
770        $c create text $lx $ly -anchor sw \
771            -fill $itk_option(-plotforeground) -tags "limits vmin"
772        $c create text [expr {$w-$lx}] $ly -anchor se \
773            -fill $itk_option(-plotforeground) -tags "limits vmax"
774        $c lower transfunc
775        $c bind transfunc <ButtonRelease-1> \
776            [itcl::code $this AddIsoMarker %x %y]
777    }
778    # Display the markers used by the active transfer function.
779    #set tf $activeTf_
780
781    array set limits [limits $tf]
782    $c itemconfigure vmin -text [format %.2g $limits(min)]
783    $c coords vmin $lx $ly
784
785    $c itemconfigure vmax -text [format %.2g $limits(max)]
786    $c coords vmax [expr {$w-$lx}] $ly
787
788    if { [info exists isomarkers_($tf)] } {
789        foreach m $isomarkers_($tf) {
790            $m visible yes
791        }
792    }
793}
794
795#
796# ReceiveData --
797#
798#       The procedure is the response from the render server to each "data
799#       follows" command.  The server sends back a "data" command invoked our
800#       the slave interpreter.  The purpose is to collect the min/max of the
801#       volume sent to the render server.  Since the client (nanovisviewer)
802#       doesn't parse 3D data formats, we rely on the server (nanovis) to
803#       tell us what the limits are.  Once we've received the limits to all
804#       the data we've sent (tracked by receiveIds_) we can then determine
805#       what the transfer functions are for these volumes.
806#
807#
808#       Note: There is a considerable tradeoff in having the server report
809#             back what the data limits are.  It means that much of the code
810#             having to do with transfer-functions has to wait for the data
811#             to come back, since the isomarkers are calculated based upon
812#             the data limits.  The client code is much messier because of
813#             this.  The alternative is to parse any of the 3D formats on the
814#             client side.
815#
816itcl::body Rappture::NanovisViewer::ReceiveData { args } {
817    if { ![isconnected] } {
818        return
819    }
820    # Arguments from server are name value pairs. Stuff them in an array.
821    array set info $args
822
823    set ivol $info(id);         # Id of volume created by server.
824
825    set limits_($ivol-min) $info(min);  # Minimum value of the volume.
826    set limits_($ivol-max) $info(max);  # Maximum value of the volume.
827    set limits_(vmin)      $info(vmin); # Overall minimum value.
828    set limits_(vmax)      $info(vmax); # Overall maximum value.
829
830    unset receiveIds_($ivol)
831    if { [array size receiveIds_] == 0 } {
832        updatetransferfuncs
833    }
834}
835
836# ----------------------------------------------------------------------
837# USAGE: Rebuild
838#
839# Called automatically whenever something changes that affects the
840# data in the widget.  Clears any existing data and rebuilds the
841# widget to display new data.
842# ----------------------------------------------------------------------
843itcl::body Rappture::NanovisViewer::Rebuild {} {
844    # Hide all the isomarkers. Can't remove them. Have to remember the
845    # settings since the user may have created/deleted/moved markers.
846
847    foreach tf [array names isomarkers_] {
848        foreach m $isomarkers_($tf) {
849            $m visible no
850        }
851    }
852
853    # in the midst of sending data? then bail out
854    if {[llength $sendobjs_] > 0} {
855        return
856    }
857
858    # Find any new data that needs to be sent to the server.  Queue this up on
859    # the sendobjs_ list, and send it out a little at a time.  Do this first,
860    # before we rebuild the rest.
861    foreach dataobj [get] {
862        set comp [lindex [$dataobj components] 0]
863        if {![info exists obj2id_($dataobj-$comp)]} {
864            set i [lsearch -exact $sendobjs_ $dataobj]
865            if {$i < 0} {
866                lappend sendobjs_ $dataobj
867            }
868        }
869    }
870    set w [winfo width $itk_component(3dview)]
871    set h [winfo height $itk_component(3dview)]
872    EventuallyResize $w $h
873
874    #
875    # Reset the camera and other view parameters
876    #
877    set xyz [Euler2XYZ $view_(theta) $view_(phi) $view_(psi)]
878    SendCmd "camera angle $xyz"
879    SendCmd "camera zoom $view_(zoom)"
880
881    set settings_($this-theta) $view_(theta)
882    set settings_($this-phi)   $view_(phi)
883    set settings_($this-psi)   $view_(psi)
884    set settings_($this-pan-x) $view_(pan-x)
885    set settings_($this-pan-y) $view_(pan-y)
886    set settings_($this-zoom)  $view_(zoom)
887
888    FixSettings light
889    FixSettings transp
890    FixSettings isosurface
891    FixSettings grid
892    FixSettings axes
893    FixSettings outline
894
895    if {[llength $sendobjs_] > 0} {
896        # send off new data objects
897        $_dispatcher event -idle !send_dataobjs
898        return
899    }
900
901    # nothing to send -- activate the proper ivol
902    set first [lindex [get] 0]
903    if {"" != $first} {
904        set axis [$first hints updir]
905        if {"" != $axis} {
906            SendCmd "up $axis"
907        }
908        foreach key [array names obj2id_ *-*] {
909            set state [string match $first-* $key]
910            SendCmd "volume state $state $obj2id_($key)"
911        }
912        #
913        # The obj2id_ and id2style_ arrays may or may not have the right
914        # information.  It's possible for the server to know about volumes
915        # that the client has assumed it's deleted.  We could add checks.
916        # But this problem needs to be fixed not bandaided.
917        set comp [lindex [$first components] 0]
918        set ivol $obj2id_($first-$comp)
919
920        foreach comp [$first components] {
921            foreach ivol $obj2id_($first-$comp) {
922                NameTransferFunc $ivol
923            }
924        }
925    }
926
927    # sync the state of slicers
928    set vols [CurrentVolumeIds -cutplanes]
929    foreach axis {x y z} {
930        SendCmd "cutplane state [State ${axis}CutButton] $axis $vols"
931        set pos [expr {0.01*[$itk_component(${axis}CutScale) get]}]
932        SendCmd "cutplane position $pos $axis $vols"
933    }
934    SendCmd "volume data state [State volume] $vols"
935    $_dispatcher event -idle !legend
936}
937
938# ----------------------------------------------------------------------
939# USAGE: CurrentVolumeIds ?-cutplanes?
940#
941# Returns a list of volume server IDs for the current volume being
942# displayed.  This is normally a single ID, but it might be a list
943# of IDs if the current data object has multiple components.
944# ----------------------------------------------------------------------
945itcl::body Rappture::NanovisViewer::CurrentVolumeIds {{what -all}} {
946    set rlist ""
947
948    set first [lindex [get] 0]
949    foreach key [array names obj2id_ *-*] {
950        if {[string match $first-* $key]} {
951            array set style {
952                -cutplanes 1
953            }
954            foreach {dataobj comp} [split $key -] break
955            array set style [lindex [$dataobj components -style $comp] 0]
956
957            if {$what != "-cutplanes" || $style(-cutplanes)} {
958                lappend rlist $obj2id_($key)
959            }
960        }
961    }
962    return $rlist
963}
964
965# ----------------------------------------------------------------------
966# USAGE: Zoom in
967# USAGE: Zoom out
968# USAGE: Zoom reset
969#
970# Called automatically when the user clicks on one of the zoom
971# controls for this widget.  Changes the zoom for the current view.
972# ----------------------------------------------------------------------
973itcl::body Rappture::NanovisViewer::Zoom {option} {
974    switch -- $option {
975        "in" {
976            set view_(zoom) [expr {$view_(zoom)*1.25}]
977            set settings_($this-zoom) $view_(zoom)
978        }
979        "out" {
980            set view_(zoom) [expr {$view_(zoom)*0.8}]
981            set settings_($this-zoom) $view_(zoom)
982        }
983        "reset" {
984            array set view_ {
985                theta   45
986                phi     45
987                psi     0
988                zoom    1.0
989                pan-x   0
990                pan-y   0
991            }
992            set first [lindex [get] 0]
993            if { $first != "" } {
994                set location [$first hints camera]
995                if { $location != "" } {
996                    array set view_ $location
997                }
998            }
999            set xyz [Euler2XYZ $view_(theta) $view_(phi) $view_(psi)]
1000            SendCmd "camera angle $xyz"
1001            PanCamera
1002            set settings_($this-theta) $view_(theta)
1003            set settings_($this-phi)   $view_(phi)
1004            set settings_($this-psi)   $view_(psi)
1005            set settings_($this-pan-x) $view_(pan-x)
1006            set settings_($this-pan-y) $view_(pan-y)
1007            set settings_($this-zoom)  $view_(zoom)
1008        }
1009    }
1010    SendCmd "camera zoom $view_(zoom)"
1011}
1012
1013itcl::body Rappture::NanovisViewer::PanCamera {} {
1014    #set x [expr ($view_(pan-x)) / $limits_(xrange)]
1015    #set y [expr ($view_(pan-y)) / $limits_(yrange)]
1016    set x $view_(pan-x)
1017    set y $view_(pan-y)
1018    SendCmd "camera pan $x $y"
1019}
1020
1021
1022# ----------------------------------------------------------------------
1023# USAGE: Rotate click <x> <y>
1024# USAGE: Rotate drag <x> <y>
1025# USAGE: Rotate release <x> <y>
1026#
1027# Called automatically when the user clicks/drags/releases in the
1028# plot area.  Moves the plot according to the user's actions.
1029# ----------------------------------------------------------------------
1030itcl::body Rappture::NanovisViewer::Rotate {option x y} {
1031    switch -- $option {
1032        click {
1033            $itk_component(3dview) configure -cursor fleur
1034            set click_(x) $x
1035            set click_(y) $y
1036            set click_(theta) $view_(theta)
1037            set click_(phi) $view_(phi)
1038        }
1039        drag {
1040            if {[array size click_] == 0} {
1041                Rotate click $x $y
1042            } else {
1043                set w [winfo width $itk_component(3dview)]
1044                set h [winfo height $itk_component(3dview)]
1045                if {$w <= 0 || $h <= 0} {
1046                    return
1047                }
1048
1049                if {[catch {
1050                    # this fails sometimes for no apparent reason
1051                    set dx [expr {double($x-$click_(x))/$w}]
1052                    set dy [expr {double($y-$click_(y))/$h}]
1053                }]} {
1054                    return
1055                }
1056
1057                #
1058                # Rotate the camera in 3D
1059                #
1060                if {$view_(psi) > 90 || $view_(psi) < -90} {
1061                    # when psi is flipped around, theta moves backwards
1062                    set dy [expr {-$dy}]
1063                }
1064                set theta [expr {$view_(theta) - $dy*180}]
1065                while {$theta < 0} { set theta [expr {$theta+180}] }
1066                while {$theta > 180} { set theta [expr {$theta-180}] }
1067
1068                if {abs($theta) >= 30 && abs($theta) <= 160} {
1069                    set phi [expr {$view_(phi) - $dx*360}]
1070                    while {$phi < 0} { set phi [expr {$phi+360}] }
1071                    while {$phi > 360} { set phi [expr {$phi-360}] }
1072                    set psi $view_(psi)
1073                } else {
1074                    set phi $view_(phi)
1075                    set psi [expr {$view_(psi) - $dx*360}]
1076                    while {$psi < -180} { set psi [expr {$psi+360}] }
1077                    while {$psi > 180} { set psi [expr {$psi-360}] }
1078                }
1079
1080                set view_(theta)        $theta
1081                set view_(phi)          $phi
1082                set view_(psi)          $psi
1083                set xyz [Euler2XYZ $theta $phi $psi]
1084                set settings_($this-theta) $view_(theta)
1085                set settings_($this-phi)   $view_(phi)
1086                set settings_($this-psi)   $view_(psi)
1087                SendCmd "camera angle $xyz"
1088                set click_(x) $x
1089                set click_(y) $y
1090            }
1091        }
1092        release {
1093            Rotate drag $x $y
1094            $itk_component(3dview) configure -cursor ""
1095            catch {unset click_}
1096        }
1097        default {
1098            error "bad option \"$option\": should be click, drag, release"
1099        }
1100    }
1101}
1102
1103# ----------------------------------------------------------------------
1104# USAGE: $this Pan click x y
1105#        $this Pan drag x y
1106#        $this Pan release x y
1107#
1108# Called automatically when the user clicks on one of the zoom
1109# controls for this widget.  Changes the zoom for the current view.
1110# ----------------------------------------------------------------------
1111itcl::body Rappture::NanovisViewer::Pan {option x y} {
1112    # Experimental stuff
1113    set w [winfo width $itk_component(3dview)]
1114    set h [winfo height $itk_component(3dview)]
1115    if { $option == "set" } {
1116        set x [expr $x / double($w)]
1117        set y [expr $y / double($h)]
1118        set view_(pan-x) [expr $view_(pan-x) + $x]
1119        set view_(pan-y) [expr $view_(pan-y) + $y]
1120        PanCamera
1121        set settings_($this-pan-x) $view_(pan-x)
1122        set settings_($this-pan-y) $view_(pan-y)
1123        return
1124    }
1125    if { $option == "click" } {
1126        set click_(x) $x
1127        set click_(y) $y
1128        $itk_component(3dview) configure -cursor hand1
1129    }
1130    if { $option == "drag" || $option == "release" } {
1131        set dx [expr ($click_(x) - $x)/double($w)]
1132        set dy [expr ($click_(y) - $y)/double($h)]
1133        set click_(x) $x
1134        set click_(y) $y
1135        set view_(pan-x) [expr $view_(pan-x) - $dx]
1136        set view_(pan-y) [expr $view_(pan-y) - $dy]
1137        PanCamera
1138        set settings_($this-pan-x) $view_(pan-x)
1139        set settings_($this-pan-y) $view_(pan-y)
1140    }
1141    if { $option == "release" } {
1142        $itk_component(3dview) configure -cursor ""
1143    }
1144}
1145
1146# ----------------------------------------------------------------------
1147# USAGE: State <component>
1148#
1149# Used internally to determine the state of a toggle button.
1150# The <component> is the itk component name of the button.
1151# Returns on/off for the state of the button.
1152# ----------------------------------------------------------------------
1153itcl::body Rappture::NanovisViewer::State { comp } {
1154    if {[$itk_component(${comp}) cget -relief] == "sunken"} {
1155        return "on"
1156    }
1157    return "off"
1158}
1159
1160# ----------------------------------------------------------------------
1161# USAGE: FixSettings <what> ?<value>?
1162#
1163# Used internally to update rendering settings whenever parameters
1164# change in the popup settings panel.  Sends the new settings off
1165# to the back end.
1166# ----------------------------------------------------------------------
1167itcl::body Rappture::NanovisViewer::FixSettings {what {value ""}} {
1168    switch -- $what {
1169        light {
1170            if {[isconnected]} {
1171                set val $settings_($this-light)
1172                set sval [expr {0.1*$val}]
1173                SendCmd "volume shading diffuse $sval"
1174                set sval [expr {sqrt($val+1.0)}]
1175                SendCmd "volume shading specular $sval"
1176            }
1177        }
1178        transp {
1179            if {[isconnected]} {
1180                set val $settings_($this-transp)
1181                set sval [expr {0.2*$val+1}]
1182                SendCmd "volume shading opacity $sval"
1183            }
1184        }
1185        opacity {
1186            if {[isconnected] && $activeTf_ != "" } {
1187                set val $settings_($this-opacity)
1188                set sval [expr { 0.01 * double($val) }]
1189                set tf $activeTf_
1190                set settings_($this-$tf-opacity) $sval
1191                updatetransferfuncs
1192            }
1193        }
1194
1195        thickness {
1196            if {[isconnected] && $activeTf_ != "" } {
1197                set val $settings_($this-thickness)
1198                # Scale values between 0.00001 and 0.01000
1199                set sval [expr {0.0001*double($val)}]
1200                set tf $activeTf_
1201                set settings_($this-$tf-thickness) $sval
1202                updatetransferfuncs
1203            }
1204        }
1205        "outline" {
1206            if {[isconnected]} {
1207                SendCmd "volume outline state $settings_($this-outline)"
1208            }
1209        }
1210        "isosurface" {
1211            if {[isconnected]} {
1212                SendCmd "volume shading isosurface $settings_($this-isosurface)"
1213            }
1214        }
1215        "grid" {
1216            if { [isconnected] } {
1217                SendCmd "grid visible $settings_($this-grid)"
1218            }
1219        }
1220        "axes" {
1221            if { [isconnected] } {
1222                SendCmd "axis visible $settings_($this-axes)"
1223            }
1224        }
1225        "legend" {
1226            if { $settings_($this-legend) } {
1227                blt::table $itk_component(plotarea) \
1228                    0,0 $itk_component(3dview) -fill both \
1229                    1,0 $itk_component(legend) -fill x
1230                blt::table configure $itk_component(plotarea) r1 -resize none
1231            } else {
1232                blt::table forget $itk_component(legend)
1233            }
1234        }
1235        "volume" {
1236            Slice volume $settings_($this-volume)
1237        }
1238        default {
1239            error "don't know how to fix $what"
1240        }
1241    }
1242}
1243
1244# ----------------------------------------------------------------------
1245# USAGE: FixLegend
1246#
1247# Used internally to update the legend area whenever it changes size
1248# or when the field changes.  Asks the server to send a new legend
1249# for the current field.
1250# ----------------------------------------------------------------------
1251itcl::body Rappture::NanovisViewer::FixLegend {} {
1252    set lineht [font metrics $itk_option(-font) -linespace]
1253    set w [expr {$width_-20}]
1254    set h [expr {[winfo height $itk_component(legend)]-20-$lineht}]
1255    if {$w > 0 && $h > 0 && "" != $activeTf_} {
1256        SendCmd "legend $activeTf_ $w $h"
1257    } else {
1258        # Can't do this as this will remove the items associated with the
1259        # isomarkers.
1260       
1261        #$itk_component(legend) delete all
1262    }
1263}
1264
1265#
1266# NameTransferFunc --
1267#
1268#       Creates a transfer function name based on the <style> settings in the
1269#       library run.xml file. This placeholder will be used later to create
1270#       and send the actual transfer function once the data info has been sent
1271#       to us by the render server. [We won't know the volume limits until the
1272#       server parses the 3D data and sends back the limits via ReceiveData.]
1273#
1274#       FIXME: The current way we generate transfer-function names completely
1275#              ignores the -markers option.  The problem is that we are forced
1276#              to compute the name from an increasing complex set of values:
1277#              color, levels, marker, opacity.  I think we're stuck doing it
1278#              now.
1279#
1280itcl::body Rappture::NanovisViewer::NameTransferFunc { ivol } {
1281    array set style {
1282        -color rainbow
1283        -levels 6
1284        -opacity 1.0
1285    }
1286    foreach {dataobj comp} $id2obj_($ivol) break
1287    array set style [lindex [$dataobj components -style $comp] 0]
1288    set tf "$style(-color):$style(-levels):$style(-opacity)"
1289
1290    set id2style_($ivol) $tf
1291    lappend obj2styles_($dataobj) $tf
1292    lappend style2ids_($tf) $ivol
1293}
1294
1295#
1296# ComputeTransferFunc --
1297#
1298#   Computes and sends the transfer function to the render server.  It's
1299#   assumed that the volume data limits are known and that the global
1300#   transfer-functions slider values have be setup.  Both parts are
1301#   needed to compute the relative value (location) of the marker, and
1302#   the alpha map of the transfer function.
1303#
1304itcl::body Rappture::NanovisViewer::ComputeTransferFunc { tf } {
1305    array set style {
1306        -color rainbow
1307        -levels 6
1308        -opacity 1.0
1309    }
1310    set dataobj ""; set comp ""
1311    foreach ivol $style2ids_($tf) {
1312        if { [info exists id2obj_($ivol)] } {
1313            foreach {dataobj comp} $id2obj_($ivol) break
1314            break
1315        }
1316    }
1317    if { $dataobj == "" } {
1318        return 0
1319    }
1320    array set style [lindex [$dataobj components -style $comp] 0]
1321
1322
1323    # We have to parse the style attributes for a volume using this
1324    # transfer-function *once*.  This sets up the initial isomarkers for the
1325    # transfer function.  The user may add/delete markers, so we have to
1326    # maintain a list of markers for each transfer-function.  We use the one
1327    # of the volumes (the first in the list) using the transfer-function as a
1328    # reference.
1329    #
1330    # FIXME: The current way we generate transfer-function names completely
1331    #        ignores the -markers option.  The problem is that we are forced
1332    #        to compute the name from an increasing complex set of values:
1333    #        color, levels, marker, opacity.  I think the cow's out of the
1334    #        barn on this one.
1335
1336    if { ![info exists isomarkers_($tf)] } {
1337        # Have to defer creation of isomarkers until we have data limits
1338        if { [info exists style(-markers)] } {
1339            ParseMarkersOption $tf $ivol $style(-markers)
1340        } else {
1341            ParseLevelsOption $tf $ivol $style(-levels)
1342        }
1343    }
1344    if {$style(-color) == "rainbow"} {
1345        set style(-color) "white:yellow:green:cyan:blue:magenta"
1346    }
1347    set clist [split $style(-color) :]
1348    set cmap "0.0 [Color2RGB white] "
1349    for {set i 0} {$i < [llength $clist]} {incr i} {
1350        set x [expr {double($i+1)/([llength $clist]+1)}]
1351        set color [lindex $clist $i]
1352        append cmap "$x [Color2RGB $color] "
1353    }
1354    append cmap "1.0 [Color2RGB $color]"
1355
1356    set tag $this-$tf
1357    if { ![info exists settings_($tag-opacity)] } {
1358        set settings_($tag-opacity) $style(-opacity)
1359    }
1360    set max $settings_($tag-opacity)
1361
1362    set isovalues {}
1363    foreach m $isomarkers_($tf) {
1364        lappend isovalues [$m relval]
1365    }
1366    # Sort the isovalues
1367    set isovalues [lsort -real $isovalues]
1368
1369    if { ![info exists settings_($tag-thickness)]} {
1370        set settings_($tag-thickness) 0.05
1371    }
1372    set delta $settings_($tag-thickness)
1373
1374    set first [lindex $isovalues 0]
1375    set last [lindex $isovalues end]
1376    set wmap ""
1377    if { $first == "" || $first != 0.0 } {
1378        lappend wmap 0.0 0.0
1379    }
1380    foreach x $isovalues {
1381        set x1 [expr {$x-$delta-0.00001}]
1382        set x2 [expr {$x-$delta}]
1383        set x3 [expr {$x+$delta}]
1384        set x4 [expr {$x+$delta+0.00001}]
1385        if { $x1 < 0.0 } {
1386            set x1 0.0
1387        } elseif { $x1 > 1.0 } {
1388            set x1 1.0
1389        }
1390        if { $x2 < 0.0 } {
1391            set x2 0.0
1392        } elseif { $x2 > 1.0 } {
1393            set x2 1.0
1394        }
1395        if { $x3 < 0.0 } {
1396            set x3 0.0
1397        } elseif { $x3 > 1.0 } {
1398            set x3 1.0
1399        }
1400        if { $x4 < 0.0 } {
1401            set x4 0.0
1402        } elseif { $x4 > 1.0 } {
1403            set x4 1.0
1404        }
1405        # add spikes in the middle
1406        lappend wmap $x1 0.0
1407        lappend wmap $x2 $max
1408        lappend wmap $x3 $max
1409        lappend wmap $x4 0.0
1410    }
1411    if { $last == "" || $last != 1.0 } {
1412        lappend wmap 1.0 0.0
1413    }
1414    SendBytes "transfunc define $tf { $cmap } { $wmap }\n"
1415    return [SendBytes "volume shading transfunc $tf $style2ids_($tf)\n"]
1416}
1417
1418# ----------------------------------------------------------------------
1419# CONFIGURATION OPTION: -plotbackground
1420# ----------------------------------------------------------------------
1421itcl::configbody Rappture::NanovisViewer::plotbackground {
1422    if { [isconnected] } {
1423        foreach {r g b} [Color2RGB $itk_option(-plotbackground)] break
1424        #fix this!
1425        #SendCmd "color background $r $g $b"
1426    }
1427}
1428
1429# ----------------------------------------------------------------------
1430# CONFIGURATION OPTION: -plotforeground
1431# ----------------------------------------------------------------------
1432itcl::configbody Rappture::NanovisViewer::plotforeground {
1433    if { [isconnected] } {
1434        foreach {r g b} [Color2RGB $itk_option(-plotforeground)] break
1435        #fix this!
1436        #SendCmd "color background $r $g $b"
1437    }
1438}
1439
1440# ----------------------------------------------------------------------
1441# CONFIGURATION OPTION: -plotoutline
1442# ----------------------------------------------------------------------
1443itcl::configbody Rappture::NanovisViewer::plotoutline {
1444    # Must check if we are connected because this routine is called from the
1445    # class body when the -plotoutline itk_option is defined.  At that point
1446    # the NanovisViewer class constructor hasn't been called, so we can't
1447    # start sending commands to visualization server.
1448    if { [isconnected] } {
1449        if {"" == $itk_option(-plotoutline)} {
1450            SendCmd "volume outline state off"
1451        } else {
1452            SendCmd "volume outline state on"
1453            SendCmd "volume outline color [Color2RGB $itk_option(-plotoutline)]"
1454        }
1455    }
1456}
1457
1458#
1459# The -levels option takes a single value that represents the number
1460# of evenly distributed markers based on the current data range. Each
1461# marker is a relative value from 0.0 to 1.0.
1462#
1463itcl::body Rappture::NanovisViewer::ParseLevelsOption { tf ivol levels } {
1464    set c $itk_component(legend)
1465    regsub -all "," $levels " " levels
1466    if {[string is int $levels]} {
1467        for {set i 1} { $i <= $levels } {incr i} {
1468            set x [expr {double($i)/($levels+1)}]
1469            set m [IsoMarker \#auto $c $this $tf]
1470            $m relval $x
1471            lappend isomarkers_($tf) $m
1472        }
1473    } else {
1474        foreach x $levels {
1475            set m [IsoMarker \#auto $c $this $tf]
1476            $m relval $x
1477            lappend isomarkers_($tf) $m
1478        }
1479    }
1480}
1481
1482#
1483# The -markers option takes a list of zero or more values (the values
1484# may be separated either by spaces or commas) that have the following
1485# format:
1486#
1487#   N%  Percent of current total data range.  Converted to
1488#       to a relative value between 0.0 and 1.0.
1489#   N   Absolute value of marker.  If the marker is outside of
1490#       the current range, it will be displayed on the outer
1491#       edge of the legends, but it range it represents will
1492#       not be seen.
1493#
1494itcl::body Rappture::NanovisViewer::ParseMarkersOption { tf ivol markers } {
1495    set c $itk_component(legend)
1496    regsub -all "," $markers " " markers
1497    foreach marker $markers {
1498        set n [scan $marker "%g%s" value suffix]
1499        if { $n == 2 && $suffix == "%" } {
1500            # ${n}% : Set relative value.
1501            set value [expr {$value * 0.01}]
1502            set m [IsoMarker \#auto $c $this $tf]
1503            $m relval $value
1504            lappend isomarkers_($tf) $m
1505        } else {
1506            # ${n} : Set absolute value.
1507            set m [IsoMarker \#auto $c $this $tf]
1508            $m absval $value
1509            lappend isomarkers_($tf) $m
1510        }
1511    }
1512}
1513
1514# ----------------------------------------------------------------------
1515# USAGE: Marker start <x> <y>
1516# USAGE: Marker update <x> <y>
1517# USAGE: Marker end <x> <y>
1518#
1519# Used internally to handle the various marker operations performed
1520# when the user clicks and drags on the legend area.  The marker changes the
1521# transfer function to highlight the area being selected in the
1522# legend.
1523# ----------------------------------------------------------------------
1524itcl::body Rappture::NanovisViewer::updatetransferfuncs {} {
1525    $_dispatcher event -idle !send_transfunc
1526}
1527
1528itcl::body Rappture::NanovisViewer::AddIsoMarker { x y } {
1529    if { $activeTf_ == "" } {
1530        error "active transfer function isn't set"
1531    }
1532    set tf $activeTf_
1533    set c $itk_component(legend)
1534    set m [IsoMarker \#auto $c $this $tf]
1535    set w [winfo width $c]
1536    $m relval [expr {double($x-10)/($w-20)}]
1537    lappend isomarkers_($tf) $m
1538    updatetransferfuncs
1539    return 1
1540}
1541
1542itcl::body Rappture::NanovisViewer::rmdupmarker { marker x } {
1543    set tf [$marker transferfunc]
1544    set bool 0
1545    if { [info exists isomarkers_($tf)] } {
1546        set list {}
1547        set marker [namespace tail $marker]
1548        foreach m $isomarkers_($tf) {
1549            set sx [$m screenpos]
1550            if { $m != $marker } {
1551                if { $x >= ($sx-3) && $x <= ($sx+3) } {
1552                    $marker relval [$m relval]
1553                    itcl::delete object $m
1554                    bell
1555                    set bool 1
1556                    continue
1557                }
1558            }
1559            lappend list $m
1560        }
1561        set isomarkers_($tf) $list
1562        updatetransferfuncs
1563    }
1564    return $bool
1565}
1566
1567itcl::body Rappture::NanovisViewer::overmarker { marker x } {
1568    set tf [$marker transferfunc]
1569    if { [info exists isomarkers_($tf)] } {
1570        set marker [namespace tail $marker]
1571        foreach m $isomarkers_($tf) {
1572            set sx [$m screenpos]
1573            if { $m != $marker } {
1574                set bool [expr { $x >= ($sx-3) && $x <= ($sx+3) }]
1575                $m activate $bool
1576            }
1577        }
1578    }
1579    return ""
1580}
1581
1582itcl::body Rappture::NanovisViewer::limits { tf } {
1583    set limits_(min) ""
1584    set limits_(max) ""
1585    foreach ivol $style2ids_($tf) {
1586        if { ![info exists limits_($ivol-min)] } {
1587            # I assume this is after a clear.
1588            # error "can't find $ivol limits"       
1589            continue
1590        }
1591        if { $limits_(min) == "" || $limits_(min) > $limits_($ivol-min) } {
1592            set limits_(min) $limits_($ivol-min)
1593        }
1594        if { $limits_(max) == "" || $limits_(max) < $limits_($ivol-max) } {
1595            set limits_(max) $limits_($ivol-max)
1596        }
1597    }
1598    return [array get limits_]
1599}
1600
1601
1602itcl::body Rappture::NanovisViewer::BuildViewTab {} {
1603    foreach { key value } {
1604        grid            0
1605        axes            1
1606        outline         1
1607        volume          1
1608        legend          1
1609        particles       1
1610        lic             1
1611    } {
1612        set settings_($this-$key) $value
1613    }
1614
1615    set fg [option get $itk_component(hull) font Font]
1616    #set bfg [option get $itk_component(hull) boldFont Font]
1617
1618    set inner [$itk_component(main) insert end \
1619        -title "View Settings" \
1620        -icon [Rappture::icon wrench]]
1621    $inner configure -borderwidth 4
1622
1623    set ::Rappture::NanovisViewer::settings_($this-isosurface) 0
1624    checkbutton $inner.isosurface \
1625        -text "Isosurface shading" \
1626        -variable [itcl::scope settings_($this-isosurface)] \
1627        -command [itcl::code $this FixSettings isosurface] \
1628        -font "Arial 9"
1629
1630    checkbutton $inner.axes \
1631        -text "Axes" \
1632        -variable [itcl::scope settings_($this-axes)] \
1633        -command [itcl::code $this FixSettings axes] \
1634        -font "Arial 9"
1635
1636    checkbutton $inner.grid \
1637        -text "Grid" \
1638        -variable [itcl::scope settings_($this-grid)] \
1639        -command [itcl::code $this FixSettings grid] \
1640        -font "Arial 9"
1641
1642    checkbutton $inner.outline \
1643        -text "Outline" \
1644        -variable [itcl::scope settings_($this-outline)] \
1645        -command [itcl::code $this FixSettings outline] \
1646        -font "Arial 9"
1647
1648    checkbutton $inner.legend \
1649        -text "Legend" \
1650        -variable [itcl::scope settings_($this-legend)] \
1651        -command [itcl::code $this FixSettings legend] \
1652        -font "Arial 9"
1653
1654    checkbutton $inner.volume \
1655        -text "Volume" \
1656        -variable [itcl::scope settings_($this-volume)] \
1657        -command [itcl::code $this FixSettings volume] \
1658        -font "Arial 9"
1659
1660    blt::table $inner \
1661        0,0 $inner.axes  -columnspan 2 -anchor w \
1662        1,0 $inner.grid  -columnspan 2 -anchor w \
1663        2,0 $inner.outline  -columnspan 2 -anchor w \
1664        3,0 $inner.volume  -columnspan 2 -anchor w \
1665        4,0 $inner.legend  -columnspan 2 -anchor w
1666
1667    blt::table configure $inner r* -resize none
1668    blt::table configure $inner r5 -resize expand
1669}
1670
1671itcl::body Rappture::NanovisViewer::BuildVolumeTab {} {
1672    foreach { key value } {
1673        light           40
1674        transp          50
1675        opacity         100
1676        thickness       350
1677    } {
1678        set settings_($this-$key) $value
1679    }
1680
1681    set inner [$itk_component(main) insert end \
1682        -title "Volume Settings" \
1683        -icon [Rappture::icon volume-on]]
1684    $inner configure -borderwidth 4
1685
1686    set fg [option get $itk_component(hull) font Font]
1687    #set bfg [option get $itk_component(hull) boldFont Font]
1688
1689    checkbutton $inner.vol -text "Show volume" -font $fg \
1690        -variable [itcl::scope settings_($this-volume)] \
1691        -command [itcl::code $this FixSettings volume]
1692    label $inner.shading -text "Shading:" -font $fg
1693
1694    label $inner.dim -text "Dim" -font $fg
1695    ::scale $inner.light -from 0 -to 100 -orient horizontal \
1696        -variable [itcl::scope settings_($this-light)] \
1697        -width 10 \
1698        -showvalue off -command [itcl::code $this FixSettings light]
1699    label $inner.bright -text "Bright" -font $fg
1700
1701    label $inner.fog -text "Fog" -font $fg
1702    ::scale $inner.transp -from 0 -to 100 -orient horizontal \
1703        -variable [itcl::scope settings_($this-transp)] \
1704        -width 10 \
1705        -showvalue off -command [itcl::code $this FixSettings transp]
1706    label $inner.plastic -text "Plastic" -font $fg
1707
1708    label $inner.clear -text "Clear" -font $fg
1709    ::scale $inner.opacity -from 0 -to 100 -orient horizontal \
1710        -variable [itcl::scope settings_($this-opacity)] \
1711        -width 10 \
1712        -showvalue off -command [itcl::code $this FixSettings opacity]
1713    label $inner.opaque -text "Opaque" -font $fg
1714
1715    label $inner.thin -text "Thin" -font $fg
1716    ::scale $inner.thickness -from 0 -to 1000 -orient horizontal \
1717        -variable [itcl::scope settings_($this-thickness)] \
1718        -width 10 \
1719        -showvalue off -command [itcl::code $this FixSettings thickness]
1720    label $inner.thick -text "Thick" -font $fg
1721
1722    blt::table $inner \
1723        0,0 $inner.vol -columnspan 4 -anchor w -pady 2 \
1724        1,0 $inner.shading -columnspan 4 -anchor w -pady {10 2} \
1725        2,0 $inner.dim -anchor e -pady 2 \
1726        2,1 $inner.light -columnspan 2 -pady 2 -fill x \
1727        2,3 $inner.bright -anchor w -pady 2 \
1728        3,0 $inner.fog -anchor e -pady 2 \
1729        3,1 $inner.transp -columnspan 2 -pady 2 -fill x \
1730        3,3 $inner.plastic -anchor w -pady 2 \
1731        4,0 $inner.clear -anchor e -pady 2 \
1732        4,1 $inner.opacity -columnspan 2 -pady 2 -fill x\
1733        4,3 $inner.opaque -anchor w -pady 2 \
1734        5,0 $inner.thin -anchor e -pady 2 \
1735        5,1 $inner.thickness -columnspan 2 -pady 2 -fill x\
1736        5,3 $inner.thick -anchor w -pady 2
1737
1738    blt::table configure $inner c0 c1 c3 r* -resize none
1739    blt::table configure $inner r6 -resize expand
1740}
1741
1742itcl::body Rappture::NanovisViewer::BuildCutplanesTab {} {
1743    set inner [$itk_component(main) insert end \
1744        -title "Cutplane Settings" \
1745        -icon [Rappture::icon cutbutton]]
1746    $inner configure -borderwidth 4
1747
1748    # X-value slicer...
1749    itk_component add xCutButton {
1750        label $inner.xbutton \
1751            -borderwidth 1 -relief raised -padx 1 -pady 1 \
1752            -image [Rappture::icon x-cutplane-off] \
1753            -highlightthickness 0
1754    } {
1755        usual
1756        ignore -borderwidth -highlightthickness -font
1757    }
1758    bind $itk_component(xCutButton) <ButtonPress> \
1759        [itcl::code $this Slice axis x toggle]
1760    Rappture::Tooltip::for $itk_component(xCutButton) \
1761        "Toggle the X cut plane on/off"
1762
1763    itk_component add xCutScale {
1764        ::scale $inner.xval -from 100 -to 0 \
1765            -width 10 -orient vertical -showvalue off \
1766            -borderwidth 1 -highlightthickness 0 \
1767            -command [itcl::code $this Slice move x]
1768    } {
1769        usual
1770        ignore -borderwidth -highlightthickness
1771    }
1772    # Set the default cutplane value before disabling the scale.
1773    $itk_component(xCutScale) set 50
1774    $itk_component(xCutScale) configure -state disabled
1775    Rappture::Tooltip::for $itk_component(xCutScale) \
1776        "@[itcl::code $this SlicerTip x]"
1777
1778    # Y-value slicer...
1779    itk_component add yCutButton {
1780        label $inner.ybutton \
1781            -borderwidth 1 -relief raised -padx 1 -pady 1 \
1782            -image [Rappture::icon y-cutplane-off] \
1783            -highlightthickness 0
1784    } {
1785        usual
1786        ignore -borderwidth -highlightthickness -font
1787    }
1788    bind $itk_component(yCutButton) <ButtonPress> \
1789        [itcl::code $this Slice axis y toggle]
1790    Rappture::Tooltip::for $itk_component(yCutButton) \
1791        "Toggle the Y cut plane on/off"
1792
1793    itk_component add yCutScale {
1794        ::scale $inner.yval -from 100 -to 0 \
1795            -width 10 -orient vertical -showvalue off \
1796            -borderwidth 1 -highlightthickness 0 \
1797            -command [itcl::code $this Slice move y]
1798    } {
1799        usual
1800        ignore -borderwidth -highlightthickness
1801    }
1802    Rappture::Tooltip::for $itk_component(yCutScale) \
1803        "@[itcl::code $this SlicerTip y]"
1804    # Set the default cutplane value before disabling the scale.
1805    $itk_component(yCutScale) set 50
1806    $itk_component(yCutScale) configure -state disabled
1807
1808    # Z-value slicer...
1809    itk_component add zCutButton {
1810        label $inner.zbutton \
1811            -borderwidth 1 -relief raised -padx 1 -pady 1 \
1812            -image [Rappture::icon z-cutplane-off] \
1813            -highlightthickness 0
1814    } {
1815        usual
1816        ignore -borderwidth -highlightthickness -font
1817    }
1818    bind $itk_component(zCutButton) <ButtonPress> \
1819        [itcl::code $this Slice axis z toggle]
1820    Rappture::Tooltip::for $itk_component(zCutButton) \
1821        "Toggle the Z cut plane on/off"
1822
1823    itk_component add zCutScale {
1824        ::scale $inner.zval -from 100 -to 0 \
1825            -width 10 -orient vertical -showvalue off \
1826            -borderwidth 1 -highlightthickness 0 \
1827            -command [itcl::code $this Slice move z]
1828    } {
1829        usual
1830        ignore -borderwidth -highlightthickness
1831    }
1832    $itk_component(zCutScale) set 50
1833    $itk_component(zCutScale) configure -state disabled
1834    #$itk_component(zCutScale) configure -state disabled
1835    Rappture::Tooltip::for $itk_component(zCutScale) \
1836        "@[itcl::code $this SlicerTip z]"
1837
1838    blt::table $inner \
1839        1,1 $itk_component(xCutButton) \
1840        1,2 $itk_component(yCutButton) \
1841        1,3 $itk_component(zCutButton) \
1842        0,1 $itk_component(xCutScale) \
1843        0,2 $itk_component(yCutScale) \
1844        0,3 $itk_component(zCutScale) \
1845
1846    blt::table configure $inner r0 r1 c* -resize none
1847    blt::table configure $inner r2 c4 -resize expand
1848    blt::table configure $inner c0 -width 2
1849    blt::table configure $inner c1 c2 c3 -padx 2
1850}
1851
1852itcl::body Rappture::NanovisViewer::BuildCameraTab {} {
1853    set inner [$itk_component(main) insert end \
1854        -title "Camera Settings" \
1855        -icon [Rappture::icon camera]]
1856    $inner configure -borderwidth 4
1857
1858    set labels { phi theta psi pan-x pan-y zoom }
1859    set row 0
1860    foreach tag $labels {
1861        label $inner.${tag}label -text $tag -font "Arial 9"
1862        entry $inner.${tag} -font "Arial 9"  -bg white \
1863            -textvariable [itcl::scope settings_($this-$tag)]
1864        bind $inner.${tag} <KeyPress-Return> \
1865            [itcl::code $this camera set ${tag}]
1866        blt::table $inner \
1867            $row,0 $inner.${tag}label -anchor e -pady 2 \
1868            $row,1 $inner.${tag} -anchor w -pady 2
1869        blt::table configure $inner r$row -resize none
1870        incr row
1871    }
1872    blt::table configure $inner c0 c1 -resize none
1873    blt::table configure $inner c2 -resize expand
1874    blt::table configure $inner r$row -resize expand
1875}
1876
1877
1878# ----------------------------------------------------------------------
1879# USAGE: Slice axis x|y|z ?on|off|toggle?
1880# USAGE: Slice move x|y|z <newval>
1881# USAGE: Slice volume ?on|off|toggle?
1882#
1883# Called automatically when the user drags the slider to move the
1884# cut plane that slices 3D data.  Gets the current value from the
1885# slider and moves the cut plane to the appropriate point in the
1886# data set.
1887# ----------------------------------------------------------------------
1888itcl::body Rappture::NanovisViewer::Slice {option args} {
1889    switch -- $option {
1890        axis {
1891            if {[llength $args] < 1 || [llength $args] > 2} {
1892                error "wrong # args: should be \"Slice axis x|y|z ?on|off|toggle?\""
1893            }
1894            set axis [lindex $args 0]
1895            set op [lindex $args 1]
1896            if {$op == ""} {
1897                set op "on"
1898            }
1899            set current [State ${axis}CutButton]
1900            if {$op == "toggle"} {
1901                if {$current == "on"} {
1902                    set op "off"
1903                } else {
1904                    set op "on"
1905                }
1906            }
1907            if {$op} {
1908                $itk_component(${axis}CutButton) configure \
1909                    -relief sunken -image [Rappture::icon ${axis}-cutplane-on]
1910                SendCmd "cutplane state 1 $axis [CurrentVolumeIds -cutplanes]"
1911                $itk_component(${axis}CutScale) configure -state normal \
1912                    -troughcolor grey65
1913            } else {
1914                $itk_component(${axis}CutButton) configure \
1915                    -relief raised -image [Rappture::icon ${axis}-cutplane-off]
1916                SendCmd "cutplane state 0 $axis [CurrentVolumeIds -cutplanes]"
1917                $itk_component(${axis}CutScale) configure -state disabled \
1918                    -troughcolor grey82
1919            }
1920        }
1921        move {
1922            if {[llength $args] != 2} {
1923                error "wrong # args: should be \"Slice move x|y|z newval\""
1924            }
1925            set axis [lindex $args 0]
1926            set newval [lindex $args 1]
1927
1928            set newpos [expr {0.01*$newval}]
1929#            set newval [expr {0.01*($newval-50)
1930#                *($limits_(${axis}max)-$limits_(${axis}min))
1931#                  + 0.5*($limits_(${axis}max)+$limits_(${axis}min))}]
1932
1933            # show the current value in the readout
1934            #puts "readout: $axis = $newval"
1935
1936            set ids [CurrentVolumeIds -cutplanes]
1937            SendCmd "cutplane position $newpos $axis $ids"
1938        }
1939        volume {
1940            if {[llength $args] > 1} {
1941                error "wrong # args: should be \"Slice volume ?on|off|toggle?\""
1942            }
1943            set op [lindex $args 0]
1944            if {$op == ""} { set op "on" }
1945
1946            set current $settings_($this-volume)
1947            if {$op == "toggle"} {
1948                if {$current} {
1949                    set op "off"
1950                } else {
1951                    set op "on"
1952                }
1953            }
1954           
1955            if {$op} {
1956                SendCmd "volume data state on [CurrentVolumeIds]"
1957                $itk_component(volume) configure -relief sunken \
1958                    -image [Rappture::icon volume-on]
1959                set settings_($this-volume) 1
1960            } else {
1961                SendCmd "volume data state off [CurrentVolumeIds]"
1962                $itk_component(volume) configure -relief raised \
1963                    -image [Rappture::icon volume-off]
1964                set settings_($this-volume) 0
1965            }
1966        }
1967        default {
1968            error "bad option \"$option\": should be axis, move, or volume"
1969        }
1970    }
1971}
1972
1973# ----------------------------------------------------------------------
1974# USAGE: SlicerTip <axis>
1975#
1976# Used internally to generate a tooltip for the x/y/z slicer controls.
1977# Returns a message that includes the current slicer value.
1978# ----------------------------------------------------------------------
1979itcl::body Rappture::NanovisViewer::SlicerTip {axis} {
1980    set val [$itk_component(${axis}CutScale) get]
1981#    set val [expr {0.01*($val-50)
1982#        *($limits_(${axis}max)-$limits_(${axis}min))
1983#          + 0.5*($limits_(${axis}max)+$limits_(${axis}min))}]
1984    return "Move the [string toupper $axis] cut plane.\nCurrently:  $axis = $val%"
1985}
1986
1987
1988itcl::body Rappture::NanovisViewer::DoResize {} {
1989    SendCmd "screen $width_ $height_"
1990}
1991
1992itcl::body Rappture::NanovisViewer::EventuallyResize { w h } {
1993    if { $width_ != $w || $height_ != $h } {
1994        set width_ $w
1995        set height_ $h
1996        $_dispatcher event -idle !resize
1997    }
1998}
1999
2000itcl::body Rappture::NanovisViewer::EventuallyResizeLegend {} {
2001    $_dispatcher event -idle !legend
2002}
2003
2004#  camera --
2005#
2006itcl::body Rappture::NanovisViewer::camera {option args} {
2007    switch -- $option {
2008        "show" {
2009            puts [array get view_]
2010        }
2011        "set" {
2012            set who [lindex $args 0]
2013            set x $settings_($this-$who)
2014            set code [catch { string is double $x } result]
2015            if { $code != 0 || !$result } {
2016                set settings_($this-$who) $view_($who)
2017                return
2018            }
2019            switch -- $who {
2020                "pan-x" - "pan-y" {
2021                    set view_($who) $settings_($this-$who)
2022                    PanCamera
2023                }
2024                "phi" - "theta" - "psi" {
2025                    set view_($who) $settings_($this-$who)
2026                    set xyz [Euler2XYZ $view_(theta) $view_(phi) $view_(psi)]
2027                    SendCmd "camera angle $xyz"
2028                }
2029                "zoom" {
2030                    set view_($who) $settings_($this-$who)
2031                    SendCmd "camera zoom $view_(zoom)"
2032                }
2033            }
2034        }
2035    }
2036}
2037
Note: See TracBrowser for help on using the repository browser.