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

Last change on this file since 1514 was 1514, checked in by gah, 15 years ago

allow user to cancel the movie download

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