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

Last change on this file since 1081 was 1081, checked in by gah, 16 years ago

fix for perl, ruby bindings; visviewer SendBytes? method sends all data at once

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