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

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