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

Last change on this file since 52 was 52, checked in by mmc, 19 years ago
  • Added a back door so we can debug Rappture applications running in the nanoHUB environment.
  • Added format and label controls to the axes of an XY plot.
  • Added a <molecule><about><emblems> directive, a boolean control indicating whether or not the molecule viewer should show the atom labels by default. If missing or "off", the labels are turned off. To turn labels on by default, this must be set to a boolean true value.
  • Fixed the packing bug that was causing tabbed notebooks to be the wrong size (size of last page, rather than max overall size).
  • Added -state option to comboboxes, so they can be disabled.
  • Added a grab stack, so that when a balloon dialog has the grab and a combobox steals it away, the balloon gets it back.
File size: 17.6 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 5i widgetDefault
18option add *MoleculeViewer.height 5i 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 _render {}
66    protected method _zoom {option}
67    protected method _move {option x y}
68    protected method _3dView {theta phi}
69    protected method _color2rgb {color}
70
71    private variable _tool ""    ;# tool containing this viewer
72    private variable _actors ""  ;# list of actors in renderer
73    private variable _label2atom ;# maps 2D text actor => underlying atom
74    private variable _view       ;# view params for 3D view
75    private variable _limits     ;# limits of x/y/z axes
76    private variable _click      ;# info used for _move operations
77}
78                                                                               
79itk::usual MoleculeViewer {
80}
81
82# ----------------------------------------------------------------------
83# CONSTRUCTOR
84# ----------------------------------------------------------------------
85itcl::body Rappture::MoleculeViewer::constructor {tool args} {
86    set _tool $tool
87
88    itk_option add hull.width hull.height
89    pack propagate $itk_component(hull) no
90
91    vtkRenderWindow $this-renWin
92    vtkRenderer $this-ren
93    $this-renWin AddRenderer $this-ren
94
95    vtkRenderWindowInteractor $this-int
96    $this-int SetRenderWindow $this-renWin
97
98    vtkSphereSource $this-sphere
99    $this-sphere SetRadius 1.0
100    $this-sphere SetThetaResolution 18
101    $this-sphere SetPhiResolution 18
102
103    vtkPolyDataMapper $this-map
104    $this-map SetInput [$this-sphere GetOutput]
105
106    vtkCoordinate $this-xyzconv
107    $this-xyzconv SetCoordinateSystemToWorld
108
109    set _view(theta) 0
110    set _view(phi) 0
111
112    itk_component add controls {
113        frame $itk_interior.cntls
114    } {
115        usual
116        rename -background -controlbackground controlBackground Background
117    }
118    pack $itk_component(controls) -side right -fill y
119
120    itk_component add reset {
121        button $itk_component(controls).reset \
122            -borderwidth 1 -padx 1 -pady 1 \
123            -bitmap MoleculeViewer-reset \
124            -command [itcl::code $this _zoom reset]
125    } {
126        usual
127        ignore -borderwidth
128        rename -highlightbackground -controlbackground controlBackground Background
129    }
130    pack $itk_component(reset) -padx 4 -pady 4
131    Rappture::Tooltip::for $itk_component(reset) "Reset the view to the default zoom level"
132
133    itk_component add zoomin {
134        button $itk_component(controls).zin \
135            -borderwidth 1 -padx 1 -pady 1 \
136            -bitmap MoleculeViewer-zoomin \
137            -command [itcl::code $this _zoom in]
138    } {
139        usual
140        ignore -borderwidth
141        rename -highlightbackground -controlbackground controlBackground Background
142    }
143    pack $itk_component(zoomin) -padx 4 -pady 4
144    Rappture::Tooltip::for $itk_component(zoomin) "Zoom in"
145
146    itk_component add zoomout {
147        button $itk_component(controls).zout \
148            -borderwidth 1 -padx 1 -pady 1 \
149            -bitmap MoleculeViewer-zoomout \
150            -command [itcl::code $this _zoom out]
151    } {
152        usual
153        ignore -borderwidth
154        rename -highlightbackground -controlbackground controlBackground Background
155    }
156    pack $itk_component(zoomout) -padx 4 -pady 4
157    Rappture::Tooltip::for $itk_component(zoomout) "Zoom out"
158
159    itk_component add labels {
160        label $itk_component(controls).labels \
161            -borderwidth 1 -padx 1 -pady 1 \
162            -bitmap MoleculeViewer-atoms
163    } {
164        usual
165        ignore -borderwidth
166        rename -highlightbackground -controlbackground controlBackground Background
167    }
168    pack $itk_component(labels) -padx 4 -pady 8 -ipadx 1 -ipady 1
169    Rappture::Tooltip::for $itk_component(labels) "Show/hide the labels on atoms"
170    bind $itk_component(labels) <ButtonPress> \
171        [itcl::code $this emblems toggle]
172
173    #
174    # RENDERING AREA
175    #
176    itk_component add area {
177        frame $itk_interior.area
178    }
179    pack $itk_component(area) -expand yes -fill both
180    bind $itk_component(area) <Configure> \
181        [itcl::code $this emblems fixPosition]
182
183    itk_component add renderer {
184        vtkTkRenderWidget $itk_component(area).ren -rw $this-renWin
185    } {
186    }
187    pack $itk_component(renderer) -expand yes -fill both
188
189    eval itk_initialize $args
190
191    # prevent interactions -- use our own
192    blt::busy hold $itk_component(area) -cursor left_ptr
193    bind $itk_component(area)_Busy <ButtonPress> \
194        [itcl::code $this _move click %x %y]
195    bind $itk_component(area)_Busy <B1-Motion> \
196        [itcl::code $this _move drag %x %y]
197    bind $itk_component(area)_Busy <ButtonRelease> \
198        [itcl::code $this _move release %x %y]
199
200    emblems on
201}
202
203# ----------------------------------------------------------------------
204# DESTRUCTOR
205# ----------------------------------------------------------------------
206itcl::body Rappture::MoleculeViewer::destructor {} {
207    after cancel [itcl::code $this _render]
208
209    rename $this-renWin ""
210    rename $this-ren ""
211    rename $this-int ""
212    rename $this-sphere ""
213    rename $this-map ""
214    rename $this-xyzconv ""
215}
216
217# ----------------------------------------------------------------------
218# USAGE: _clear
219#
220# Used internally to clear the scene whenever it is about to change.
221# ----------------------------------------------------------------------
222itcl::body Rappture::MoleculeViewer::_clear {} {
223    foreach a $_actors {
224        $this-ren RemoveActor $a
225        rename $a ""
226    }
227    set _actors ""
228    catch {unset _label2atom}
229
230    foreach lim {xmin xmax ymin ymax zmin zmax} {
231        set _limits($lim) ""
232    }
233
234    $this-ren ResetCamera
235    $this-renWin Render
236}
237
238# ----------------------------------------------------------------------
239# USAGE: _render
240#
241# Used internally to rebuild the scene whenever options within this
242# widget change.  Destroys all actors and rebuilds them from scratch.
243# ----------------------------------------------------------------------
244itcl::body Rappture::MoleculeViewer::_render {} {
245    _clear
246
247    if {$itk_option(-device) != ""} {
248        set dev $itk_option(-device)
249        set lib [Rappture::library standard]
250
251        set counter 0
252        foreach atom [$dev children -type atom components.molecule] {
253            set symbol [$dev get components.molecule.$atom.symbol]
254            set xyz [$dev get components.molecule.$atom.xyz]
255            regsub {,} $xyz {} xyz
256
257            # update overall limits for molecules along all axes
258            foreach axis {x y z} val $xyz {
259                if {"" == $_limits(${axis}min)} {
260                    set _limits(${axis}min) $val
261                    set _limits(${axis}max) $val
262                } else {
263                    if {$val < $_limits(${axis}min)} {
264                        set _limits(${axis}min) $val
265                    }
266                    if {$val > $_limits(${axis}max)} {
267                        set _limits(${axis}max) $val
268                    }
269                }
270            }
271
272            # create an actor for each atom
273            set aname $this-actor[incr counter]
274            vtkActor $aname
275            $aname SetMapper $this-map
276            eval $aname SetPosition $xyz
277            $this-ren AddActor $aname
278
279            set sfac 0.7
280            set scale [$lib get elements.($symbol).scale]
281            if {$scale != ""} {
282                $aname SetScale [expr {$sfac*$scale}]
283            }
284            set color [$lib get elements.($symbol).color]
285            if {$color != ""} {
286                eval [$aname GetProperty] SetColor [_color2rgb $color]
287            }
288
289            lappend _actors $aname
290
291            # create a label for each atom
292            set lname $this-label$counter
293            vtkTextActor $lname
294            $lname SetInput "$counter $symbol"
295            $lname ScaledTextOff
296
297            set tprop [$lname GetTextProperty]
298            $tprop SetJustificationToCentered
299            $tprop SetVerticalJustificationToCentered
300            $tprop ShadowOn
301            $tprop SetColor 1 1 1
302
303            set _label2atom($lname) $aname
304            lappend _actors $lname
305        }
306        if {[$itk_component(labels) cget -relief] == "sunken"} {
307            emblems on
308        }
309        after cancel [list catch [itcl::code $this _zoom reset]]
310        after 200 [list catch [itcl::code $this _zoom reset]]
311    }
312    $this-ren ResetCamera
313    $this-renWin Render
314}
315
316# ----------------------------------------------------------------------
317# USAGE: _zoom in
318# USAGE: _zoom out
319# USAGE: _zoom reset
320#
321# Called automatically when the user clicks on one of the zoom
322# controls for this widget.  Changes the zoom for the current view.
323# ----------------------------------------------------------------------
324itcl::body Rappture::MoleculeViewer::_zoom {option} {
325    switch -- $option {
326        in {
327            [$this-ren GetActiveCamera] Zoom 1.25
328            emblems fixPosition
329            $this-renWin Render
330        }
331        out {
332            [$this-ren GetActiveCamera] Zoom 0.8
333            emblems fixPosition
334            $this-renWin Render
335        }
336        reset {
337            [$this-ren GetActiveCamera] SetViewAngle 30
338            $this-ren ResetCamera
339            [$this-ren GetActiveCamera] Zoom 1.25
340            _3dView 45 45
341            $this-renWin Render
342
343            after cancel [list catch [itcl::code $this emblems fixPosition]]
344            after 2000 [list catch [itcl::code $this emblems fixPosition]]
345        }
346    }
347}
348
349# ----------------------------------------------------------------------
350# USAGE: _move click <x> <y>
351# USAGE: _move drag <x> <y>
352# USAGE: _move release <x> <y>
353#
354# Called automatically when the user clicks/drags/releases in the
355# plot area.  Moves the plot according to the user's actions.
356# ----------------------------------------------------------------------
357itcl::body Rappture::MoleculeViewer::_move {option x y} {
358    switch -- $option {
359        click {
360            blt::busy configure $itk_component(area) -cursor fleur
361            set _click(x) $x
362            set _click(y) $y
363            set _click(theta) $_view(theta)
364            set _click(phi) $_view(phi)
365        }
366        drag {
367            if {[array size _click] == 0} {
368                _move click $x $y
369            } else {
370                set w [winfo width $itk_component(renderer)]
371                set h [winfo height $itk_component(renderer)]
372                set dx [expr {double($x-$_click(x))/$w}]
373                set dy [expr {double($y-$_click(y))/$h}]
374
375                #
376                # Rotate the camera in 3D
377                #
378                set theta [expr {$_view(theta) - $dy*180}]
379                if {$theta < 2} { set theta 2 }
380                if {$theta > 178} { set theta 178 }
381                set phi [expr {$_view(phi) - $dx*360}]
382
383                _3dView $theta $phi
384                emblems fixPosition
385                $this-renWin Render
386
387                set _click(x) $x
388                set _click(y) $y
389            }
390        }
391        release {
392            _move drag $x $y
393            blt::busy configure $itk_component(area) -cursor left_ptr
394            catch {unset _click}
395        }
396        default {
397            error "bad option \"$option\": should be click, drag, release"
398        }
399    }
400}
401
402# ----------------------------------------------------------------------
403# USAGE: _3dView <theta> <phi>
404#
405# Used internally to change the position of the camera for 3D data
406# sets.  Sets the camera according to the angles <theta> (angle from
407# the z-axis) and <phi> (angle from the x-axis in the x-y plane).
408# Both angles are in degrees.
409# ----------------------------------------------------------------------
410itcl::body Rappture::MoleculeViewer::_3dView {theta phi} {
411    set deg2rad 0.0174532927778
412    set xn [expr {sin($theta*$deg2rad)*cos($phi*$deg2rad)}]
413    set yn [expr {sin($theta*$deg2rad)*sin($phi*$deg2rad)}]
414    set zn [expr {cos($theta*$deg2rad)}]
415
416    set xm [expr {0.5*($_limits(xmax)+$_limits(xmin))}]
417    set ym [expr {0.5*($_limits(ymax)+$_limits(ymin))}]
418    set zm [expr {0.5*($_limits(zmax)+$_limits(zmin))}]
419
420    set cam [$this-ren GetActiveCamera]
421    set zoom [$cam GetViewAngle]
422    $cam SetViewAngle 30
423
424    $cam SetFocalPoint $xm $ym $zm
425    $cam SetPosition [expr {$xm-$xn}] [expr {$ym-$yn}] [expr {$zm+$zn}]
426    $cam ComputeViewPlaneNormal
427    $cam SetViewUp 0 0 1  ;# z-dir is up
428    $cam OrthogonalizeViewUp
429    $this-ren ResetCamera
430    $cam SetViewAngle $zoom
431
432    # fix up the labels so they sit over the new atom positions
433    emblems fixPosition
434
435    set _view(theta) $theta
436    set _view(phi) $phi
437}
438
439# ----------------------------------------------------------------------
440# USAGE: emblems on
441# USAGE: emblems off
442# USAGE: emblems toggle
443# USAGE: emblems fixPosition
444#
445# Used internally to turn labels associated with atoms on/off, and to
446# update the positions of the labels so they sit on top of each atom.
447# ----------------------------------------------------------------------
448itcl::body Rappture::MoleculeViewer::emblems {option} {
449    switch -- $option {
450        on {
451            set state 1
452        }
453        off {
454            set state 0
455        }
456        toggle {
457            if {[$itk_component(labels) cget -relief] == "sunken"} {
458                set state 0
459            } else {
460                set state 1
461            }
462        }
463        fixPosition {
464            foreach lname [array names _label2atom] {
465                set aname $_label2atom($lname)
466                set xyz [$aname GetPosition]
467                eval $this-xyzconv SetValue $xyz
468                set xy [$this-xyzconv GetComputedViewportValue $this-ren]
469                eval $lname SetDisplayPosition $xy
470            }
471            return
472        }
473        default {
474            error "bad option \"$option\": should be on, off, toggle, fixPosition"
475        }
476    }
477
478    if {$state} {
479        $itk_component(labels) configure -relief sunken
480        foreach lname [array names _label2atom] {
481            catch {$this-ren AddActor2D $lname}
482        }
483        emblems fixPosition
484    } else {
485        $itk_component(labels) configure -relief raised
486        foreach lname [array names _label2atom] {
487            catch {$this-ren RemoveActor $lname}
488        }
489    }
490    $this-renWin Render
491}
492
493# ----------------------------------------------------------------------
494# USAGE: _color2rgb color
495#
496# Used internally to convert a Tk color name into the r,g,b values
497# used in Vtk (scaled 0-1).
498# ----------------------------------------------------------------------
499itcl::body Rappture::MoleculeViewer::_color2rgb {color} {
500    foreach {r g b} [winfo rgb $itk_component(hull) $color] {}
501    set r [expr {$r/65535.0}]
502    set g [expr {$g/65535.0}]
503    set b [expr {$b/65535.0}]
504    return [list $r $g $b]
505}
506
507# ----------------------------------------------------------------------
508# OPTION: -backdrop
509# ----------------------------------------------------------------------
510itcl::configbody Rappture::MoleculeViewer::backdrop {
511    eval $this-ren SetBackground [_color2rgb $itk_option(-backdrop)]
512    $this-renWin Render
513}
514
515# ----------------------------------------------------------------------
516# OPTION: -device
517# ----------------------------------------------------------------------
518itcl::configbody Rappture::MoleculeViewer::device {
519    if {$itk_option(-device) != ""
520          && ![Rappture::library isvalid $itk_option(-device)]} {
521        error "bad value \"$itk_option(-device)\": should be Rappture::library object"
522    }
523    _clear
524
525    if {$itk_option(-device) != ""} {
526        set state [$itk_option(-device) get components.molecule.about.emblems]
527        if {$state == "" || ![string is boolean $state] || !$state} {
528            emblems off
529        } else {
530            emblems on
531        }
532    }
533    after idle [itcl::code $this _render]
534}
Note: See TracBrowser for help on using the repository browser.