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

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

changes to allow panning and zooming (via scrollwhell)

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