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

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