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

Last change on this file since 2035 was 1839, checked in by gah, 14 years ago

removed -tkwait flag to molvisviewer addmethod

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