source: vmdshow/branches/0.2/vmdserver.tcl @ 6513

Last change on this file since 6513 was 6502, checked in by ldelgass, 8 years ago

sync with mdshowcase tool

  • Property svn:eol-style set to native
File size: 37.7 KB
Line 
1# -*- mode: tcl; indent-tabs-mode: nil -*-
2# ----------------------------------------------------------------------
3#  HUBZERO: server for VMD
4#
5#  This program runs VMD and acts as a server for client applications.
6# ----------------------------------------------------------------------
7#  Michael McLennan (mmclennan@purdue.edu)
8# ======================================================================
9#  Copyright (c) 2013 - HUBzero Foundation, LLC
10# ======================================================================
11
12proc bgerror {mesg} {
13    puts stderr "SERVER ERROR: $mesg"
14}
15
16proc FramesDefView { frameNum matrixNameList matrixValueList } {
17    global Views
18    if { ![string is int $frameNum] } {
19        error "bad frame value \"$frameNum\""
20    }
21    set Views($frameNum) [list $matrixNameList $matrixValueList]
22}
23
24proc FramesSetCmds { frameNum cmds } {
25    global ViewCmds
26    if { ![string is int $frameNum] } {
27        error "bad frame value \"$frameNum\""
28    }
29    set ViewCmds($frameNum) [join $cmds \;]
30}
31
32
33# ========================================================================
34#        Command aliases for VMD cmds in the slave interpreter
35# ========================================================================
36
37# ----------------------------------------------------------------------
38# USAGE: animate <option> <args>...
39#
40# The usual VMD "animate" command is problematic for this server.  If we're
41# going to play the animation, the client will do it.  Intercept any
42# "animate" commands in the scene scripts and do nothing.
43# ----------------------------------------------------------------------
44proc Animate {args} {
45    # do nothing
46}
47
48# ----------------------------------------------------------------------
49# USAGE: atomselect <args>...
50#
51# Allow the usual VMD "atomselect" command to pass through, prohibiting
52# only the "writepdb" operation.
53# ----------------------------------------------------------------------
54proc AtomSelect {args} {
55    foreach arg $args {
56        if { [string match "write*" $arg] } {
57            error "atomselect \"$arg\" option is disallowed"
58        }
59    }
60    return [uplevel 1 [concat atomselect $args]]
61}
62
63# ----------------------------------------------------------------------
64# USAGE: atomselect_instance <args>...
65#
66# Called by the unknown proceduce when it encounters atom selections in the
67# form "atomselect[0-9]+".  Pass through commands to the VMD interpreter.
68# ----------------------------------------------------------------------
69proc AtomSelectInstance {args} {
70    set cmd [lindex $args 0]
71    if { [regexp {atomselect[0-9]+} $cmd] } {
72        return [uplevel 1 $args]
73    }
74}
75
76# ----------------------------------------------------------------------
77# USAGE: display option ?arg arg...?
78#
79# Executes the "command arg arg..." string in the server and substitutes
80# the result into the template string in place of each "%v" field.
81# Sends the result back to the client.
82# ----------------------------------------------------------------------
83proc Display {args} {
84    set option [lindex $args 0]
85    if {[lsearch {resize reposition rendermode update fps} $option] >= 0} {
86        # ignore these commands -- they cause trouble
87        return ""
88    }
89    eval display $args
90}
91
92# ----------------------------------------------------------------------
93# USAGE: Drag start|end
94#
95# Resizes the visualization window to the given width <w> and height
96# <h>.  The next image sent should be this size.
97# ----------------------------------------------------------------------
98proc Drag {action} {
99    global DisplayProps
100
101    switch -- $action {
102        start {
103            # simplify rendering so it goes faster during drag operations
104            set neww [expr {round($DisplayProps(framew)/2.0)}]
105            set newh [expr {round($DisplayProps(frameh)/2.0)}]
106            server_safe_resize $neww $newh
107            display rendermode Normal
108            display shadows off
109
110            # gah: turn off rep change
111            if 0 {
112                foreach nmol [molinfo list] {
113                    set max [molinfo $nmol get numreps]
114                    for {set nrep 0} {$nrep < $max} {incr nrep} {
115                        mol modstyle $nrep $nmol "Lines"
116                    }
117                }
118            }
119        }
120        end {
121            # put original rendering options back
122            server_safe_resize $DisplayProps(framew) $DisplayProps(frameh)
123            display rendermode $DisplayProps(rendermode)
124            display shadows $DisplayProps(shadows)
125
126            # gah: turn off rep change
127            if 0 {
128                # restore rendering methods for all representations
129                foreach nmol [molinfo list] {
130                    set max [molinfo $nmol get numreps]
131                    for {set nrep 0} {$nrep < $max} {incr nrep} {
132                        mol modstyle $nrep $nmol $DisplayProps(rep-$nmol-$nrep)
133                    }
134                }
135               }
136        }
137        default {
138            error "bad option \"$action\": should be start or end"
139        }
140    }
141}
142
143# ----------------------------------------------------------------------
144# USAGE: frames defview <frame> {matrixNames...} {matrixValues...}
145# USAGE: frames time <epochValue> <start> ?<finish>? ?<inc>? ?-defview?
146# USAGE: frames rotate <epochValue> <xa> <ya> <za> <number>
147# USAGE: frames max
148#
149# Used to request one or more frames for an animation.  A "time"
150# animation is a series of frames between two time points.  A "rotate"
151# animation is a series of frames that rotate the view 360 degrees.
152#
153# The <epochValue> is passed by the client to indicate the relevance of
154# the request.  Whenever the client enters a new epoch, it is no longer
155# concerned with any earlier epochs, so the server can ignore pending
156# images that are out of date.  The server sends back the epoch with
157# all frames so the client can understand if the frames are relevant.
158#
159# The "defview" operation sets the default view associated with each
160# frame.  Animation scripts can change the default view to a series of
161# fly-through views.  This operation provides a way of storing those
162# views.
163#
164# For a "time" animation, the <start> is a number of a requested frame.
165# The <finish> is the last frame in the series.  The <inc> is the step
166# by which the frames should be generated, which may be larger than 1.
167#
168# For a "rotate" animation, the <xa>,<ya>,<za> angles indicate the
169# direction of the rotation.  The <number> is the number of frames
170# requested for a full 360 degree rotation.
171#
172# The "frames max" query returns the maximum number of frames in the
173# trajectory.  The server uses this to figure out the limits of
174# animation.
175# ----------------------------------------------------------------------
176proc Frames {what args} {
177    global client Epoch Work Views
178
179    # check incoming parameters
180    switch -- $what {
181        time {
182            set epochValue [lindex $args 0]
183            set start [lindex $args 1]
184
185            set i [lsearch $args -defview]
186            if {$i >= 0} {
187                set defview 1
188                set args [lreplace $args $i $i]
189            } else {
190                set defview 0
191            }
192
193            set finish [lindex $args 2]
194            if {$finish eq ""} {
195                set finish $start
196            }
197            set inc [lindex $args 3]
198            if {$inc eq ""} {
199                set inc 1
200            }
201
202            if {![string is integer $finish]} {
203                server_oops $client \
204                    "bad animation end \"$finish\" should be integer"
205                return
206            }
207            if {![string is integer $inc] || $inc == 0} {
208                server_oops $client \
209                    "bad animation inc \"$inc\" should be non-zero integer"
210                return
211            }
212            if {($finish < $start && $inc > 0) ||
213                ($finish > $start && $inc < 0)} {
214                server_oops $client \
215                    "bad animation limits: from $start to $finish by $inc"
216            }
217
218            # new epoch? then clean out work queue
219            if {$epochValue > $Epoch} {
220                array unset Work
221                set Work(queue) ""
222                set Epoch $epochValue
223            }
224
225            # add these frames to the queue
226            if {$inc > 0} {
227                # generate frames in play>> direction
228                for {set n $start} {$n <= $finish} {incr n $inc} {
229                    if {![info exists Work($n)]} {
230                        lappend Work(queue) [list epoch $epochValue frame $n num $n defview $defview]
231                        set Work($n) 1
232                    }
233                }
234            } else {
235                # generate frames in <<play direction
236                for {set n $start} {$n >= $finish} {incr n $inc} {
237                    if {![info exists Work($n)]} {
238                        lappend Work(queue) [list epoch $epochValue frame $n num $n defview $defview]
239                        set Work($n) 1
240                    }
241                }
242            }
243        }
244        rotate {
245            set epochValue [lindex $args 0]
246            set mx [lindex $args 1]
247            if {![string is double -strict $mx]} {
248                server_oops $client \
249                    "bad mx rotation value \"$mx\" should be double"
250                return
251            }
252            set my [lindex $args 2]
253            if {![string is double -strict $my]} {
254                server_oops $client \
255                    "bad my rotation value \"$my\" should be double"
256                return
257            }
258            set mz [lindex $args 3]
259            if {![string is double -strict $mz]} {
260                server_oops $client \
261                    "bad mz rotation value \"$mz\" should be double"
262                return
263            }
264            set num [lindex $args 4]
265            if {![string is integer -strict $num] || $num < 2} {
266                server_oops $client \
267            "bad number of rotation frames \"$num\" should be integer > 1"
268                return
269            }
270
271            #
272            # Compute the rotation matrix for each rotated view.  Start
273            # with the current rotation matrix.  Rotate that around a
274            # vector perpendicular to the plane of rotation for the given
275            # angles (mx,my,mz).  Find vector that by rotating some vector
276            # such as (1,1,1) by the angles (mx,my,mz).  Do a couple of
277            # times and compute the differences between those vectors.
278            # Then, compute the cross product of the differences.  The
279            # result is the axis of rotation.
280            #
281            set lastrotx [trans axis x $mx deg]
282            set lastroty [trans axis y $my deg]
283            set lastrotz [trans axis z $mz deg]
284            set lastrot [transmult $lastrotx $lastroty $lastrotz]
285
286            set lastvec [list 1 1 1]
287            foreach v {1 2} {
288                foreach row $lastrot comp {x y z w} {
289                    # multiply each row by last vector
290                    set vec($comp) 0
291                    for {set i 0} {$i < 3} {incr i} {
292                        set vec($comp) [expr {$vec($comp) + [lindex $row $i]}]
293                    }
294                }
295                set vec${v}(x) [expr {$vec(x)-[lindex $lastvec 0]}]
296                set vec${v}(y) [expr {$vec(y)-[lindex $lastvec 1]}]
297                set vec${v}(z) [expr {$vec(z)-[lindex $lastvec 2]}]
298
299                set lastvec [list $vec(x) $vec(y) $vec(z)]
300                set lastrot [transmult $lastrot $lastrotx $lastroty $lastrotz]
301            }
302
303            set crx [expr {$vec1(y)*$vec2(z)-$vec1(z)*$vec2(y)}]
304            set cry [expr {$vec1(z)*$vec2(x)-$vec1(x)*$vec2(z)}]
305            set crz [expr {$vec1(x)*$vec2(y)-$vec1(y)*$vec2(x)}]
306
307            set angle [expr {360.0/$num}]
308            set rotby [transabout [list $crx $cry $crz] $angle deg]
309            set rotm [lindex [molinfo top get rotate_matrix] 0]
310
311            # compute cross product of (1,1,1,0) and rotated vector from above
312
313            for {set n 0} {$n < $num} {incr n} {
314                lappend Work(queue) \
315                    [list epoch $epochValue rotate $rotm num $n defview 0]
316                set rotm [transmult $rotby $rotm]
317                set Work($n) 1
318            }
319        }
320        defview {
321            eval FramesDefView $args
322        }
323        setcmds {
324            eval FramesSetCmds $args
325        }
326        max {
327            set maxFrames 0
328            foreach mol [molinfo list] {
329                set n [molinfo $mol get numframes]
330                if { $n > $maxFrames } {
331                    set maxFrames $n
332                }
333            }
334            return $maxFrames
335            # gah: fix to return max correct max frames.
336            if 0 {
337                set nmol [lindex [molinfo list] 0]
338                if {$nmol ne ""} {
339                    return [molinfo $nmol get numframes]
340                }
341                return 0
342            }
343        }
344        default {
345            error "bad option \"$what\": should be defview, time, rotate, setcmds, or max"
346        }
347    }
348
349    # service the queue at some point
350    server_send_image -eventually
351}
352
353# ----------------------------------------------------------------------
354# USAGE: getview
355#
356# Used to query the scaling and centering of the initial view set
357# by VMD after a molecule is loaded.  Returns the following:
358#   <viewName> -rotate <mtx> -global <mtx> -scale <mtx> -center <mtx>
359# ----------------------------------------------------------------------
360proc GetView {} {
361    global Scenes
362
363    if { [llength [molinfo list]] == 0 } {
364        return ""
365    }
366    if { $Scenes(@CURRENT) eq "" } {
367        return ""
368    }
369
370    set rval [list $Scenes(@CURRENT)]  ;# start with the scene id
371
372    lappend rval \
373        -rotate [lindex [molinfo top get rotate_matrix] 0] \
374        -scale  [lindex [molinfo top get scale_matrix] 0]  \
375        -center [lindex [molinfo top get center_matrix] 0] \
376        -global [lindex [molinfo top get global_matrix] 0]
377
378    return $rval
379}
380
381# ----------------------------------------------------------------------
382# USAGE: load <file> <file>...
383#
384# Loads the molecule data from one or more files, which may be PDB,
385# DCD, PSF, etc.
386# ----------------------------------------------------------------------
387proc Load { fileList } {
388    global MolInfo MolNames tmpDir
389
390    # clear all existing molecules
391    foreach nmol [molinfo list] {
392        mol delete $nmol
393    }
394    catch {unset MolInfo}
395    set MolNames ""
396
397    # load new files
398    if {![regexp {^@name:} $fileList]} {
399        # make sure that there is at least one name in the list
400        set fileList [linsert $fileList 0 "@name:0"]
401    }
402
403    set slot 0
404    set op "badOp"
405    foreach file $fileList {
406        if {[regexp {^@name:(.+)} $file match name]} {
407            set op "new"
408            continue
409        }
410        if { $tmpDir != "" } {
411            set tmpFile [file join $tmpDir [file tail $file]]
412            if { [file exists $tmpFile] } {
413                set file $tmpFile
414            }
415        }
416        mol $op $file waitfor all
417        if { ![info exists name] } {
418            puts stderr "can't find name for file: file=$file"
419        }
420        if {$op eq "new"} {
421            set newnum [lindex [molinfo list] end]
422            if {[lsearch -exact MolNames $name] < 0} {
423                lappend MolNames $name
424            }
425            set MolInfo($name) $newnum
426            set MolInfo($slot) $newnum
427            incr slot
428            set op "addfile"
429        }
430    }
431
432    # BE CAREFUL -- force a "display update" here
433    # that triggers something in VMD that changes view matrices now,
434    # so if we change them later, the new values stick
435    display update
436}
437
438# ----------------------------------------------------------------------
439# These commands just confuse things, so ignore them silently.
440# ----------------------------------------------------------------------
441proc NoOp {args} {
442    # do nothing
443}
444
445# ----------------------------------------------------------------------
446# USAGE: queryinfo <x> <y> ?-prev atomid atomid?
447# USAGE: queryinfo <x> <y> ?-prev atomid?
448# USAGE: queryinfo <x> <y>
449#
450# Picks the atom at screen coordinate <x>,<y> and returns information
451# about it.  If one previous atom is specified, then this command
452# returns the bond length between the previous atom and the current
453# one.  If two previous atoms are specified, then it returns the
454# angle between the three atoms.
455# ----------------------------------------------------------------------
456proc QueryInfo {x y args} {
457    global DisplayProps MolNames MolInfo
458
459    # handle command arguments
460    set prevatoms ""
461    while {[llength $args] > 0} {
462        set option [lindex $args 0]
463        set args [lrange $args 1 end]
464        if {$option eq "-prev"} {
465            while {[llength $args] > 0} {
466                set val [lindex $args 0]
467                if {[regexp {^[0-9]} $val]} {
468                    lappend prevatoms $val
469                    set args [lrange $args 1 end]
470                } else {
471                    break
472                }
473            }
474        } else {
475            error "bad option \"$option\": should be -prev"
476        }
477    }
478
479    # be careful -- VMD uses coordinates from LOWER-left corner of window
480    set vmdy [expr {$DisplayProps(frameh)-$y}]
481
482    set vals [pick $x $vmdy]
483    if {$vals ne ""} {
484        array set data $vals
485
486        # map the molecule ID back to the name used within MD Showcase
487        foreach molname $MolNames {
488            if {$MolInfo($molname) == $data(mol)} {
489                set data(molname) $molname
490                break
491            }
492        }
493
494        # pass back the click coord on screen so we know where this came from
495        set data(screenx) $x
496        set data(screeny) $y
497
498        # if there are -prev atoms, query extra info
499        set curr [list $data(index) $data(mol)]
500        set meas $prevatoms
501        set i [lsearch -exact $meas $curr]
502        if {$i >= 0} {
503            set meas [lreplace $meas $i $i]
504        }
505        set meas [linsert $meas 0 $curr]
506        set meas [lrange $meas 0 2]
507
508        switch -- [llength $meas] {
509            2 {
510                set data(bondlength) [measure bond $meas]
511            }
512            3 {
513                set data(bondlength) [measure bond [lrange $meas 0 1]]
514                set data(bondlength2) [measure bond [lrange $meas 1 2]]
515                set data(angle) [measure angle $meas]
516            }
517        }
518
519        # convert data back to return value
520        set vals [array get data]
521    }
522    return $vals
523}
524
525
526# ----------------------------------------------------------------------
527# USAGE: resize <w> <h>
528#
529# Resizes the visualization window to the given width <w> and height
530# <h>.  The next image sent should be this size.
531# ----------------------------------------------------------------------
532proc Resize {w h} {
533    global DisplayProps
534
535    # store the desired size in case we downscale
536    set DisplayProps(framew) $w
537    set DisplayProps(frameh) $h
538
539    server_safe_resize $w $h
540}
541
542# ----------------------------------------------------------------------
543# USAGE: rock off
544# USAGE: rock x|y|z by <step> ?<n>?
545#
546# The usual VMD "rock" command is problematic for this server.  If we're
547# going to rock the animation, the client will do it.  Intercept any "rock"
548# commands in the scene scripts and do nothing.
549# ----------------------------------------------------------------------
550proc Rock {args} {
551    # do nothing
552}
553
554
555# ----------------------------------------------------------------------
556# USAGE: scene define id <script>
557# USAGE: scene show id ?-before <viewCmd>? ?-after <viewCmd>?
558# USAGE: scene clear
559# USAGE: scene forget ?id id...?
560#
561# Used to define and manipulate scenes of the trajectory information
562# loaded previously by the "load" command.  The "define" operation
563# defines the script that loads a scene called <id>.  The "show"
564# operation executes that script to show the scene.  The "clear"
565# operation clears the current scene (usually in preparation for
566# showing another scene).  The "forget" operation erases one or more
567# scene definitions; if no ids are specified, then all scenes are
568# forgotten.
569# ----------------------------------------------------------------------
570proc Scene {option args} {
571    global Scenes Views MolInfo DisplayProps parser
572
573    switch -- $option {
574        define {
575            if {[llength $args] != 2} {
576                error "wrong # args: should be \"scene define id script\""
577            }
578            set id [lindex $args 0]
579            set script [lindex $args 1]
580            set Scenes($id) $script
581        }
582        show {
583            if {[llength $args] < 1 || [llength $args] > 5} {
584                error "wrong # args: should be \"scene show id ?-before cmd? ?-after cmd?\""
585            }
586            set id [lindex $args 0]
587            if {![info exists Scenes($id)]} {
588                error "bad scene id \"$id\": should be one of [join [array names Scenes] {, }]"
589            }
590
591            set triggers(before) ""
592            set triggers(after) ""
593            foreach {key val} [lrange $args 1 end] {
594                switch -- $key {
595                    -before {
596                        set triggers(before) $val
597                    }
598                    -after {
599                        set triggers(after) $val
600                    }
601                    default {
602                        error "bad option \"$key\": should be -before, -after"
603                    }
604                }
605            }
606
607            # if -before arg was given, send back the view right now
608            if {$triggers(before) ne "" && $Scenes(@CURRENT) ne ""} {
609                TellMe $triggers(before) getview
610            }
611
612            # clear the old scene
613            Scene clear
614            display resetview
615
616            # use a safe interp to keep things safe
617            foreach val [$parser eval {info vars}] {
618                # clear all variables created by previous scripts
619                $parser eval [list catch [list unset $val]]
620            }
621            $parser eval [list array set mol [array get MolInfo]]
622
623            if {[catch {$parser eval $Scenes($id)} result]} {
624                global errorInfo
625                error "$errorInfo\n$result\nwhile loading scene \"$id\""
626            }
627
628            # capture display characteristics in case we ever need to reset
629            set DisplayProps(rendermode) "Normal"
630            set DisplayProps(shadows) [display get shadows]
631
632            foreach nmol [molinfo list] {
633                set max [molinfo $nmol get numreps]
634                for {set nrep 0} {$nrep < $max} {incr nrep} {
635                    set style [lindex [molinfo $nmol get "{rep $nrep}"] 0]
636                    set DisplayProps(rep-$nmol-$nrep) $style
637                }
638            }
639
640            # store the scene id for later
641            set Scenes(@CURRENT) $id
642
643            # if -after arg was given, send back the view after the script
644            if {$triggers(after) ne ""} {
645                TellMe $triggers(after) getview
646            }
647        }
648        clear {
649            foreach mol [molinfo list] {
650                set numOfRep [lindex [mol list $mol] 12]
651                for {set i 1} {$i <= $numOfRep} {incr i} {
652                    mol delrep 0 $mol
653                }
654            }
655            set Scenes(@CURRENT) ""
656            array unset Views
657            array unset ViewCmds
658
659            # reset the server properties
660            axes location off
661            color Display Background black
662            eval $DisplayProps(options)
663        }
664        forget {
665            if {[llength $args] == 0} {
666                set args [array names Scenes]
667            }
668            foreach id $args {
669                if {$id eq "@CURRENT"} continue
670                catch {unset Scenes($id)}
671                if {$id eq $Scenes(@CURRENT)} {
672                    set Scenes(@CURRENT) ""
673                }
674            }
675        }
676        default {
677            error "bad option \"$option\": should be define, show, clear, forget"
678        }
679    }
680}
681
682# ----------------------------------------------------------------------
683# USAGE: setquality normal|high
684#
685# Sets the rendering quality for the scene--either "high" (GLSL) or
686# normal.
687# ----------------------------------------------------------------------
688proc SetQuality {newval} {
689    global DisplayProps
690
691    switch -- $newval {
692        high {
693            display rendermode GLSL
694            set DisplayProps(rendermode) "GLSL"
695        }
696        normal {
697            display rendermode Normal
698            set DisplayProps(rendermode) "Normal"
699        }
700        default {
701            error "bad quality value \"$newval\": should be normal or high"
702        }
703    }
704}
705
706# ----------------------------------------------------------------------
707# USAGE: setview ?-rotate <mtx>? ?-scale <mtx>? ?-center <mtx>? ?-global <mtx>?
708#
709# Sets the view matrix for one or more components of the view.  This
710# is a convenient way of getting a view for a particular frame just
711# right in one shot.
712# ----------------------------------------------------------------------
713proc SetView {args} {
714    if {[llength $args] == 8} {
715        # setting all matrices? then start clean
716        display resetview
717    }
718    foreach {key val} $args {
719        switch -- $key {
720            -rotate {
721                foreach mol [molinfo list] {
722                    molinfo $mol set rotate_matrix [list $val]
723                }
724            }
725            -scale {
726                foreach mol [molinfo list] {
727                    molinfo $mol set scale_matrix [list $val]
728                }
729            }
730            -center {
731                foreach mol [molinfo list] {
732                    molinfo $mol set center_matrix [list $val]
733                }
734            }
735            -global {
736                foreach mol [molinfo list] {
737                    molinfo $mol set global_matrix [list $val]
738                }
739            }
740            default {
741                error "bad option \"$key\": should be -rotate, -scale, -center, or -global"
742            }
743        }
744    }
745}
746
747# ----------------------------------------------------------------------
748# USAGE: smoothreps <value>
749#
750# Changes the smoothing factor for all representations of the current
751# molecule.
752# ----------------------------------------------------------------------
753proc SmoothReps {val} {
754    if {$val < 0} {
755        error "bad smoothing value \"$val\": should be >= 0"
756    }
757    foreach nmol [molinfo list] {
758        set max [molinfo $nmol get numreps]
759        for {set nrep 0} {$nrep < $max} {incr nrep} {
760            mol smoothrep $nmol $nrep $val
761        }
762    }
763}
764
765# ----------------------------------------------------------------------
766# USAGE: tellme "command template with %v" command arg arg...
767#
768# Executes the "command arg arg..." string in the server and substitutes
769# the result into the template string in place of each "%v" field.
770# Sends the result back to the client.
771# ----------------------------------------------------------------------
772proc TellMe {fmt args} {
773    global parser client
774
775    # evaluate args as a command and subst the result in the fmt string
776    if {[catch {$parser eval $args} result] == 0} {
777        server_send_result $client "nv>[string map [list %v $result] $fmt]"
778    } else {
779        server_oops $client $result
780    }
781}
782
783
784# ========================================================================
785#        Server procedures
786# ========================================================================
787
788#
789# USAGE: server_safe_resize <width> <height>
790#
791# Use this version instead of "display resize" whenever possible.
792# The VMD "display resize" goes into the event loop, so calling that
793# causes things to execute out of order.  Use this method instead to
794# store the change and actually resize later.
795#
796proc server_safe_resize {w h} {
797    global DisplaySize
798
799    if {$w != $DisplaySize(w) || $h != $DisplaySize(h)} {
800        set DisplaySize(w) $w
801        set DisplaySize(h) $h
802        set DisplaySize(changed) yes
803    }
804}
805
806# ----------------------------------------------------------------------
807# SERVER CORE
808# ----------------------------------------------------------------------
809proc server_accept {cid addr port} {
810    global env
811
812    fileevent $cid readable [list server_handle $cid $cid]
813    fconfigure $cid -buffering none -blocking 0
814
815    if {[info exists env(LOCAL)]} {
816        # identify server type to this client
817        # VMD on the hub has this built in, but stock versions can
818        # set the environment variable as a work-around
819        puts $cid "vmd 0.1"
820    }
821}
822
823proc server_handle {cin cout} {
824    global parser buffer client
825
826    if {[gets $cin line] < 0} {
827        # when client drops connection, we can exit
828        # nanoscale will spawn a new server next time we need it
829        if {[eof $cin]} {
830            server_exit $cin $cout 0
831        }
832    } else {
833        append buffer($cin) $line "\n"
834        if {[info complete $buffer($cin)]} {
835            set request $buffer($cin)
836            set buffer($cin) ""
837            set client $cout
838            if {[catch {$parser eval $request} result] == 0} {
839                server_send_image -eventually
840            } else {
841                server_oops $cout $result
842                if { [string match "invalid command*" $result] } {
843                    bgerror "server received invalid command: $result"
844                    server_exit $cin $cout 1
845                }
846            }
847        }
848    }
849}
850
851proc server_send {cout} {
852    global Epoch Sendqueue
853
854    # grab the next chunk of output and send it along
855    # discard any chunks from an older epoch
856    while {[llength $Sendqueue] > 0} {
857        set chunk [lindex $Sendqueue 0]
858        set Sendqueue [lrange $Sendqueue 1 end]
859
860        catch {unset data}; array set data $chunk
861        if {$data(epoch) < 0 || $data(epoch) == $Epoch} {
862            catch {puts $cout $data(cmd)}
863
864            # if this command has a binary data block, send it specially
865            if {[string length $data(bytes)] > 0} {
866                fconfigure $cout -translation binary
867                catch {puts $cout $data(bytes)}
868                fconfigure $cout -translation auto
869            }
870            break
871        }
872    }
873
874    # nothing left? Then stop callbacks until we get more
875    if {[llength $Sendqueue] == 0} {
876        fileevent $cout writable ""
877        server_send_image -eventually
878    }
879}
880
881proc server_exit {cin cout code} {
882    catch {close $cin}
883    catch {exit $code}
884
885}
886
887# ----------------------------------------------------------------------
888# SERVER RESPONSES
889# ----------------------------------------------------------------------
890
891proc server_send_image {{when -now}} {
892    global client Epoch Work Views ViewCmds Sendqueue DisplaySize
893
894    if {$when eq "-eventually"} {
895        after cancel server_send_image
896        after 1 server_send_image
897        return
898    } elseif {$when ne "-now"} {
899        error "bad option \"$when\" for server_send_image: should be -now or -eventually"
900    }
901
902    # is there a display resize pending? then resize and try again later
903    if {$DisplaySize(changed)} {
904        set DisplaySize(changed) 0
905        after idle [list display resize $DisplaySize(w) $DisplaySize(h)]
906        after 20 server_send_image
907        return
908    }
909
910    # loop through requests in the work queue and skip any from an older epoch
911    while {1} {
912        if {[llength $Work(queue)] == 0} {
913            return
914        }
915
916        set rec [lindex $Work(queue) 0]
917        set Work(queue) [lrange $Work(queue) 1 end]
918
919        catch {unset item}; array set item $rec
920        if {$item(epoch) < $Epoch} {
921            catch {unset Work($item(num))}
922            continue
923        }
924
925        # set the frame characteristics and render this frame
926        if {[info exists item(frame)]} {
927            animate goto $item(frame)
928        } elseif {[info exists item(rotate)]} {
929            foreach mol [molinfo list] {
930                molinfo $mol set rotate_matrix [list $item(rotate)]
931            }
932            # send rotation matrix back to the client so we can pause later
933            server_send_latest $client [list nv>rotatemtx $item(num) $item(rotate)]
934        } else {
935            puts "ERROR: bad work frame: [array get item]"
936        }
937
938        # flag to use the stored default view? then set that
939        if {[info exists item(defview)] && $item(defview)} {
940            if {[info exists Views($item(frame))]} {
941                foreach mol [molinfo list] {
942                    eval molinfo $mol set $Views($item(frame))
943                }
944            }
945        }
946        if { [info exists item(frame)] &&
947             [info exists ViewCmds($item(frame))] } {
948            if { [catch {
949                eval $ViewCmds($item(frame))
950            } errs] != 0 }  {
951                puts stderr "viewcmd error: $errs"
952            }
953        }
954        array unset Work $item(num)
955        break
956    }
957
958    # force VMD to update and grab the screen
959    display update
960    tkrender SnapShot
961
962    set data [SnapShot data -format PPM]
963    server_send_latest $client "nv>image epoch $item(epoch) frame $item(num) length [string length $data]" $data
964
965    # if there's more work in the queue, try again later
966    if {[llength $Work(queue)] > 0} {
967        after 1 server_send_image
968    }
969}
970
971proc SetTemporaryDirectory { path } {
972    global tmpDir
973
974    set tmpDir $path
975}
976
977proc server_send_result {cout cmd {data ""}} {
978    global Sendqueue
979
980    # add this result to the output queue
981    # use the epoch -1 to force the send even if the epoch has changed
982    lappend Sendqueue [list epoch -1 cmd $cmd bytes $data]
983    fileevent $cout writable [list server_send $cout]
984}
985
986proc server_send_latest {cout cmd {data ""}} {
987    global Epoch Sendqueue
988
989    # add this result to the output queue
990    # wait until the client is ready, then send the output
991    lappend Sendqueue [list epoch $Epoch cmd $cmd bytes $data]
992    fileevent $cout writable [list server_send $cout]
993}
994
995proc server_oops {cout mesg} {
996    # remove newlines -- all lines must start with nv>
997    set mesg [string map {\n " "} $mesg]
998    server_send_result $cout "nv>oops [list $mesg]"
999}
1000
1001# =========================================================================
1002
1003# turn off constant updates -- only need them during server_send_image
1004display update off
1005
1006# parse command line args
1007set Paradigm "socket"
1008while {[llength $argv] > 0} {
1009    set opt [lindex $argv 0]
1010    set argv [lrange $argv 1 end]
1011
1012    switch -- $opt {
1013        -socket { set Paradigm "socket" }
1014        -stdio  { set Paradigm "stdio" }
1015        default {
1016            puts stderr "bad option \"$opt\": should be -socket or -stdio"
1017        }
1018    }
1019}
1020
1021# use this to take snapshots to send to clients
1022image create photo SnapShot
1023
1024# set the screen to a good size
1025set DisplaySize(w) 300
1026set DisplaySize(h) 300
1027display resize $DisplaySize(w) $DisplaySize(h)
1028set DisplaySize(changed) 0
1029
1030# capture initial display settings for later reset
1031display antialias on
1032
1033set DisplayProps(options) ""
1034foreach key {
1035    ambientocclusion
1036    antialias
1037    aoambient
1038    aodirect
1039    backgroundgradient
1040    cuedensity
1041    cueend
1042    cuemode
1043    cuestart
1044    culling
1045    depthcue
1046    distance
1047    eyesep
1048    farclip
1049    focallength
1050    height
1051    nearclip
1052    projection
1053    shadows
1054    stereo
1055} {
1056    if {$key eq "nearclip" || $key eq "farclip"} {
1057        append DisplayProps(options) [list display $key set [display get $key]] "\n"
1058    } else {
1059        append DisplayProps(options) [list display $key [display get $key]] "\n"
1060    }
1061}
1062
1063# initialize work queue and epoch counter (see server_send_image)
1064set Epoch 0
1065set Work(queue) ""
1066set Sendqueue ""
1067set Scenes(@CURRENT) ""
1068set tmpDir ""
1069
1070set parser [interp create -safe]
1071
1072$parser eval {
1073    # Add unknown procedure to safe interpreter to handle generated
1074    # instances (commands) of atom selections.
1075    proc unknown { args } {
1076        set cmd [lindex $args 0]
1077        if { [regexp {atomselect[0-9]+} $cmd] } {
1078            return [uplevel 1 [eval list atomselect_instance $args]]
1079        }
1080        return -code error "unknown command $args"
1081    }
1082}
1083
1084foreach cmd {
1085    axes
1086    color
1087    gettimestep
1088    graphics
1089    imd
1090    label
1091    vmd_label
1092    light
1093    material
1094    measure
1095    mobile
1096    mol
1097    molinfo
1098    molecule
1099    mouse
1100    parallel
1101    plugin
1102    rawtimestep
1103    render
1104    rotate
1105    rotmat
1106    scale
1107    vmd_scale
1108    spaceball
1109    stage
1110    tkrender
1111    tool
1112    translate
1113    user
1114    vecadd
1115    veccross
1116    vecdist
1117    vecdot
1118    vecinvert
1119    veclength
1120    veclength2
1121    vecmean
1122    vecmul
1123    vecnorm
1124    vecscale
1125    vecscale
1126    vecstddev
1127    vecsub
1128    vecsum
1129    veczero
1130    vmdcollab
1131    vmdcon
1132    vmdinfo
1133    vmdbench
1134    volmap
1135} {
1136    $parser alias $cmd $cmd
1137}
1138
1139$parser alias animate                   Animate
1140$parser alias atomselect                AtomSelect
1141$parser alias atomselect_instance       AtomSelectInstance
1142$parser alias display                   Display
1143$parser alias drag                      Drag
1144$parser alias frames                    Frames
1145$parser alias getview                   GetView
1146$parser alias load                      Load
1147$parser alias menu                      NoOp
1148$parser alias play                      NoOp
1149$parser alias queryinfo                 QueryInfo
1150$parser alias quit                      NoOp
1151$parser alias resize                    Resize
1152$parser alias rock                      Rock
1153$parser alias scene                     Scene
1154$parser alias setquality                SetQuality
1155$parser alias setview                   SetView
1156$parser alias sleep                     NoOp
1157$parser alias smoothreps                SmoothReps
1158$parser alias tellme                    TellMe
1159$parser alias vmd_menu                  NoOp
1160
1161$parser alias set_temporary_directory   SetTemporaryDirectory
1162
1163if {$Paradigm eq "socket"} {
1164    socket -server server_accept 2018
1165} else {
1166    set cin $vmd_client(read)
1167    set cout $vmd_client(write)
1168
1169    fileevent $cin readable [list server_handle $cin $cout]
1170    fconfigure $cout -buffering none -blocking 0
1171}
1172
1173# vmd automatically drops into an event loop at this point...
1174#
1175# The VMD TCL interpreter is by default interactive.  Their version
1176# of tkconsole always turns this on.  Turn this off
1177# so that unknown commands like "scene" don't get exec-ed.
1178set ::tcl_interactive 0
Note: See TracBrowser for help on using the repository browser.