# ---------------------------------------------------------------------- # COMPONENT: nanovisviewer - 3D volume rendering # # This widget performs volume rendering on 3D scalar/vector datasets. # It connects to the Nanovis server running on a rendering farm, # transmits data, and displays the results. # ====================================================================== # AUTHOR: Michael McLennan, Purdue University # Copyright (c) 2004-2005 Purdue Research Foundation # # See the file "license.terms" for information on usage and # redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES. # ====================================================================== package require Itk package require BLT package require Img # # FIXME: # Need to Add DX readers this client to examine the data before # it's sent to the server. This will eliminate 90% of the insanity in # computing the limits of all the volumes. I can rip out all the # "receive data" "send transfer function" event crap. # # This means we can compute the transfer function (relative values) and # draw the legend min/max values without waiting for the information to # come from the server. This will also prevent the flashing that occurs # when a new volume is drawn (using the default transfer function) and # then when the correct transfer function has been sent and linked to # the volume. # option add *NanovisViewer.width 4i widgetDefault option add *NanovisViewer*cursor crosshair widgetDefault option add *NanovisViewer.height 4i widgetDefault option add *NanovisViewer.foreground black widgetDefault option add *NanovisViewer.controlBackground gray widgetDefault option add *NanovisViewer.controlDarkBackground #999999 widgetDefault option add *NanovisViewer.plotBackground black widgetDefault option add *NanovisViewer.plotForeground white widgetDefault option add *NanovisViewer.plotOutline gray widgetDefault option add *NanovisViewer.font \ -*-helvetica-medium-r-normal-*-12-* widgetDefault # must use this name -- plugs into Rappture::resources::load proc NanovisViewer_init_resources {} { Rappture::resources::register \ nanovis_server Rappture::NanovisViewer::SetServerList } itcl::class Rappture::NanovisViewer { inherit Rappture::VisViewer itk_option define -plotforeground plotForeground Foreground "" itk_option define -plotbackground plotBackground Background "" itk_option define -plotoutline plotOutline PlotOutline "" constructor { hostlist args } { Rappture::VisViewer::constructor $hostlist } { # defined below } destructor { # defined below } public proc SetServerList { namelist } { Rappture::VisViewer::SetServerList "nanovis" $namelist } public method add {dataobj {settings ""}} public method camera {option args} public method delete {args} public method disconnect {} public method download {option args} public method get {args} public method isconnected {} public method limits { tf } public method overmarker { m x } public method sendto { string } public method parameters {title args} { # do nothing } public method rmdupmarker { m x } public method scale {args} public method updatetransferfuncs {} protected method Connect {} protected method CurrentVolumes {{what -all}} protected method Disconnect {} protected method DoResize {} protected method FixLegend {} protected method FixSettings {what {value ""}} protected method Pan {option x y} protected method Rebuild {} protected method ReceiveData { args } protected method ReceiveImage { args } protected method ReceiveLegend { tf vmin vmax size } protected method Rotate {option x y} protected method SendCmd {string} protected method SendTransferFuncs {} protected method Slice {option args} protected method SlicerTip {axis} protected method Zoom {option} # The following methods are only used by this class. private method AddIsoMarker { x y } private method BuildCameraTab {} private method BuildCutplanesTab {} private method BuildViewTab {} private method BuildVolumeTab {} private method ComputeTransferFunc { tf } private method EventuallyResize { w h } private method EventuallyResizeLegend { } private method NameTransferFunc { dataobj comp } private method PanCamera {} private method ParseLevelsOption { tf levels } private method ParseMarkersOption { tf markers } private method volume { tag name } private method GetVolumeInfo { w } private method SetOrientation {} private variable _outbuf ;# buffer for outgoing commands private variable _dlist "" ;# list of data objects private variable _allDataObjs private variable _obj2ovride ;# maps dataobj => style override private variable _serverVols ;# contains all the dataobj-component ;# to volumes in the server private variable _serverTfs ;# contains all the transfer functions ;# in the server. private variable _recvdVols ;# list of data objs to send to server private variable _vol2style ;# maps dataobj-component to transfunc private variable _style2vols ;# maps tf back to list of # dataobj-components using the tf. private variable _click ;# info used for rotate operations private variable _limits ;# autoscale min/max for all axes private variable _view ;# view params for 3D view private variable _isomarkers ;# array of isosurface level values 0..1 private common _settings # Array of transfer functions in server. If 0 the transfer has been # defined but not loaded. If 1 the transfer function has been named # and loaded. private variable _activeTfs private variable _first "" ;# This is the topmost volume. private variable _buffering 0 # This # indicates which isomarkers and transfer # function to use when changing markers, # opacity, or thickness. common _downloadPopup ;# download options from popup private common _hardcopy private variable _width 0 private variable _height 0 private variable _resizePending 0 private variable _resizeLegendPending 0 } itk::usual NanovisViewer { keep -background -foreground -cursor -font keep -plotbackground -plotforeground } # ---------------------------------------------------------------------- # CONSTRUCTOR # ---------------------------------------------------------------------- itcl::body Rappture::NanovisViewer::constructor {hostlist args} { set _serverType "nanovis" # Draw legend event $_dispatcher register !legend $_dispatcher dispatch $this !legend "[itcl::code $this FixLegend]; list" # Send transfer functions event $_dispatcher register !send_transfunc $_dispatcher dispatch $this !send_transfunc \ "[itcl::code $this SendTransferFuncs]; list" # Rebuild event $_dispatcher register !rebuild $_dispatcher dispatch $this !rebuild "[itcl::code $this Rebuild]; list" # Resize event $_dispatcher register !resize $_dispatcher dispatch $this !resize "[itcl::code $this DoResize]; list" set _outbuf "" # # Populate parser with commands handle incoming requests # $_parser alias image [itcl::code $this ReceiveImage] $_parser alias legend [itcl::code $this ReceiveLegend] $_parser alias data [itcl::code $this ReceiveData] # Initialize the view to some default parameters. array set _view { theta 45 phi 45 psi 0 zoom 1.0 pan-x 0 pan-y 0 } set _limits(vmin) 0.0 set _limits(vmax) 1.0 array set _settings [subst { $this-pan-x $_view(pan-x) $this-pan-y $_view(pan-y) $this-phi $_view(phi) $this-psi $_view(psi) $this-theta $_view(theta) $this-volume 1 $this-xcutplane 0 $this-xcutposition 0 $this-ycutplane 0 $this-ycutposition 0 $this-zcutplane 0 $this-zcutposition 0 $this-zoom $_view(zoom) }] itk_component add 3dview { label $itk_component(plotarea).view -image $_image(plot) \ -highlightthickness 0 -borderwidth 0 } { usual ignore -highlightthickness -borderwidth -background } set f [$itk_component(main) component controls] itk_component add reset { button $f.reset -borderwidth 1 -padx 1 -pady 1 \ -highlightthickness 0 \ -image [Rappture::icon reset-view] \ -command [itcl::code $this Zoom reset] } { usual ignore -highlightthickness } pack $itk_component(reset) -side top -padx 2 -pady 2 Rappture::Tooltip::for $itk_component(reset) "Reset the view to the default zoom level" itk_component add zoomin { button $f.zin -borderwidth 1 -padx 1 -pady 1 \ -highlightthickness 0 \ -image [Rappture::icon zoom-in] \ -command [itcl::code $this Zoom in] } { usual ignore -highlightthickness } pack $itk_component(zoomin) -side top -padx 2 -pady 2 Rappture::Tooltip::for $itk_component(zoomin) "Zoom in" itk_component add zoomout { button $f.zout -borderwidth 1 -padx 1 -pady 1 \ -highlightthickness 0 \ -image [Rappture::icon zoom-out] \ -command [itcl::code $this Zoom out] } { usual ignore -highlightthickness } pack $itk_component(zoomout) -side top -padx 2 -pady 2 Rappture::Tooltip::for $itk_component(zoomout) "Zoom out" itk_component add volume { Rappture::PushButton $f.volume \ -onimage [Rappture::icon volume-on] \ -offimage [Rappture::icon volume-off] \ -command [itcl::code $this FixSettings volume] \ -variable [itcl::scope _settings($this-volume)] } $itk_component(volume) select Rappture::Tooltip::for $itk_component(volume) \ "Toggle the volume cloud on/off" pack $itk_component(volume) -padx 2 -pady 2 BuildViewTab BuildVolumeTab BuildCutplanesTab BuildCameraTab # Legend set _image(legend) [image create photo] itk_component add legend { canvas $itk_component(plotarea).legend -height 50 -highlightthickness 0 } { usual ignore -highlightthickness rename -background -plotbackground plotBackground Background } bind $itk_component(legend) \ [itcl::code $this EventuallyResizeLegend] # Hack around the Tk panewindow. The problem is that the requested # size of the 3d view isn't set until an image is retrieved from # the server. So the panewindow uses the tiny size. set w 10000 pack forget $itk_component(3dview) blt::table $itk_component(plotarea) \ 0,0 $itk_component(3dview) -fill both -reqwidth $w \ 1,0 $itk_component(legend) -fill x blt::table configure $itk_component(plotarea) r1 -resize none # Bindings for rotation via mouse bind $itk_component(3dview) \ [itcl::code $this Rotate click %x %y] bind $itk_component(3dview) \ [itcl::code $this Rotate drag %x %y] bind $itk_component(3dview) \ [itcl::code $this Rotate release %x %y] bind $itk_component(3dview) \ [itcl::code $this EventuallyResize %w %h] # Bindings for panning via mouse bind $itk_component(3dview) \ [itcl::code $this Pan click %x %y] bind $itk_component(3dview) \ [itcl::code $this Pan drag %x %y] bind $itk_component(3dview) \ [itcl::code $this Pan release %x %y] # Bindings for panning via keyboard bind $itk_component(3dview) \ [itcl::code $this Pan set -10 0] bind $itk_component(3dview) \ [itcl::code $this Pan set 10 0] bind $itk_component(3dview) \ [itcl::code $this Pan set 0 -10] bind $itk_component(3dview) \ [itcl::code $this Pan set 0 10] bind $itk_component(3dview) \ [itcl::code $this Pan set -2 0] bind $itk_component(3dview) \ [itcl::code $this Pan set 2 0] bind $itk_component(3dview) \ [itcl::code $this Pan set 0 -2] bind $itk_component(3dview) \ [itcl::code $this Pan set 0 2] # Bindings for zoom via keyboard bind $itk_component(3dview) \ [itcl::code $this Zoom out] bind $itk_component(3dview) \ [itcl::code $this Zoom in] bind $itk_component(3dview) "focus $itk_component(3dview)" if {[string equal "x11" [tk windowingsystem]]} { # Bindings for zoom via mouse bind $itk_component(3dview) <4> [itcl::code $this Zoom out] bind $itk_component(3dview) <5> [itcl::code $this Zoom in] } set _image(download) [image create photo] eval itk_initialize $args Connect } # ---------------------------------------------------------------------- # DESTRUCTOR # ---------------------------------------------------------------------- itcl::body Rappture::NanovisViewer::destructor {} { $_dispatcher cancel !rebuild $_dispatcher cancel !send_transfunc $_dispatcher cancel !resize image delete $_image(plot) image delete $_image(legend) image delete $_image(download) array unset _settings $this-* } # ---------------------------------------------------------------------- # USAGE: add ?? # # Clients use this to add a data object to the plot. The optional # are used to configure the plot. Allowed settings are # -color, -brightness, -width, -linestyle, and -raise. # ---------------------------------------------------------------------- itcl::body Rappture::NanovisViewer::add {dataobj {settings ""}} { array set params { -color auto -width 1 -linestyle solid -brightness 0 -raise 0 -description "" -param "" } foreach {opt val} $settings { if {![info exists params($opt)]} { error "bad setting \"$opt\": should be [join [lsort [array names params]] {, }]" } set params($opt) $val } if {$params(-color) == "auto" || $params(-color) == "autoreset"} { # can't handle -autocolors yet set params(-color) black } set pos [lsearch -exact $dataobj $_dlist] if {$pos < 0} { lappend _dlist $dataobj set _allDataObjs($dataobj) 1 set _obj2ovride($dataobj-color) $params(-color) set _obj2ovride($dataobj-width) $params(-width) set _obj2ovride($dataobj-raise) $params(-raise) $_dispatcher event -idle !rebuild } } # ---------------------------------------------------------------------- # USAGE: get ?-objects? # USAGE: get ?-image 3dview|legend? # # Clients use this to query the list of objects being plotted, in # order from bottom to top of this result. The optional "-image" # flag can also request the internal images being shown. # ---------------------------------------------------------------------- itcl::body Rappture::NanovisViewer::get {args} { if {[llength $args] == 0} { set args "-objects" } set op [lindex $args 0] switch -- $op { -objects { # put the dataobj list in order according to -raise options set dlist $_dlist foreach obj $dlist { if {[info exists _obj2ovride($obj-raise)] && $_obj2ovride($obj-raise)} { set i [lsearch -exact $dlist $obj] if {$i >= 0} { set dlist [lreplace $dlist $i $i] lappend dlist $obj } } } return $dlist } -image { if {[llength $args] != 2} { error "wrong # args: should be \"get -image 3dview|legend\"" } switch -- [lindex $args end] { 3dview { return $_image(plot) } legend { return $_image(legend) } default { error "bad image name \"[lindex $args end]\": should be 3dview or legend" } } } default { error "bad option \"$op\": should be -objects or -image" } } } # ---------------------------------------------------------------------- # USAGE: delete ? ...? # # Clients use this to delete a dataobj from the plot. If no dataobjs # are specified, then all dataobjs are deleted. No data objects are # deleted. They are only removed from the display list. # # ---------------------------------------------------------------------- itcl::body Rappture::NanovisViewer::delete {args} { if {[llength $args] == 0} { set args $_dlist } # Delete all specified dataobjs set changed 0 foreach dataobj $args { set pos [lsearch -exact $_dlist $dataobj] if { $pos >= 0 } { set _dlist [lreplace $_dlist $pos $pos] array unset _limits $dataobj* array unset _obj2ovride $dataobj-* array unset _vol2style $dataobj-* set changed 1 } } # If anything changed, then rebuild the plot if {$changed} { $_dispatcher event -idle !rebuild } } # ---------------------------------------------------------------------- # USAGE: scale ? ...? # # Sets the default limits for the overall plot according to the # limits of the data for all of the given objects. This # accounts for all objects--even those not showing on the screen. # Because of this, the limits are appropriate for all objects as # the user scans through data in the ResultSet viewer. # ---------------------------------------------------------------------- itcl::body Rappture::NanovisViewer::scale {args} { foreach val {xmin xmax ymin ymax zmin zmax vmin vmax} { set _limits($val) "" } foreach obj $args { foreach axis {x y z v} { foreach { min max } [$obj limits $axis] break if {"" != $min && "" != $max} { if {"" == $_limits(${axis}min)} { set _limits(${axis}min) $min set _limits(${axis}max) $max } else { if {$min < $_limits(${axis}min)} { set _limits(${axis}min) $min } if {$max > $_limits(${axis}max)} { set _limits(${axis}max) $max } } } } } } # ---------------------------------------------------------------------- # USAGE: download coming # USAGE: download controls # USAGE: download now # # Clients use this method to create a downloadable representation # of the plot. Returns a list of the form {ext string}, where # "ext" is the file extension (indicating the type of data) and # "string" is the data itself. # ---------------------------------------------------------------------- itcl::body Rappture::NanovisViewer::download {option args} { switch $option { coming { if {[catch { blt::winop snap $itk_component(plotarea) $_image(download) }]} { $_image(download) configure -width 1 -height 1 $_image(download) put #000000 } } controls { # no controls for this download yet return "" } now { # Get the image data (as base64) and decode it back to binary. # This is better than writing to temporary files. When we switch # to the BLT picture image it won't be necessary to decode the # image data. if { [image width $_image(plot)] > 0 && [image height $_image(plot)] > 0 } { set bytes [$_image(plot) data -format "jpeg -quality 100"] set bytes [Rappture::encoding::decode -as b64 $bytes] return [list .jpg $bytes] } return "" } default { error "bad option \"$option\": should be coming, controls, now" } } } # ---------------------------------------------------------------------- # USAGE: Connect ?,...? # # Clients use this method to establish a connection to a new # server, or to reestablish a connection to the previous server. # Any existing connection is automatically closed. # ---------------------------------------------------------------------- itcl::body Rappture::NanovisViewer::Connect {} { set _hosts [GetServerList "nanovis"] if { "" == $_hosts } { return 0 } set result [VisViewer::Connect $_hosts] if { $result } { set w [winfo width $itk_component(3dview)] set h [winfo height $itk_component(3dview)] EventuallyResize $w $h } return $result } # # isconnected -- # # Indicates if we are currently connected to the visualization server. # itcl::body Rappture::NanovisViewer::isconnected {} { return [VisViewer::IsConnected] } # # disconnect -- # itcl::body Rappture::NanovisViewer::disconnect {} { Disconnect } # # Disconnect -- # # Clients use this method to disconnect from the current rendering # server. # itcl::body Rappture::NanovisViewer::Disconnect {} { VisViewer::Disconnect # disconnected -- no more data sitting on server set _outbuf "" array unset _serverVols } # # sendto -- # itcl::body Rappture::NanovisViewer::sendto { bytes } { SendBytes "$bytes\n" } # # SendCmd # # Send commands off to the rendering server. If we're currently # sending data objects to the server, buffer the commands to be # sent later. # itcl::body Rappture::NanovisViewer::SendCmd {string} { if { $_buffering } { append _outbuf $string "\n" } else { foreach line [split $string \n] { SendEcho >>line $line } SendBytes "$string\n" } } # ---------------------------------------------------------------------- # USAGE: SendTransferFuncs # ---------------------------------------------------------------------- itcl::body Rappture::NanovisViewer::SendTransferFuncs {} { if { $_first == "" } { puts stderr "first not set" return } # Ensure that the global opacity and thickness settings (in the slider # settings widgets) are used for the active transfer-function. Update # the values in the _settings varible. set opacity [expr { double($_settings($this-opacity)) * 0.01 }] # Scale values between 0.00001 and 0.01000 set thickness [expr {double($_settings($this-thickness)) * 0.0001}] foreach vol [CurrentVolumes] { if { ![info exists _serverVols($vol)] || !$_serverVols($vol) } { # The volume hasn't reached the server yet. How did we get # here? continue } if { ![info exists _vol2style($vol)] } { puts stderr "unknown volume $vol" continue; # How does this happen? } set tf $_vol2style($vol) set _settings($this-$tf-opacity) $opacity set _settings($this-$tf-thickness) $thickness ComputeTransferFunc $tf # FIXME: Need to the send information as to what transfer functions # to update so that we only update the transfer function # as necessary. Right now, all transfer functions are # updated. This makes moving the isomarker slider chunky. if { ![info exists _activeTfs($tf)] || !$_activeTfs($tf) } { set _activeTfs($tf) 1 } SendCmd "volume shading transfunc $tf $vol" } FixLegend } # ---------------------------------------------------------------------- # USAGE: ReceiveImage -bytes -type -token # # Invoked automatically whenever the "image" command comes in from # the rendering server. Indicates that binary image data with the # specified will follow. # ---------------------------------------------------------------------- itcl::body Rappture::NanovisViewer::ReceiveImage { args } { array set info { -token "???" -bytes 0 -type image } array set info $args set bytes [ReceiveBytes $info(-bytes)] ReceiveEcho <" $_image(plot) configure -data $bytes } elseif { $info(type) == "print" } { set tag $this-print-$info(-token) set _hardcopy($tag) $bytes } } # # ReceiveLegend -- # # The procedure is the response from the render server to each "legend" # command. The server sends back a "legend" command invoked our # the slave interpreter. The purpose is to collect data of the image # representing the legend in the canvas. In addition, the isomarkers # of the active transfer function are displayed. # # I don't know is this is the right place to display the isomarkers. # I don't know all the different paths used to draw the plot. There's # "Rebuild", "add", etc. # itcl::body Rappture::NanovisViewer::ReceiveLegend { tf vmin vmax size } { if { ![isconnected] } { return } set bytes [ReceiveBytes $size] $_image(legend) configure -data $bytes ReceiveEcho <" set c $itk_component(legend) set w [winfo width $c] set h [winfo height $c] set lx 10 set ly [expr {$h - 1}] if {"" == [$c find withtag transfunc]} { $c create image 10 10 -anchor nw \ -image $_image(legend) -tags transfunc $c create text $lx $ly -anchor sw \ -fill $itk_option(-plotforeground) -tags "limits vmin" $c create text [expr {$w-$lx}] $ly -anchor se \ -fill $itk_option(-plotforeground) -tags "limits vmax" $c lower transfunc $c bind transfunc \ [itcl::code $this AddIsoMarker %x %y] } # Display the markers used by the active transfer function. array set limits [limits $tf] $c itemconfigure vmin -text [format %.2g $limits(min)] $c coords vmin $lx $ly $c itemconfigure vmax -text [format %.2g $limits(max)] $c coords vmax [expr {$w-$lx}] $ly if { [info exists _isomarkers($tf)] } { foreach m $_isomarkers($tf) { $m visible yes } } # The colormap may have changed. Resync the slicers with the colormap. set vols [CurrentVolumes -cutplanes] SendCmd "volume data state $_settings($this-volume) $vols" # Adjust the cutplane for only the first component in the topmost volume # (i.e. the first volume designated in the field). set vol [lindex $vols 0] foreach axis {x y z} { # Turn off cutplanes for all volumes SendCmd "cutplane state 0 $axis" if { $_settings($this-${axis}cutplane) } { # Turn on cutplane for this particular volume and set the position SendCmd "cutplane state 1 $axis $vol" set pos [expr {0.01*$_settings($this-${axis}cutposition)}] SendCmd "cutplane position $pos $axis $vol" } } } # # ReceiveData -- # # The procedure is the response from the render server to each "data # follows" command. The server sends back a "data" command invoked our # the slave interpreter. The purpose is to collect the min/max of the # volume sent to the render server. Since the client (nanovisviewer) # doesn't parse 3D data formats, we rely on the server (nanovis) to # tell us what the limits are. Once we've received the limits to all # the data we've sent (tracked by _recvdVols) we can then determine # what the transfer functions are for these volumes. # # # Note: There is a considerable tradeoff in having the server report # back what the data limits are. It means that much of the code # having to do with transfer-functions has to wait for the data # to come back, since the isomarkers are calculated based upon # the data limits. The client code is much messier because of # this. The alternative is to parse any of the 3D formats on the # client side. # itcl::body Rappture::NanovisViewer::ReceiveData { args } { if { ![isconnected] } { return } # Arguments from server are name value pairs. Stuff them in an array. array set info $args set tag $info(tag) set parts [split $tag -] # # Volumes don't exist until we're told about them. # set dataobj [lindex $parts 0] set _serverVols($tag) 1 if { $_settings($this-volume) && $dataobj == $_first } { SendCmd "volume state 1 $tag" } set _limits($tag-min) $info(min); # Minimum value of the volume. set _limits($tag-max) $info(max); # Maximum value of the volume. set _limits(vmin) $info(vmin); # Overall minimum value. set _limits(vmax) $info(vmax); # Overall maximum value. unset _recvdVols($tag) if { [array size _recvdVols] == 0 } { # The active transfer function is by default the first component of # the first data object. This assumes that the data is always # successfully transferred. updatetransferfuncs } } # ---------------------------------------------------------------------- # USAGE: Rebuild # # Called automatically whenever something changes that affects the # data in the widget. Clears any existing data and rebuilds the # widget to display new data. # ---------------------------------------------------------------------- itcl::body Rappture::NanovisViewer::Rebuild {} { # Turn on buffering of commands to the server. We don't want to # be preempted by a server disconnect/reconnect (which automatically # generates a new call to Rebuild). set _buffering 1 # Hide all the isomarkers. Can't remove them. Have to remember the # settings since the user may have created/deleted/moved markers. foreach tf [array names _isomarkers] { foreach m $_isomarkers($tf) { $m visible no } } set w [winfo width $itk_component(3dview)] set h [winfo height $itk_component(3dview)] EventuallyResize $w $h foreach dataobj [get] { foreach comp [$dataobj components] { set vol $dataobj-$comp if { ![info exists _serverVols($vol)] } { # Send the data as one huge base64-encoded mess -- yuck! set data [$dataobj values $comp] set nbytes [string length $data] append _outbuf "volume data follows $nbytes $vol\n" append _outbuf $data set _recvdVols($vol) 1 set _serverVols($vol) 0 } NameTransferFunc $dataobj $comp } } # # Reset the camera and other view parameters # set _settings($this-theta) $_view(theta) set _settings($this-phi) $_view(phi) set _settings($this-psi) $_view(psi) set _settings($this-pan-x) $_view(pan-x) set _settings($this-pan-y) $_view(pan-y) set _settings($this-zoom) $_view(zoom) set xyz [Euler2XYZ $_view(theta) $_view(phi) $_view(psi)] SendCmd "camera angle $xyz" PanCamera SendCmd "camera zoom $_view(zoom)" FixSettings light FixSettings transp FixSettings isosurface FixSettings grid FixSettings axes FixSettings outline # nothing to send -- activate the proper ivol SendCmd "volume state 0" set _first [lindex [get] 0] if {"" != $_first} { set axis [$_first hints updir] if { "" != $axis } { SendCmd "up $axis" } set location [$_first hints camera] if { $location != "" } { array set _view $location } set vols [array names _serverVols $_first-*] if { $vols != "" } { SendCmd "volume state 1 $vols" } } # If the first volume already exists on the server, then make sure we # display the proper transfer function in the legend. set comp [lindex [$_first components] 0] if { [info exists _serverVols($_first-$comp)] } { updatetransferfuncs } foreach axis {x y z} { # Turn off cutplanes for all volumes SendCmd "cutplane state 0 $axis" } set _buffering 0; # Turn off buffering. # Actually write the commands to the server socket. If it fails, we don't # care. We're finished here. blt::busy hold $itk_component(hull) SendBytes $_outbuf; blt::busy release $itk_component(hull) set _outbuf ""; # Clear the buffer. } # ---------------------------------------------------------------------- # USAGE: CurrentVolumes ?-cutplanes? # # Returns a list of volume server IDs for the current volume being # displayed. This is normally a single ID, but it might be a list # of IDs if the current data object has multiple components. # ---------------------------------------------------------------------- itcl::body Rappture::NanovisViewer::CurrentVolumes {{what -all}} { set rlist "" if { $_first == "" } { return } foreach comp [$_first components] { set vol $_first-$comp if { [info exists _serverVols($vol)] && $_serverVols($vol) } { array set style { -cutplanes 1 } array set style [lindex [$_first components -style $comp] 0] if { $what != "-cutplanes" || $style(-cutplanes) } { lappend rlist $vol } } } return $rlist } # ---------------------------------------------------------------------- # USAGE: Zoom in # USAGE: Zoom out # USAGE: Zoom reset # # Called automatically when the user clicks on one of the zoom # controls for this widget. Changes the zoom for the current view. # ---------------------------------------------------------------------- itcl::body Rappture::NanovisViewer::Zoom {option} { switch -- $option { "in" { set _view(zoom) [expr {$_view(zoom)*1.25}] set _settings($this-zoom) $_view(zoom) } "out" { set _view(zoom) [expr {$_view(zoom)*0.8}] set _settings($this-zoom) $_view(zoom) } "reset" { array set _view { theta 45 phi 45 psi 0 zoom 1.0 pan-x 0 pan-y 0 } if { $_first != "" } { set location [$_first hints camera] if { $location != "" } { array set _view $location } } set xyz [Euler2XYZ $_view(theta) $_view(phi) $_view(psi)] SendCmd "camera angle $xyz" PanCamera set _settings($this-theta) $_view(theta) set _settings($this-phi) $_view(phi) set _settings($this-psi) $_view(psi) set _settings($this-pan-x) $_view(pan-x) set _settings($this-pan-y) $_view(pan-y) set _settings($this-zoom) $_view(zoom) } } SendCmd "camera zoom $_view(zoom)" } itcl::body Rappture::NanovisViewer::PanCamera {} { #set x [expr ($_view(pan-x)) / $_limits(xrange)] #set y [expr ($_view(pan-y)) / $_limits(yrange)] set x $_view(pan-x) set y $_view(pan-y) SendCmd "camera pan $x $y" } # ---------------------------------------------------------------------- # USAGE: Rotate click # USAGE: Rotate drag # USAGE: Rotate release # # Called automatically when the user clicks/drags/releases in the # plot area. Moves the plot according to the user's actions. # ---------------------------------------------------------------------- itcl::body Rappture::NanovisViewer::Rotate {option x y} { switch -- $option { click { $itk_component(3dview) configure -cursor fleur set _click(x) $x set _click(y) $y set _click(theta) $_view(theta) set _click(phi) $_view(phi) } drag { if {[array size _click] == 0} { Rotate click $x $y } else { set w [winfo width $itk_component(3dview)] set h [winfo height $itk_component(3dview)] if {$w <= 0 || $h <= 0} { return } if {[catch { # this fails sometimes for no apparent reason set dx [expr {double($x-$_click(x))/$w}] set dy [expr {double($y-$_click(y))/$h}] }]} { return } # # Rotate the camera in 3D # if {$_view(psi) > 90 || $_view(psi) < -90} { # when psi is flipped around, theta moves backwards set dy [expr {-$dy}] } set theta [expr {$_view(theta) - $dy*180}] while {$theta < 0} { set theta [expr {$theta+180}] } while {$theta > 180} { set theta [expr {$theta-180}] } if {abs($theta) >= 30 && abs($theta) <= 160} { set phi [expr {$_view(phi) - $dx*360}] while {$phi < 0} { set phi [expr {$phi+360}] } while {$phi > 360} { set phi [expr {$phi-360}] } set psi $_view(psi) } else { set phi $_view(phi) set psi [expr {$_view(psi) - $dx*360}] while {$psi < -180} { set psi [expr {$psi+360}] } while {$psi > 180} { set psi [expr {$psi-360}] } } set _view(theta) $theta set _view(phi) $phi set _view(psi) $psi set xyz [Euler2XYZ $theta $phi $psi] set _settings($this-theta) $_view(theta) set _settings($this-phi) $_view(phi) set _settings($this-psi) $_view(psi) SendCmd "camera angle $xyz" set _click(x) $x set _click(y) $y } } release { Rotate drag $x $y $itk_component(3dview) configure -cursor "" catch {unset _click} } default { error "bad option \"$option\": should be click, drag, release" } } } # ---------------------------------------------------------------------- # USAGE: $this Pan click x y # $this Pan drag x y # $this Pan release x y # # Called automatically when the user clicks on one of the zoom # controls for this widget. Changes the zoom for the current view. # ---------------------------------------------------------------------- itcl::body Rappture::NanovisViewer::Pan {option x y} { # Experimental stuff set w [winfo width $itk_component(3dview)] set h [winfo height $itk_component(3dview)] if { $option == "set" } { set x [expr $x / double($w)] set y [expr $y / double($h)] set _view(pan-x) [expr $_view(pan-x) + $x] set _view(pan-y) [expr $_view(pan-y) + $y] PanCamera set _settings($this-pan-x) $_view(pan-x) set _settings($this-pan-y) $_view(pan-y) return } if { $option == "click" } { set _click(x) $x set _click(y) $y $itk_component(3dview) configure -cursor hand1 } if { $option == "drag" || $option == "release" } { set dx [expr ($_click(x) - $x)/double($w)] set dy [expr ($_click(y) - $y)/double($h)] set _click(x) $x set _click(y) $y set _view(pan-x) [expr $_view(pan-x) - $dx] set _view(pan-y) [expr $_view(pan-y) - $dy] PanCamera set _settings($this-pan-x) $_view(pan-x) set _settings($this-pan-y) $_view(pan-y) } if { $option == "release" } { $itk_component(3dview) configure -cursor "" } } # ---------------------------------------------------------------------- # USAGE: FixSettings ?? # # Used internally to update rendering settings whenever parameters # change in the popup settings panel. Sends the new settings off # to the back end. # ---------------------------------------------------------------------- itcl::body Rappture::NanovisViewer::FixSettings {what {value ""}} { switch -- $what { light { if {[isconnected]} { set val $_settings($this-light) set sval [expr {0.01*$val}] SendCmd "volume shading diffuse $sval" set sval [expr {sqrt($val+1.0)}] SendCmd "volume shading specular $sval" } } transp { if {[isconnected]} { set val $_settings($this-transp) set sval [expr {0.2*$val+1}] SendCmd "volume shading opacity $sval" } } opacity { if {[isconnected] && [array size _activeTfs] > 0 } { set val $_settings($this-opacity) set sval [expr { 0.01 * double($val) }] foreach tf [array names _activeTfs] { set _settings($this-$tf-opacity) $sval set _activeTfs($tf) 0 } updatetransferfuncs } } thickness { if {[isconnected] && [array names _activeTfs] > 0 } { set val $_settings($this-thickness) # Scale values between 0.00001 and 0.01000 set sval [expr {0.0001*double($val)}] foreach tf [array names _activeTfs] { set _settings($this-$tf-thickness) $sval set _activeTfs($tf) 0 } updatetransferfuncs } } "outline" { if {[isconnected]} { SendCmd "volume outline state $_settings($this-outline)" } } "isosurface" { if {[isconnected]} { SendCmd "volume shading isosurface $_settings($this-isosurface)" } } "grid" { if { [isconnected] } { SendCmd "grid visible $_settings($this-grid)" } } "axes" { if { [isconnected] } { SendCmd "axis visible $_settings($this-axes)" } } "legend" { if { $_settings($this-legend) } { blt::table $itk_component(plotarea) \ 0,0 $itk_component(3dview) -fill both \ 1,0 $itk_component(legend) -fill x blt::table configure $itk_component(plotarea) r1 -resize none } else { blt::table forget $itk_component(legend) } } "volume" { if { [isconnected] } { set vols [CurrentVolumes -cutplanes] SendCmd "volume data state $_settings($this-volume) $vols" } } "xcutplane" - "ycutplane" - "zcutplane" { set axis [string range $what 0 0] set bool $_settings($this-$what) if { [isconnected] } { set vols [CurrentVolumes -cutplanes] set vol [lindex $vols 0] SendCmd "cutplane state $bool $axis $vol" } if { $bool } { $itk_component(${axis}CutScale) configure -state normal \ -troughcolor white } else { $itk_component(${axis}CutScale) configure -state disabled \ -troughcolor grey82 } } default { error "don't know how to fix $what" } } } # ---------------------------------------------------------------------- # USAGE: FixLegend # # Used internally to update the legend area whenever it changes size # or when the field changes. Asks the server to send a new legend # for the current field. # ---------------------------------------------------------------------- itcl::body Rappture::NanovisViewer::FixLegend {} { set _resizeLegendPending 0 set lineht [font metrics $itk_option(-font) -linespace] set w [expr {$_width-20}] set h [expr {[winfo height $itk_component(legend)]-20-$lineht}] if {$w > 0 && $h > 0 && [array names _activeTfs] > 0 && $_first != "" } { set vol [lindex [CurrentVolumes] 0] if { [info exists _vol2style($vol)] } { SendCmd "legend $_vol2style($vol) $w $h" } } else { # Can't do this as this will remove the items associated with the # isomarkers. #$itk_component(legend) delete all } } # # NameTransferFunc -- # # Creates a transfer function name based on the