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

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

Updated all copyright notices.

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