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

Last change on this file since 74 was 74, checked in by mmc, 19 years ago

Added structure examples to the zoo.

Fixed the molecule viewer to pop up a little smaller.

Fixed the controls panel to pop down to a small size
when it is empty.

File size: 18.9 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
10#  Purdue Research Foundation, West Lafayette, IN
11# ======================================================================
12package require Itk
13package require vtk
14package require vtkinteraction
15package require BLT
16
17option add *MoleculeViewer.width 3i widgetDefault
18option add *MoleculeViewer.height 3i widgetDefault
19option add *MoleculeViewer.backdrop black widgetDefault
20
21blt::bitmap define MoleculeViewer-reset {
22#define reset_width 12
23#define reset_height 12
24static unsigned char reset_bits[] = {
25   0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x04, 0x02, 0x04, 0x02, 0x04, 0x02,
26   0x04, 0x02, 0x04, 0x02, 0x04, 0x02, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00};
27}
28
29blt::bitmap define MoleculeViewer-zoomin {
30#define zoomin_width 12
31#define zoomin_height 12
32static unsigned char zoomin_bits[] = {
33   0x7c, 0x00, 0x82, 0x00, 0x11, 0x01, 0x11, 0x01, 0x7d, 0x01, 0x11, 0x01,
34   0x11, 0x01, 0x82, 0x03, 0xfc, 0x07, 0x80, 0x0f, 0x00, 0x0f, 0x00, 0x06};
35}
36
37blt::bitmap define MoleculeViewer-zoomout {
38#define zoomout_width 12
39#define zoomout_height 12
40static unsigned char zoomout_bits[] = {
41   0x7c, 0x00, 0x82, 0x00, 0x01, 0x01, 0x01, 0x01, 0x7d, 0x01, 0x01, 0x01,
42   0x01, 0x01, 0x82, 0x03, 0xfc, 0x07, 0x80, 0x0f, 0x00, 0x0f, 0x00, 0x06};
43}
44
45blt::bitmap define MoleculeViewer-atoms {
46#define atoms_width 12
47#define atoms_height 12
48static unsigned char atoms_bits[] = {
49   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x02, 0x4c, 0x02, 0xc8, 0x03,
50   0x48, 0x02, 0x48, 0x02, 0x5c, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
51}
52
53itcl::class Rappture::MoleculeViewer {
54    inherit itk::Widget
55
56    itk_option define -backdrop backdrop Backdrop "black"
57    itk_option define -device device Device ""
58
59    constructor {tool args} { # defined below }
60    destructor { # defined below }
61
62    public method emblems {option}
63
64    protected method _clear {}
65    protected method _redraw {}
66    protected method _zoom {option}
67    protected method _move {option x y}
68    protected method _3dView {theta phi psi}
69    protected method _color2rgb {color}
70
71    private variable _dispatcher "" ;# dispatcher for !events
72
73    private variable _tool ""    ;# tool containing this viewer
74    private variable _actors ""  ;# list of actors in renderer
75    private variable _label2atom ;# maps 2D text actor => underlying atom
76    private variable _view       ;# view params for 3D view
77    private variable _limits     ;# limits of x/y/z axes
78    private variable _click      ;# info used for _move operations
79}
80                                                                               
81itk::usual MoleculeViewer {
82}
83
84# ----------------------------------------------------------------------
85# CONSTRUCTOR
86# ----------------------------------------------------------------------
87itcl::body Rappture::MoleculeViewer::constructor {tool args} {
88    set _tool $tool
89
90    Rappture::dispatcher _dispatcher
91    $_dispatcher register !redraw
92    $_dispatcher dispatch $this !redraw "[itcl::code $this _redraw]; list"
93    $_dispatcher register !render
94    $_dispatcher dispatch $this !render "$this-renWin Render; list"
95    $_dispatcher register !fixsize
96    $_dispatcher dispatch $this !fixsize \
97        "[itcl::code $this emblems fixPosition]; list"
98
99    itk_option add hull.width hull.height
100    pack propagate $itk_component(hull) no
101
102    vtkRenderWindow $this-renWin
103    vtkRenderer $this-ren
104    $this-renWin AddRenderer $this-ren
105
106    vtkRenderWindowInteractor $this-int
107    $this-int SetRenderWindow $this-renWin
108
109    vtkSphereSource $this-sphere
110    $this-sphere SetRadius 1.0
111    $this-sphere SetThetaResolution 18
112    $this-sphere SetPhiResolution 18
113
114    vtkPolyDataMapper $this-map
115    $this-map SetInput [$this-sphere GetOutput]
116
117    vtkCoordinate $this-xyzconv
118    $this-xyzconv SetCoordinateSystemToWorld
119
120    set _view(theta) 0
121    set _view(phi) 0
122    set _view(psi) 0
123
124    itk_component add controls {
125        frame $itk_interior.cntls
126    } {
127        usual
128        rename -background -controlbackground controlBackground Background
129    }
130    pack $itk_component(controls) -side right -fill y
131
132    itk_component add reset {
133        button $itk_component(controls).reset \
134            -borderwidth 1 -padx 1 -pady 1 \
135            -bitmap MoleculeViewer-reset \
136            -command [itcl::code $this _zoom reset]
137    } {
138        usual
139        ignore -borderwidth
140        rename -highlightbackground -controlbackground controlBackground Background
141    }
142    pack $itk_component(reset) -padx 4 -pady 4
143    Rappture::Tooltip::for $itk_component(reset) "Reset the view to the default zoom level"
144
145    itk_component add zoomin {
146        button $itk_component(controls).zin \
147            -borderwidth 1 -padx 1 -pady 1 \
148            -bitmap MoleculeViewer-zoomin \
149            -command [itcl::code $this _zoom in]
150    } {
151        usual
152        ignore -borderwidth
153        rename -highlightbackground -controlbackground controlBackground Background
154    }
155    pack $itk_component(zoomin) -padx 4 -pady 4
156    Rappture::Tooltip::for $itk_component(zoomin) "Zoom in"
157
158    itk_component add zoomout {
159        button $itk_component(controls).zout \
160            -borderwidth 1 -padx 1 -pady 1 \
161            -bitmap MoleculeViewer-zoomout \
162            -command [itcl::code $this _zoom out]
163    } {
164        usual
165        ignore -borderwidth
166        rename -highlightbackground -controlbackground controlBackground Background
167    }
168    pack $itk_component(zoomout) -padx 4 -pady 4
169    Rappture::Tooltip::for $itk_component(zoomout) "Zoom out"
170
171    itk_component add labels {
172        label $itk_component(controls).labels \
173            -borderwidth 1 -padx 1 -pady 1 \
174            -bitmap MoleculeViewer-atoms
175    } {
176        usual
177        ignore -borderwidth
178        rename -highlightbackground -controlbackground controlBackground Background
179    }
180    pack $itk_component(labels) -padx 4 -pady 8 -ipadx 1 -ipady 1
181    Rappture::Tooltip::for $itk_component(labels) "Show/hide the labels on atoms"
182    bind $itk_component(labels) <ButtonPress> \
183        [itcl::code $this emblems toggle]
184
185    #
186    # RENDERING AREA
187    #
188    itk_component add area {
189        frame $itk_interior.area
190    }
191    pack $itk_component(area) -expand yes -fill both
192    bind $itk_component(area) <Configure> \
193        [list $_dispatcher event -idle !fixsize]
194
195    itk_component add renderer {
196        vtkTkRenderWidget $itk_component(area).ren -rw $this-renWin
197    } {
198    }
199    pack $itk_component(renderer) -expand yes -fill both
200
201    eval itk_initialize $args
202
203    # prevent interactions -- use our own
204    blt::busy hold $itk_component(area) -cursor left_ptr
205    bind $itk_component(area)_Busy <ButtonPress> \
206        [itcl::code $this _move click %x %y]
207    bind $itk_component(area)_Busy <B1-Motion> \
208        [itcl::code $this _move drag %x %y]
209    bind $itk_component(area)_Busy <ButtonRelease> \
210        [itcl::code $this _move release %x %y]
211
212    emblems on
213}
214
215# ----------------------------------------------------------------------
216# DESTRUCTOR
217# ----------------------------------------------------------------------
218itcl::body Rappture::MoleculeViewer::destructor {} {
219    rename $this-renWin ""
220    rename $this-ren ""
221    rename $this-int ""
222    rename $this-sphere ""
223    rename $this-map ""
224    rename $this-xyzconv ""
225}
226
227# ----------------------------------------------------------------------
228# USAGE: _clear
229#
230# Used internally to clear the scene whenever it is about to change.
231# ----------------------------------------------------------------------
232itcl::body Rappture::MoleculeViewer::_clear {} {
233    foreach a $_actors {
234        $this-ren RemoveActor $a
235        rename $a ""
236    }
237    set _actors ""
238    catch {unset _label2atom}
239
240    foreach lim {xmin xmax ymin ymax zmin zmax} {
241        set _limits($lim) ""
242    }
243
244    $this-ren ResetCamera
245    $_dispatcher event -now !render
246}
247
248# ----------------------------------------------------------------------
249# USAGE: _redraw
250#
251# Used internally to rebuild the scene whenever options within this
252# widget change.  Destroys all actors and rebuilds them from scratch.
253# ----------------------------------------------------------------------
254itcl::body Rappture::MoleculeViewer::_redraw {} {
255    blt::busy hold $itk_component(hull); update
256
257    _clear
258
259    if {$itk_option(-device) != ""} {
260        set dev $itk_option(-device)
261        set lib [Rappture::library standard]
262
263        set counter 0
264        foreach atom [$dev children -type atom components.molecule] {
265            set symbol [$dev get components.molecule.$atom.symbol]
266            set xyz [$dev get components.molecule.$atom.xyz]
267            regsub {,} $xyz {} xyz
268
269            # update overall limits for molecules along all axes
270            foreach axis {x y z} val $xyz {
271                if {"" == $_limits(${axis}min)} {
272                    set _limits(${axis}min) $val
273                    set _limits(${axis}max) $val
274                } else {
275                    if {$val < $_limits(${axis}min)} {
276                        set _limits(${axis}min) $val
277                    }
278                    if {$val > $_limits(${axis}max)} {
279                        set _limits(${axis}max) $val
280                    }
281                }
282            }
283
284            # create an actor for each atom
285            set aname $this-actor[incr counter]
286            vtkActor $aname
287            $aname SetMapper $this-map
288            eval $aname SetPosition $xyz
289            $this-ren AddActor $aname
290
291            set sfac 0.7
292            set scale [$lib get elements.($symbol).scale]
293            if {$scale != ""} {
294                $aname SetScale [expr {$sfac*$scale}]
295            }
296            set color [$lib get elements.($symbol).color]
297            if {$color != ""} {
298                eval [$aname GetProperty] SetColor [_color2rgb $color]
299            }
300
301            lappend _actors $aname
302
303            # create a label for each atom
304            set lname $this-label$counter
305            vtkTextActor $lname
306            $lname SetInput "$counter $symbol"
307            $lname ScaledTextOff
308
309            set tprop [$lname GetTextProperty]
310            $tprop SetJustificationToCentered
311            $tprop SetVerticalJustificationToCentered
312            $tprop ShadowOn
313            $tprop SetColor 1 1 1
314
315            set _label2atom($lname) $aname
316            lappend _actors $lname
317        }
318        if {[$itk_component(labels) cget -relief] == "sunken"} {
319            emblems on
320        }
321        _zoom reset
322    }
323    $this-ren ResetCamera
324    $_dispatcher event -idle !render
325
326    blt::busy release $itk_component(hull)
327}
328
329# ----------------------------------------------------------------------
330# USAGE: _zoom in
331# USAGE: _zoom out
332# USAGE: _zoom reset
333#
334# Called automatically when the user clicks on one of the zoom
335# controls for this widget.  Changes the zoom for the current view.
336# ----------------------------------------------------------------------
337itcl::body Rappture::MoleculeViewer::_zoom {option} {
338    switch -- $option {
339        in {
340            [$this-ren GetActiveCamera] Zoom 1.25
341        }
342        out {
343            [$this-ren GetActiveCamera] Zoom 0.8
344        }
345        reset {
346            $this-ren ResetCamera
347            _3dView 45 45 0
348        }
349    }
350    $_dispatcher event -later !fixsize
351    $_dispatcher event -idle !render
352}
353
354# ----------------------------------------------------------------------
355# USAGE: _move click <x> <y>
356# USAGE: _move drag <x> <y>
357# USAGE: _move release <x> <y>
358#
359# Called automatically when the user clicks/drags/releases in the
360# plot area.  Moves the plot according to the user's actions.
361# ----------------------------------------------------------------------
362itcl::body Rappture::MoleculeViewer::_move {option x y} {
363    switch -- $option {
364        click {
365            blt::busy configure $itk_component(area) -cursor fleur
366            set _click(x) $x
367            set _click(y) $y
368            set _click(theta) $_view(theta)
369            set _click(phi) $_view(phi)
370            set _click(psi) $_view(psi)
371        }
372        drag {
373            if {[array size _click] == 0} {
374                _move click $x $y
375            } else {
376                set w [winfo width $itk_component(renderer)]
377                set h [winfo height $itk_component(renderer)]
378                if {$w <= 0 || $h <= 0} {
379                    return
380                }
381
382                if {[catch {
383                    # this fails sometimes for no apparent reason
384                    set dx [expr {double($x-$_click(x))/$w}]
385                    set dy [expr {double($y-$_click(y))/$h}]
386                }]} {
387                    return
388                }
389
390                #
391                # Rotate the camera in 3D
392                #
393                if {$_view(psi) > 90 || $_view(psi) < -90} {
394                    # when psi is flipped around, theta moves backwards
395                    set dy [expr {-$dy}]
396                }
397                set theta [expr {$_view(theta) - $dy*180}]
398                while {$theta < 0} { set theta [expr {$theta+180}] }
399                while {$theta > 180} { set theta [expr {$theta-180}] }
400                #if {$theta < 2} { set theta 2 }
401                #if {$theta > 178} { set theta 178 }
402
403                if {$theta > 45 && $theta < 135} {
404                    set phi [expr {$_view(phi) - $dx*360}]
405                    while {$phi < 0} { set phi [expr {$phi+360}] }
406                    while {$phi > 360} { set phi [expr {$phi-360}] }
407                    set psi $_view(psi)
408                } else {
409                    set phi $_view(phi)
410                    set psi [expr {$_view(psi) - $dx*360}]
411                    while {$psi < -180} { set psi [expr {$psi+360}] }
412                    while {$psi > 180} { set psi [expr {$psi-360}] }
413                }
414
415                _3dView $theta $phi $psi
416                emblems fixPosition
417                $_dispatcher event -idle !render
418
419                set _click(x) $x
420                set _click(y) $y
421            }
422        }
423        release {
424            _move drag $x $y
425            blt::busy configure $itk_component(area) -cursor left_ptr
426            catch {unset _click}
427        }
428        default {
429            error "bad option \"$option\": should be click, drag, release"
430        }
431    }
432}
433
434# ----------------------------------------------------------------------
435# USAGE: _3dView <theta> <phi> <psi>
436#
437# Used internally to change the position of the camera for 3D data
438# sets.  Sets the camera according to the angles <theta> (angle from
439# the z-axis) and <phi> (angle from the x-axis in the x-y plane).
440# Both angles are in degrees.
441# ----------------------------------------------------------------------
442itcl::body Rappture::MoleculeViewer::_3dView {theta phi psi} {
443    set deg2rad 0.0174532927778
444    set xp [expr {sin($theta*$deg2rad)*cos($phi*$deg2rad)}]
445    set yp [expr {sin($theta*$deg2rad)*sin($phi*$deg2rad)}]
446    set zp [expr {cos($theta*$deg2rad)}]
447
448    set xm [expr {0.5*($_limits(xmax)+$_limits(xmin))}]
449    set ym [expr {0.5*($_limits(ymax)+$_limits(ymin))}]
450    set zm [expr {0.5*($_limits(zmax)+$_limits(zmin))}]
451
452    set cam [$this-ren GetActiveCamera]
453    set zoom [$cam GetViewAngle]
454    $cam SetViewAngle 30
455
456    $cam SetFocalPoint $xm $ym $zm
457    $cam SetPosition [expr {$xm-$xp}] [expr {$ym-$yp}] [expr {$zm+$zp}]
458    $cam ComputeViewPlaneNormal
459    $cam SetViewUp 0 0 1  ;# z-dir is up
460    $cam OrthogonalizeViewUp
461    $cam Azimuth $psi
462    $this-ren ResetCamera
463    $cam SetViewAngle $zoom
464
465    # fix up the labels so they sit over the new atom positions
466    emblems fixPosition
467
468    set _view(theta) $theta
469    set _view(phi) $phi
470    set _view(psi) $psi
471}
472
473# ----------------------------------------------------------------------
474# USAGE: emblems on
475# USAGE: emblems off
476# USAGE: emblems toggle
477# USAGE: emblems fixPosition
478#
479# Used internally to turn labels associated with atoms on/off, and to
480# update the positions of the labels so they sit on top of each atom.
481# ----------------------------------------------------------------------
482itcl::body Rappture::MoleculeViewer::emblems {option} {
483    switch -- $option {
484        on {
485            set state 1
486        }
487        off {
488            set state 0
489        }
490        toggle {
491            if {[$itk_component(labels) cget -relief] == "sunken"} {
492                set state 0
493            } else {
494                set state 1
495            }
496        }
497        fixPosition {
498            foreach lname [array names _label2atom] {
499                set aname $_label2atom($lname)
500                set xyz [$aname GetPosition]
501                eval $this-xyzconv SetValue $xyz
502                set xy [$this-xyzconv GetComputedViewportValue $this-ren]
503                eval $lname SetDisplayPosition $xy
504            }
505            return
506        }
507        default {
508            error "bad option \"$option\": should be on, off, toggle, fixPosition"
509        }
510    }
511
512    if {$state} {
513        $itk_component(labels) configure -relief sunken
514        foreach lname [array names _label2atom] {
515            catch {$this-ren AddActor2D $lname}
516        }
517        emblems fixPosition
518    } else {
519        $itk_component(labels) configure -relief raised
520        foreach lname [array names _label2atom] {
521            catch {$this-ren RemoveActor $lname}
522        }
523    }
524    $_dispatcher event -idle !render
525}
526
527# ----------------------------------------------------------------------
528# USAGE: _color2rgb color
529#
530# Used internally to convert a Tk color name into the r,g,b values
531# used in Vtk (scaled 0-1).
532# ----------------------------------------------------------------------
533itcl::body Rappture::MoleculeViewer::_color2rgb {color} {
534    foreach {r g b} [winfo rgb $itk_component(hull) $color] {}
535    set r [expr {$r/65535.0}]
536    set g [expr {$g/65535.0}]
537    set b [expr {$b/65535.0}]
538    return [list $r $g $b]
539}
540
541# ----------------------------------------------------------------------
542# OPTION: -backdrop
543# ----------------------------------------------------------------------
544itcl::configbody Rappture::MoleculeViewer::backdrop {
545    eval $this-ren SetBackground [_color2rgb $itk_option(-backdrop)]
546    $_dispatcher event -idle !render
547}
548
549# ----------------------------------------------------------------------
550# OPTION: -device
551# ----------------------------------------------------------------------
552itcl::configbody Rappture::MoleculeViewer::device {
553    if {$itk_option(-device) != ""
554          && ![Rappture::library isvalid $itk_option(-device)]} {
555        error "bad value \"$itk_option(-device)\": should be Rappture::library object"
556    }
557    _clear
558
559    if {$itk_option(-device) != ""} {
560        set state [$itk_option(-device) get components.molecule.about.emblems]
561        if {$state == "" || ![string is boolean $state] || !$state} {
562            emblems off
563        } else {
564            emblems on
565        }
566    }
567    $_dispatcher event -idle !redraw
568}
Note: See TracBrowser for help on using the repository browser.