# -*- mode: tcl; indent-tabs-mode: nil -*- # ---------------------------------------------------------------------- # COMPONENT: field - extracts data from an XML description of a field # # This object represents one field in an XML description of a device. # It simplifies the process of extracting data vectors that represent # the field. # ====================================================================== # 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. # ====================================================================== # TODO: # # o How to describe vector values in a field? # 3 # # # Does anything need to know the limits for each component of the vector? # # # Possible field dataset types: # # 2D Datasets # vtk (range of z-axis is zero). # unirect2d (deprecated except where extents > 1) # cloud (x,y point coordinates) (deprecated) # mesh # 3D Datasets # vtk # unirect3d # cloud (x,y,z coordinates) (deprecated) # mesh # dx (FIXME: make dx-to-vtk converter work) # ucd avs # # Viewers: # Format Dim Description Viewer Server # vtk 2 vtk file data. contour vtkvis # vtk 3 vtk file data. isosurface vtkvis # mesh 2 points-on-mesh heightmap vtkvis # mesh 3 points-on-mesh isosurface vtkvis # dx 3 DX volume nanovis # unirect2d 2 unirect3d + extents > 1 flow flow nanovis # unirect3d 3 unirect2d + extents > 1 flow flow nanovis # # With , can specify which viewer for a specific datasets. So it's OK # to if the same dataset can be viewed in more than one way. # o Any 2D dataset can be viewed as a contour/heightmap. # o Any 3D dataset can be viewed as a isosurface. # o Any 2D dataset with vector data can be streamlines. # o Any 3D uniform rectilinear dataset can be viewed as a volume. # o Any 3D dataset with vector data can be streamlines or flow. # # Need to properly do things like qdot: volume with a polydata # transparent shell. The view will combine the two objects and # (??) into a single viewer. # package require Itcl package require BLT namespace eval Rappture { # forward declaration } itcl::class Rappture::Field { private variable _dim 0; # Dimension of the mesh private variable _xmlobj ""; # ref to XML obj with field data private variable _limits; # maps axis name => {z0 z1} limits private variable _field "" private variable _comp2fldName ; # cname => field names. private variable _fld2Components; # field name => number of components private variable _fld2Label; # field name => label private variable _fld2Units; # field name => units private variable _hints private variable _viewer ""; # Hints which viewer to use private variable _xv ""; # For 1D meshes only. Holds the points private variable _isValid 0; # Indicates if the field contains # valid data. private variable _isValidComponent constructor {xmlobj path} { # defined below } destructor { # defined below } public method blob { cname } public method components {args} public method controls {option args} public method extents {{cname -overall}} public method fieldlimits {} public method flowhints { cname } public method hints {{key ""}} public method isunirect2d {} public method isunirect3d {} public method limits {axis} public method mesh {{cname -overall}} public method style { cname } public method type {} public method values { cname } public method vtkdata {cname} public method fieldnames { cname } { if { ![info exists _comp2fldName($cname)] } { return "" } return $_comp2fldName($cname) } public method fieldinfo { fname } { lappend out $_fld2Label($fname) lappend out $_fld2Units($fname) lappend out $_fld2Components($fname) return $out } public method isvalid {} { return $_isValid } public method viewer {} { return $_viewer } protected method Build {} protected method _getValue {expr} private variable _path ""; # Path of this object in the XML private variable _units "" ; # system of units for this field private variable _zmax 0 ;# length of the device private variable _comp2dims ;# maps component name => dimensionality private variable _comp2xy ;# maps component name => x,y vectors private variable _comp2vtk ;# maps component name => vtk file data private variable _comp2dx ;# maps component name => OpenDX data private variable _comp2unirect2d ;# maps component name => unirect2d obj private variable _comp2unirect3d ;# maps component name => unirect3d obj private variable _comp2style ;# maps component name => style settings private variable _comp2cntls ;# maps component name => x,y control points private variable _comp2extents private variable _comp2limits; # Array of limits per component private variable _type "" private variable _comp2flowhints private variable _comp2mesh private common _counter 0 ;# counter for unique vector names private method BuildPointsOnMesh { cname } private method ConvertToVtkData { cname } private method ReadVtkDataSet { cname contents } private method AvsToVtk { cname contents } private variable _values "" } # ---------------------------------------------------------------------- # CONSTRUCTOR # ---------------------------------------------------------------------- itcl::body Rappture::Field::constructor {xmlobj path} { package require vtk if {![Rappture::library isvalid $xmlobj]} { error "bad value \"$xmlobj\": should be Rappture::library" } set _xmlobj $xmlobj set _path $path set _field [$xmlobj element -as object $path] set _units [$_field get units] set xunits [$xmlobj get units] if {"" == $xunits || "arbitrary" == $xunits} { set xunits "um" } # determine the overall size of the device set z0 [set z1 0] foreach elem [$_xmlobj children components] { switch -glob -- $elem { box* { if {![regexp {[0-9]$} $elem]} { set elem "${elem}0" } set z0 [$_xmlobj get components.$elem.corner0] set z0 [Rappture::Units::convert $z0 \ -context $xunits -to $xunits -units off] set z1 [$_xmlobj get components.$elem.corner1] set z1 [Rappture::Units::convert $z1 \ -context $xunits -to $xunits -units off] set _limits($elem) [list $z0 $z1] } } } set _zmax $z1 # build up vectors for various components of the field Build } # ---------------------------------------------------------------------- # DESTRUCTOR # ---------------------------------------------------------------------- itcl::body Rappture::Field::destructor {} { itcl::delete object $_field # don't destroy the _xmlobj! we don't own it! foreach name [array names _comp2xy] { eval blt::vector destroy $_comp2xy($name) } foreach name [array names _comp2unirect2d] { itcl::delete object $_comp2unirect2d($name) } foreach name [array names _comp2unirect3d] { itcl::delete object $_comp2unirect3d($name) } foreach name [array names _comp2flowhints] { itcl::delete object $_comp2flowhints($name) } foreach name [array names _comp2mesh] { # Data is in the form of a mesh and a vector. foreach { mesh vector } $_comp2mesh($name) break # Release the mesh (may be shared) set class [$mesh info class] ${class}::release $mesh # Destroy the vector blt::vector destroy $vector } } # ---------------------------------------------------------------------- # USAGE: components ?-name|-dimensions|-style? ?? # # Returns a list of names or types for the various components of # this field. If the optional glob-style is specified, # then it returns only the components with names matching the pattern. # ---------------------------------------------------------------------- itcl::body Rappture::Field::components {args} { Rappture::getopts args params { flag what -name default flag what -dimensions flag what -style flag what -particles flag what -flow flag what -box } set pattern * if {[llength $args] > 0} { set pattern [lindex $args 0] set args [lrange $args 1 end] } if {[llength $args] > 0} { error "wrong # args: should be \"components ?switches? ?pattern?\"" } # There's only one dimension of the field. Components can't have # different dimensions in the same field. They would by definition be # using different meshes and viewers. if { $params(what) == "-dimensions" } { return "${_dim}D" } # BE CAREFUL: return component names in proper order set rlist "" set components {} # First compile a list of valid components that match the pattern foreach cname [$_field children -type component] { if { ![info exists _isValidComponent($cname)] } { continue } if { [string match $pattern $cname] } { lappend components $cname } } # Now handle the tests. switch -- $params(what) { -name { set rlist $components } -style { foreach cname $components { if { [info exists _comp2style($cname)] } { lappend rlist $_comp2style($cname) } } } } return $rlist } # ---------------------------------------------------------------------- # USAGE: mesh ?? # # Returns a list {xvec yvec} for the specified field component . # If the name is not specified, then it returns the vectors for the # overall field (sum of all components). # ---------------------------------------------------------------------- itcl::body Rappture::Field::mesh {{cname -overall}} { if {$cname == "-overall" || $cname == "component0"} { set cname [lindex [components -name] 0] } if {[info exists _comp2xy($cname)]} { return [lindex $_comp2xy($cname) 0] ;# return xv } if { [info exists _comp2vtk($cname)] } { # FIXME: extract mesh from VTK file data. if { $_comp2dims($cname) == "1D" } { return $_xv } error "method \"mesh\" is not implemented for VTK file data" } if {[info exists _comp2dx($cname)]} { return "" ;# no mesh -- it's embedded in the value data } if {[info exists _comp2mesh($cname)]} { return "" ;# no mesh -- it's embedded in the value data } if {[info exists _comp2unirect2d($cname)]} { set mobj [lindex $_comp2unirect2d($cname) 0] return [$mobj mesh] } if {[info exists _comp2unirect3d($cname)]} { set mobj [lindex $_comp2unirect3d($cname) 0] return [$mobj mesh] } error "can't get field mesh: Unknown component \"$cname\": should be one of [join [lsort [array names _comp2dims]] {, }]" } # ---------------------------------------------------------------------- # USAGE: values ?? # # Returns a list {xvec yvec} for the specified field component . # If the name is not specified, then it returns the vectors for the # overall field (sum of all components). # ---------------------------------------------------------------------- itcl::body Rappture::Field::values {cname} { if {$cname == "component0"} { set cname "component" } if {[info exists _comp2xy($cname)]} { return [lindex $_comp2xy($cname) 1] ;# return yv } # VTK file data if { [info exists _comp2vtk($cname)] } { # FIXME: extract the values from the VTK file data if { $_comp2dims($cname) == "1D" } { return $_values } error "method \"values\" is not implemented for vtk file data" } # Points-on-mesh if { [info exists _comp2mesh($cname)] } { set vector [lindex $_comp2mesh($cname) 1] return [$vector range 0 end] } if {[info exists _comp2dx($cname)]} { return $_comp2dx($cname) ;# return gzipped, base64-encoded DX data } if {[info exists _comp2unirect2d($cname)]} { return [$_comp2unirect2d($cname) values] } if {[info exists _comp2unirect3d($cname)]} { return [$_comp2unirect3d($cname) blob] } error "can't get field values. Unknown component \"$cname\": should be [join [lsort [array names _comp2dims]] {, }]" } # ---------------------------------------------------------------------- # USAGE: blob ?? # # Returns a string representing the blob of data for the mesh and values. # ---------------------------------------------------------------------- itcl::body Rappture::Field::blob {cname} { if {$cname == "component0"} { set cname "component" } if {[info exists _comp2xy($cname)]} { return "" } if { [info exists _comp2vtk($cname)] } { error "blob not implemented for VTK file data" } if {[info exists _comp2dx($cname)]} { return $_comp2dx($cname) ;# return gzipped, base64-encoded DX data } if {[info exists _comp2unirect2d($cname)]} { set blob [$_comp2unirect2d($cname) blob] lappend blob "values" $_values return $blob } if {[info exists _comp2unirect3d($cname)]} { return [$_comp2unirect3d($cname) blob] } error "can't get field blob: Unknown component \"$cname\": should be one of [join [lsort [array names _comp2dims]] {, }]" } # ---------------------------------------------------------------------- # USAGE: limits # # Returns a list {min max} representing the limits for the specified # axis. # ---------------------------------------------------------------------- itcl::body Rappture::Field::limits {which} { set min "" set max "" blt::vector tmp zero foreach cname [array names _comp2dims] { switch -- $_comp2dims($cname) { 1D { switch -- $which { x - xlin { set pos 0; set log 0; set axis x } xlog { set pos 0; set log 1; set axis x } y - ylin - v - vlin { set pos 1; set log 0; set axis y } ylog - vlog { set pos 1; set log 1; set axis y } default { error "bad axis \"$which\": should be x, xlin, xlog, y, ylin, ylog, v, vlin, vlog" } } set vname [lindex $_comp2xy($cname) $pos] $vname variable vec if {$log} { # on a log scale, use abs value and ignore 0's $vname dup tmp $vname dup zero zero expr {tmp == 0} ;# find the 0's tmp expr {abs(tmp)} ;# get the abs value tmp expr {tmp + zero*max(tmp)} ;# replace 0's with abs max set axisMin [blt::vector expr min(tmp)] set axisMax [blt::vector expr max(tmp)] } else { set axisMin $vec(min) set axisMax $vec(max) } if {"" == $min} { set min $axisMin } elseif {$axisMin < $min} { set min $axisMin } if {"" == $max} { set max $axisMax } elseif {$axisMax > $max} { set max $axisMax } } 2D - 3D { if {[info exists _comp2unirect3d($cname)]} { set limits [$_comp2unirect3d($cname) limits $which] foreach {axisMin axisMax} $limits break set axis v } elseif {[info exists _comp2limits($cname)]} { array set limits $_comp2limits($cname) switch -- $which { x - xlin - xlog { set axis x foreach {axisMin axisMax} $limits(x) break } y - ylin - ylog { set axis y foreach {axisMin axisMax} $limits(y) break } z - zlin - zlog { set axis z foreach {axisMin axisMax} $limits(z) break } v - vlin - vlog { set axis v foreach {axisMin axisMax} $limits(v) break } default { if { ![info exists limits($which)] } { error "limits: unknown axis \"$which\"" } set axis v foreach {axisMin axisMax} $limits($which) break } } } else { set axisMin 0 ;# HACK ALERT! must be OpenDX data set axisMax 1 set axis v } } } if { "" == $min || $axisMin < $min } { set min $axisMin } if { "" == $max || $axisMax > $max } { set max $axisMax } } blt::vector destroy tmp zero set val [$_field get "${axis}axis.min"] if {"" != $val && "" != $min} { if {$val > $min} { # tool specified this min -- don't go any lower set min $val } } set val [$_field get "${axis}axis.max"] if {"" != $val && "" != $max} { if {$val < $max} { # tool specified this max -- don't go any higher set max $val } } return [list $min $max] } # ---------------------------------------------------------------------- # USAGE: fieldlimits # # Returns a list {min max} representing the limits for the specified # axis. # ---------------------------------------------------------------------- itcl::body Rappture::Field::fieldlimits {} { foreach cname [array names _comp2limits] { array set limits $_comp2limits($cname) foreach fname $_comp2fldName($cname) { if { ![info exists limits($fname)] } { puts stderr "ERROR: field \"$fname\" unknown in \"$cname\"" continue } foreach {min max} $limits($fname) break if { ![info exists overall($fname)] } { set overall($fname) $limits($fname) continue } foreach {omin omax} $overall($fname) break if { $min < $omin } { set omin $min } if { $max > $omax } { set omax $max } set overall($fname) [list $min $max] } } if { [info exists overall] } { return [array get overall] } return "" } # ---------------------------------------------------------------------- # USAGE: controls get ?? # USAGE: controls validate # USAGE: controls put # # Returns a list {path1 x1 y1 val1 path2 x2 y2 val2 ...} representing # control points for the specified field component . # ---------------------------------------------------------------------- itcl::body Rappture::Field::controls {option args} { switch -- $option { get { set cname [lindex $args 0] if {[info exists _comp2cntls($cname)]} { return $_comp2cntls($cname) } return "" } validate { set path [lindex $args 0] set value [lindex $args 1] set units [$_xmlobj get $path.units] if {"" != $units} { set nv [Rappture::Units::convert \ $value -context $units -to $units -units off] } else { set nv $value } if {![string is double $nv] || [regexp -nocase {^(inf|nan)$} $nv]} { error "Value out of range" } set rawmin [$_xmlobj get $path.min] if {"" != $rawmin} { set minv $rawmin if {"" != $units} { set minv [Rappture::Units::convert \ $minv -context $units -to $units -units off] set nv [Rappture::Units::convert \ $value -context $units -to $units -units off] } # fix for the case when the user tries to # compare values like minv=-500 nv=-0600 set nv [format "%g" $nv] set minv [format "%g" $minv] if {$nv < $minv} { error "Minimum value allowed here is $rawmin" } } set rawmax [$_xmlobj get $path.max] if {"" != $rawmax} { set maxv $rawmax if {"" != $units} { set maxv [Rappture::Units::convert \ $maxv -context $units -to $units -units off] set nv [Rappture::Units::convert \ $value -context $units -to $units -units off] } # fix for the case when the user tries to # compare values like maxv=-500 nv=-0600 set nv [format "%g" $nv] set maxv [format "%g" $maxv] if {$nv > $maxv} { error "Maximum value allowed here is $rawmax" } } return "ok" } put { set path [lindex $args 0] set value [lindex $args 1] $_xmlobj put $path.current $value Build } default { error "bad field controls option \"$option\": should be get or put" } } } # ---------------------------------------------------------------------- # USAGE: hints ?? # # Returns a list of key/value pairs for various hints about plotting # this field. If a particular is specified, then it returns # the hint for that , if it exists. # ---------------------------------------------------------------------- itcl::body Rappture::Field::hints {{keyword ""}} { if { ![info exists _hints] } { foreach {key path} { camera camera.position color about.color default about.default group about.group label about.label scale about.scale seeds about.seeds style about.style type about.type xlabel about.xaxis.label ylabel about.yaxis.label zlabel about.zaxis.label xunits about.xaxis.units yunits about.yaxis.units zunits about.zaxis.units units units updir updir vectors about.vectors } { set str [$_field get $path] if { "" != $str } { set _hints($key) $str } } foreach {key path} { toolid tool.id toolname tool.name toolcommand tool.execute tooltitle tool.title toolrevision tool.version.application.revision } { set str [$_xmlobj get $path] if { "" != $str } { set _hints($key) $str } } # Set toolip and path hints set _hints(path) $_path if { [info exists _hints(group)] && [info exists _hints(label)] } { # pop-up help for each curve set _hints(tooltip) $_hints(label) } } if { $keyword != "" } { if {[info exists _hints($keyword)]} { return $_hints($keyword) } return "" } return [array get _hints] } # ---------------------------------------------------------------------- # USAGE: Build # # Used internally to build up the vector representation for the # field when the object is first constructed, or whenever the field # data changes. Discards any existing vectors and builds everything # from scratch. # ---------------------------------------------------------------------- itcl::body Rappture::Field::Build {} { # Discard any existing data foreach name [array names _comp2xy] { eval blt::vector destroy $_comp2xy($name) } array unset _comp2vtk foreach name [array names _comp2unirect2d] { eval itcl::delete object $_comp2unirect2d($name) } foreach name [array names _comp2unirect3d] { eval itcl::delete object $_comp2unirect3d($name) } catch {unset _comp2xy} catch {unset _comp2dx} catch {unset _comp2dims} catch {unset _comp2style} array unset _comp2unirect2d array unset _comp2unirect3d array unset _comp2extents array unset _dataobj2type # # Scan through the components of the field and create # vectors for each part. # array unset _isValidComponent foreach cname [$_field children -type component] { set type "" if { ([$_field element $cname.constant] != "" && [$_field element $cname.domain] != "") || [$_field element $cname.xy] != "" } { set type "1D" } elseif { [$_field element $cname.mesh] != "" && [$_field element $cname.values] != ""} { set type "points-on-mesh" } elseif { [$_field element $cname.vtk] != ""} { set viewer [$_field get "about.view"] set type "vtk" if { $viewer != "" } { set _viewer $viewer } } elseif {[$_field element $cname.opendx] != ""} { global env if { [info exists env(VTKVOLUME)] } { set type "vtkvolume" } else { set type "opendx" } } elseif {[$_field element $cname.dx] != ""} { global env if { [info exists env(VTKVOLUME)] } { set type "vtkvolume" } else { set type "dx" } } elseif {[$_field element $cname.ucd] != ""} { set type "ucd" } set _comp2style($cname) "" if { $type == "" } { puts stderr "WARNING: ignoring field component \"$_path.$cname\": no data found." continue } # Save the extents of the component if { [$_field element $cname.extents] != "" } { set extents [$_field get $cname.extents] } else { set extents 1 } set _comp2extents($cname) $extents set _type $type if {$type == "1D"} { # # 1D data can be represented as 2 BLT vectors, # one for x and the other for y. # set xv "" set yv "" set val [$_field get $cname.constant] if {$val != ""} { set domain [$_field get $cname.domain] if {$domain == "" || ![info exists _limits($domain)]} { set z0 0 set z1 $_zmax } else { foreach {z0 z1} $_limits($domain) { break } } set xv [blt::vector create x$_counter] $xv append $z0 $z1 foreach {val pcomp} [_getValue $val] break set yv [blt::vector create y$_counter] $yv append $val $val if {$pcomp != ""} { set zm [expr {0.5*($z0+$z1)}] set _comp2cntls($cname) \ [list $pcomp $zm $val "$val$_units"] } } else { set xydata [$_field get $cname.xy] if {"" != $xydata} { set xv [blt::vector create x$_counter] set yv [blt::vector create y$_counter] set tmp [blt::vector create \#auto] $tmp set $xydata $tmp split $xv $yv blt::vector destroy $tmp } } if {$xv != "" && $yv != ""} { # sort x-coords in increasing order $xv sort $yv set _comp2dims($cname) "1D" set _comp2xy($cname) [list $xv $yv] incr _counter } } elseif {$type == "points-on-mesh"} { if { ![BuildPointsOnMesh $cname] } { continue; # Ignore this component } } elseif {$type == "vtk"} { set contents [$_field get $cname.vtk] if { $contents == "" } { puts stderr "WARNING: no data fo \"$_path.$cname.vtk\"" continue; # Ignore this component } ReadVtkDataSet $cname $contents set _comp2vtk($cname) $contents set _comp2style($cname) [$_field get $cname.style] incr _counter } elseif {$type == "dx" || $type == "opendx" } { # # HACK ALERT! Extract gzipped, base64-encoded OpenDX # data. Assume that it's 3D. Pass it straight # off to the NanoVis visualizer. # set _viewer "nanovis" set _dim 3 set _type "dx" set _comp2dims($cname) "3D" set contents [$_field get -decode no $cname.$type] if { $contents == "" } { puts stderr "WARNING: no data for \"$_path.$cname.$type\"" continue; # Ignore this component } set _comp2dx($cname) $contents if 0 { set hdr "@@RP-ENC:zb64\n" set data [$_field get -decode no $cname.$type] set data "$hdr$data" set data [Rappture::encoding::decode $data] set data [Rappture::DxToVtk $data] set f [open /tmp/$_path.$cname.vtk "w"] puts $f $data close $f } set _comp2style($cname) [$_field get $cname.style] if {[$_field element $cname.flow] != ""} { set _comp2flowhints($cname) \ [Rappture::FlowHints ::\#auto $_field $cname $_units] } incr _counter } elseif { $type == "ucd"} { set contents [$_field get $cname.ucd] if { $contents == "" } { continue; # Ignore this compoennt } set vtkdata [AvsToVtk $cname $contents] ReadVtkDataSet $cname $vtkdata set _comp2vtk($cname) $vtkdata set _comp2style($cname) [$_field get $cname.style] incr _counter } set _isValidComponent($cname) 1 } if { [array size _isValidComponent] == 0 } { puts stderr "WARNING: no valid components for field \"$_path\"" return 0 } # Sanity check. Verify that all components of the field have the same # dimension. set dim "" foreach cname [array names _comp2dims] { if { $dim == "" } { set dim $_comp2dims($cname) continue } if { $dim != $_comp2dims($cname) } { puts stderr "WARNING: field can't have components of different dimensions: [join [array get _comp2dims] ,]" return 0 } } # FIXME: about.scalars and about.vectors are temporary. With views # the label and units for each field will be specified there. # # FIXME: Test that every has the same field names, # units, components. # # Override what we found in the VTK file with names that the user # selected. We override the field label and units. foreach { fname label units } [$_field get about.scalars] { if { ![info exists _fld2Name($fname)] } { set _fld2Name($fname) $fname set _fld2Components($fname) 1 } set _fld2Label($fname) $label set _fld2Units($fname) $units } foreach { fname label units } [$_field get about.vectors] { if { ![info exists _fld2Name($fname)] } { set _fld2Name($fname) $fname # We're just marking the field as vector (> 1) for now. set _fld2Components($fname) 3 } set _fld2Label($fname) $label set _fld2Units($fname) $units } set _isValid 1 return 1 } # ---------------------------------------------------------------------- # USAGE: _getValue # # Used internally to get the value for an expression . Returns # a list of the form {val parameterPath}, where val is the numeric # value of the expression, and parameterPath is the XML path to the # parameter representing the value, or "" if the does not # depend on any parameters. # ---------------------------------------------------------------------- itcl::body Rappture::Field::_getValue {expr} { # # First, look for the expression among the 's # associated with the device. # set found 0 foreach pcomp [$_xmlobj children parameters] { set id [$_xmlobj element -as id parameters.$pcomp] if {[string equal $id $expr]} { set val [$_xmlobj get parameters.$pcomp.current] if {"" == $val} { set val [$_xmlobj get parameters.$pcomp.default] } if {"" != $val} { set expr $val set found 1 break } } } if {$found} { set pcomp "parameters.$pcomp" } else { set pcomp "" } if {$_units != ""} { set expr [Rappture::Units::convert $expr \ -context $_units -to $_units -units off] } return [list $expr $pcomp] } # # isunirect2d -- # # Returns if the field is a unirect2d object. # itcl::body Rappture::Field::isunirect2d { } { return [expr [array size _comp2unirect2d] > 0] } # # isunirect3d -- # # Returns if the field is a unirect3d object. # itcl::body Rappture::Field::isunirect3d { } { return [expr [array size _comp2unirect3d] > 0] } # # flowhints -- # # Returns the hints associated with a flow vector field. # itcl::body Rappture::Field::flowhints { cname } { if { [info exists _comp2flowhints($cname)] } { return $_comp2flowhints($cname) } return "" } # # style -- # # Returns the style associated with a component of the field. # itcl::body Rappture::Field::style { cname } { if { [info exists _comp2style($cname)] } { return $_comp2style($cname) } return "" } # # type -- # # Returns the style associated with a component of the field. # itcl::body Rappture::Field::type {} { return $_type } # # extents -- # # Returns if the field is a unirect2d object. # itcl::body Rappture::Field::extents {{cname -overall}} { if {$cname == "-overall" } { set max 0 foreach cname [$_field children -type component] { if { ![info exists _comp2unirect3d($cname)] && ![info exists _comp2extents($cname)] } { continue } set value $_comp2extents($cname) if { $max < $value } { set max $value } } return $max } if { $cname == "component0"} { set cname [lindex [components -name] 0] } return $_comp2extents($cname) } itcl::body Rappture::Field::ConvertToVtkData { cname } { set ds "" switch -- [typeof $cname] { "unirect2d" { foreach { x1 x2 xN y1 y2 yN } [$dataobj mesh $cname] break set spacingX [expr {double($x2 - $x1)/double($xN - 1)}] set spacingY [expr {double($y2 - $y1)/double($yN - 1)}] set ds [vtkImageData $this-grdataTemp] $ds SetDimensions $xN $yN 1 $ds SetOrigin $x1 $y1 0 $ds SetSpacing $spacingX $spacingY 0 set arr [vtkDoubleArray $this-arrTemp] foreach {val} [$dataobj values $cname] { $arr InsertNextValue $val } [$ds GetPointData] SetScalars $arr } "unirect3d" { foreach { x1 x2 xN y1 y2 yN z1 z2 zN } [$dataobj mesh $cname] break set spacingX [expr {double($x2 - $x1)/double($xN - 1)}] set spacingY [expr {double($y2 - $y1)/double($yN - 1)}] set spacingZ [expr {double($z2 - $z1)/double($zN - 1)}] set ds [vtkImageData $this-grdataTemp] $ds SetDimensions $xN $yN $zN $ds SetOrigin $x1 $y1 $z1 $ds SetSpacing $spacingX $spacingY $spacingZ set arr [vtkDoubleArray $this-arrTemp] foreach {val} [$dataobj values $cname] { $arr InsertNextValue $val } [$ds GetPointData] SetScalars $val } "contour" { return [$dataobj blob $cname] } "dx" { return [Rappture::DxToVtk $_comp2dx($cname)] } default { set mesh [$dataobj mesh $cname] switch -- [$mesh GetClassName] { vtkPoints { # handle cloud of points set ds [vtkPolyData $this-polydataTemp] $ds SetPoints $mesh [$ds GetPointData] SetScalars [$dataobj values $cname] } vtkPolyData { set ds [vtkPolyData $this-polydataTemp] $ds ShallowCopy $mesh [$ds GetPointData] SetScalars [$dataobj values $cname] } vtkUnstructuredGrid { # handle 3D grid with connectivity set ds [vtkUnstructuredGrid $this-grdataTemp] $ds ShallowCopy $mesh [$ds GetPointData] SetScalars [$dataobj values $cname] } vtkRectilinearGrid { # handle 3D grid with connectivity set ds [vtkRectilinearGrid $this-grdataTemp] $ds ShallowCopy $mesh [$ds GetPointData] SetScalars [$dataobj values $cname] } default { error "don't know how to handle [$mesh GetClassName] data" } } } } if {"" != $ds} { set writer [vtkDataSetWriter $this-dsWriterTmp] $writer SetInput $ds $writer SetFileTypeToASCII $writer WriteToOutputStringOn $writer Write set out [$writer GetOutputString] $ds Delete $writer Delete } else { set out "" error "No DataSet to write" } append out "\n" return $out } itcl::body Rappture::Field::ReadVtkDataSet { cname contents } { package require vtk set reader $this-datasetreader vtkDataSetReader $reader # Write the contents to a file just in case it's binary. set tmpfile file[pid].vtk set f [open "$tmpfile" "w"] fconfigure $f -translation binary -encoding binary puts $f $contents close $f $reader SetFileName $tmpfile $reader ReadAllScalarsOn $reader ReadAllVectorsOn $reader ReadAllFieldsOn $reader Update set dataset [$reader GetOutput] set limits {} foreach {xmin xmax ymin ymax zmin zmax} [$dataset GetBounds] break # Figure out the dimension of the mesh from the bounds. set _dim 0 if { $xmax > $xmin } { incr _dim } if { $ymax > $ymin } { incr _dim } if { $zmax > $zmin } { incr _dim } if { $_viewer == "" } { if { $_dim == 2 } { set _viewer contour } else { set _viewer isosurface } } set _comp2dims($cname) ${_dim}D if { $_dim < 2 } { set points [$dataset GetPoints] set numPoints [$points GetNumberOfPoints] set xv [blt::vector create \#auto] for { set i 0 } { $i < $numPoints } { incr i } { set point [$points GetPoint 0] $xv append [lindex $point 0] } set yv [blt::vector create \#auto] $yv seq 0 1 [$xv length] set _comp2xy($cname) [list $xv $yv] } lappend limits x [list $xmin $xmax] lappend limits y [list $ymin $ymax] lappend limits z [list $zmin $zmax] set dataAttrs [$dataset GetPointData] if { $_dim == 1 } { set numArrays [$dataAttrs GetNumberOfArrays] } if { $dataAttrs == ""} { puts stderr "WARNING: no point data found in \"$_path\"" return 0 } set vmin 0 set vmax 1 set numArrays [$dataAttrs GetNumberOfArrays] if { $numArrays > 0 } { set array [$dataAttrs GetArray 0] foreach {vmin vmax} [$array GetRange] break for {set i 0} {$i < [$dataAttrs GetNumberOfArrays] } {incr i} { set array [$dataAttrs GetArray $i] set fname [$dataAttrs GetArrayName $i] foreach {min max} [$array GetRange] break lappend limits $fname [list $min $max] set _fld2Units($fname) "" set _fld2Label($fname) $fname set _fld2Components($fname) [$array GetNumberOfComponents] lappend _comp2fldName($cname) $fname } } lappend limits v [list $vmin $vmax] set _comp2limits($cname) $limits file delete $tmpfile rename $reader "" } # # vtkdata -- # # Returns a string representing the mesh and field data for a specific # component in the legacy VTK file format. # itcl::body Rappture::Field::vtkdata {cname} { if {$cname == "component0"} { set cname "component" } # DX: Convert DX to VTK if {[info exists _comp2dx($cname)]} { return [Rappture::DxToVtk $_comp2dx($cname)] } # Unirect3d: isosurface if {[info exists _comp2unirect3d($cname)]} { return [$_comp2unirect3d($cname) vtkdata] } # VTK file data: if { [info exists _comp2vtk($cname)] } { return $_comp2vtk($cname) } # Points on mesh: Construct VTK file output. if { [info exists _comp2mesh($cname)] } { # Data is in the form mesh and vector foreach {mesh vector} $_comp2mesh($cname) break set label $cname regsub -all { } $label {_} label append out "# vtk DataFile Version 3.0\n" append out "[hints label]\n" append out "ASCII\n" append out [$mesh vtkdata] append out "POINT_DATA [$vector length]\n" append out "SCALARS $label double 1\n" append out "LOOKUP_TABLE default\n" append out "[$vector range 0 end]\n" return $out } error "can't find vtkdata for $cname. This method should only be called by the vtkheightmap widget" } # # BuildPointsOnMesh -- # # Parses the field XML description to build a mesh and values vector # representing the field. Right now we handle the deprecated types # of "cloud", "unirect2d", and "unirect3d" (mostly for flows). # itcl::body Rappture::Field::BuildPointsOnMesh {cname} { # # More complex 2D/3D data is represented by a mesh # object and an associated vector for field values. # set path [$_field get $cname.mesh] if {[$_xmlobj element $path] == ""} { # Unknown mesh designated. return 0 } set _viewer [$_field get "about.view"] set element [$_xmlobj element -as type $path] set name $cname regsub -all { } $name {_} name set _fld2Label($name) $name set label [hints zlabel] if { $label != "" } { set _fld2Label($name) $label } set _fld2Units($name) [hints zunits] set _fld2Components($name) 1 lappend _comp2fldName($cname) $name # Handle bizarre cases that hopefully will be deprecated. if { $element == "unirect3d" } { # Special case: unirect3d (should be deprecated) + flow. if { [$_field element $cname.extents] != "" } { set extents [$_field get $cname.extents] } else { set extents 1 } set _dim 3 if { $_viewer == "" } { set _viewer flowvis } set _comp2dims($cname) "3D" set _comp2unirect3d($cname) \ [Rappture::Unirect3d \#auto $_xmlobj $_field $cname $extents] set _comp2style($cname) [$_field get $cname.style] if {[$_field element $cname.flow] != ""} { set _comp2flowhints($cname) \ [Rappture::FlowHints ::\#auto $_field $cname $_units] } incr _counter return 1 } if { $element == "unirect2d" && [$_field element $cname.flow] != "" } { # Special case: unirect2d (normally deprecated) + flow. if { [$_field element $cname.extents] != "" } { set extents [$_field get $cname.extents] } else { set extents 1 } set _dim 2 if { $_viewer == "" } { set _viewer "flowvis" } set _comp2dims($cname) "2D" set _comp2unirect2d($cname) \ [Rappture::Unirect2d \#auto $_xmlobj $path] set _comp2style($cname) [$_field get $cname.style] set _comp2flowhints($cname) \ [Rappture::FlowHints ::\#auto $_field $cname $_units] set _values [$_field get $cname.values] set limits {} foreach axis { x y } { lappend limits $axis [$_comp2unirect2d($cname) limits $axis] } set xv [blt::vector create \#auto] $xv set $_values lappend limits $cname [$xv limits] lappend limits v [$xv limits] blt::vector destroy $xv set _comp2limits($cname) $limits incr _counter return 1 } switch -- $element { "cloud" { set mesh [Rappture::Cloud::fetch $_xmlobj $path] } "mesh" { set mesh [Rappture::Mesh::fetch $_xmlobj $path] } "unirect2d" { if { $_viewer == "" } { set _viewer "heightmap" } set mesh [Rappture::Unirect2d::fetch $_xmlobj $path] } } if { ![$mesh isvalid] } { return 0 } set _dim [$mesh dimensions] if {$_dim == 1} { # Is this used anywhere? # # OOPS! This is 1D data # Forget the cloud/field -- store BLT vectors # # Is there a natural growth path in generating output from 1D to # higher dimensions? If there isn't, let's kill this in favor # or explicitly using a instead. Otherwise, the features # (methods such as xmarkers) or the need to be added # to the . # set xv [blt::vector create x$_counter] set yv [blt::vector create y$_counter] $yv set [$mesh points] $xv seq 0 1 [$yv length] # sort x-coords in increasing order $xv sort $yv set _comp2dims($cname) "1D" set _comp2xy($cname) [list $xv $yv] incr _counter return 1 } if {$_dim == 2} { set _type "heightmap" set v [blt::vector create \#auto] $v set [$_field get $cname.values] if { [$v length] == 0 } { return 0 } set _comp2dims($cname) "[$mesh dimensions]D" set _comp2mesh($cname) [list $mesh $v] set _comp2style($cname) [$_field get $cname.style] incr _counter array unset _comp2limits $cname lappend _comp2limits($cname) x [$mesh limits x] lappend _comp2limits($cname) y [$mesh limits y] lappend _comp2limits($cname) $cname [$v limits] lappend _comp2limits($cname) v [$v limits] return 1 } if {$_dim == 3} { # # 3D data: Store cloud/field as components # set values [$_field get $cname.values] set farray [vtkFloatArray ::vals$_counter] foreach v $values { if {"" != $_units} { set v [Rappture::Units::convert $v \ -context $_units -to $_units -units off] } $farray InsertNextValue $v } if { $_viewer == "" } { set _viewer "isosurface" } set _type "isosurface" set v [blt::vector create \#auto] $v set [$_field get $cname.values] if { [$v length] == 0 } { return 0 } set _comp2dims($cname) "[$mesh dimensions]D" set _comp2mesh($cname) [list $mesh $v] set _comp2style($cname) [$_field get $cname.style] incr _counter lappend _comp2limits($cname) x [$mesh limits x] lappend _comp2limits($cname) y [$mesh limits y] lappend _comp2limits($cname) z [$mesh limits z] lappend _comp2limits($cname) $cname [$v limits] lappend _comp2limits($cname) v [$v limits] return 1 } error "unhandled case in field dim=$_dim element=$element" } itcl::body Rappture::Field::AvsToVtk { cname contents } { package require vtk set reader $this-datasetreader vtkAVSucdReader $reader # Write the contents to a file just in case it's binary. set tmpfile $cname[pid].ucd set f [open "$tmpfile" "w"] fconfigure $f -translation binary -encoding binary puts $f $contents close $f $reader SetFileName $tmpfile $reader Update file delete $tmpfile set output [$reader GetOutput] set pointData [$output GetPointData] set _scalars {} for { set i 0 } { $i < [$pointData GetNumberOfArrays] } { incr i } { set name [$pointData GetArrayName $i] lappend _scalars $name $name "???" } set tmpfile $cname[pid].vtk set writer $this-datasetwriter vtkDataSetWriter $writer $writer SetInputConnection [$reader GetOutputPort] $writer SetFileName $tmpfile $writer Write rename $reader "" rename $writer "" set f [open "$tmpfile" "r"] fconfigure $f -translation binary -encoding binary set vtkdata [read $f] close $f file delete $tmpfile return $vtkdata }