source: trunk/gui/scripts/molvisviewer.tcl @ 640

Last change on this file since 640 was 609, checked in by nkissebe, 18 years ago

added molvisviewer; remote visualization using pymol

File size: 22.4 KB
Line 
1# ----------------------------------------------------------------------
2#  COMPONENT: molvisviewer - view a molecule in 3D
3#
4#  This widget brings up a 3D representation of a molecule
5#  It connects to the Molvis server running on a rendering farm,
6#  transmits data, and displays the results.
7# ======================================================================
8#  AUTHOR:  Michael McLennan, Purdue University
9#  Copyright (c) 2004-2005  Purdue Research Foundation
10#
11#  See the file "license.terms" for information on usage and
12#  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
13# ======================================================================
14package require Itk
15package require BLT
16package require Img
17
18option add *MolvisViewer.width 4i widgetDefault
19option add *MolvisViewer.height 4i widgetDefault
20option add *MolvisViewer.foreground black widgetDefault
21option add *MolvisViewer.controlBackground gray widgetDefault
22option add *MolvisViewer.controlDarkBackground #999999 widgetDefault
23option add *MolvisViewer.font -*-helvetica-medium-r-normal-*-*-120-* widgetDefault
24
25itcl::class Rappture::MolvisViewer {
26    inherit itk::Widget
27    itk_option define -device device Device ""
28
29    constructor {hostlist args} { # defined below }
30    destructor { # defined below }
31
32    public method emblems {option}
33
34    public method connect {{hostlist ""}}
35    public method disconnect {}
36    public method isconnected {}
37
38    protected method _send {args}
39    protected method _receive {}
40    protected method _receive_image {size}
41    protected method _rebuild {}
42    protected method _zoom {option}
43    protected method _move {option x y}
44    protected method _speed {option}
45    protected method _serverDown {}
46
47    private variable _dispatcher "" ;# dispatcher for !events
48    private variable _sid ""       ;# socket connection to nanovis server
49    private variable _image        ;# image displayed in plotting area
50
51    private variable _click        ;# info used for _move operations
52}
53
54itk::usual MolvisViewer {
55    keep -background -foreground -cursor -font
56}
57
58# ----------------------------------------------------------------------
59# CONSTRUCTOR
60# ----------------------------------------------------------------------
61itcl::body Rappture::MolvisViewer::constructor {hostlist args} {
62    #puts stderr "MolvisViewer::_constructor()"
63    Rappture::dispatcher _dispatcher
64    $_dispatcher register !serverDown
65    $_dispatcher dispatch $this !serverDown "[itcl::code $this _serverDown]; list"
66
67    #
68    # Set up the widgets in the main body
69    #
70    option add hull.width hull.height
71    pack propagate $itk_component(hull) no
72
73    itk_component add bottom_controls {
74        frame $itk_interior.b_cntls
75    } {
76        usual
77        rename -background -controlbackground controlBackground Background
78    }
79    pack $itk_component(bottom_controls) -side bottom -fill y
80
81    itk_component add mrewind {
82            button $itk_component(bottom_controls).mrewind \
83            -borderwidth 1 -padx 1 -pady 1 \
84            -text "|<" \
85            -command [itcl::code $this _send rewind]
86    } {
87        usual
88        ignore -borderwidth
89        rename -highlightbackground -controlbackground controlBackground Background
90    }
91    pack $itk_component(mrewind) -padx 4 -pady 4 -side left
92
93    itk_component add mbackward {
94            button $itk_component(bottom_controls).mbackward \
95            -borderwidth 1 -padx 1 -pady 1 \
96            -text "<" \
97            -command [itcl::code $this _send backward]
98    } {
99        usual
100        ignore -borderwidth
101        rename -highlightbackground -controlbackground controlBackground Background
102    }
103    pack $itk_component(mbackward) -padx 4 -pady 4 -side left
104
105    itk_component add mstop {
106            button $itk_component(bottom_controls).mstop \
107            -borderwidth 1 -padx 1 -pady 1 \
108            -text "Stop" \
109            -command [itcl::code $this _send mstop]
110    } {
111        usual
112        ignore -borderwidth
113        rename -highlightbackground -controlbackground controlBackground Background
114    }
115    pack $itk_component(mstop) -padx 4 -pady 4 -side left
116
117    itk_component add mplay {
118            button $itk_component(bottom_controls).mplay \
119            -borderwidth 1 -padx 1 -pady 1 \
120            -text "Play" \
121            -command [itcl::code $this _send mplay]
122    } {
123        usual
124        ignore -borderwidth
125        rename -highlightbackground -controlbackground controlBackground Background
126    }
127    pack $itk_component(mplay) -padx 4 -pady 4 -side left
128   
129    itk_component add mforward {
130            button $itk_component(bottom_controls).mforward \
131            -borderwidth 1 -padx 1 -pady 1 \
132            -text ">" \
133            -command [itcl::code $this _send forward]
134    } {
135        usual
136        ignore -borderwidth
137        rename -highlightbackground -controlbackground controlBackground Background
138    }
139    pack $itk_component(mforward) -padx 4 -pady 4 -side left
140   
141    itk_component add mend {
142            button $itk_component(bottom_controls).mend \
143            -borderwidth 1 -padx 1 -pady 1 \
144            -text ">|" \
145            -command [itcl::code $this _send ending]
146    } {
147        usual
148        ignore -borderwidth
149        rename -highlightbackground -controlbackground controlBackground Background
150    }
151    pack $itk_component(mend) -padx 4 -pady 4 -side left
152   
153    itk_component add mclear {
154            button $itk_component(bottom_controls).mclear \
155            -borderwidth 1 -padx 1 -pady 1 \
156            -text "MClear" \
157            -command [itcl::code $this _send mclear]
158    } {
159        usual
160        ignore -borderwidth
161        rename -highlightbackground -controlbackground controlBackground Background
162    }
163    pack $itk_component(mclear) -padx 4 -pady 4 -side left
164   
165    itk_component add speed {
166            ::scale $itk_component(bottom_controls).speed \
167            -borderwidth 1 \
168            -from 100 -to 1000 -orient horizontal \
169            -command [itcl::code $this _speed]
170    } {
171        usual
172        ignore -borderwidth
173        rename -highlightbackground -controlbackground controlBackground Background
174    }
175    pack $itk_component(speed) -padx 4 -pady 4 -side right
176           
177    itk_component add left_controls {
178        frame $itk_interior.l_cntls
179        } {
180        usual
181        rename -background -controlbackground controlBackground Background
182        }
183    pack $itk_component(left_controls) -side left -fill y
184
185    itk_component add show_ball_and_stick {
186            button $itk_component(left_controls).sbs \
187            -borderwidth 2 -padx 0 -pady 0 \
188            -image [Rappture::icon ballnstick] \
189            -command [itcl::code $this _send ball_and_stick]
190    } {
191        usual
192        ignore -borderwidth
193        rename -highlightbackground -controlbackground controlBackground Background
194    }
195    pack $itk_component(show_ball_and_stick) -padx 4 -pady 4
196
197    itk_component add show_spheres {
198            button $itk_component(left_controls).ss \
199            -borderwidth 1 -padx 1 -pady 1 \
200            -image [Rappture::icon spheres] \
201            -command [itcl::code $this _send spheres]
202    } {
203        usual
204        ignore -borderwidth
205        rename -highlightbackground -controlbackground controlBackground Background
206    }
207    pack $itk_component(show_spheres) -padx 4 -pady 4
208
209    itk_component add show_lines {
210            button $itk_component(left_controls).sl \
211            -borderwidth 1 -padx 1 -pady 1 \
212            -image [Rappture::icon lines] \
213            -command [itcl::code $this _send lines]
214    } {
215        usual
216        ignore -borderwidth
217        rename -highlightbackground -controlbackground controlBackground Background
218    }
219    pack $itk_component(show_lines) -padx 4 -pady 4
220
221    itk_component add controls {
222        frame $itk_interior.cntls
223    } {
224        usual
225        rename -background -controlbackground controlBackground Background
226    }
227    pack $itk_component(controls) -side right -fill y
228
229    itk_component add reset {
230        button $itk_component(controls).reset \
231            -borderwidth 1 -padx 1 -pady 1 \
232            -bitmap [Rappture::icon reset] \
233            -command [itcl::code $this _send reset]
234    } {
235        usual
236        ignore -borderwidth
237        rename -highlightbackground -controlbackground controlBackground Background
238    }
239    pack $itk_component(reset) -padx 4 -pady 4
240    Rappture::Tooltip::for $itk_component(reset) "Reset the view to the default zoom level"
241
242    itk_component add zoomin {
243        button $itk_component(controls).zin \
244            -borderwidth 1 -padx 1 -pady 1 \
245            -bitmap [Rappture::icon zoomin] \
246            -command [itcl::code $this _zoom in]
247    } {
248        usual
249        ignore -borderwidth
250        rename -highlightbackground -controlbackground controlBackground Background
251    }
252    pack $itk_component(zoomin) -padx 4 -pady 4
253    Rappture::Tooltip::for $itk_component(zoomin) "Zoom in"
254
255    itk_component add zoomout {
256        button $itk_component(controls).zout \
257            -borderwidth 1 -padx 1 -pady 1 \
258            -bitmap [Rappture::icon zoomout] \
259            -command [itcl::code $this _zoom out]
260    } {
261        usual
262        ignore -borderwidth
263        rename -highlightbackground -controlbackground controlBackground Background
264    }
265    pack $itk_component(zoomout) -padx 4 -pady 4
266    Rappture::Tooltip::for $itk_component(zoomout) "Zoom out"
267
268    itk_component add labels {
269        label $itk_component(controls).labels \
270            -borderwidth 1 -padx 1 -pady 1 \
271            -bitmap [Rappture::icon atoms]
272    } {
273        usual
274        ignore -borderwidth
275        rename -highlightbackground -controlbackground controlBackground Background
276    }
277    pack $itk_component(labels) -padx 4 -pady 8 -ipadx 1 -ipady 1
278    Rappture::Tooltip::for $itk_component(labels) "Show/hide the labels on atoms"
279    bind $itk_component(labels) <ButtonPress> \
280        [itcl::code $this emblems toggle]
281
282    itk_component add rock {
283        button $itk_component(controls).rock \
284            -borderwidth 1 -padx 1 -pady 1 \
285            -text "R" \
286            -command [itcl::code $this _send rock]
287    } {
288        usual
289        ignore -borderwidth
290        rename -highlightbackground -controlbackground controlBackground Background
291    }
292    pack $itk_component(rock) -padx 4 -pady 8 -ipadx 1 -ipady 1
293
294    #
295    # RENDERING AREA
296    #
297
298    itk_component add area {
299        frame $itk_interior.area
300    }
301    pack $itk_component(area) -expand yes -fill both
302
303    set _image(plot) [image create photo]
304
305    itk_component add 3dview {
306        label $itk_component(area).vol -image $_image(plot) \
307            -highlightthickness 0
308    } {
309        usual
310        ignore -highlightthickness
311    }
312    pack $itk_component(3dview) -expand yes -fill both
313
314    # set up bindings for rotation
315    bind $itk_component(3dview) <ButtonPress> \
316        [itcl::code $this _move click %x %y]
317    bind $itk_component(3dview) <B1-Motion> \
318        [itcl::code $this _move drag %x %y]
319    bind $itk_component(3dview) <ButtonRelease> \
320        [itcl::code $this _move release %x %y]
321    bind $itk_component(3dview) <Configure> \
322        [itcl::code $this _send screen %w %h]
323
324    connect $hostlist
325
326    $_dispatcher register !rebuild
327    $_dispatcher dispatch $this !rebuild "[itcl::code $this _rebuild]; list"
328   
329    eval itk_initialize $args
330}
331
332# ----------------------------------------------------------------------
333# DESTRUCTOR
334# ----------------------------------------------------------------------
335itcl::body Rappture::MolvisViewer::destructor {} {
336    # puts stderr "MolvisViewer::destructor()"
337    after cancel [itcl::code $this _rebuild]
338    image delete $_image(plot)
339}
340
341# ----------------------------------------------------------------------
342# USAGE: connect ?<host:port>,<host:port>...?
343#
344# Clients use this method to establish a connection to a new
345# server, or to reestablish a connection to the previous server.
346# Any existing connection is automatically closed.
347# ----------------------------------------------------------------------
348itcl::body Rappture::MolvisViewer::connect {{hostlist ""}} {
349    # puts stderr "MolvisViewer::connect()"
350
351    if ([isconnected]) {
352        disconnect
353    }
354
355    if {"" == $hostlist} {
356        return 0
357    }
358
359    blt::busy hold $itk_component(hull);
360   
361    update idletasks
362
363    #
364    # Connect to the hubvis server. 
365    # If it's too busy, that server may
366    # forward us to another.
367    #
368
369    set hosts [split $hostlist ,]
370
371    foreach {hostname port} [split [lindex $hosts 0] :] break
372
373    set hosts [lrange $hosts 1 end]
374
375    while {1} {
376        if {[catch {socket $hostname $port} sid]} {
377            if {[llength $hosts] == 0} {
378                blt::busy release $itk_component(hull)
379                return 0
380            }
381            foreach {hostname port} [split [lindex $hosts 0] :] break
382            set hosts [lrange $hosts 1 end]
383            continue
384        }
385        fconfigure $sid -translation binary -encoding binary -buffering line
386        puts -nonewline $sid "AB01"
387        flush $sid
388
389        # read back a reconnection order
390        set data [read $sid 4]
391
392        if {[binary scan $data cccc b1 b2 b3 b4] != 4} {
393            error "couldn't read redirection request"
394        }
395
396        set hostname [format "%u.%u.%u.%u" \
397            [expr {$b1 & 0xff}] \
398            [expr {$b2 & 0xff}] \
399            [expr {$b3 & 0xff}] \
400            [expr {$b4 & 0xff}]]
401
402        if {[string equal $hostname "0.0.0.0"]} {
403            fileevent $sid readable [itcl::code $this _receive]
404            set _sid $sid
405            blt::busy release $itk_component(hull)
406            return 1
407        }
408    }
409
410    blt::busy release $itk_component(hull)
411
412    return 0
413}
414
415# ----------------------------------------------------------------------
416# USAGE: disconnect
417#
418# Clients use this method to disconnect from the current rendering
419# server.
420# ----------------------------------------------------------------------
421itcl::body Rappture::MolvisViewer::disconnect {} {
422    #puts stderr "MolvisViewer::disconnect()"
423
424    if {"" != $_sid} {
425        catch {
426            close $_sid
427        }
428        set _sid ""
429    }
430}
431
432# ----------------------------------------------------------------------
433# USAGE: isconnected
434#
435# Clients use this method to see if we are currently connected to
436# a server.
437# ----------------------------------------------------------------------
438itcl::body Rappture::MolvisViewer::isconnected {} {
439    #puts stderr "MolvisViewer::isconnected()"
440    return [expr {"" != $_sid}]
441}
442
443# ----------------------------------------------------------------------
444# USAGE: _send <arg> <arg> ...
445#
446# Used internally to send commands off to the rendering server.
447# ----------------------------------------------------------------------
448itcl::body Rappture::MolvisViewer::_send {args} {
449    # puts stderr "MolvisViewer::_send() $args"
450    if {"" == $_sid} {
451        $_dispatcher cancel !serverDown
452        set x [expr {[winfo rootx $itk_component(area)]+10}]
453        set y [expr {[winfo rooty $itk_component(area)]+10}]
454        Rappture::Tooltip::cue @$x,$y "Connecting..."
455
456        if {[catch {connect} ok] == 0 && $ok} {
457            set w [winfo width $itk_component(3dview)]
458            set h [winfo height $itk_component(3dview)]
459            puts $_sid "screen $w $h"
460            after idle [itcl::code $this _rebuild]
461            Rappture::Tooltip::cue hide
462            return
463        }
464
465        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."
466       
467        return
468    }
469
470    if {"" != $_sid} {
471        puts $_sid $args
472    }
473}
474
475# ----------------------------------------------------------------------
476# USAGE: _receive
477#
478# Invoked automatically whenever a command is received from the
479# rendering server.  Reads the incoming command and executes it in
480# a safe interpreter to handle the action.
481# ----------------------------------------------------------------------
482itcl::body Rappture::MolvisViewer::_receive {} {
483    #puts stderr "MolvisViewer::_receive()"
484    if {"" != $_sid} {
485        if {[gets $_sid line] < 0} {
486            disconnect
487            $_dispatcher event -after 750 !serverDown
488        } elseif {[regexp {^\s*nv>\s*image\s+(\d+)\s*$} $line whole match]} {
489            set bytes [read $_sid $match]
490            $_image(plot) configure -data $bytes
491            update idletasks
492        } else {
493            # this shows errors coming back from the engine
494            puts $line
495        }
496    }
497}
498
499# ----------------------------------------------------------------------
500# USAGE: _rebuild
501#
502# Called automatically whenever something changes that affects the
503# data in the widget.  Clears any existing data and rebuilds the
504# widget to display new data.
505# ----------------------------------------------------------------------
506itcl::body Rappture::MolvisViewer::_rebuild {} {
507    #puts stderr "MolvisViewer::_rebuild()"
508    set recname  "ATOM  "
509    set serial   0
510    set atom     ""
511    set altLoc   ""
512    set resName  ""
513    set chainID  ""
514    set Seqno    ""
515    set x        0
516    set y        0
517    set z        0
518    set occupancy  1
519    set tempFactor 0
520    set recID      ""
521    set segID      ""
522    set element    ""
523    set charge     ""
524    set data1      ""
525    set data2      ""
526   
527    if {$itk_option(-device) != ""} {
528        set dev $itk_option(-device)
529
530        foreach _atom [$dev children -type atom components.molecule] {
531            set symbol [$dev get components.molecule.$_atom.symbol]
532            set xyz [$dev get components.molecule.$_atom.xyz]
533            regsub {,} $xyz {} xyz
534            scan $xyz "%f %f %f" x y z
535            set atom $symbol
536            set line [format "%6s%5d %4s%1s%3s %1s%5s   %8.3f%8.3f%8.3f%6.2f%6.2f%8s\n" $recname $serial $atom $altLoc $resName $chainID $Seqno $x $y $z $occupancy $tempFactor $recID]
537            append data1 $line
538            incr serial
539        }
540
541        set data2 [$dev get components.molecule.pdb]
542
543    }
544
545    if {"" != $data1} {
546        eval _send loadpdb \"$data1\" data1
547    }
548
549    if {"" != $data2} {
550        eval _send loadpdb \"$data2\" data2
551    }
552}
553
554itcl::body Rappture::MolvisViewer::_speed {option} {
555        #puts stderr "MolvisViewer::_speed($option)"
556        _send mspeed $option
557}
558
559# ----------------------------------------------------------------------
560# USAGE: _zoom in
561# USAGE: _zoom out
562# USAGE: _zoom reset
563#
564# Called automatically when the user clicks on one of the zoom
565# controls for this widget.  Changes the zoom for the current view.
566# ----------------------------------------------------------------------
567itcl::body Rappture::MolvisViewer::_zoom {option} {
568    #puts stderr "MolvisViewer::_zoom()"
569    switch -- $option {
570        in {
571            _send camera zoom 10
572        }
573        out {
574            _send camera zoom -10
575        }
576        reset {
577            _send reset
578        }
579    }
580}
581
582# ----------------------------------------------------------------------
583# USAGE: _move click <x> <y>
584# USAGE: _move drag <x> <y>
585# USAGE: _move release <x> <y>
586#
587# Called automatically when the user clicks/drags/releases in the
588# plot area.  Moves the plot according to the user's actions.
589# ----------------------------------------------------------------------
590itcl::body Rappture::MolvisViewer::_move {option x y} {
591    #puts stderr "MolvisViewer::_move($option $x $y)"
592    switch -- $option {
593        click {
594            $itk_component(3dview) configure -cursor fleur
595            set _click(x) $x
596            set _click(y) $y
597            set _click(time) [clock clicks -milliseconds]
598        }
599        drag {
600            if {[array size _click] == 0} {
601                _move click $x $y
602            } else {
603                set now [clock clicks -milliseconds]
604                set diff [expr {abs($_click(time) - $now)}]
605                if {$diff < 75} { # 75ms between motion updates
606                        return
607                }
608                set w [winfo width $itk_component(3dview)]
609                set h [winfo height $itk_component(3dview)]
610                if {$w <= 0 || $h <= 0} {
611                    return
612                }
613
614                eval _send camera angle [expr $y-$_click(y)] [expr $x-$_click(x)]
615
616                set _click(x) $x
617                set _click(y) $y
618                set _click(time) $now
619            }
620        }
621        release {
622            _move drag $x $y
623            $itk_component(3dview) configure -cursor ""
624            catch {unset _click}
625        }
626        default {
627            error "bad option \"$option\": should be click, drag, release"
628        }
629    }
630}
631
632# ----------------------------------------------------------------------
633# USAGE: _serverDown
634#
635# Used internally to let the user know when the connection to the
636# visualization server has been lost.  Puts up a tip encouraging the
637# user to press any control to reconnect.
638# ----------------------------------------------------------------------
639itcl::body Rappture::MolvisViewer::_serverDown {} {
640    #puts stderr "MolvisViewer::_serverDown()"
641    set x [expr {[winfo rootx $itk_component(area)]+10}]
642    set y [expr {[winfo rooty $itk_component(area)]+10}]
643    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."
644}
645
646# ----------------------------------------------------------------------
647# USAGE: emblems on
648# USAGE: emblems off
649# USAGE: emblems toggle
650#
651# Used internally to turn labels associated with atoms on/off, and to
652# update the positions of the labels so they sit on top of each atom.
653# ----------------------------------------------------------------------
654itcl::body Rappture::MolvisViewer::emblems {option} {
655    #puts stderr "MolvisViewer::emblems($option)"
656    switch -- $option {
657        on {
658            set state 1
659        }
660        off {
661            set state 0
662        }
663        toggle {
664            if {[$itk_component(labels) cget -relief] == "sunken"} {
665                set state 0
666            } else {
667                set state 1
668            }
669        }
670        default {
671            error "bad option \"$option\": should be on, off, toggle"
672        }
673    }
674
675    if {$state} {
676        $itk_component(labels) configure -relief sunken
677        _send label on
678    } else {
679        $itk_component(labels) configure -relief raised
680        _send label off
681    }
682}
683
684# ----------------------------------------------------------------------
685# OPTION: -device
686# ----------------------------------------------------------------------
687itcl::configbody Rappture::MolvisViewer::device {
688    #puts stderr "MolvisViewer::device()"
689
690    if {$itk_option(-device) != "" } {
691
692        if {![Rappture::library isvalid $itk_option(-device)]} {
693            error "bad value \"$itk_option(-device)\": should be Rappture::library object"
694        }
695
696        set state [$itk_option(-device) get components.molecule.about.emblems]
697
698        if {$state == "" || ![string is boolean $state] || !$state} {
699            emblems off
700        } else {
701            emblems on
702        }
703    }
704
705    $_dispatcher event -idle !rebuild
706}
707
Note: See TracBrowser for help on using the repository browser.