Changeset 6502 for vmdshow/trunk/vmdserver.tcl
- Timestamp:
- Aug 28, 2016, 8:34:32 PM (8 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
vmdshow/trunk/vmdserver.tcl
r6498 r6502 10 10 # ====================================================================== 11 11 12 # The VMD TCL interpreter is by default interactive. Turn this off13 # so that unknown commands like "scene" don't get exec-ed.14 set ::tcl_interactive 015 16 12 proc bgerror {mesg} { 17 13 puts stderr "SERVER ERROR: $mesg" 18 14 } 15 16 proc 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 24 proc 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 # ---------------------------------------------------------------------- 44 proc 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 # ---------------------------------------------------------------------- 54 proc 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 # ---------------------------------------------------------------------- 69 proc 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 # ---------------------------------------------------------------------- 83 proc 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 # ---------------------------------------------------------------------- 98 proc 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 # ---------------------------------------------------------------------- 176 proc 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 # ---------------------------------------------------------------------- 360 proc 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 # ---------------------------------------------------------------------- 387 proc 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 # ---------------------------------------------------------------------- 441 proc 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 # ---------------------------------------------------------------------- 456 proc 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 # ---------------------------------------------------------------------- 532 proc 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 # ---------------------------------------------------------------------- 550 proc 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 # ---------------------------------------------------------------------- 570 proc 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 # ---------------------------------------------------------------------- 688 proc 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 # ---------------------------------------------------------------------- 713 proc 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 # ---------------------------------------------------------------------- 753 proc 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 # ---------------------------------------------------------------------- 772 proc 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 # 796 proc 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 # ---------------------------------------------------------------------- 809 proc 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 823 proc 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 851 proc 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 881 proc server_exit {cin cout code} { 882 catch {close $cin} 883 catch {exit $code} 884 885 } 886 887 # ---------------------------------------------------------------------- 888 # SERVER RESPONSES 889 # ---------------------------------------------------------------------- 890 891 proc 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 971 proc SetTemporaryDirectory { path } { 972 global tmpDir 973 974 set tmpDir $path 975 } 976 977 proc 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 986 proc 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 995 proc 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 1004 display update off 19 1005 20 1006 # parse command line args … … 83 1069 84 1070 set 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 } 85 1083 86 1084 foreach cmd { … … 139 1137 } 140 1138 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 1099 1162 1100 1163 if {$Paradigm eq "socket"} {
Note: See TracChangeset
for help on using the changeset viewer.