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

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