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

Last change on this file since 459 was 459, checked in by mmc, 15 years ago

Added a legend to the nanovis viewer. Added bullet-proofing for
dropped connections, including automatic reconnect on next operation.
Added download of current item for sequences. (Can't download the
whole movie yet.)

File size: 50.9 KB
Line 
1# ----------------------------------------------------------------------
2#  COMPONENT: nanovisviewer - 3D volume rendering
3#
4#  This widget performs volume rendering on 3D scalar/vector datasets.
5#  It connects to the Nanovis server running on a rendering farm,
6#  transmits data, and displays the results.
7# ======================================================================
8#  AUTHOR:  Michael McLennan, Purdue University
9#  Copyright (c) 2004-2005  Purdue Research Foundation
10#
11#  See the file "license.terms" for information on usage and
12#  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
13# ======================================================================
14package require Itk
15package require BLT
16package require Img
17
18option add *NanovisViewer.width 4i widgetDefault
19option add *NanovisViewer.height 4i widgetDefault
20option add *NanovisViewer.foreground black widgetDefault
21option add *NanovisViewer.controlBackground gray widgetDefault
22option add *NanovisViewer.controlDarkBackground #999999 widgetDefault
23option add *NanovisViewer.plotBackground black widgetDefault
24option add *NanovisViewer.plotForeground white widgetDefault
25option add *NanovisViewer.plotOutline gray widgetDefault
26option add *NanovisViewer.font \
27    -*-helvetica-medium-r-normal-*-*-120-* widgetDefault
28
29itcl::class Rappture::NanovisViewer {
30    inherit itk::Widget
31
32    itk_option define -plotforeground plotForeground Foreground ""
33    itk_option define -plotbackground plotBackground Background ""
34    itk_option define -plotoutline plotOutline PlotOutline ""
35
36    constructor {host port args} { # defined below }
37    destructor { # defined below }
38
39    public method add {dataobj {settings ""}}
40    public method get {}
41    public method delete {args}
42    public method scale {args}
43    public method download {option}
44
45    public method connect {{host ""} {port ""}}
46    public method disconnect {}
47    public method isconnected {}
48
49    protected method _send {args}
50    protected method _send_dataobjs {}
51    protected method _receive {}
52    protected method _receive_image {option size}
53    protected method _receive_legend {ivol vmin vmax size}
54
55    protected method _rebuild {}
56    protected method _currentVolumeIds {}
57    protected method _zoom {option}
58    protected method _move {option x y}
59    protected method _slice {option args}
60    protected method _slicertip {axis}
61    protected method _probe {option args}
62
63    protected method _state {comp}
64    protected method _fixSettings {what {value ""}}
65    protected method _fixLegend {}
66    protected method _serverDown {}
67    protected method _getTransfuncData {dataobj comp}
68    protected method _color2rgb {color}
69    protected method _euler2xyz {theta phi psi}
70
71    private variable _dispatcher "" ;# dispatcher for !events
72
73    private variable _nvhost ""    ;# host name for nanovis server
74    private variable _nvport ""    ;# port number for nanovis server
75    private variable _sid ""       ;# socket connection to nanovis server
76    private variable _parser ""    ;# interpreter for incoming commands
77    private variable _buffer       ;# buffer for incoming/outgoing commands
78    private variable _image        ;# image displayed in plotting area
79
80    private variable _dlist ""     ;# list of data objects
81    private variable _dims ""      ;# dimensionality of data objects
82    private variable _obj2style    ;# maps dataobj => style settings
83    private variable _obj2ovride   ;# maps dataobj => style override
84    private variable _obj2id       ;# maps dataobj => volume ID in server
85    private variable _sendobjs ""  ;# list of data objs to send to server
86
87    private variable _click        ;# info used for _move operations
88    private variable _limits       ;# autoscale min/max for all axes
89    private variable _view         ;# view params for 3D view
90}
91
92itk::usual NanovisViewer {
93    keep -background -foreground -cursor -font
94    keep -plotbackground -plotforeground
95}
96
97# ----------------------------------------------------------------------
98# CONSTRUCTOR
99# ----------------------------------------------------------------------
100itcl::body Rappture::NanovisViewer::constructor {nvhost nvport args} {
101    Rappture::dispatcher _dispatcher
102    $_dispatcher register !legend
103    $_dispatcher dispatch $this !legend "[itcl::code $this _fixLegend]; list"
104    $_dispatcher register !serverDown
105    $_dispatcher dispatch $this !serverDown "[itcl::code $this _serverDown]; list"
106
107    set _buffer(in) ""
108    set _buffer(out) ""
109
110    #
111    # Create a parser to handle incoming requests
112    #
113    set _parser [interp create -safe]
114    foreach cmd [$_parser eval {info commands}] {
115        $_parser hide $cmd
116    }
117    $_parser alias image [itcl::code $this _receive_image]
118    $_parser alias legend [itcl::code $this _receive_legend]
119
120    #
121    # Set up the widgets in the main body
122    #
123    option add hull.width hull.height
124    pack propagate $itk_component(hull) no
125
126    set _view(theta) 45
127    set _view(phi) 45
128    set _view(psi) 0
129    set _view(zoom) 1
130    set _view(xfocus) 0
131    set _view(yfocus) 0
132    set _view(zfocus) 0
133    set _obj2id(count) 0
134
135    itk_component add controls {
136        frame $itk_interior.cntls
137    } {
138        usual
139        rename -background -controlbackground controlBackground Background
140    }
141    pack $itk_component(controls) -side right -fill y
142
143    itk_component add zoom {
144        frame $itk_component(controls).zoom
145    } {
146        usual
147        rename -background -controlbackground controlBackground Background
148    }
149    pack $itk_component(zoom) -side top
150
151    itk_component add reset {
152        button $itk_component(zoom).reset \
153            -borderwidth 1 -padx 1 -pady 1 \
154            -bitmap [Rappture::icon reset] \
155            -command [itcl::code $this _zoom reset]
156    } {
157        usual
158        ignore -borderwidth
159        rename -highlightbackground -controlbackground controlBackground Background
160    }
161    pack $itk_component(reset) -side left -padx {4 1} -pady 4
162    Rappture::Tooltip::for $itk_component(reset) "Reset the view to the default zoom level"
163
164    itk_component add zoomin {
165        button $itk_component(zoom).zin \
166            -borderwidth 1 -padx 1 -pady 1 \
167            -bitmap [Rappture::icon zoomin] \
168            -command [itcl::code $this _zoom in]
169    } {
170        usual
171        ignore -borderwidth
172        rename -highlightbackground -controlbackground controlBackground Background
173    }
174    pack $itk_component(zoomin) -side left -padx 1 -pady 4
175    Rappture::Tooltip::for $itk_component(zoomin) "Zoom in"
176
177    itk_component add zoomout {
178        button $itk_component(zoom).zout \
179            -borderwidth 1 -padx 1 -pady 1 \
180            -bitmap [Rappture::icon zoomout] \
181            -command [itcl::code $this _zoom out]
182    } {
183        usual
184        ignore -borderwidth
185        rename -highlightbackground -controlbackground controlBackground Background
186    }
187    pack $itk_component(zoomout) -side left -padx {1 4} -pady 4
188    Rappture::Tooltip::for $itk_component(zoomout) "Zoom out"
189
190    #
191    # Create slicer controls...
192    #
193    itk_component add slicers {
194        frame $itk_component(controls).slicers
195    } {
196        usual
197        rename -background -controlbackground controlBackground Background
198    }
199    pack $itk_component(slicers) -side bottom -padx 4 -pady 4
200    grid rowconfigure $itk_component(slicers) 1 -weight 1
201    #
202    # X-value slicer...
203    #
204    itk_component add xslice {
205        label $itk_component(slicers).xslice \
206            -borderwidth 1 -relief raised -padx 1 -pady 1 \
207            -bitmap [Rappture::icon x]
208    } {
209        usual
210        ignore -borderwidth
211        rename -highlightbackground -controlbackground controlBackground Background
212    }
213    bind $itk_component(xslice) <ButtonPress> \
214        [itcl::code $this _slice axis x toggle]
215    Rappture::Tooltip::for $itk_component(xslice) \
216        "Toggle the X cut plane on/off"
217    grid $itk_component(xslice) -row 1 -column 0 -sticky ew -padx 1
218
219    itk_component add xslicer {
220        ::scale $itk_component(slicers).xval -from 100 -to 0 \
221            -width 10 -orient vertical -showvalue off \
222            -borderwidth 1 -highlightthickness 0 \
223            -command [itcl::code $this _slice move x]
224    } {
225        usual
226        ignore -borderwidth
227        ignore -highlightthickness
228        rename -highlightbackground -controlbackground controlBackground Background
229        rename -troughcolor -controldarkbackground controlDarkBackground Background
230    }
231    $itk_component(xslicer) set 50
232    $itk_component(xslicer) configure -state disabled
233    grid $itk_component(xslicer) -row 2 -column 0 -padx 1
234    Rappture::Tooltip::for $itk_component(xslicer) \
235        "@[itcl::code $this _slicertip x]"
236
237    #
238    # Y-value slicer...
239    #
240    itk_component add yslice {
241        label $itk_component(slicers).yslice \
242            -borderwidth 1 -relief raised -padx 1 -pady 1 \
243            -bitmap [Rappture::icon y]
244    } {
245        usual
246        ignore -borderwidth
247        rename -highlightbackground -controlbackground controlBackground Background
248    }
249    bind $itk_component(yslice) <ButtonPress> \
250        [itcl::code $this _slice axis y toggle]
251    Rappture::Tooltip::for $itk_component(yslice) \
252        "Toggle the Y cut plane on/off"
253    grid $itk_component(yslice) -row 1 -column 1 -sticky ew -padx 1
254
255    itk_component add yslicer {
256        ::scale $itk_component(slicers).yval -from 100 -to 0 \
257            -width 10 -orient vertical -showvalue off \
258            -borderwidth 1 -highlightthickness 0 \
259            -command [itcl::code $this _slice move y]
260    } {
261        usual
262        ignore -borderwidth
263        ignore -highlightthickness
264        rename -highlightbackground -controlbackground controlBackground Background
265        rename -troughcolor -controldarkbackground controlDarkBackground Background
266    }
267    $itk_component(yslicer) set 50
268    $itk_component(yslicer) configure -state disabled
269    grid $itk_component(yslicer) -row 2 -column 1 -padx 1
270    Rappture::Tooltip::for $itk_component(yslicer) \
271        "@[itcl::code $this _slicertip y]"
272
273    #
274    # Z-value slicer...
275    #
276    itk_component add zslice {
277        label $itk_component(slicers).zslice \
278            -borderwidth 1 -relief raised -padx 1 -pady 1 \
279            -bitmap [Rappture::icon z]
280    } {
281        usual
282        ignore -borderwidth
283        rename -highlightbackground -controlbackground controlBackground Background
284    }
285    grid $itk_component(zslice) -row 1 -column 2 -sticky ew -padx 1
286    bind $itk_component(zslice) <ButtonPress> \
287        [itcl::code $this _slice axis z toggle]
288    Rappture::Tooltip::for $itk_component(zslice) \
289        "Toggle the Z cut plane on/off"
290
291    itk_component add zslicer {
292        ::scale $itk_component(slicers).zval -from 100 -to 0 \
293            -width 10 -orient vertical -showvalue off \
294            -borderwidth 1 -highlightthickness 0 \
295            -command [itcl::code $this _slice move z]
296    } {
297        usual
298        ignore -borderwidth
299        ignore -highlightthickness
300        rename -highlightbackground -controlbackground controlBackground Background
301        rename -troughcolor -controldarkbackground controlDarkBackground Background
302    }
303    $itk_component(zslicer) set 50
304    $itk_component(zslicer) configure -state disabled
305    grid $itk_component(zslicer) -row 2 -column 2 -padx 1
306    Rappture::Tooltip::for $itk_component(zslicer) \
307        "@[itcl::code $this _slicertip z]"
308
309    #
310    # Volume toggle...
311    #
312    itk_component add volume {
313        label $itk_component(slicers).volume \
314            -borderwidth 1 -relief sunken -padx 1 -pady 1 \
315            -text "Volume"
316    } {
317        usual
318        ignore -borderwidth
319        rename -highlightbackground -controlbackground controlBackground Background
320    }
321    bind $itk_component(volume) <ButtonPress> \
322        [itcl::code $this _slice volume toggle]
323    Rappture::Tooltip::for $itk_component(volume) \
324        "Toggle the volume cloud on/off"
325    grid $itk_component(volume) -row 0 -column 0 -columnspan 3 \
326        -sticky ew -padx 1 -pady 3
327
328    #
329    # Settings panel...
330    #
331    itk_component add settings {
332        button $itk_component(controls).settings -text "Settings..." \
333            -borderwidth 1 -relief flat -overrelief raised \
334            -padx 2 -pady 1 \
335            -command [list $itk_component(controls).panel activate $itk_component(controls).settings left]
336    } {
337        usual
338        ignore -borderwidth
339        rename -background -controlbackground controlBackground Background
340        rename -highlightbackground -controlbackground controlBackground Background
341    }
342    pack $itk_component(settings) -side top -pady 8
343
344    Rappture::Balloon $itk_component(controls).panel -title "Settings"
345    set inner [$itk_component(controls).panel component inner]
346    frame $inner.scales
347    pack $inner.scales -side top -fill x
348    grid columnconfigure $inner.scales 1 -weight 1
349    set fg [option get $itk_component(hull) font Font]
350
351    label $inner.scales.diml -text "Dim" -font $fg
352    grid $inner.scales.diml -row 0 -column 0 -sticky e
353    ::scale $inner.scales.light -from 0 -to 100 -orient horizontal \
354        -showvalue off -command [itcl::code $this _fixSettings light]
355    grid $inner.scales.light -row 0 -column 1 -sticky ew
356    label $inner.scales.brightl -text "Bright" -font $fg
357    grid $inner.scales.brightl -row 0 -column 2 -sticky w
358    $inner.scales.light set 40
359
360    label $inner.scales.fogl -text "Fog" -font $fg
361    grid $inner.scales.fogl -row 1 -column 0 -sticky e
362    ::scale $inner.scales.transp -from 0 -to 100 -orient horizontal \
363        -showvalue off -command [itcl::code $this _fixSettings transp]
364    grid $inner.scales.transp -row 1 -column 1 -sticky ew
365    label $inner.scales.plasticl -text "Plastic" -font $fg
366    grid $inner.scales.plasticl -row 1 -column 2 -sticky w
367    $inner.scales.transp set 50
368
369    #
370    # RENDERING AREA
371    #
372    itk_component add area {
373        frame $itk_interior.area
374    }
375    pack $itk_component(area) -expand yes -fill both
376
377    set _image(legend) [image create photo]
378    itk_component add legend {
379        canvas $itk_component(area).legend -height 50 -highlightthickness 0
380    } {
381        usual
382        ignore -highlightthickness
383        rename -background -plotbackground plotBackground Background
384    }
385    pack $itk_component(legend) -side bottom -fill x
386    bind $itk_component(legend) <Configure> \
387        [list $_dispatcher event -idle !legend]
388
389    set _image(plot) [image create photo]
390    itk_component add 3dview {
391        label $itk_component(area).vol -image $_image(plot) \
392            -highlightthickness 0
393    } {
394        usual
395        ignore -highlightthickness
396        rename -background -plotbackground plotBackground Background
397    }
398    pack $itk_component(3dview) -expand yes -fill both
399
400    # set up bindings for rotation
401    bind $itk_component(3dview) <ButtonPress> \
402        [itcl::code $this _move click %x %y]
403    bind $itk_component(3dview) <B1-Motion> \
404        [itcl::code $this _move drag %x %y]
405    bind $itk_component(3dview) <ButtonRelease> \
406        [itcl::code $this _move release %x %y]
407    bind $itk_component(3dview) <Configure> \
408        [itcl::code $this _send screen %w %h]
409
410    set _image(download) [image create photo]
411
412    eval itk_initialize $args
413
414    connect $nvhost $nvport
415}
416
417# ----------------------------------------------------------------------
418# DESTRUCTOR
419# ----------------------------------------------------------------------
420itcl::body Rappture::NanovisViewer::destructor {} {
421    set _sendobjs ""  ;# stop any send in progress
422    after cancel [itcl::code $this _send_dataobjs]
423    after cancel [itcl::code $this _rebuild]
424    image delete $_image(plot)
425    image delete $_image(legend)
426    image delete $_image(download)
427    interp delete $_parser
428}
429
430# ----------------------------------------------------------------------
431# USAGE: add <dataobj> ?<settings>?
432#
433# Clients use this to add a data object to the plot.  The optional
434# <settings> are used to configure the plot.  Allowed settings are
435# -color, -brightness, -width, -linestyle, and -raise.
436# ----------------------------------------------------------------------
437itcl::body Rappture::NanovisViewer::add {dataobj {settings ""}} {
438    array set params {
439        -color auto
440        -width 1
441        -linestyle solid
442        -brightness 0
443        -raise 0
444    }
445    foreach {opt val} $settings {
446        if {![info exists params($opt)]} {
447            error "bad setting \"$opt\": should be [join [lsort [array names params]] {, }]"
448        }
449        set params($opt) $val
450    }
451    if {$params(-color) == "auto" || $params(-color) == "autoreset"} {
452        # can't handle -autocolors yet
453        set params(-color) black
454    }
455
456    set pos [lsearch -exact $dataobj $_dlist]
457    if {$pos < 0} {
458        lappend _dlist $dataobj
459        set _obj2ovride($dataobj-color) $params(-color)
460        set _obj2ovride($dataobj-width) $params(-width)
461        set _obj2ovride($dataobj-raise) $params(-raise)
462
463        after cancel [itcl::code $this _rebuild]
464        after idle [itcl::code $this _rebuild]
465    }
466}
467
468# ----------------------------------------------------------------------
469# USAGE: get
470#
471# Clients use this to query the list of objects being plotted, in
472# order from bottom to top of this result.
473# ----------------------------------------------------------------------
474itcl::body Rappture::NanovisViewer::get {} {
475    # put the dataobj list in order according to -raise options
476    set dlist $_dlist
477    foreach obj $dlist {
478        if {[info exists _obj2ovride($obj-raise)] && $_obj2ovride($obj-raise)} {
479            set i [lsearch -exact $dlist $obj]
480            if {$i >= 0} {
481                set dlist [lreplace $dlist $i $i]
482                lappend dlist $obj
483            }
484        }
485    }
486    return $dlist
487}
488
489# ----------------------------------------------------------------------
490# USAGE: delete ?<dataobj1> <dataobj2> ...?
491#
492# Clients use this to delete a dataobj from the plot.  If no dataobjs
493# are specified, then all dataobjs are deleted.
494# ----------------------------------------------------------------------
495itcl::body Rappture::NanovisViewer::delete {args} {
496    if {[llength $args] == 0} {
497        set args $_dlist
498    }
499
500    # delete all specified dataobjs
501    set changed 0
502    foreach dataobj $args {
503        set pos [lsearch -exact $_dlist $dataobj]
504        if {$pos >= 0} {
505            set _dlist [lreplace $_dlist $pos $pos]
506            foreach key [array names _obj2ovride $dataobj-*] {
507                unset _obj2ovride($key)
508            }
509            set changed 1
510        }
511    }
512
513    # if anything changed, then rebuild the plot
514    if {$changed} {
515        after cancel [itcl::code $this _rebuild]
516        after idle [itcl::code $this _rebuild]
517    }
518}
519
520# ----------------------------------------------------------------------
521# USAGE: scale ?<data1> <data2> ...?
522#
523# Sets the default limits for the overall plot according to the
524# limits of the data for all of the given <data> objects.  This
525# accounts for all objects--even those not showing on the screen.
526# Because of this, the limits are appropriate for all objects as
527# the user scans through data in the ResultSet viewer.
528# ----------------------------------------------------------------------
529itcl::body Rappture::NanovisViewer::scale {args} {
530    foreach val {xmin xmax ymin ymax zmin zmax vmin vmax} {
531        set _limits($val) ""
532    }
533    foreach obj $args {
534        foreach axis {x y z v} {
535            foreach {min max} [$obj limits $axis] break
536            if {"" != $min && "" != $max} {
537                if {"" == $_limits(${axis}min)} {
538                    set _limits(${axis}min) $min
539                    set _limits(${axis}max) $max
540                } else {
541                    if {$min < $_limits(${axis}min)} {
542                        set _limits(${axis}min) $min
543                    }
544                    if {$max > $_limits(${axis}max)} {
545                        set _limits(${axis}max) $max
546                    }
547                }
548            }
549        }
550    }
551}
552
553# ----------------------------------------------------------------------
554# USAGE: download coming
555# USAGE: download now
556#
557# Clients use this method to create a downloadable representation
558# of the plot.  Returns a list of the form {ext string}, where
559# "ext" is the file extension (indicating the type of data) and
560# "string" is the data itself.
561# ----------------------------------------------------------------------
562itcl::body Rappture::NanovisViewer::download {option} {
563    switch $option {
564        coming {
565            if {[catch {blt::winop snap $itk_component(area) $_image(download)}]} {
566                $_image(download) configure -width 1 -height 1
567                $_image(download) put #000000
568            }
569        }
570        now {
571            #
572            # Hack alert!  Need data in binary format,
573            # so we'll save to a file and read it back.
574            #
575            set tmpfile /tmp/image[pid].jpg
576            $_image(download) write $tmpfile -format jpeg
577            set fid [open $tmpfile r]
578            fconfigure $fid -encoding binary -translation binary
579            set bytes [read $fid]
580            close $fid
581            file delete -force $tmpfile
582
583            return [list .jpg $bytes]
584        }
585        default {
586            error "bad option \"$option\": should be coming, now"
587        }
588    }
589}
590
591# ----------------------------------------------------------------------
592# USAGE: connect ?<host>? ?<port>?
593#
594# Clients use this method to establish a connection to a new
595# server, or to reestablish a connection to the previous server.
596# Any existing connection is automatically closed.
597# ----------------------------------------------------------------------
598itcl::body Rappture::NanovisViewer::connect {{host ""} {port ""}} {
599    disconnect
600
601    if {"" != $host} { set _nvhost $host }
602    if {"" != $port} { set _nvport $port }
603
604    if {"" == $_nvhost || "" == $_nvport} {
605        return 0
606    }
607
608    blt::busy hold $itk_component(hull); update idletasks
609
610    # HACK ALERT! punt on this for now
611    set memorySize 10000
612
613    #
614    # Connect to the nanovis server.  Send the server some estimate
615    # of the size of our job.  If it's too busy, that server may
616    # forward us to another.
617    #
618    while {1} {
619        if {[catch {socket $_nvhost $_nvport} sid]} {
620            return 0
621        }
622        fconfigure $sid -translation binary -encoding binary
623
624        # send memory requirement to the load balancer
625        puts -nonewline $sid [binary format i $memorySize]
626        flush $sid
627
628        # read back a reconnection order
629        set data [read $sid 4]
630        if {[binary scan $data cccc b1 b2 b3 b4] != 4} {
631            error "couldn't read redirection request"
632        }
633        set addr [format "%u.%u.%u.%u" \
634            [expr {$b1 & 0xff}] \
635            [expr {$b2 & 0xff}] \
636            [expr {$b3 & 0xff}] \
637            [expr {$b4 & 0xff}]]
638
639        if {[string equal $addr "0.0.0.0"]} {
640            fconfigure $sid -buffering line
641            fileevent $sid readable [itcl::code $this _receive]
642            set _sid $sid
643            return 1
644        }
645        set hostname $addr
646    }
647    blt::busy release $itk_component(hull)
648
649    return 0
650}
651
652# ----------------------------------------------------------------------
653# USAGE: disconnect
654#
655# Clients use this method to disconnect from the current rendering
656# server.
657# ----------------------------------------------------------------------
658itcl::body Rappture::NanovisViewer::disconnect {} {
659    if {"" != $_sid} {
660        catch {close $_sid}
661        set _sid ""
662    }
663
664    set _buffer(in) ""
665    set _buffer(out) ""
666
667    # disconnected -- no more data sitting on server
668    catch {unset _obj2id}
669    set _obj2id(count) 0
670    set _sendobjs ""
671}
672
673# ----------------------------------------------------------------------
674# USAGE: isconnected
675#
676# Clients use this method to see if we are currently connected to
677# a server.
678# ----------------------------------------------------------------------
679itcl::body Rappture::NanovisViewer::isconnected {} {
680    return [expr {"" != $_sid}]
681}
682
683# ----------------------------------------------------------------------
684# USAGE: _send <arg> <arg> ...
685#
686# Used internally to send commands off to the rendering server.
687# ----------------------------------------------------------------------
688itcl::body Rappture::NanovisViewer::_send {args} {
689    if {"" == $_sid} {
690        $_dispatcher cancel !serverDown
691        set x [expr {[winfo rootx $itk_component(area)]+10}]
692        set y [expr {[winfo rooty $itk_component(area)]+10}]
693        Rappture::Tooltip::cue @$x,$y "Connecting..."
694
695        if {[catch {connect} ok] == 0 && $ok} {
696            set w [winfo width $itk_component(3dview)]
697            set h [winfo height $itk_component(3dview)]
698            puts $_sid "screen $w $h"
699            set _view(theta) 45
700            set _view(phi) 45
701            set _view(psi) 0
702            set _view(zoom) 1.0
703            after idle [itcl::code $this _rebuild]
704            Rappture::Tooltip::cue hide
705            return
706        }
707        Rappture::Tooltip::cue @$x,$y "Can't connect to visualization server.  This may be a network problem.  Wait a few moments and try resetting the view."
708        return
709    }
710    if {"" != $_sid} {
711        # if we're transmitting objects, then buffer this command
712        if {[llength $_sendobjs] > 0} {
713            append _buffer(out) $args "\n"
714        } else {
715            puts $_sid $args
716        }
717    }
718}
719
720# ----------------------------------------------------------------------
721# USAGE: _send_dataobjs
722#
723# Used internally to send a series of volume objects off to the
724# server.  Sends each object, a little at a time, with updates in
725# between so the interface doesn't lock up.
726# ----------------------------------------------------------------------
727itcl::body Rappture::NanovisViewer::_send_dataobjs {} {
728    blt::busy hold $itk_component(hull); update idletasks
729
730    foreach dataobj $_sendobjs {
731        foreach comp [$dataobj components] {
732            # send the data as one huge base64-encoded mess -- yuck!
733            set data [$dataobj values $comp]
734
735            # tell the engine to expect some data
736            set cmdstr "volume data follows [string length $data]"
737            if {[catch {puts $_sid $cmdstr} err]} {
738                disconnect
739                $_dispatcher event -after 750 !serverDown
740                return
741            }
742
743            while {[string length $data] > 0} {
744                update
745
746                set chunk [string range $data 0 8095]
747                set data [string range $data 8096 end]
748
749                if {[catch {puts -nonewline $_sid $chunk} err]} {
750                    disconnect
751                    $_dispatcher event -after 750 !serverDown
752                    return
753                }
754                catch {flush $_sid}
755            }
756            puts $_sid ""
757
758            set _obj2id($dataobj-$comp) $_obj2id(count)
759            incr _obj2id(count)
760
761            #
762            # Determine the transfer function needed for this volume
763            # and make sure that it's defined on the server.
764            #
765            foreach {sname cmap wmap} [_getTransfuncData $dataobj $comp] break
766
767            set cmdstr [list transfunc define $sname $cmap $wmap]
768            if {[catch {puts $_sid $cmdstr} err]} {
769                disconnect
770                $_dispatcher event -after 750 !serverDown
771                return
772            }
773
774            set _obj2style($dataobj-$comp) $sname
775        }
776    }
777    set _sendobjs ""
778    blt::busy release $itk_component(hull)
779
780    # activate the proper volume
781    set first [lindex [get] 0]
782
783    set axis [$first hints updir]
784    if {"" != $axis} {
785        _send up $axis
786    }
787
788    foreach key [array names _obj2id *-*] {
789        set state [string match $first-* $key]
790        _send volume state $state $_obj2id($key)
791        if {[info exists _obj2style($key)]} {
792            _send volume shading transfunc $_obj2style($key) $_obj2id($key)
793        }
794    }
795
796    # sync the state of slicers
797    foreach axis {x y z} {
798        eval _send cutplane state [_state ${axis}slice] \
799            $axis [_currentVolumeIds]
800    }
801    eval _send volume data state [_state volume] [_currentVolumeIds]
802
803    # if there are any commands in the buffer, send them now that we're done
804    if {[catch {puts $_sid $_buffer(out)} err]} {
805        disconnect
806        $_dispatcher event -after 750 !serverDown
807    }
808    set _buffer(out) ""
809
810    $_dispatcher event -idle !legend
811}
812
813# ----------------------------------------------------------------------
814# USAGE: _receive
815#
816# Invoked automatically whenever a command is received from the
817# rendering server.  Reads the incoming command and executes it in
818# a safe interpreter to handle the action.
819# ----------------------------------------------------------------------
820itcl::body Rappture::NanovisViewer::_receive {} {
821    if {"" != $_sid} {
822        if {[gets $_sid line] < 0} {
823            disconnect
824            $_dispatcher event -after 750 !serverDown
825        } elseif {[string equal [string range $line 0 2] "nv>"]} {
826            append _buffer(in) [string range $line 3 end]
827            if {[info complete $_buffer(in)]} {
828                set request $_buffer(in)
829                set _buffer(in) ""
830                $_parser eval $request
831            }
832        } else {
833            # this shows errors coming back from the engine
834            ##puts $line
835        }
836    }
837}
838
839# ----------------------------------------------------------------------
840# USAGE: _receive_image -bytes <size>
841#
842# Invoked automatically whenever the "image" command comes in from
843# the rendering server.  Indicates that binary image data with the
844# specified <size> will follow.
845# ----------------------------------------------------------------------
846itcl::body Rappture::NanovisViewer::_receive_image {option size} {
847    if {"" != $_sid} {
848        set bytes [read $_sid $size]
849        $_image(plot) configure -data $bytes
850    }
851}
852
853# ----------------------------------------------------------------------
854# USAGE: _receive_legend <volume> <vmin> <vmax> <size>
855#
856# Invoked automatically whenever the "legend" command comes in from
857# the rendering server.  Indicates that binary image data with the
858# specified <size> will follow.
859# ----------------------------------------------------------------------
860itcl::body Rappture::NanovisViewer::_receive_legend {ivol vmin vmax size} {
861    if {"" != $_sid} {
862        set bytes [read $_sid $size]
863        $_image(legend) configure -data $bytes
864
865        set c $itk_component(legend)
866        set w [winfo width $c]
867        set h [winfo height $c]
868        if {"" == [$c find withtag transfunc]} {
869            $c create image 10 10 -anchor nw \
870                 -image $_image(legend) -tags transfunc
871
872            $c bind transfunc <ButtonPress-1> \
873                 [itcl::code $this _probe start %x %y]
874            $c bind transfunc <B1-Motion> \
875                 [itcl::code $this _probe update %x %y]
876            $c bind transfunc <ButtonRelease-1> \
877                 [itcl::code $this _probe end %x %y]
878
879            $c create text 10 [expr {$h-8}] -anchor sw \
880                 -fill $itk_option(-plotforeground) -tags vmin
881            $c create text [expr {$w-10}] [expr {$h-8}] -anchor se \
882                 -fill $itk_option(-plotforeground) -tags vmax
883        }
884
885        $c itemconfigure vmin -text $vmin
886        $c coords vmin 10 [expr {$h-8}]
887
888        $c itemconfigure vmax -text $vmax
889        $c coords vmax [expr {$w-10}] [expr {$h-8}]
890    }
891}
892
893# ----------------------------------------------------------------------
894# USAGE: _rebuild
895#
896# Called automatically whenever something changes that affects the
897# data in the widget.  Clears any existing data and rebuilds the
898# widget to display new data.
899# ----------------------------------------------------------------------
900itcl::body Rappture::NanovisViewer::_rebuild {} {
901    # in the midst of sending data? then bail out
902    if {[llength $_sendobjs] > 0} {
903        return
904    }
905
906    #
907    # Find any new data that needs to be sent to the server.
908    # Queue this up on the _sendobjs list, and send it out
909    # a little at a time.  Do this first, before we rebuild
910    # the rest.
911    #
912    foreach dataobj [get] {
913        set comp [lindex [$dataobj components] 0]
914        if {![info exists _obj2id($dataobj-$comp)]} {
915            set i [lsearch -exact $_sendobjs $dataobj]
916            if {$i < 0} {
917                lappend _sendobjs $dataobj
918            }
919        }
920    }
921    if {[llength $_sendobjs] > 0} {
922        # send off new data objects
923        after idle [itcl::code $this _send_dataobjs]
924    } else {
925        # nothing to send -- activate the proper volume
926        set first [lindex [get] 0]
927
928        set axis [$first hints updir]
929        if {"" != $axis} {
930            _send up $axis
931        }
932        foreach key [array names _obj2id *-*] {
933            set state [string match $first-* $key]
934            _send volume state $state $_obj2id($key)
935            if {[info exists _obj2style($key)]} {
936                _send volume shading transfunc $_obj2style($key) $_obj2id($key)
937            }
938        }
939
940        # sync the state of slicers
941        foreach axis {x y z} {
942            eval _send cutplane state [_state ${axis}slice] \
943                $axis [_currentVolumeIds]
944        }
945        eval _send volume data state [_state volume] [_currentVolumeIds]
946        $_dispatcher event -idle !legend
947    }
948
949    #
950    # Reset the camera and other view parameters
951    #
952    eval _send camera angle [_euler2xyz $_view(theta) $_view(phi) $_view(psi)]
953    _send camera zoom $_view(zoom)
954    #_fixSettings light
955    #_fixSettings transp
956
957    if {"" == $itk_option(-plotoutline)} {
958        _send volume outline state off
959    } else {
960        _send volume outline state on
961        _send volume outline color [_color2rgb $itk_option(-plotoutline)]
962    }
963    _send volume axis label x ""
964    _send volume axis label y ""
965    _send volume axis label z ""
966}
967
968# ----------------------------------------------------------------------
969# USAGE: _currentVolumeIds
970#
971# Returns a list of volume server IDs for the current volume being
972# displayed.  This is normally a single ID, but it might be a list
973# of IDs if the current data object has multiple components.
974# ----------------------------------------------------------------------
975itcl::body Rappture::NanovisViewer::_currentVolumeIds {} {
976    set rlist ""
977
978    set first [lindex [get] 0]
979    foreach key [array names _obj2id *-*] {
980        if {[string match $first-* $key]} {
981            lappend rlist $_obj2id($key)
982        }
983    }
984    return $rlist
985}
986
987# ----------------------------------------------------------------------
988# USAGE: _zoom in
989# USAGE: _zoom out
990# USAGE: _zoom reset
991#
992# Called automatically when the user clicks on one of the zoom
993# controls for this widget.  Changes the zoom for the current view.
994# ----------------------------------------------------------------------
995itcl::body Rappture::NanovisViewer::_zoom {option} {
996    switch -- $option {
997        in {
998            set _view(zoom) [expr {$_view(zoom)*1.25}]
999            _send camera zoom $_view(zoom)
1000        }
1001        out {
1002            set _view(zoom) [expr {$_view(zoom)*0.8}]
1003            _send camera zoom $_view(zoom)
1004        }
1005        reset {
1006            set _view(theta) 45
1007            set _view(phi) 45
1008            set _view(psi) 0
1009            set _view(zoom) 1.0
1010            eval _send camera angle [_euler2xyz $_view(theta) $_view(phi) $_view(psi)]
1011            _send camera zoom $_view(zoom)
1012        }
1013    }
1014}
1015
1016# ----------------------------------------------------------------------
1017# USAGE: _move click <x> <y>
1018# USAGE: _move drag <x> <y>
1019# USAGE: _move release <x> <y>
1020#
1021# Called automatically when the user clicks/drags/releases in the
1022# plot area.  Moves the plot according to the user's actions.
1023# ----------------------------------------------------------------------
1024itcl::body Rappture::NanovisViewer::_move {option x y} {
1025    switch -- $option {
1026        click {
1027            $itk_component(3dview) configure -cursor fleur
1028            set _click(x) $x
1029            set _click(y) $y
1030            set _click(theta) $_view(theta)
1031            set _click(phi) $_view(phi)
1032        }
1033        drag {
1034            if {[array size _click] == 0} {
1035                _move click $x $y
1036            } else {
1037                set w [winfo width $itk_component(3dview)]
1038                set h [winfo height $itk_component(3dview)]
1039                if {$w <= 0 || $h <= 0} {
1040                    return
1041                }
1042
1043                if {[catch {
1044                    # this fails sometimes for no apparent reason
1045                    set dx [expr {double($x-$_click(x))/$w}]
1046                    set dy [expr {double($y-$_click(y))/$h}]
1047                }]} {
1048                    return
1049                }
1050
1051                #
1052                # Rotate the camera in 3D
1053                #
1054                if {$_view(psi) > 90 || $_view(psi) < -90} {
1055                    # when psi is flipped around, theta moves backwards
1056                    set dy [expr {-$dy}]
1057                }
1058                set theta [expr {$_view(theta) - $dy*180}]
1059                while {$theta < 0} { set theta [expr {$theta+180}] }
1060                while {$theta > 180} { set theta [expr {$theta-180}] }
1061
1062                if {abs($theta) >= 30 && abs($theta) <= 160} {
1063                    set phi [expr {$_view(phi) - $dx*360}]
1064                    while {$phi < 0} { set phi [expr {$phi+360}] }
1065                    while {$phi > 360} { set phi [expr {$phi-360}] }
1066                    set psi $_view(psi)
1067                } else {
1068                    set phi $_view(phi)
1069                    set psi [expr {$_view(psi) - $dx*360}]
1070                    while {$psi < -180} { set psi [expr {$psi+360}] }
1071                    while {$psi > 180} { set psi [expr {$psi-360}] }
1072                }
1073
1074                set _view(theta) $theta
1075                set _view(phi) $phi
1076                set _view(psi) $psi
1077                eval _send camera angle [_euler2xyz $_view(theta) $_view(phi) $_view(psi)]
1078
1079                set _click(x) $x
1080                set _click(y) $y
1081            }
1082        }
1083        release {
1084            _move drag $x $y
1085            $itk_component(3dview) configure -cursor ""
1086            catch {unset _click}
1087        }
1088        default {
1089            error "bad option \"$option\": should be click, drag, release"
1090        }
1091    }
1092}
1093
1094# ----------------------------------------------------------------------
1095# USAGE: _slice axis x|y|z ?on|off|toggle?
1096# USAGE: _slice move x|y|z <newval>
1097# USAGE: _slice volume ?on|off|toggle?
1098#
1099# Called automatically when the user drags the slider to move the
1100# cut plane that slices 3D data.  Gets the current value from the
1101# slider and moves the cut plane to the appropriate point in the
1102# data set.
1103# ----------------------------------------------------------------------
1104itcl::body Rappture::NanovisViewer::_slice {option args} {
1105    switch -- $option {
1106        axis {
1107            if {[llength $args] < 1 || [llength $args] > 2} {
1108                error "wrong # args: should be \"_slice axis x|y|z ?on|off|toggle?\""
1109            }
1110            set axis [lindex $args 0]
1111            set op [lindex $args 1]
1112            if {$op == ""} { set op "on" }
1113
1114            set current [_state ${axis}slice]
1115            if {$op == "toggle"} {
1116                if {$current == "on"} { set op "off" } else { set op "on" }
1117            }
1118
1119            if {$op} {
1120                $itk_component(${axis}slicer) configure -state normal
1121                eval _send cutplane state on $axis [_currentVolumeIds]
1122                $itk_component(${axis}slice) configure -relief sunken
1123            } else {
1124                $itk_component(${axis}slicer) configure -state disabled
1125                eval _send cutplane state off $axis [_currentVolumeIds]
1126                $itk_component(${axis}slice) configure -relief raised
1127            }
1128        }
1129        move {
1130            if {[llength $args] != 2} {
1131                error "wrong # args: should be \"_slice move x|y|z newval\""
1132            }
1133            set axis [lindex $args 0]
1134            set newval [lindex $args 1]
1135
1136            set newpos [expr {0.01*$newval}]
1137#            set newval [expr {0.01*($newval-50)
1138#                *($_limits(${axis}max)-$_limits(${axis}min))
1139#                  + 0.5*($_limits(${axis}max)+$_limits(${axis}min))}]
1140
1141            # show the current value in the readout
1142#puts "readout: $axis = $newval"
1143
1144            eval _send cutplane position $newpos $axis [_currentVolumeIds]
1145        }
1146        volume {
1147            if {[llength $args] > 1} {
1148                error "wrong # args: should be \"_slice volume ?on|off|toggle?\""
1149            }
1150            set op [lindex $args 0]
1151            if {$op == ""} { set op "on" }
1152
1153            set current [_state volume]
1154            if {$op == "toggle"} {
1155                if {$current == "on"} { set op "off" } else { set op "on" }
1156            }
1157
1158            if {$op} {
1159                eval _send volume data state on [_currentVolumeIds]
1160                $itk_component(volume) configure -relief sunken
1161            } else {
1162                eval _send volume data state off [_currentVolumeIds]
1163                $itk_component(volume) configure -relief raised
1164            }
1165        }
1166        default {
1167            error "bad option \"$option\": should be axis, move, or volume"
1168        }
1169    }
1170}
1171
1172# ----------------------------------------------------------------------
1173# USAGE: _slicertip <axis>
1174#
1175# Used internally to generate a tooltip for the x/y/z slicer controls.
1176# Returns a message that includes the current slicer value.
1177# ----------------------------------------------------------------------
1178itcl::body Rappture::NanovisViewer::_slicertip {axis} {
1179    set val [$itk_component(${axis}slicer) get]
1180#    set val [expr {0.01*($val-50)
1181#        *($_limits(${axis}max)-$_limits(${axis}min))
1182#          + 0.5*($_limits(${axis}max)+$_limits(${axis}min))}]
1183    return "Move the [string toupper $axis] cut plane.\nCurrently:  $axis = $val%"
1184}
1185
1186# ----------------------------------------------------------------------
1187# USAGE: _probe start <x> <y>
1188# USAGE: _probe update <x> <y>
1189# USAGE: _probe end <x> <y>
1190#
1191# Used internally to handle the various probe operations, when the
1192# user clicks and drags on the legend area.  The probe changes the
1193# transfer function to highlight the area being selected in the
1194# legend.
1195# ----------------------------------------------------------------------
1196itcl::body Rappture::NanovisViewer::_probe {option args} {
1197    set c $itk_component(legend)
1198    set w [winfo width $c]
1199    set h [winfo height $c]
1200    set y0 10
1201    set y1 [expr {$y0+[image height $_image(legend)]-1}]
1202
1203    set dataobj [lindex [get] 0]
1204    set comp [lindex [$dataobj components] 0]
1205    if {![info exists _obj2style($dataobj-$comp)]} {
1206        return
1207    }
1208
1209    switch -- $option {
1210        start {
1211            # create the probe marker on the legend
1212            $c create rect 0 0 5 $h -width 3 \
1213                -outline black -fill "" -tags markerbg
1214            $c create rect 0 0 5 $h -width 1 \
1215                -outline white -fill "" -tags marker
1216
1217            # define a new transfer function
1218            _send transfunc define probe {0 0 0 0 1 0 0 0} {0 0 1 0}
1219            _send volume shading transfunc probe $_obj2id($dataobj-$comp)
1220
1221            # now, probe this point
1222            eval _probe update $args
1223        }
1224        update {
1225            set x [lindex $args 0]
1226            if {$x < 10} {set x 10}
1227            if {$x > $w-10} {set x [expr {$w-10}]}
1228            foreach tag {markerbg marker} {
1229                $c coords $tag [expr {$x-2}] [expr {$y0-2}] \
1230                    [expr {$x+2}] [expr {$y1+2}]
1231            }
1232
1233            # value of the probe point, in the range 0-1
1234            set val [expr {double($x-10)/($w-20)}]
1235            set dl [expr {($val > 0.1) ? 0.1 : $val}]
1236            set dr [expr {($val < 0.9) ? 0.1 : 1-$val}]
1237
1238            # compute a transfer function for the probe value
1239            foreach {sname cmap wmap} [_getTransfuncData $dataobj $comp] break
1240            set wmap "0.0 0.0 [expr {$val-$dl}] 0.0 $val 1.0 [expr {$val+$dr}] 0.0 1.0 0.0"
1241            _send transfunc define probe $cmap $wmap
1242        }
1243        end {
1244            $c delete marker markerbg
1245
1246            # put the volume back to its old transfer function
1247            _send volume shading transfunc $_obj2style($dataobj-$comp) $_obj2id($dataobj-$comp)
1248        }
1249        default {
1250            error "bad option \"$option\": should be start, update, end"
1251        }
1252    }
1253}
1254
1255# ----------------------------------------------------------------------
1256# USAGE: _state <component>
1257#
1258# Used internally to determine the state of a toggle button.
1259# The <component> is the itk component name of the button.
1260# Returns on/off for the state of the button.
1261# ----------------------------------------------------------------------
1262itcl::body Rappture::NanovisViewer::_state {comp} {
1263    if {[$itk_component($comp) cget -relief] == "sunken"} {
1264        return "on"
1265    }
1266    return "off"
1267}
1268
1269# ----------------------------------------------------------------------
1270# USAGE: _fixSettings <what> ?<value>?
1271#
1272# Used internally to update rendering settings whenever parameters
1273# change in the popup settings panel.  Sends the new settings off
1274# to the back end.
1275# ----------------------------------------------------------------------
1276itcl::body Rappture::NanovisViewer::_fixSettings {what {value ""}} {
1277    set inner [$itk_component(controls).panel component inner]
1278    switch -- $what {
1279        light {
1280            if {[isconnected]} {
1281                set val [$inner.scales.light get]
1282                set sval [expr {0.1*$val}]
1283                _send volume shading diffuse $sval
1284
1285                set sval [expr {sqrt($val+1.0)}]
1286                _send volume shading specular $sval
1287            }
1288        }
1289        transp {
1290            if {[isconnected]} {
1291                set val [$inner.scales.transp get]
1292                set sval [expr {0.2*$val+1}]
1293                _send volume shading opacity $sval
1294            }
1295        }
1296        default {
1297            error "don't know how to fix $what"
1298        }
1299    }
1300}
1301
1302# ----------------------------------------------------------------------
1303# USAGE: _fixLegend
1304#
1305# Used internally to update the legend area whenever it changes size
1306# or when the field changes.  Asks the server to send a new legend
1307# for the current field.
1308# ----------------------------------------------------------------------
1309itcl::body Rappture::NanovisViewer::_fixLegend {} {
1310    set lineht [font metrics $itk_option(-font) -linespace]
1311    set w [expr {[winfo width $itk_component(legend)]-20}]
1312    set h [expr {[winfo height $itk_component(legend)]-20-$lineht}]
1313    set ivol ""
1314
1315    set dataobj [lindex [get] 0]
1316    set comp [lindex [$dataobj components] 0]
1317    if {[info exists _obj2id($dataobj-$comp)]} {
1318        set ivol $_obj2id($dataobj-$comp)
1319    }
1320
1321    if {$w > 0 && $h > 0 && "" != $ivol} {
1322        _send legend $ivol $w $h
1323    } else {
1324        $itk_component(legend) delete all
1325    }
1326}
1327
1328# ----------------------------------------------------------------------
1329# USAGE: _serverDown
1330#
1331# Used internally to let the user know when the connection to the
1332# visualization server has been lost.  Puts up a tip encouraging the
1333# user to press any control to reconnect.
1334# ----------------------------------------------------------------------
1335itcl::body Rappture::NanovisViewer::_serverDown {} {
1336    set x [expr {[winfo rootx $itk_component(area)]+10}]
1337    set y [expr {[winfo rooty $itk_component(area)]+10}]
1338    Rappture::Tooltip::cue @$x,$y "Lost connection to visualization server.  This happens sometimes when there are too many users and the system runs out of memory.\n\nTo reconnect, reset the view or press any other control.  Your picture should come right back up."
1339}
1340
1341# ----------------------------------------------------------------------
1342# USAGE: _getTransfuncData <dataobj> <comp>
1343#
1344# Used internally to compute the colormap and alpha map used to define
1345# a transfer function for the specified component in a data object.
1346# Returns: name {v r g b ...} {v w ...}
1347# ----------------------------------------------------------------------
1348itcl::body Rappture::NanovisViewer::_getTransfuncData {dataobj comp} {
1349    array set style {
1350        -color rainbow
1351        -levels 6
1352        -opacity 0.5
1353    }
1354    array set style [lindex [$dataobj components -style $comp] 0]
1355    set sname "$style(-color):$style(-levels):$style(-opacity)"
1356
1357    if {$style(-color) == "rainbow"} {
1358        set style(-color) "white:yellow:green:cyan:blue:magenta"
1359    }
1360    set clist [split $style(-color) :]
1361    set cmap "0.0 [_color2rgb white] "
1362    for {set i 0} {$i < [llength $clist]} {incr i} {
1363        set xval [expr {double($i+1)/([llength $clist]+1)}]
1364        set color [lindex $clist $i]
1365        append cmap "$xval [_color2rgb $color] "
1366    }
1367    append cmap "1.0 [_color2rgb $color]"
1368
1369    set max $style(-opacity)
1370    set levels $style(-levels)
1371    set wmap "0.0 0.0 "
1372    set delta [expr {0.125/($levels+1)}]
1373    for {set i 1} {$i <= $levels} {incr i} {
1374        # add spikes in the middle
1375        set xval [expr {double($i)/($levels+1)}]
1376        append wmap "[expr {$xval-$delta-0.01}] 0.0  [expr {$xval-$delta}] $max [expr {$xval+$delta}] $max  [expr {$xval+$delta+0.01}] 0.0 "
1377    }
1378    append wmap "1.0 0.0 "
1379
1380    return [list $sname $cmap $wmap]
1381}
1382
1383# ----------------------------------------------------------------------
1384# USAGE: _color2rgb <color>
1385#
1386# Used internally to convert a color name to a set of {r g b} values
1387# needed for the engine.  Each r/g/b component is scaled in the
1388# range 0-1.
1389# ----------------------------------------------------------------------
1390itcl::body Rappture::NanovisViewer::_color2rgb {color} {
1391    foreach {r g b} [winfo rgb $itk_component(hull) $color] break
1392    set r [expr {$r/65535.0}]
1393    set g [expr {$g/65535.0}]
1394    set b [expr {$b/65535.0}]
1395    return [list $r $g $b]
1396}
1397
1398# ----------------------------------------------------------------------
1399# USAGE: _euler2xyz <theta> <phi> <psi>
1400#
1401# Used internally to convert euler angles for the camera placement
1402# the to angles of rotation about the x/y/z axes, used by the engine.
1403# Returns a list:  {xangle, yangle, zangle}.
1404# ----------------------------------------------------------------------
1405itcl::body Rappture::NanovisViewer::_euler2xyz {theta phi psi} {
1406    set xangle [expr {$theta-90.0}]
1407    set yangle [expr {180-$phi}]
1408    set zangle $psi
1409    return [list $xangle $yangle $zangle]
1410}
1411
1412# ----------------------------------------------------------------------
1413# CONFIGURATION OPTION: -plotbackground
1414# ----------------------------------------------------------------------
1415itcl::configbody Rappture::NanovisViewer::plotbackground {
1416    foreach {r g b} [_color2rgb $itk_option(-plotbackground)] break
1417    #fix this!
1418    #_send color background $r $g $b
1419}
1420
1421# ----------------------------------------------------------------------
1422# CONFIGURATION OPTION: -plotforeground
1423# ----------------------------------------------------------------------
1424itcl::configbody Rappture::NanovisViewer::plotforeground {
1425    foreach {r g b} [_color2rgb $itk_option(-plotforeground)] break
1426    #fix this!
1427    #_send color background $r $g $b
1428}
1429
1430# ----------------------------------------------------------------------
1431# CONFIGURATION OPTION: -plotoutline
1432# ----------------------------------------------------------------------
1433itcl::configbody Rappture::NanovisViewer::plotoutline {
1434    if {[isconnected]} {
1435        if {"" == $itk_option(-plotoutline)} {
1436            _send volume outline state off
1437        } else {
1438            _send volume outline state on
1439            _send volume outline color [_color2rgb $itk_option(-plotoutline)]
1440        }
1441    }
1442}
Note: See TracBrowser for help on using the repository browser.