source: trunk/gui/scripts/visviewer.tcl @ 2219

Last change on this file since 2219 was 2194, checked in by ldelgass, 13 years ago

Fix pan/zoom for VTK contours, add command to control data range mapping for
multiple datasets. Still need to fix 3D panning, allow absolute rotation/
orientation of the camera.

File size: 16.8 KB
Line 
1
2# ----------------------------------------------------------------------
3#  VisViewer -
4#
5#  This class is the base class for the various visualization viewers
6#  that use the nanoserver render farm.
7#
8# ======================================================================
9#  AUTHOR:  Michael McLennan, Purdue University
10#  Copyright (c) 2004-2005  Purdue Research Foundation
11#
12#  See the file "license.terms" for information on usage and
13#  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
14# ======================================================================
15
16itcl::class ::Rappture::VisViewer {
17    inherit itk::Widget
18
19    itk_option define -sendcommand sendCommand SendCommand ""
20    itk_option define -receivecommand receiveCommand ReceiveCommand ""
21
22    private common _servers         ;# array of visualization server lists
23    set _servers(nanovis) "localhost:2000"
24    set _servers(pymol)   "localhost:2020"
25    set _servers(vtkvis)  "localhost:2010"
26
27    private variable _sid ""        ;# socket connection to server
28    private common _done            ;# Used to indicate status of send.
29    private variable _buffer        ;# buffer for incoming/outgoing commands
30    private variable _initialized
31    private variable _isOpen 0
32    # Number of milliseconds to wait before idle timeout.
33    # If greater than 0, automatically disconnect from the visualization
34    # server when idle timeout is reached.
35    private variable _idleTimeout 43200000; # 12 hours
36    #private variable _idleTimeout 5000;    # 5 seconds
37    #private variable _idleTimeout 0;       # No timeout
38
39    protected variable _dispatcher ""   ;# dispatcher for !events
40    protected variable _hosts ""    ;# list of hosts for server
41    protected variable _parser ""   ;# interpreter for incoming commands
42    protected variable _image
43
44    constructor { hostlist args } {
45        # defined below
46    }
47    destructor {
48        # defined below
49    }
50    # Used internally only.
51    private method Shuffle { hostlist }
52    private method ReceiveHelper {}
53    private method ServerDown {}
54    private method SendHelper {}
55    private method SendHelper.old {}
56    private method CheckConnection {}
57
58    protected method SendEcho { channel {data ""} }
59    protected method ReceiveEcho { channel {data ""} }
60    protected method Connect { hostlist }
61    protected method Disconnect {}
62    protected method IsConnected {}
63    protected method SendBytes { bytes }
64    protected method ReceiveBytes { nbytes }
65    protected method Flush {}
66    protected method Color2RGB { color }
67    protected method Euler2XYZ { theta phi psi }
68
69    private proc CheckNameList { namelist }  {
70        set pattern {^[a-zA-Z0-9\.]+:[0-9]+(,[a-zA-Z0-9\.]+:[0-9]+)*$}
71        if { ![regexp $pattern $namelist match] } {
72            error "bad visualization server address \"$namelist\": should be host:port,host:port,..."
73        }
74    }
75    public proc GetServerList { tag } {
76        return $_servers($tag)
77    }
78    public proc SetServerList { tag namelist } {
79        CheckNameList $namelist
80        set _servers($tag) $namelist
81    }
82    public proc SetPymolServerList { namelist } {
83        SetServerList "pymol" $namelist
84    }
85    public proc SetNanovisServerList { namelist } {
86        SetServerList "nanovis" $namelist
87    }
88    public proc SetVtkServerList { namelist } {
89        SetServerList "vtk" $namelist
90    }
91}
92
93itk::usual Panedwindow {
94    keep -background -cursor
95}
96
97# ----------------------------------------------------------------------
98# CONSTRUCTOR
99# ----------------------------------------------------------------------
100itcl::body Rappture::VisViewer::constructor { hostlist args } {
101
102    Rappture::dispatcher _dispatcher
103    $_dispatcher register !serverDown
104    $_dispatcher dispatch $this !serverDown "[itcl::code $this ServerDown]; list"
105    $_dispatcher register !timeout
106    $_dispatcher dispatch $this !timeout "[itcl::code $this Disconnect]; list"
107
108    CheckNameList $hostlist
109    set _hostlist $hostlist
110    set _buffer(in) ""
111    set _buffer(out) ""
112    #
113    # Create a parser to handle incoming requests
114    #
115    set _parser [interp create -safe]
116    foreach cmd [$_parser eval {info commands}] {
117        $_parser hide $cmd
118    }
119
120    #
121    # Set up the widgets in the main body
122    #
123    option add hull.width hull.height
124    pack propagate $itk_component(hull) no
125
126    itk_component add main {
127        Rappture::SidebarFrame $itk_interior.main
128    }
129    pack $itk_component(main) -expand yes -fill both
130    set f [$itk_component(main) component frame]
131
132    itk_component add plotarea {
133        frame $f.plotarea -highlightthickness 0 -background black
134    } {
135        ignore -background
136    }
137    pack $itk_component(plotarea) -fill both -expand yes
138    set _image(plot) [image create photo]
139    eval itk_initialize $args
140}
141
142#
143# destructor --
144#
145itcl::body Rappture::VisViewer::destructor {} {
146    $_dispatcher cancel !timeout
147    interp delete $_parser
148    array unset _done $this
149}
150
151#
152# Shuffle --
153#
154#   Shuffle the list of server hosts.
155#
156itcl::body Rappture::VisViewer::Shuffle { hostlist } {
157    set hosts [split $hostlist ,]
158    set randomHosts {}
159    set ticks [clock clicks]
160    expr {srand($ticks)}
161    for { set i [llength $hosts] } { $i > 0 } { incr i -1 } {
162        set index [expr {round(rand()*$i - 0.5)}]
163        if { $index == $i } {
164            set index [expr $i - 1]
165        }
166        lappend randomHosts [lindex $hosts $index]
167        set hosts [lreplace $hosts $index $index]
168    }
169    return $randomHosts
170}
171
172#
173# ServerDown --
174#
175#    Used internally to let the user know when the connection to the
176#    visualization server has been lost.  Puts up a tip encouraging the
177#    user to press any control to reconnect.
178#
179itcl::body Rappture::VisViewer::ServerDown {} {
180    if { [info exists itk_component(plotarea)] } {
181        set x [expr {[winfo rootx $itk_component(plotarea)]+10}]
182        set y [expr {[winfo rooty $itk_component(plotarea)]+10}]
183    } else {
184        set x 0; set y 0
185    }
186    Rappture::Tooltip::cue @$x,$y "Lost connection to visualization server.  This happens sometimes when there are too many users and the system runs out of memory.\n\nTo reconnect, reset the view or press any other control.  Your picture should come right back up."
187}
188
189#
190# Connect --
191#
192#    Connect to the visualization server (e.g. nanovis, pymolproxy).
193#    Creates an event callback that is triggered when we are idle
194#    (no I/O with the server) for some specified time. Sends the server
195#    some estimate of the size of our job [soon to be deprecated].
196#    If it's too busy, that server may forward us to another [this
197#    was been turned off in nanoscale].
198#
199itcl::body Rappture::VisViewer::Connect { hostlist } {
200    blt::busy hold $itk_component(hull) -cursor watch
201    # Can't call update because of all the pending stuff going on
202    #update
203
204    # Shuffle the list of servers so as to pick random
205    set servers [Shuffle $hostlist]
206
207    set memorySize 10000
208    # Get the first server
209    foreach {hostname port} [split [lindex $servers 0] :] break
210    set servers [lrange $servers 1 end]
211
212    while {1} {
213        SendEcho <<line "connecting to $hostname:$port..."
214        if { [catch {socket $hostname $port} _sid] != 0 } {
215            if {[llength $servers] == 0} {
216                blt::busy release $itk_component(hull)
217                return 0
218            }
219            # Get the next server
220            foreach {hostname port} [split [lindex $servers 0] :] break
221            set servers [lrange $servers 1 end]
222            continue
223        }
224        fconfigure $_sid -translation binary -encoding binary
225
226        # Send memory requirement to the load balancer
227        puts -nonewline $_sid [binary format I $memorySize]
228        flush $_sid
229
230        # Read back a reconnection order
231        set data [read $_sid 4]
232        if {[binary scan $data cccc b1 b2 b3 b4] != 4} {
233            blt::busy release $itk_component(hull)
234            error "couldn't read redirection request"
235        }
236        set addr [format "%u.%u.%u.%u" \
237            [expr {$b1 & 0xff}] \
238            [expr {$b2 & 0xff}] \
239            [expr {$b3 & 0xff}] \
240            [expr {$b4 & 0xff}]]
241
242        if { [string equal $addr "0.0.0.0"] } {
243            # We're connected. Cancel any pending serverDown events and
244            # release the busy window over the hull.
245            $_dispatcher cancel !serverDown
246            if { $_idleTimeout > 0 } {
247                $_dispatcher event -after $_idleTimeout !timeout
248            }
249            blt::busy release $itk_component(hull)
250            fconfigure $_sid -buffering line
251            fileevent $_sid readable [itcl::code $this ReceiveHelper]
252            return 1
253        }
254        set hostname $addr
255    }
256    #NOTREACHED
257    blt::busy release $itk_component(hull)
258    return 0
259}
260
261
262#
263# Disconnect --
264#
265#    Clients use this method to disconnect from the current rendering
266#    server.  Cancel any pending idle timeout events.
267#
268itcl::body Rappture::VisViewer::Disconnect {} {
269    $_dispatcher cancel !timeout
270    catch {close $_sid}
271    set _sid ""
272    set _buffer(in) ""
273}
274
275#
276# IsConnected --
277#
278#    Indicates if we are currently connected to a server.
279#
280itcl::body Rappture::VisViewer::IsConnected {} {
281    return [expr {"" != $_sid}]
282}
283
284#
285# CheckConection --
286#
287#   This routine is called whenever we're about to send/recieve data on
288#   the socket connection to the visualization server.  If we're connected,
289#   then reset the timeout event.  Otherwise try to reconnect to the
290#   visualization server.
291#
292itcl::body Rappture::VisViewer::CheckConnection {} {
293    if { [IsConnected] } {
294        if { [eof $_sid] } {
295            error "unexpected eof on socket"
296        }
297        $_dispatcher cancel !timeout
298        if { $_idleTimeout > 0 } {
299            $_dispatcher event -after $_idleTimeout !timeout
300        }
301        return 1
302    }
303    # If we aren't connected, assume it's because the connection to the
304    # visualization server broke. Try to open a connection and trigger a
305    # rebuild.
306    $_dispatcher cancel !serverDown
307    set x [expr {[winfo rootx $itk_component(plotarea)]+10}]
308    set y [expr {[winfo rooty $itk_component(plotarea)]+10}]
309    Rappture::Tooltip::cue @$x,$y "Connecting..."
310    set code [catch { Connect } ok]
311    if { $code == 0 && $ok} {
312        $_dispatcher event -idle !rebuild
313        Rappture::Tooltip::cue hide
314    } else {
315        Rappture::Tooltip::cue @$x,$y "Can't connect to visualization server.  This may be a network problem.  Wait a few moments and try resetting the view."
316        return 0
317    }
318    return 1
319}
320
321#
322# Flush --
323#
324#    Flushes the socket.
325#
326itcl::body Rappture::VisViewer::Flush {} {
327    if { [CheckConnection] } {
328        flush $_sid
329    }
330}
331
332
333#
334# SendHelper --
335#
336#   Helper routine called from a file event to send data when the
337#   connection is writable (i.e. not blocked).  Sets a magic
338#   variable _done($this) when we're done.
339#
340itcl::body Rappture::VisViewer::SendHelper {} {
341    if { ![CheckConnection] } {
342        return 0
343    }
344    puts -nonewline $_sid $_buffer(out)
345    flush $_sid
346    set _done($this) 1;     # Success
347}
348
349#
350# SendHelper.old --
351#
352#   Helper routine called from a file event to send data when the
353#   connection is writable (i.e. not blocked).  Sends data in chunks
354#   of 8k (or less).  Sets magic variable _done($this) to indicate
355#   that we're either finished (success) or could not send bytes to
356#   the server (failure).
357#
358itcl::body Rappture::VisViewer::SendHelper.old {} {
359    if { ![CheckConnection] } {
360        return 0
361    }
362    set bytesLeft [string length $_buffer(out)]
363    if { $bytesLeft > 0} {
364        set chunk [string range $_buffer(out) 0 8095]
365        set _buffer(out)  [string range $_buffer(out) 8096 end]
366        incr bytesLeft -8096
367        set code [catch {
368            if { $bytesLeft > 0 } {
369                puts -nonewline $_sid $chunk
370            } else {
371                puts $_sid $chunk
372            }
373        } err]
374        if { $code != 0 } {
375            puts stderr "error sending data to $_sid: $err"
376            Disconnect
377            set _done($this) 0;     # Failure
378        }
379    } else {
380        set _done($this) 1;     # Success
381    }
382}
383
384#
385# SendBytes --
386#
387#   Send a a string to the visualization server.
388#
389itcl::body Rappture::VisViewer::SendBytes { bytes } {
390    SendEcho >>line $bytes
391    if { ![CheckConnection] } {
392        return 0
393    }
394    # Even though the data is sent in only 1 "puts", we need to verify that
395    # the server is ready first.  Wait for the socket to become writable
396    # before sending anything.
397    set _done($this) 1
398    set _buffer(out) $bytes
399    fileevent $_sid writable [itcl::code $this SendHelper]
400    tkwait variable ::Rappture::VisViewer::_done($this)
401    set _buffer(out) ""
402    if { [IsConnected] } {
403        # The connection may have closed while we were writing to the server.
404        # This can happen if what we sent the server caused it to barf.
405        fileevent $_sid writable ""
406        flush $_sid
407    }
408    if 0 {
409    if { ![CheckConnection] } {
410        puts stderr "connection is now down"
411        return 0
412    }
413    }
414    return $_done($this)
415}
416
417#
418# ReceiveBytes --
419#
420#    Read some number of bytes from the visualization server.
421#
422itcl::body Rappture::VisViewer::ReceiveBytes { size } {
423    if { ![CheckConnection] } {
424        return 0
425    }
426    set bytes [read $_sid $size]
427    ReceiveEcho <<line "<read $size bytes"
428    return $bytes
429}
430
431#
432# ReceiveHelper --
433#
434#   Helper routine called from a file event when the connection is
435#   readable (i.e. a command response has been sent by the rendering
436#   server.  Reads the incoming command and executes it in a safe
437#   interpreter to handle the action.
438#
439#       Note: This routine currently only handles command responses from
440#         the visualization server.  It doesn't handle non-blocking
441#         reads from the visualization server.
442#
443#       nv>image -bytes 100000      yes
444#       ...following 100000 bytes...    no
445#
446#   Note: All commands from the render server are on one line.
447#         This is because the render server can send anything
448#         as an error message (restricted again to one line).
449#
450itcl::body Rappture::VisViewer::ReceiveHelper {} {
451    if { ![CheckConnection] } {
452        return 0
453    }
454    set n [gets $_sid line]
455
456    if { $n < 0 } {
457        Disconnect
458        return 0
459    }
460    set line [string trim $line]
461    if { $line == "" } {
462        return
463    }
464    if { [string compare -length 3 $line "nv>"] == 0 } {
465        ReceiveEcho <<line $line
466        append _buffer(in) [string range $line 3 end]
467        append _buffer(in) "\n"
468        if {[info complete $_buffer(in)]} {
469            set request $_buffer(in)
470            set _buffer(in) ""
471            if { [catch {$_parser eval $request} err]  != 0 } {
472                global errorInfo
473                puts stderr "err=$err errorInfo=$errorInfo"
474            }
475        }
476    } elseif { [string compare -length 21 $line "NanoVis Server Error:"] == 0 ||
477               [string compare -length 20 $line "VtkVis Server Error:"] == 0} {
478        # this shows errors coming back from the engine
479        ReceiveEcho <<error $line
480        puts stderr "Render Server Error: $line\n"
481    } else {
482        # this shows errors coming back from the engine
483        ReceiveEcho <<error $line
484        puts stderr "Garbled message: $line\n"
485    }
486}
487
488
489#
490# Color2RGB --
491#
492#   Converts a color name to a list of r,g,b values needed for the
493#   engine.  Each r/g/b component is scaled in the # range 0-1.
494#
495itcl::body Rappture::VisViewer::Color2RGB {color} {
496    foreach {r g b} [winfo rgb $itk_component(hull) $color] break
497    set r [expr {$r/65535.0}]
498    set g [expr {$g/65535.0}]
499    set b [expr {$b/65535.0}]
500    return [list $r $g $b]
501}
502
503#
504# Euler2XYZ --
505#
506#   Converts euler angles for the camera placement the to angles of
507#   rotation about the x/y/z axes, used by the engine.  Returns a list:
508#   {xangle, yangle, zangle}.
509#
510itcl::body Rappture::VisViewer::Euler2XYZ {theta phi psi} {
511    set xangle [expr {$theta-90.0}]
512    set yangle [expr {180-$phi}]
513    set zangle $psi
514    return [list $xangle $yangle $zangle]
515}
516
517
518#
519# SendEcho --
520#
521#     Used internally to echo sent data to clients interested in this widget.
522#     If the -sendcommand option is set, then it is invoked in the global scope
523#     with the <channel> and <data> values as arguments.  Otherwise, this does
524#     nothing.
525#
526itcl::body Rappture::VisViewer::SendEcho {channel {data ""}} {
527    #puts stderr ">>($data)"
528    if {[string length $itk_option(-sendcommand)] > 0} {
529        uplevel #0 $itk_option(-sendcommand) [list $channel $data]
530    }
531}
532
533#
534# ReceiveEcho --
535#
536#     Echoes received data to clients interested in this widget.  If the
537#     -receivecommand option is set, then it is invoked in the global
538#     scope with the <channel> and <data> values as arguments.  Otherwise,
539#     this does nothing.
540#
541itcl::body Rappture::VisViewer::ReceiveEcho {channel {data ""}} {
542    #puts stderr "<<line $data"
543    if {[string length $itk_option(-receivecommand)] > 0} {
544        uplevel #0 $itk_option(-receivecommand) [list $channel $data]
545    }
546}
Note: See TracBrowser for help on using the repository browser.