source: trunk/gui/scripts/moleculeViewer.tcl @ 711

Last change on this file since 711 was 711, checked in by mmc, 17 years ago

Fixed the older MoleculeViewer? to work properly with the add/delete
calls needed for the new PyMol?-based viewer.

Disabled the syncCutBuffer stuff in mainwin that used to be used for
copy/paste with desktop. This never worked very well, and we have
the newer upload/download stuff in place now.

A few small tweaks to the graph example and the loader example.

File size: 23.0 KB
Line 
1# ----------------------------------------------------------------------
2#  COMPONENT: MoleculeViewer - view a molecule in 3D
3#
4#  This widget brings up a 3D representation of a molecule, which you
5#  can rotate.  It extracts atoms and bonds from the Rappture XML
6#  representation for a <molecule>.
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 vtk
16package require vtkinteraction
17package require BLT
18package require Img
19
20option add *MoleculeViewer.width 3i widgetDefault
21option add *MoleculeViewer.height 3i widgetDefault
22option add *MoleculeViewer.backdrop black widgetDefault
23
24itcl::class Rappture::MoleculeViewer {
25    inherit itk::Widget
26
27    itk_option define -backdrop backdrop Backdrop "black"
28    itk_option define -device device Device ""
29
30    constructor {tool args} { # defined below }
31    destructor { # defined below }
32
33    public method add {dataobj {settings ""}}
34    public method get {}
35    public method delete {args}
36
37    public method emblems {option}
38    public method download {option args}
39
40    protected method _clear {}
41    protected method _redraw {}
42    protected method _zoom {option}
43    protected method _move {option x y}
44    protected method _3dView {theta phi psi}
45    protected method _color2rgb {color}
46
47    private variable _dispatcher "" ;# dispatcher for !events
48
49    private variable _tool ""    ;# tool containing this viewer
50    private variable _dlist ""   ;# list of dataobj objects
51    private variable _dobj2raise ;# maps dataobj => raise flag
52    private variable _actors ""  ;# list of actors in renderer
53    private variable _label2atom ;# maps 2D text actor => underlying atom
54    private variable _view       ;# view params for 3D view
55    private variable _limits     ;# limits of x/y/z axes
56    private variable _click      ;# info used for _move operations
57    private variable _download "";# snapshot for download
58}
59                                                                               
60itk::usual MoleculeViewer {
61}
62
63# ----------------------------------------------------------------------
64# CONSTRUCTOR
65# ----------------------------------------------------------------------
66itcl::body Rappture::MoleculeViewer::constructor {tool args} {
67    set _tool $tool
68
69    Rappture::dispatcher _dispatcher
70    $_dispatcher register !redraw
71    $_dispatcher dispatch $this !redraw "[itcl::code $this _redraw]; list"
72    $_dispatcher register !render
73    $_dispatcher dispatch $this !render "$this-renWin Render; list"
74    $_dispatcher register !fixsize
75    $_dispatcher dispatch $this !fixsize \
76        "[itcl::code $this emblems fixPosition]; list"
77
78    itk_option add hull.width hull.height
79    pack propagate $itk_component(hull) no
80
81    vtkRenderWindow $this-renWin
82    vtkRenderer $this-ren
83    $this-renWin AddRenderer $this-ren
84
85    vtkRenderWindowInteractor $this-int
86    $this-int SetRenderWindow $this-renWin
87
88    vtkSphereSource $this-sphere
89    $this-sphere SetRadius 1.0
90    $this-sphere SetThetaResolution 18
91    $this-sphere SetPhiResolution 18
92
93    vtkPolyDataMapper $this-map
94    $this-map SetInput [$this-sphere GetOutput]
95
96    vtkCoordinate $this-xyzconv
97    $this-xyzconv SetCoordinateSystemToWorld
98
99    set _view(theta) 0
100    set _view(phi) 0
101    set _view(psi) 0
102
103    itk_component add controls {
104        frame $itk_interior.cntls
105    } {
106        usual
107        rename -background -controlbackground controlBackground Background
108    }
109    pack $itk_component(controls) -side right -fill y
110
111    itk_component add reset {
112        button $itk_component(controls).reset \
113            -borderwidth 1 -padx 1 -pady 1 \
114            -bitmap [Rappture::icon reset] \
115            -command [itcl::code $this _zoom reset]
116    } {
117        usual
118        ignore -borderwidth
119        rename -highlightbackground -controlbackground controlBackground Background
120    }
121    pack $itk_component(reset) -padx 4 -pady 4
122    Rappture::Tooltip::for $itk_component(reset) "Reset the view to the default zoom level"
123
124    itk_component add zoomin {
125        button $itk_component(controls).zin \
126            -borderwidth 1 -padx 1 -pady 1 \
127            -bitmap [Rappture::icon zoomin] \
128            -command [itcl::code $this _zoom in]
129    } {
130        usual
131        ignore -borderwidth
132        rename -highlightbackground -controlbackground controlBackground Background
133    }
134    pack $itk_component(zoomin) -padx 4 -pady 4
135    Rappture::Tooltip::for $itk_component(zoomin) "Zoom in"
136
137    itk_component add zoomout {
138        button $itk_component(controls).zout \
139            -borderwidth 1 -padx 1 -pady 1 \
140            -bitmap [Rappture::icon zoomout] \
141            -command [itcl::code $this _zoom out]
142    } {
143        usual
144        ignore -borderwidth
145        rename -highlightbackground -controlbackground controlBackground Background
146    }
147    pack $itk_component(zoomout) -padx 4 -pady 4
148    Rappture::Tooltip::for $itk_component(zoomout) "Zoom out"
149
150    itk_component add labels {
151        label $itk_component(controls).labels \
152            -borderwidth 1 -padx 1 -pady 1 \
153            -bitmap [Rappture::icon atoms]
154    } {
155        usual
156        ignore -borderwidth
157        rename -highlightbackground -controlbackground controlBackground Background
158    }
159    pack $itk_component(labels) -padx 4 -pady 8 -ipadx 1 -ipady 1
160    Rappture::Tooltip::for $itk_component(labels) "Show/hide the labels on atoms"
161    bind $itk_component(labels) <ButtonPress> \
162        [itcl::code $this emblems toggle]
163
164    #
165    # RENDERING AREA
166    #
167    itk_component add area {
168        frame $itk_interior.area
169    }
170    pack $itk_component(area) -expand yes -fill both
171    bind $itk_component(area) <Configure> \
172        [list $_dispatcher event -idle !fixsize]
173
174    itk_component add renderer {
175        vtkTkRenderWidget $itk_component(area).ren -rw $this-renWin
176    } {
177    }
178    pack $itk_component(renderer) -expand yes -fill both
179
180    eval itk_initialize $args
181
182    # prevent interactions -- use our own
183    blt::busy hold $itk_component(area) -cursor left_ptr
184    bind $itk_component(area)_Busy <ButtonPress> \
185        [itcl::code $this _move click %x %y]
186    bind $itk_component(area)_Busy <B1-Motion> \
187        [itcl::code $this _move drag %x %y]
188    bind $itk_component(area)_Busy <ButtonRelease> \
189        [itcl::code $this _move release %x %y]
190
191    emblems on
192
193    # create a photo for download snapshots
194    set _download [image create photo]
195}
196
197# ----------------------------------------------------------------------
198# DESTRUCTOR
199# ----------------------------------------------------------------------
200itcl::body Rappture::MoleculeViewer::destructor {} {
201    rename $this-renWin ""
202    rename $this-ren ""
203    rename $this-int ""
204    rename $this-sphere ""
205    rename $this-map ""
206    rename $this-xyzconv ""
207
208    image delete $_download
209}
210
211# ----------------------------------------------------------------------
212# USAGE: add <dataobj> ?<settings>?
213#
214# Clients use this to add a data object to the plot.  The optional
215# <settings> are used to configure the plot.  Allowed settings are
216# -color, -brightness, -width, -linestyle, and -raise. Only
217# -brightness and -raise do anything.
218# ----------------------------------------------------------------------
219itcl::body Rappture::MoleculeViewer::add {dataobj {settings ""}} {
220    array set params {
221        -color auto
222        -brightness 0
223        -width 1
224        -raise 0
225        -linestyle solid
226        -description ""
227    }
228    foreach {opt val} $settings {
229        if {![info exists params($opt)]} {
230            error "bad settings \"$opt\": should be [join [lsort [array names params]] {, }]"
231        }
232        set params($opt) $val
233    }
234 
235    set pos [lsearch -exact $dataobj $_dlist]
236
237    if {$pos < 0} {
238        if {![Rappture::library isvalid $dataobj]} {
239            error "bad value \"$dataobj\": should be Rappture::library object"
240        }
241   
242        set emblem [$dataobj get components.molecule.about.emblems]
243        if {$emblem == "" || ![string is boolean $emblem] || !$emblem} {
244            emblems off
245        } else {
246            emblems on
247        }
248
249        lappend _dlist $dataobj
250        set _dobj2raise($dataobj) $params(-raise)
251
252        $_dispatcher event -idle !redraw
253    }
254}
255
256# ----------------------------------------------------------------------
257# USAGE: get
258#
259# Clients use this to query the list of objects being plotted, in
260# order from bottom to top of this result.
261# ----------------------------------------------------------------------
262itcl::body Rappture::MoleculeViewer::get {} {
263    # put the dataobj list in order according to -raise options
264    set dlist $_dlist
265    foreach obj $dlist {
266        if {[info exists _dobj2raise($obj)] && $_dobj2raise($obj)} {
267            set i [lsearch -exact $dlist $obj]
268            if {$i >= 0} {
269                set dlist [lreplace $dlist $i $i]
270                lappend dlist $obj
271            }
272        }
273    }
274    return $dlist
275}
276
277# ----------------------------------------------------------------------
278# USAGE: delete ?<dataobj> <dataobj> ...?
279#
280# Clients use this to delete a dataobj from the plot. If no dataobjs
281# are specified, then all dataobjs are deleted.
282# ----------------------------------------------------------------------
283itcl::body Rappture::MoleculeViewer::delete {args} {
284    if {[llength $args] == 0} {
285        set args $_dlist
286    }
287
288    # delete all specified dataobjs
289    set changed 0
290    foreach dataobj $args {
291        set pos [lsearch -exact $_dlist $dataobj]
292        if {$pos >= 0} {
293            set _dlist [lreplace $_dlist $pos $pos]
294            catch {unset _dobj2raise($dataobj)}
295            set changed 1
296        }
297    }
298
299    # if anything changed, then rebuild the plot
300    if {$changed} {
301        $_dispatcher event -idle !redraw
302    }
303}
304
305# ----------------------------------------------------------------------
306# USAGE: download coming
307# USAGE: download controls <downloadCommand>
308# USAGE: download now
309#
310# Clients use this method to create a downloadable representation
311# of the plot.  Returns a list of the form {ext string}, where
312# "ext" is the file extension (indicating the type of data) and
313# "string" is the data itself.
314# ----------------------------------------------------------------------
315itcl::body Rappture::MoleculeViewer::download {option args} {
316    switch $option {
317        coming {
318            if {[catch {blt::winop snap $itk_component(area) $_download}]} {
319                $_download configure -width 1 -height 1
320                $_download put #000000
321            }
322        }
323        controls {
324            # no controls for this download yet
325            return ""
326        }
327        now {
328            #
329            # Hack alert!  Need data in binary format,
330            # so we'll save to a file and read it back.
331            #
332            set tmpfile /tmp/image[pid].jpg
333            $_download write $tmpfile -format jpeg
334            set fid [open $tmpfile r]
335            fconfigure $fid -encoding binary -translation binary
336            set bytes [read $fid]
337            close $fid
338            file delete -force $tmpfile
339
340            return [list .jpg $bytes]
341        }
342        default {
343            error "bad option \"$option\": should be coming, controls, now"
344        }
345    }
346}
347
348# ----------------------------------------------------------------------
349# USAGE: _clear
350#
351# Used internally to clear the scene whenever it is about to change.
352# ----------------------------------------------------------------------
353itcl::body Rappture::MoleculeViewer::_clear {} {
354    foreach a $_actors {
355        $this-ren RemoveActor $a
356        rename $a ""
357    }
358    set _actors ""
359    catch {unset _label2atom}
360
361    foreach lim {xmin xmax ymin ymax zmin zmax} {
362        set _limits($lim) ""
363    }
364
365    $this-ren ResetCamera
366    $_dispatcher event -now !render
367}
368
369# ----------------------------------------------------------------------
370# USAGE: _redraw
371#
372# Used internally to rebuild the scene whenever options within this
373# widget change.  Destroys all actors and rebuilds them from scratch.
374# ----------------------------------------------------------------------
375itcl::body Rappture::MoleculeViewer::_redraw {} {
376    blt::busy hold $itk_component(hull); update
377
378    _clear
379
380    set dev [lindex [get] end]
381    if {"" != $dev} {
382        set lib [Rappture::library standard]
383
384        set counter 0
385        foreach atom [$dev children -type atom components.molecule] {
386            set symbol [$dev get components.molecule.$atom.symbol]
387            set xyz [$dev get components.molecule.$atom.xyz]
388            regsub {,} $xyz {} xyz
389
390            # update overall limits for molecules along all axes
391            foreach axis {x y z} val $xyz {
392                if {"" == $_limits(${axis}min)} {
393                    set _limits(${axis}min) $val
394                    set _limits(${axis}max) $val
395                } else {
396                    if {$val < $_limits(${axis}min)} {
397                        set _limits(${axis}min) $val
398                    }
399                    if {$val > $_limits(${axis}max)} {
400                        set _limits(${axis}max) $val
401                    }
402                }
403            }
404
405            # create an actor for each atom
406            set aname $this-actor[incr counter]
407            vtkActor $aname
408            $aname SetMapper $this-map
409            eval $aname SetPosition $xyz
410            $this-ren AddActor $aname
411
412            set sfac 0.7
413            set scale [$lib get elements.($symbol).scale]
414            if {$scale != ""} {
415                $aname SetScale [expr {$sfac*$scale}]
416            }
417            set color [$lib get elements.($symbol).color]
418            if {$color != ""} {
419                eval [$aname GetProperty] SetColor [_color2rgb $color]
420            }
421
422            lappend _actors $aname
423
424            # create a label for each atom
425            set lname $this-label$counter
426            vtkTextActor $lname
427            $lname SetInput "$counter $symbol"
428            $lname ScaledTextOff
429
430            set tprop [$lname GetTextProperty]
431            $tprop SetJustificationToCentered
432            $tprop SetVerticalJustificationToCentered
433            $tprop ShadowOn
434            $tprop SetColor 1 1 1
435
436            set _label2atom($lname) $aname
437            lappend _actors $lname
438        }
439        if {[$itk_component(labels) cget -relief] == "sunken"} {
440            emblems on
441        }
442        _zoom reset
443    }
444    $this-ren ResetCamera
445    $_dispatcher event -idle !render
446
447    blt::busy release $itk_component(hull)
448}
449
450# ----------------------------------------------------------------------
451# USAGE: _zoom in
452# USAGE: _zoom out
453# USAGE: _zoom reset
454#
455# Called automatically when the user clicks on one of the zoom
456# controls for this widget.  Changes the zoom for the current view.
457# ----------------------------------------------------------------------
458itcl::body Rappture::MoleculeViewer::_zoom {option} {
459    switch -- $option {
460        in {
461            [$this-ren GetActiveCamera] Zoom 1.25
462        }
463        out {
464            [$this-ren GetActiveCamera] Zoom 0.8
465        }
466        reset {
467            $this-ren ResetCamera
468            _3dView 45 45 0
469        }
470    }
471    $_dispatcher event -later !fixsize
472    $_dispatcher event -idle !render
473}
474
475# ----------------------------------------------------------------------
476# USAGE: _move click <x> <y>
477# USAGE: _move drag <x> <y>
478# USAGE: _move release <x> <y>
479#
480# Called automatically when the user clicks/drags/releases in the
481# plot area.  Moves the plot according to the user's actions.
482# ----------------------------------------------------------------------
483itcl::body Rappture::MoleculeViewer::_move {option x y} {
484    switch -- $option {
485        click {
486            blt::busy configure $itk_component(area) -cursor fleur
487            set _click(x) $x
488            set _click(y) $y
489            set _click(theta) $_view(theta)
490            set _click(phi) $_view(phi)
491            set _click(psi) $_view(psi)
492        }
493        drag {
494            if {[array size _click] == 0} {
495                _move click $x $y
496            } else {
497                set w [winfo width $itk_component(renderer)]
498                set h [winfo height $itk_component(renderer)]
499                if {$w <= 0 || $h <= 0} {
500                    return
501                }
502
503                if {[catch {
504                    # this fails sometimes for no apparent reason
505                    set dx [expr {double($x-$_click(x))/$w}]
506                    set dy [expr {double($y-$_click(y))/$h}]
507                }]} {
508                    return
509                }
510
511                #
512                # Rotate the camera in 3D
513                #
514                if {$_view(psi) > 90 || $_view(psi) < -90} {
515                    # when psi is flipped around, theta moves backwards
516                    set dy [expr {-$dy}]
517                }
518                set theta [expr {$_view(theta) - $dy*180}]
519                while {$theta < 0} { set theta [expr {$theta+180}] }
520                while {$theta > 180} { set theta [expr {$theta-180}] }
521                #if {$theta < 2} { set theta 2 }
522                #if {$theta > 178} { set theta 178 }
523
524                if {$theta > 45 && $theta < 135} {
525                    set phi [expr {$_view(phi) - $dx*360}]
526                    while {$phi < 0} { set phi [expr {$phi+360}] }
527                    while {$phi > 360} { set phi [expr {$phi-360}] }
528                    set psi $_view(psi)
529                } else {
530                    set phi $_view(phi)
531                    set psi [expr {$_view(psi) - $dx*360}]
532                    while {$psi < -180} { set psi [expr {$psi+360}] }
533                    while {$psi > 180} { set psi [expr {$psi-360}] }
534                }
535
536                _3dView $theta $phi $psi
537                emblems fixPosition
538                $_dispatcher event -idle !render
539
540                set _click(x) $x
541                set _click(y) $y
542            }
543        }
544        release {
545            _move drag $x $y
546            blt::busy configure $itk_component(area) -cursor left_ptr
547            catch {unset _click}
548        }
549        default {
550            error "bad option \"$option\": should be click, drag, release"
551        }
552    }
553}
554
555# ----------------------------------------------------------------------
556# USAGE: _3dView <theta> <phi> <psi>
557#
558# Used internally to change the position of the camera for 3D data
559# sets.  Sets the camera according to the angles <theta> (angle from
560# the z-axis) and <phi> (angle from the x-axis in the x-y plane).
561# Both angles are in degrees.
562# ----------------------------------------------------------------------
563itcl::body Rappture::MoleculeViewer::_3dView {theta phi psi} {
564    set deg2rad 0.0174532927778
565    set xp [expr {sin($theta*$deg2rad)*cos($phi*$deg2rad)}]
566    set yp [expr {sin($theta*$deg2rad)*sin($phi*$deg2rad)}]
567    set zp [expr {cos($theta*$deg2rad)}]
568
569    set blank 0
570    foreach lim {xmin xmax ymin ymax zmin zmax} {
571        if {"" == $_limits($lim)} {
572            set blank 1
573            break
574        }
575    }
576    if {$blank} {
577        set xm 0
578        set ym 0
579        set zm 0
580    } else {
581        set xm [expr {0.5*($_limits(xmax)+$_limits(xmin))}]
582        set ym [expr {0.5*($_limits(ymax)+$_limits(ymin))}]
583        set zm [expr {0.5*($_limits(zmax)+$_limits(zmin))}]
584    }
585
586    set cam [$this-ren GetActiveCamera]
587    set zoom [$cam GetViewAngle]
588    $cam SetViewAngle 30
589
590    $cam SetFocalPoint $xm $ym $zm
591    $cam SetPosition [expr {$xm-$xp}] [expr {$ym-$yp}] [expr {$zm+$zp}]
592    $cam ComputeViewPlaneNormal
593    $cam SetViewUp 0 0 1  ;# z-dir is up
594    $cam OrthogonalizeViewUp
595    $cam Azimuth $psi
596    $this-ren ResetCamera
597    $cam SetViewAngle $zoom
598
599    # fix up the labels so they sit over the new atom positions
600    emblems fixPosition
601
602    set _view(theta) $theta
603    set _view(phi) $phi
604    set _view(psi) $psi
605}
606
607# ----------------------------------------------------------------------
608# USAGE: emblems on
609# USAGE: emblems off
610# USAGE: emblems toggle
611# USAGE: emblems fixPosition
612#
613# Used internally to turn labels associated with atoms on/off, and to
614# update the positions of the labels so they sit on top of each atom.
615# ----------------------------------------------------------------------
616itcl::body Rappture::MoleculeViewer::emblems {option} {
617    switch -- $option {
618        on {
619            set state 1
620        }
621        off {
622            set state 0
623        }
624        toggle {
625            if {[$itk_component(labels) cget -relief] == "sunken"} {
626                set state 0
627            } else {
628                set state 1
629            }
630        }
631        fixPosition {
632            foreach lname [array names _label2atom] {
633                set aname $_label2atom($lname)
634                set xyz [$aname GetPosition]
635                eval $this-xyzconv SetValue $xyz
636                set xy [$this-xyzconv GetComputedViewportValue $this-ren]
637                eval $lname SetDisplayPosition $xy
638            }
639            return
640        }
641        default {
642            error "bad option \"$option\": should be on, off, toggle, fixPosition"
643        }
644    }
645
646    if {$state} {
647        $itk_component(labels) configure -relief sunken
648        foreach lname [array names _label2atom] {
649            catch {$this-ren AddActor2D $lname}
650        }
651        emblems fixPosition
652    } else {
653        $itk_component(labels) configure -relief raised
654        foreach lname [array names _label2atom] {
655            catch {$this-ren RemoveActor $lname}
656        }
657    }
658    $_dispatcher event -idle !render
659}
660
661# ----------------------------------------------------------------------
662# USAGE: _color2rgb color
663#
664# Used internally to convert a Tk color name into the r,g,b values
665# used in Vtk (scaled 0-1).
666# ----------------------------------------------------------------------
667itcl::body Rappture::MoleculeViewer::_color2rgb {color} {
668    foreach {r g b} [winfo rgb $itk_component(hull) $color] {}
669    set r [expr {$r/65535.0}]
670    set g [expr {$g/65535.0}]
671    set b [expr {$b/65535.0}]
672    return [list $r $g $b]
673}
674
675# ----------------------------------------------------------------------
676# OPTION: -backdrop
677# ----------------------------------------------------------------------
678itcl::configbody Rappture::MoleculeViewer::backdrop {
679    eval $this-ren SetBackground [_color2rgb $itk_option(-backdrop)]
680    $_dispatcher event -idle !render
681}
682
683# ----------------------------------------------------------------------
684# OPTION: -device
685# ----------------------------------------------------------------------
686itcl::configbody Rappture::MoleculeViewer::device {
687    if {"" != $itk_option(-device)
688          && ![Rappture::library isvalid $itk_option(-device)]} {
689        error "bad value \"$itk_option(-device)\": should be Rappture::library object"
690    }
691    delete
692
693    if {"" != $itk_option(-device)} {
694        add $itk_option(-device)
695        set state [$itk_option(-device) get components.molecule.about.emblems]
696        if {$state == "" || ![string is boolean $state] || !$state} {
697            emblems off
698        } else {
699            emblems on
700        }
701    }
702    $_dispatcher event -idle !redraw
703}
Note: See TracBrowser for help on using the repository browser.