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

Last change on this file since 750 was 713, checked in by nkissebe, 18 years ago

nanovisviewer should send memory reservation in big endian (network byte
order).

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