source: trunk/packages/vizservers/vmd/vmdserver.tcl @ 4152

Last change on this file since 4152 was 4152, checked in by gah, 7 years ago

fixes to vmdserver script and renderservers entry for vmdshow

File size: 26.0 KB
Line 
1# ----------------------------------------------------------------------
2#  HUBZERO: server for VMD
3#
4#  This program runs VMD and acts as a server for client applications.
5# ----------------------------------------------------------------------
6#  Michael McLennan (mmclennan@purdue.edu)
7# ======================================================================
8#  Copyright (c) 2013 - HUBzero Foundation, LLC
9# ======================================================================
10
11# The VMD TCL interpreter is by default interactive.  Turn this off
12# so that unknown commands like "scene" don't get exec-ed.
13set ::tcl_interactive 0
14
15proc bgerror {mesg} {
16    puts stderr "SERVER ERROR: $mesg"
17}
18
19# parse command line args
20set Paradigm "socket"
21while {[llength $argv] > 0} {
22    set opt [lindex $argv 0]
23    set argv [lrange $argv 1 end]
24
25    switch -- $opt {
26        -socket { set Paradigm "socket" }
27        -stdio  { set Paradigm "stdio" }
28        default {
29            puts stderr "bad option \"$opt\": should be -socket or -stdio"
30        }
31    }
32}
33
34# use this to take snapshots to send to clients
35image create photo SnapShot
36
37# set the screen to a good size
38set DisplaySize(w) 300
39set DisplaySize(h) 300
40display resize $DisplaySize(w) $DisplaySize(h)
41set DisplaySize(changed) 0
42
43# initialize work queue and epoch counter (see server_send_image)
44set Epoch 0
45set Work(queue) ""
46set Sendqueue ""
47set Scenes(@CURRENT) ""
48
49set parser [interp create -safe]
50
51foreach cmd {
52  vmdinfo
53  vmdbench
54  color
55  axes
56  display
57  imd
58  vmdcollab
59  vmd_label
60  light
61  material
62  vmd_menu
63  stage
64  light
65  user
66  mol
67  molinfo
68  molecule
69  mouse
70  mobile
71  spaceball
72  plugin
73  render
74  tkrender
75  rotate
76  rotmat
77  vmd_scale
78  translate
79  sleep
80  tool
81  measure
82  rawtimestep
83  gettimestep
84  vmdcon
85  volmap
86  parallel
87} {
88    $parser alias $cmd $cmd
89}
90
91# ----------------------------------------------------------------------
92# USAGE: tellme "command template with %v" command arg arg...
93#
94# Executes the "command arg arg..." string in the server and substitutes
95# the result into the template string in place of each "%v" field.
96# Sends the result back to the client.
97# ----------------------------------------------------------------------
98proc cmd_tellme {fmt args} {
99    global parser client
100
101    # evaluate args as a command and subst the result in the fmt string
102    if {[catch {$parser eval $args} result] == 0} {
103        server_send_result $client "nv>[string map [list %v $result] $fmt]"
104    } else {
105        server_oops $client $result
106    }
107}
108$parser alias tellme cmd_tellme
109
110# ----------------------------------------------------------------------
111# USAGE: resize <w> <h>
112#
113# Resizes the visualization window to the given width <w> and height
114# <h>.  The next image sent should be this size.
115# ----------------------------------------------------------------------
116proc cmd_resize {w h} {
117    global DisplayProps
118
119    # store the desired size in case we downscale
120    set DisplayProps(framew) $w
121    set DisplayProps(frameh) $h
122
123    server_safe_resize $w $h
124}
125$parser alias resize cmd_resize
126
127# ----------------------------------------------------------------------
128# USAGE: setview ?-rotate <mtx>? ?-scale <mtx>? ?-center <mtx>? ?-global <mtx>?
129#
130# Sets the view matrix for one or more components of the view.  This
131# is a convenient way of getting a view for a particular frame just
132# right in one shot.
133# ----------------------------------------------------------------------
134proc cmd_setview {args} {
135    if {[llength $args] == 8} {
136        # setting all matrices? then start clean
137        display resetview
138    }
139    foreach {key val} $args {
140        switch -- $key {
141            -rotate {
142                molinfo top set rotate_matrix [list $val]
143            }
144            -scale {
145                molinfo top set scale_matrix [list $val]
146            }
147            -center {
148                molinfo top set center_matrix [list $val]
149            }
150            -global {
151                molinfo top set global_matrix [list $val]
152            }
153            default {
154                error "bad option \"$key\": should be -rotate, -scale, -center, or -global"
155            }
156        }
157    }
158}
159$parser alias setview cmd_setview
160
161# ----------------------------------------------------------------------
162# USAGE: drag start|end
163#
164# Resizes the visualization window to the given width <w> and height
165# <h>.  The next image sent should be this size.
166# ----------------------------------------------------------------------
167proc cmd_drag {action} {
168    global DisplayProps
169
170    switch -- $action {
171        start {
172            # simplify rendering so it goes faster during drag operations
173            set neww [expr {round($DisplayProps(framew)/2.0)}]
174            set newh [expr {round($DisplayProps(frameh)/2.0)}]
175            server_safe_resize $neww $newh
176            display rendermode Normal
177            display shadows off
178
179            foreach nmol [molinfo list] {
180                set max [molinfo $nmol get numreps]
181                for {set nrep 0} {$nrep < $max} {incr nrep} {
182                    mol modstyle $nrep $nmol "Lines"
183                }
184            }
185        }
186        end {
187            # put original rendering options back
188            server_safe_resize $DisplayProps(framew) $DisplayProps(frameh)
189            display rendermode $DisplayProps(rendermode)
190            display shadows $DisplayProps(shadows)
191
192            # restore rendering methods for all representations
193            foreach nmol [molinfo list] {
194                set max [molinfo $nmol get numreps]
195                for {set nrep 0} {$nrep < $max} {incr nrep} {
196                    mol modstyle $nrep $nmol $DisplayProps(rep-$nmol-$nrep)
197                }
198            }
199        }
200        default {
201            error "bad option \"$action\": should be start or end"
202        }
203    }
204}
205$parser alias drag cmd_drag
206
207# ----------------------------------------------------------------------
208# USAGE: smoothreps <value>
209#
210# Changes the smoothing factor for all representations of the current
211# molecule.
212# ----------------------------------------------------------------------
213proc cmd_smoothreps {val} {
214    if {$val < 0} {
215        error "bad smoothing value \"$val\": should be >= 0"
216    }
217    foreach nmol [molinfo list] {
218        set max [molinfo $nmol get numreps]
219        for {set nrep 0} {$nrep < $max} {incr nrep} {
220            mol smoothrep $nmol $nrep $val
221        }
222    }
223}
224$parser alias smoothreps cmd_smoothreps
225
226# ----------------------------------------------------------------------
227# USAGE: animate <option> <args>...
228# USAGE: rock off
229# USAGE: rock x|y|z by <step> ?<n>?
230#
231# The usual VMD "animate" and "rock" commands are problematic for this
232# server.  If we're going to rock or play the animation, the client
233# will do it.  Intercept any "animate" and "rock" commands in the scene
234# scripts and do nothing.
235# ----------------------------------------------------------------------
236proc cmd_animate {args} {
237    # do nothing
238}
239$parser alias animate cmd_animate
240
241proc cmd_rock {args} {
242    # do nothing
243}
244$parser alias rock cmd_rock
245
246# ----------------------------------------------------------------------
247# USAGE: load <file> <file>...
248#
249# Loads the molecule data from one or more files, which may be PDB,
250# DCD, PSF, etc.
251# ----------------------------------------------------------------------
252proc cmd_load {args} {
253    # clear all existing molecules
254    foreach nmol [molinfo list] {
255        mol delete $nmol
256    }
257
258    # load new files
259    set op "new"
260    foreach file $args {
261        mol $op $file waitfor all
262        set op "addfile"
263    }
264}
265$parser alias load cmd_load
266
267# ----------------------------------------------------------------------
268# USAGE: scene define <name> <script>
269# USAGE: scene show <name> ?-send <initialViewCmd>?
270# USAGE: scene clear
271# USAGE: scene forget ?<name> <name>...?
272#
273# Used to define and manipulate scenes of the trajectory information
274# loaded previously by the "load" command.  The "define" operation
275# defines the script that loads a scene called <name>.  The "show"
276# operation executes that script to show the scene.  The "clear"
277# operation clears the current scene (usually in preparation for
278# showing another scene).  The "forget" operation erases one or more
279# scene definitions; if no names are specified, then all scenes are
280# forgotten.
281# ----------------------------------------------------------------------
282proc cmd_scene {option args} {
283    global Scenes Views DisplayProps parser
284
285    switch -- $option {
286        define {
287            if {[llength $args] != 2} {
288                error "wrong # args: should be \"scene define name script\""
289            }
290            set name [lindex $args 0]
291            set script [lindex $args 1]
292            set Scenes($name) $script
293        }
294        show {
295            if {[llength $args] < 1 || [llength $args] > 3} {
296                error "wrong # args: should be \"scene show name ?-send cmd?\""
297            }
298            set name [lindex $args 0]
299            if {![info exists Scenes($name)]} {
300                error "bad scene name \"$name\": should be one of [join [array names Scenes] {, }]"
301            }
302
303            set sendcmd ""
304            foreach {key val} [lrange $args 1 end] {
305                switch -- $key {
306                    -send { set sendcmd $val }
307                    default { error "bad option \"$key\": should be -send" }
308                }
309            }
310
311            # clear the old scene
312            cmd_scene clear
313
314            # use a safe interp to keep things safe
315            display resetview
316            if {[catch {$parser eval $Scenes($name)} result]} {
317                error "$result\nwhile loading scene \"$name\""
318            }
319
320            # capture display characteristics in case we ever need to reset
321            set DisplayProps(rendermode) [display get rendermode]
322            set DisplayProps(shadows) [display get shadows]
323
324            foreach nmol [molinfo list] {
325                set max [molinfo $nmol get numreps]
326                for {set nrep 0} {$nrep < $max} {incr nrep} {
327                    set style [lindex [molinfo $nmol get "{rep $nrep}"] 0]
328                    set DisplayProps(rep-$nmol-$nrep) $style
329                }
330            }
331
332            # store the scene name for later
333            set Scenes(@CURRENT) $name
334
335            # if -send arg was given, send back the view after the script
336            if {$sendcmd ne ""} {
337                cmd_tellme $sendcmd getview
338            }
339        }
340        clear {
341            foreach mol [molinfo list] {
342                set numOfRep [lindex [mol list $mol] 12]
343                for {set i 1} {$i <= $numOfRep} {incr i} {
344                    mol delrep 0 $mol
345                }
346            }
347            set Scenes(@CURRENT) ""
348            catch {unset Views}
349
350            # reset the server properties
351            axes location off
352            color Display Background black
353            display backgroundgradient off
354        }
355        forget {
356            if {[llength $args] == 0} {
357                set args [array names Scenes]
358            }
359            foreach name $args {
360                if {$name eq "@CURRENT"} continue
361                catch {unset Scenes($name)}
362                if {$name eq $Scenes(@CURRENT)} {
363                    set Scenes(@CURRENT) ""
364                }
365            }
366        }
367        default {
368            error "bad option \"$option\": should be define, show, clear, forget"
369        }
370    }
371}
372$parser alias scene cmd_scene
373
374# ----------------------------------------------------------------------
375# USAGE: frames defview <frame> {matrixNames...} {matrixValues...}
376# USAGE: frames time <epochValue> <start> ?<finish>? ?<inc>? ?-defview?
377# USAGE: frames rotate <epochValue> <xa> <ya> <za> <number>
378# USAGE: frames max
379#
380# Used to request one or more frames for an animation.  A "time"
381# animation is a series of frames between two time points.  A "rotate"
382# animation is a series of frames that rotate the view 360 degrees.
383#
384# The <epochValue> is passed by the client to indicate the relevance of
385# the request.  Whenever the client enters a new epoch, it is no longer
386# concerned with any earlier epochs, so the server can ignore pending
387# images that are out of date.  The server sends back the epoch with
388# all frames so the client can understand if the frames are relevant.
389#
390# The "defview" operation sets the default view associated with each
391# frame.  Animation scripts can change the default view to a series of
392# fly-through views.  This operation provides a way of storing those
393# views.
394#
395# For a "time" animation, the <start> is a number of a requested frame.
396# The <finish> is the last frame in the series.  The <inc> is the step
397# by which the frames should be generated, which may be larger than 1.
398#
399# For a "rotate" animation, the <xa>,<ya>,<za> angles indicate the
400# direction of the rotation.  The <number> is the number of frames
401# requested for a full 360 degree rotation.
402#
403# The "frames max" query returns the maximum number of frames in the
404# trajectory.  The server uses this to figure out the limits of
405# animation.
406# ----------------------------------------------------------------------
407proc cmd_frames {what args} {
408    global client Epoch Work Views
409
410    # check incoming parameters
411    switch -- $what {
412      time {
413        set epochValue [lindex $args 0]
414        set start [lindex $args 1]
415
416        set i [lsearch $args -defview]
417        if {$i >= 0} {
418            set defview 1
419            set args [lreplace $args $i $i]
420        } else {
421            set defview 0
422        }
423
424        set finish [lindex $args 2]
425        if {$finish eq ""} { set finish $start }
426        set inc [lindex $args 3]
427        if {$inc eq ""} { set inc 1 }
428
429        if {![string is integer $finish]} {
430            server_oops $client "bad animation end \"$finish\" should be integer"
431            return
432        }
433        if {![string is integer $inc] || $inc == 0} {
434            server_oops $client "bad animation inc \"$inc\" should be non-zero integer"
435            return
436        }
437        if {($finish < $start && $inc > 0) || ($finish > $start && $inc < 0)} {
438            server_oops $client "bad animation limits: from $start to $finish by $inc"
439        }
440
441        # new epoch? then clean out work queue
442        if {$epochValue > $Epoch} {
443            catch {unset Work}
444            set Work(queue) ""
445            set Epoch $epochValue
446        }
447
448        # add these frames to the queue
449        if {$inc > 0} {
450            # generate frames in play>> direction
451            for {set n $start} {$n <= $finish} {incr n $inc} {
452                if {![info exists Work($n)]} {
453                    lappend Work(queue) [list epoch $epochValue frame $n num $n defview $defview]
454                    set Work($n) 1
455                }
456            }
457        } else {
458            # generate frames in <<play direction
459            for {set n $start} {$n >= $finish} {incr n $inc} {
460                if {![info exists Work($n)]} {
461                    lappend Work(queue) [list epoch $epochValue frame $n num $n defview $defview]
462                    set Work($n) 1
463                }
464            }
465        }
466      }
467      rotate {
468        set epochValue [lindex $args 0]
469        set mx [lindex $args 1]
470        if {![string is double -strict $mx]} {
471            server_oops $client "bad mx rotation value \"$mx\" should be double"
472            return
473        }
474        set my [lindex $args 2]
475        if {![string is double -strict $my]} {
476            server_oops $client "bad my rotation value \"$my\" should be double"
477            return
478        }
479        set mz [lindex $args 3]
480        if {![string is double -strict $mz]} {
481            server_oops $client "bad mz rotation value \"$mz\" should be double"
482            return
483        }
484        set num [lindex $args 4]
485        if {![string is integer -strict $num] || $num < 2} {
486            server_oops $client "bad number of rotation frames \"$num\" should be integer > 1"
487            return
488        }
489
490        #
491        # Compute the rotation matrix for each rotated view.
492        # Start with the current rotation matrix.  Rotate that around
493        # a vector perpendicular to the plane of rotation for the given
494        # angles (mx,my,mz).  Find vector that by rotating some vector
495        # such as (1,1,1) by the angles (mx,my,mz).  Do a couple of
496        # times and compute the differences between those vectors.
497        # Then, compute the cross product of the differences.  The
498        # result is the axis of rotation.
499        #
500        set lastrotx [trans axis x $mx deg]
501        set lastroty [trans axis y $my deg]
502        set lastrotz [trans axis z $mz deg]
503        set lastrot [transmult $lastrotx $lastroty $lastrotz]
504
505        set lastvec [list 1 1 1]
506        foreach v {1 2} {
507            foreach row $lastrot comp {x y z w} {
508                # multiply each row by last vector
509                set vec($comp) 0
510                for {set i 0} {$i < 3} {incr i} {
511                    set vec($comp) [expr {$vec($comp) + [lindex $row $i]}]
512                }
513            }
514            set vec${v}(x) [expr {$vec(x)-[lindex $lastvec 0]}]
515            set vec${v}(y) [expr {$vec(y)-[lindex $lastvec 1]}]
516            set vec${v}(z) [expr {$vec(z)-[lindex $lastvec 2]}]
517
518            set lastvec [list $vec(x) $vec(y) $vec(z)]
519            set lastrot [transmult $lastrot $lastrotx $lastroty $lastrotz]
520        }
521
522        set crx [expr {$vec1(y)*$vec2(z)-$vec1(z)*$vec2(y)}]
523        set cry [expr {$vec1(z)*$vec2(x)-$vec1(x)*$vec2(z)}]
524        set crz [expr {$vec1(x)*$vec2(y)-$vec1(y)*$vec2(x)}]
525
526        set angle [expr {360.0/$num}]
527        set rotby [transabout [list $crx $cry $crz] $angle deg]
528        set rotm [lindex [molinfo top get rotate_matrix] 0]
529
530        # compute cross product of (1,1,1,0) and rotated vector from above
531
532        for {set n 0} {$n < $num} {incr n} {
533            lappend Work(queue) [list epoch $epochValue rotate $rotm num $n defview 0]
534            set rotm [transmult $rotby $rotm]
535            set Work($n) 1
536        }
537      }
538      defview {
539          if {[llength $args] != 3} { error "wrong # args: should be \"defview matrixNameList matrixValueList\"" }
540          set n [lindex $args 0]
541          if {![string is int $n]} { error "bad frame value \"$n\"" }
542          set Views($n) [lrange $args 1 end]
543      }
544      max {
545        set nmol [lindex [molinfo list] 0]
546        if {$nmol ne ""} {
547            return [molinfo $nmol get numframes]
548        }
549        return 0
550      }
551      default {
552        error "bad option \"$what\": should be defview, time, rotate, max"
553      }
554    }
555
556    # service the queue at some point
557    server_send_image -eventually
558}
559$parser alias frames cmd_frames
560
561# ----------------------------------------------------------------------
562# USAGE: getview
563#
564# Used to query the scaling and centering of the initial view set
565# by VMD after a molecule is loaded.  Returns the following:
566#   <viewName> -rotate <mtx> -global <mtx> -scale <mtx> -center <mtx>
567# ----------------------------------------------------------------------
568proc cmd_getview {} {
569    global Scenes
570
571    if {[llength [molinfo list]] == 0} { return "" }
572    if {$Scenes(@CURRENT) eq ""} { return "" }
573
574    set rval [list $Scenes(@CURRENT)]  ;# start with the scene name
575
576    lappend rval -rotate [lindex [molinfo top get rotate_matrix] 0] \
577                 -scale [lindex [molinfo top get scale_matrix] 0] \
578                 -center [lindex [molinfo top get center_matrix] 0] \
579                 -global [lindex [molinfo top get global_matrix] 0]
580
581    return $rval
582}
583$parser alias getview cmd_getview
584
585#
586# USAGE: server_safe_resize <width> <height>
587#
588# Use this version instead of "display resize" whenever possible.
589# The VMD "display resize" goes into the event loop, so calling that
590# causes things to execute out of order.  Use this method instead to
591# store the change and actually resize later.
592#
593proc server_safe_resize {w h} {
594    global DisplaySize
595
596    if {$w != $DisplaySize(w) || $h != $DisplaySize(h)} {
597        set DisplaySize(w) $w
598        set DisplaySize(h) $h
599        set DisplaySize(changed) yes
600    }
601}
602
603# ----------------------------------------------------------------------
604# SERVER CORE
605# ----------------------------------------------------------------------
606proc server_accept {cid addr port} {
607    fileevent $cid readable [list server_handle $cid $cid]
608    fconfigure $cid -buffering none -blocking 0
609}
610
611proc server_handle {cin cout} {
612    global parser buffer client
613
614    if {[gets $cin line] < 0} {
615        # when client drops connection, we can exit
616        # nanoscale will spawn a new server next time we need it
617        server_exit $cin $cout
618    } else {
619        append buffer($cin) $line "\n"
620        if {[info complete $buffer($cin)]} {
621            set request $buffer($cin)
622            set buffer($cin) ""
623            set client $cout
624            if {[catch {$parser eval $request} result] == 0} {
625                server_send_image -eventually
626            } else {
627                server_oops $cout $result
628                if { [string match "invalid command*" $result] } {
629                    bgerror "I got a invalid command: $result"
630                    exit 1
631                }
632            }
633        }
634    }
635}
636
637proc server_send {cout} {
638    global Epoch Sendqueue
639
640    # grab the next chunk of output and send it along
641    # discard any chunks from an older epoch
642    while {[llength $Sendqueue] > 0} {
643        set chunk [lindex $Sendqueue 0]
644        set Sendqueue [lrange $Sendqueue 1 end]
645
646        catch {unset data}; array set data $chunk
647        if {$data(epoch) == $Epoch} {
648            catch {puts $cout $data(cmd)}
649
650            # if this command has a binary data block, send it specially
651            if {[string length $data(bytes)] > 0} {
652                fconfigure $cout -translation binary
653                catch {puts $cout $data(bytes)}
654                fconfigure $cout -translation auto
655            }
656            break
657        }
658    }
659
660    # nothing left? Then stop callbacks until we get more
661    if {[llength $Sendqueue] == 0} {
662        fileevent $cout writable ""
663        server_send_image -eventually
664    }
665}
666
667proc server_exit {cin cout} {
668    catch {close $cin}
669    catch {exit 0}
670}
671
672# ----------------------------------------------------------------------
673# SERVER RESPONSES
674# ----------------------------------------------------------------------
675
676# turn off constant updates -- only need them during server_send_image
677display update off
678
679proc server_send_image {{when -now}} {
680    global client Epoch Work Views Sendqueue DisplaySize
681
682    if {$when eq "-eventually"} {
683        after cancel server_send_image
684        after 1 server_send_image
685        return
686    } elseif {$when ne "-now"} {
687        error "bad option \"$when\" for server_send_image: should be -now or -eventually"
688    }
689
690    # is there a display resize pending? then resize and try again later
691    if {$DisplaySize(changed)} {
692        set DisplaySize(changed) 0
693        after idle [list display resize $DisplaySize(w) $DisplaySize(h)]
694        after 20 server_send_image
695        return
696    }
697
698    # loop through requests in the work queue and skip any from an older epoch
699    while {1} {
700        if {[llength $Work(queue)] == 0} {
701            return
702        }
703
704        set rec [lindex $Work(queue) 0]
705        set Work(queue) [lrange $Work(queue) 1 end]
706
707        catch {unset item}; array set item $rec
708        if {$item(epoch) < $Epoch} {
709            catch {unset Work($item(num))}
710            continue
711        }
712
713        # set the frame characteristics and render this frame
714        if {[info exists item(frame)]} {
715            animate goto $item(frame)
716        } elseif {[info exists item(rotate)]} {
717            molinfo top set rotate_matrix [list $item(rotate)]
718            # send rotation matrix back to the client so we can pause later
719            server_send_result $client [list nv>rotatemtx $item(num) $item(rotate)]
720        } else {
721            puts "ERROR: bad work frame: [array get item]"
722        }
723
724        # flag to use the stored default view? then set that
725        if {[info exists item(defview)] && $item(defview)} {
726            if {[info exists Views($item(frame))]} {
727                eval molinfo top set $Views($item(frame))
728            }
729        }
730        catch {unset Work($item(num))}
731        break
732    }
733
734    # force VMD to update and grab the screen
735    display update
736    tkrender SnapShot
737
738    set data [SnapShot data -format PPM]
739    server_send_result $client "nv>image epoch $item(epoch) frame $item(num) length [string length $data]" $data
740
741    # if there's more work in the queue, try again later
742    if {[llength $Work(queue)] > 0} {
743        after 1 server_send_image
744    }
745}
746
747proc server_send_result {cout cmd {data ""}} {
748    global Epoch Sendqueue
749
750    # add this result to the output queue
751    # wait until the client is ready, then send the output
752    lappend Sendqueue [list epoch $Epoch cmd $cmd bytes $data]
753    fileevent $cout writable [list server_send $cout]
754}
755
756proc server_oops {cout mesg} {
757    # remove newlines -- all lines must start with nv>
758    set mesg [string map {\n " "} $mesg]
759    server_send_result $cout "nv>oops [list $mesg]"
760}
761
762if {$Paradigm eq "socket"} {
763    socket -server server_accept 2018
764} else {
765    set cin $vmd_client(read)
766    set cout $vmd_client(write)
767
768    fileevent $cin readable [list server_handle $cin $cout]
769    fconfigure $cout -buffering none -blocking 0
770}
771
772# vmd automatically drops into an event loop at this point...
773#
774# The VMD TCL interpreter is by default interactive.  Their version
775# of tkconsole always turns this on.  Turn this off
776# so that unknown commands like "scene" don't get exec-ed.
777set ::tcl_interactive 0
Note: See TracBrowser for help on using the repository browser.