# -*- mode: tcl; indent-tabs-mode: nil -*- # ---------------------------------------------------------------------- # 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-2012 HUBzero Foundation, LLC # # 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 parameters {title args} { # do nothing } public method rmdupmarker { m x } public method scale {args} public method updatetransferfuncs {} protected method Connect {} protected method CurrentDatasets {{what -all}} protected method Disconnect {} protected method DoResize {} protected method FixLegend {} protected method AdjustSetting {what {value ""}} protected method InitSettings { args } 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 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 ResetColormap { color } 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 { side } private variable _arcball "" private variable _dlist "" ;# list of data objects private variable _allDataObjs private variable _obj2ovride ;# maps dataobj => style override private variable _serverDatasets ;# contains all the dataobj-component ;# to volumes in the server private variable _serverTfs ;# contains all the transfer functions ;# in the server. private variable _recvdDatasets ;# list of data objs to send to server private variable _dataset2style ;# maps dataobj-component to transfunc private variable _style2datasets ;# maps tf back to list of # dataobj-components using the tf. private variable _reset 1; # Connection to server has been reset 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 variable _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. # 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" # # 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 { qw 0.853553 qx -0.353553 qy 0.353553 qz 0.146447 zoom 1.0 xpan 0 ypan 0 } set _arcball [blt::arcball create 100 100] set q [list $_view(qw) $_view(qx) $_view(qy) $_view(qz)] $_arcball quaternion $q set _limits(vmin) 0.0 set _limits(vmax) 1.0 set _reset 1 array set _settings [subst { $this-qw $_view(qw) $this-qx $_view(qx) $this-qy $_view(qy) $this-qz $_view(qz) $this-zoom $_view(zoom) $this-xpan $_view(xpan) $this-ypan $_view(ypan) $this-volume 1 $this-xcutplane 0 $this-xcutposition 0 $this-ycutplane 0 $this-ycutposition 0 $this-zcutplane 0 $this-zcutposition 0 }] itk_component add 3dview { label $itk_component(plotarea).view -image $_image(plot) \ -highlightthickness 0 -borderwidth 0 } { usual ignore -highlightthickness -borderwidth -background } bind $itk_component(3dview) [itcl::code $this ToggleConsole] 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 AdjustSetting 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 if { [catch { BuildViewTab BuildVolumeTab BuildCutplanesTab BuildCameraTab } errs] != 0 } { global errorInfo puts stderr "errs=$errs errorInfo=$errorInfo" } # 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) catch { blt::arcball destroy $_arcball } 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 ""}} { if { ![$dataobj isvalid] } { return; # Object doesn't contain valid data. } array set params { -color auto -width 1 -linestyle solid -brightness 0 -raise 0 -description "" -param "" } array set params $settings if {$params(-color) == "auto" || $params(-color) == "autoreset"} { # can't handle -autocolors yet set params(-color) black } set pos [lsearch -exact $_dlist $dataobj] 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-* 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 dataobj $args { if { ![$dataobj isvalid] } { continue; # Object doesn't contain valid data. } foreach axis {x y z v} { foreach { min max } [$dataobj 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 _reset 1 set result [VisViewer::Connect $_hosts] if { $result } { if { $_reportClientInfo } { # Tell the server the viewer, hub, user and session. # Do this immediately on connect before buffing any commands global env set info {} set user "???" if { [info exists env(USER)] } { set user $env(USER) } set session "???" if { [info exists env(SESSION)] } { set session $env(SESSION) } lappend info "version" "$Rappture::version" lappend info "build" "$Rappture::build" lappend info "svnurl" "$Rappture::svnurl" lappend info "installdir" "$Rappture::installdir" lappend info "hub" [exec hostname] lappend info "client" "nanovisviewer" lappend info "user" $user lappend info "session" $session SendCmd "clientinfo [list $info]" } 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 array unset _serverDatasets } # ---------------------------------------------------------------------- # 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 tag [CurrentDatasets] { if { ![info exists _serverDatasets($tag)] || !$_serverDatasets($tag) } { # The volume hasn't reached the server yet. How did we get # here? puts stderr "Don't have $tag in _serverDatasets" continue } if { ![info exists _dataset2style($tag)] } { puts stderr "don't have style for volume $tag" continue; # How does this happen? } set tf $_dataset2style($tag) 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 $tag" } 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 datasets [CurrentDatasets -cutplanes] SendCmd "volume data state $_settings($this-volume) $datasets" # Adjust the cutplane for only the first component in the topmost volume # (i.e. the first volume designated in the field). set tag [lindex $datasets 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 $tag" set pos [expr {0.01*$_settings($this-${axis}cutposition)}] SendCmd "cutplane position $pos $axis $tag" } } } # # 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 _recvdDatasets) 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 _serverDatasets($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 _recvdDatasets($tag) if { [array size _recvdDatasets] == 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 {} { set w [winfo width $itk_component(3dview)] set h [winfo height $itk_component(3dview)] if { $w < 2 || $h < 2 } { $_dispatcher event -idle !rebuild return } # 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). StartBufferingCommands # 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 } } if { $_width != $w || $_height != $h || $_reset } { set _width $w set _height $h $_arcball resize $w $h DoResize } foreach dataobj [get] { foreach cname [$dataobj components] { set tag $dataobj-$cname if { ![info exists _serverDatasets($tag)] } { # Send the data as one huge base64-encoded mess -- yuck! if { [$dataobj type] == "dx" } { if { ![$dataobj isvalid] } { puts stderr "??? $dataobj is invalid" } set data [$dataobj values $cname] } else { set data [$dataobj vtkdata $cname] if 0 { set f [open "/tmp/volume.vtk" "w"] puts $f $data close $f } } set nbytes [string length $data] if { $_reportClientInfo } { set info {} lappend info "tool_id" [$dataobj hints toolid] lappend info "tool_name" [$dataobj hints toolname] lappend info "tool_title" [$dataobj hints tooltitle] lappend info "tool_command" [$dataobj hints toolcommand] lappend info "tool_revision" [$dataobj hints toolrevision] lappend info "dataset_label" [$dataobj hints label] lappend info "dataset_size" $nbytes lappend info "dataset_tag" $tag SendCmd "clientinfo [list $info]" } SendCmd "volume data follows $nbytes $tag" append _outbuf $data set _recvdDatasets($tag) 1 set _serverDatasets($tag) 0 } NameTransferFunc $dataobj $cname } } set _first [lindex [get] 0] if { $_reset } { # # Reset the camera and other view parameters # set _settings($this-qw) $_view(qw) set _settings($this-qx) $_view(qx) set _settings($this-qy) $_view(qy) set _settings($this-qz) $_view(qz) set _settings($this-xpan) $_view(xpan) set _settings($this-ypan) $_view(ypan) set _settings($this-zoom) $_view(zoom) set q [list $_view(qw) $_view(qx) $_view(qy) $_view(qz)] $_arcball quaternion $q SendCmd "camera orient $q" SendCmd "camera reset" PanCamera SendCmd "camera zoom $_view(zoom)" InitSettings light2side light transp isosurface grid axes foreach axis {x y z} { # Turn off cutplanes for all volumes SendCmd "cutplane state 0 $axis" } if {"" != $_first} { set axis [$_first hints updir] if { "" != $axis } { SendCmd "up $axis" } set location [$_first hints camera] if { $location != "" } { array set _view $location } } } # Outline seems to need to be reset every update. InitSettings outline # nothing to send -- activate the proper ivol SendCmd "volume state 0" if {"" != $_first} { set datasets [array names _serverDatasets $_first-*] if { $datasets != "" } { SendCmd "volume state 1 $datasets" } # If the first volume already exists on the server, then make sure # we display the proper transfer function in the legend. set cname [lindex [$_first components] 0] if { [info exists _serverDatasets($_first-$cname)] } { updatetransferfuncs } } # 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) StopBufferingCommands blt::busy release $itk_component(hull) set _reset 0 } # ---------------------------------------------------------------------- # USAGE: CurrentDatasets ?-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::CurrentDatasets {{what -all}} { set rlist "" if { $_first == "" } { return } foreach cname [$_first components] { set tag $_first-$cname if { [info exists _serverDatasets($tag)] && $_serverDatasets($tag) } { array set style { -cutplanes 1 } array set style [lindex [$_first components -style $cname] 0] if { $what != "-cutplanes" || $style(-cutplanes) } { lappend rlist $tag } } } 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) SendCmd "camera zoom $_view(zoom)" } "out" { set _view(zoom) [expr {$_view(zoom)*0.8}] set _settings($this-zoom) $_view(zoom) SendCmd "camera zoom $_view(zoom)" } "reset" { array set _view { qw 0.853553 qx -0.353553 qy 0.353553 qz 0.146447 zoom 1.0 xpan 0 ypan 0 } if { $_first != "" } { set location [$_first hints camera] if { $location != "" } { array set _view $location } } set q [list $_view(qw) $_view(qx) $_view(qy) $_view(qz)] $_arcball quaternion $q SendCmd "camera orient $q" SendCmd "camera reset" set _settings($this-qw) $_view(qw) set _settings($this-qx) $_view(qx) set _settings($this-qy) $_view(qy) set _settings($this-qz) $_view(qz) set _settings($this-xpan) $_view(xpan) set _settings($this-ypan) $_view(ypan) set _settings($this-zoom) $_view(zoom) } } } itcl::body Rappture::NanovisViewer::PanCamera {} { #set x [expr ($_view(xpan)) / $_limits(xrange)] #set y [expr ($_view(ypan)) / $_limits(yrange)] set x $_view(xpan) set y $_view(ypan) 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 } 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 } set q [$_arcball rotate $x $y $_click(x) $_click(y)] foreach { _view(qw) _view(qx) _view(qy) _view(qz) } $q break set _settings($this-qw) $_view(qw) set _settings($this-qx) $_view(qx) set _settings($this-qy) $_view(qy) set _settings($this-qz) $_view(qz) SendCmd "camera orient $q" 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(xpan) [expr $_view(xpan) + $x] set _view(ypan) [expr $_view(ypan) + $y] PanCamera set _settings($this-xpan) $_view(xpan) set _settings($this-ypan) $_view(ypan) 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(xpan) [expr $_view(xpan) - $dx] set _view(ypan) [expr $_view(ypan) - $dy] PanCamera set _settings($this-xpan) $_view(xpan) set _settings($this-ypan) $_view(ypan) } if { $option == "release" } { $itk_component(3dview) configure -cursor "" } } # ---------------------------------------------------------------------- # USAGE: InitSettings ?? # # 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::InitSettings { args } { foreach arg $args { AdjustSetting $arg } } # ---------------------------------------------------------------------- # USAGE: AdjustSetting ?? # # 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::AdjustSetting {what {value ""}} { if {![isconnected]} { return } switch -- $what { light { set val $_settings($this-light) set diffuse [expr {0.01*$val}] set ambient [expr {1.0-$diffuse}] set specularLevel 0.3 set specularExp 90.0 SendCmd "volume shading ambient $ambient" SendCmd "volume shading diffuse $diffuse" SendCmd "volume shading specularLevel $specularLevel" SendCmd "volume shading specularExp $specularExp" } light2side { set val $_settings($this-light2side) SendCmd "volume shading light2side $val" } transp { set val $_settings($this-transp) set sval [expr { 0.01 * double($val) }] SendCmd "volume shading opacity $sval" } opacity { 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 { [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" { SendCmd "volume outline state $_settings($this-outline)" } "isosurface" { SendCmd "volume shading isosurface $_settings($this-isosurface)" } "colormap" { set color [$itk_component(colormap) value] set _settings(colormap) $color # Only set the colormap on the first volume. Ignore the others. #ResetColormap $color } "grid" { SendCmd "grid visible $_settings($this-grid)" } "axes" { 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" { set datasets [CurrentDatasets -cutplanes] SendCmd "volume data state $_settings($this-volume) $datasets" } "xcutplane" - "ycutplane" - "zcutplane" { set axis [string range $what 0 0] set bool $_settings($this-$what) set datasets [CurrentDatasets -cutplanes] set tag [lindex $datasets 0] SendCmd "cutplane state $bool $axis $tag" 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 tag [lindex [CurrentDatasets] 0] if { [info exists _dataset2style($tag)] } { SendCmd "legend $_dataset2style($tag) $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