Changeset 6502 for vmdshow/trunk


Ignore:
Timestamp:
Aug 28, 2016, 8:34:32 PM (8 years ago)
Author:
ldelgass
Message:

sync with mdshowcase tool

File:
1 edited

Legend:

Unmodified
Added
Removed
  • vmdshow/trunk/vmdserver.tcl

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