Package Pmv :: Module measureCommands
[hide private]
[frames] | no frames]

Source Code for Module Pmv.measureCommands

   1  ############################################################################# 
   2  # 
   3  # Author: Michel F. SANNER, Ruth HUEY 
   4  # 
   5  # Copyright: M. Sanner TSRI 2000 
   6  # 
   7  ############################################################################# 
   8   
   9  # 
  10  # $Header: /opt/cvs/python/packages/share1.5/Pmv/measureCommands.py,v 1.42 2007/06/23 00:07:49 vareille Exp $ 
  11  # 
  12  # $Id: measureCommands.py,v 1.42 2007/06/23 00:07:49 vareille Exp $ 
  13  # 
  14  import Numeric, math, types 
  15  from Pmv.mvCommand import MVCommand, MVAtomICOM 
  16  from DejaVu.Geom import Geom 
  17  from DejaVu.Spheres import Spheres 
  18  from DejaVu.IndexedPolylines import IndexedPolylines 
  19  #from DejaVu.Labels import Labels 
  20  from DejaVu.glfLabels import GlfLabels 
  21  from ViewerFramework.VFCommand import CommandGUI 
  22  from MolKit.molecule import Atom, AtomSet 
  23   
  24  """ 
  25      MeasureCommands contains commands to measure distances, angles and  
  26      torsions between atoms: 
  27        measureDistance-> returns distance between 2 atoms in Angstrom 
  28        measureAngle->    returns angle defined by 3 atoms in Degrees 
  29        measureTorsion->  returns torsion angle defined by 4 atoms in Degrees. 
  30   
  31      This module also contains GuiCommands which provide the user-interfaces 
  32      to the measure commands listed above.  Atoms picked for measurements  
  33      are marked with spheres: yellow for distance, orange for angle and  
  34      cyan for torsion.  In addition, these Gui Commands mark the property  
  35      measured as follows (with the same color scheme): 
  36        measureDistanceGC->   stippled lines labelled with distance 
  37        measureAngleGC ->     2D fan, stipple line and angle label 
  38        measureTorsionGC->    polygon-fill on torsion and angle label 
  39   
  40  """ 
  41   
  42   
43 -def check_measure_geoms(VFGUI):
44 measure_geoms_list = VFGUI.VIEWER.findGeomsByName('measure_geoms') 45 if measure_geoms_list==[]: 46 measure_geoms = Geom("measure_geoms", shape=(0,0), protected=True) 47 VFGUI.VIEWER.AddObject(measure_geoms, parent=VFGUI.miscGeom) 48 measure_geoms_list = [measure_geoms] 49 return measure_geoms_list[0]
50
51 -class MeasureAtomCommand(MVCommand):
52 """Base class for commands which measure between atoms.Implements applyTransformation, getTransformedCoords and vvmult. 53 \nPackage : Pmv 54 \nModule : measureCommands 55 \nClass : MeasureAtomCommand 56 """ 57
58 - def getTransformedCoords(self, atom):
59 # when there is no viewer, the geomContainer is None 60 if not atom.top.geomContainer: 61 return atom.coords 62 g = atom.top.geomContainer.geoms['master'] 63 c = self.vf.transformedCoordinatesWithInstances(AtomSet([atom])) 64 return Numeric.array(c[0], 'f')
65 66
67 - def vvmult(self, a, b):
68 """ 69 Compute a vector product for 3D vectors 70 """ 71 import Numeric 72 res = Numeric.zeros(3, 'f') 73 res[0] = a[1]*b[2] - a[2]*b[1] 74 res[1] = a[2]*b[0] - a[0]*b[2] 75 res[2] = a[0]*b[1] - a[1]*b[0] 76 return res
77 78
79 -class MeasureDistance(MeasureAtomCommand):
80 """Computes the distance between atom1, atom2, atom3.All coordinates are Cartesian; result is in Angstrom. 81 \nPackage : Pmv 82 \nModule : measureCommands 83 \nClass : MeasureDistance 84 \nCommand : measureDistance 85 \nSynopsis:\n 86 float <--- measureDistance(atom1, atom2, **kw) 87 \nRequired Argument:\n 88 atom1 --- first atom 89 \natom2 --- second atom 90 """ 91 92
93 - def doit(self, atom1, atom2):
94 c1 = self.getTransformedCoords(atom1) 95 c2 = self.getTransformedCoords(atom2) 96 d = c2 - c1 97 return math.sqrt(Numeric.sum(d*d))
98 99
100 - def __call__(self, atom1, atom2, **kw):
101 """float <--- measureDistance(atom1, atom2, **kw) 102 \natom1 --- first atom 103 \natom2 --- second atom 104 """ 105 ats = self.vf.expandNodes(atom1) 106 if not len(ats): return 'ERROR' 107 atom1 = ats[0] 108 ats = self.vf.expandNodes(atom2) 109 if not len(ats): return 'ERROR' 110 atom2 = ats[0] 111 return apply(self.doitWrapper, (atom1, atom2), kw)
112 113 114
115 -class MeasureAngle(MeasureAtomCommand):
116 """Compute the angle between atom1, atom2, atom3.All coordinates are Cartesian; result is in degrees. 117 \nPackage : Pmv 118 \nModule : measureCommands 119 \nClass : MeasureAngle 120 \nCommand : measureAngle 121 \nSynopsis:\n 122 float <--- measureAngle(atom1, atom2, atom3 **kw) 123 \nRequired Argument:\n 124 atom1 --- first atom 125 \natom2 --- second atom 126 \natom3 --- third atom 127 """ 128 129
130 - def angle(self, c1, c2, c3):
131 v1 = Numeric.array(c1) - Numeric.array(c2) 132 distance1 = math.sqrt(Numeric.sum(v1*v1)) 133 v2 = Numeric.array(c3) - Numeric.array(c2) 134 distance2 = math.sqrt(Numeric.sum(v2*v2)) 135 sca = Numeric.dot(v1, v2)/(distance1*distance2) 136 if sca<-1.0: 137 sca = -1.0 138 elif sca>1.0: 139 sca = 1.0 140 return math.acos(sca)*180/math.pi
141 142
143 - def doit(self, atom1, atom2, atom3):
144 c1 = self.getTransformedCoords(atom1) 145 c2 = self.getTransformedCoords(atom2) 146 c3 = self.getTransformedCoords(atom3) 147 return self.angle(c1, c2, c3)
148 149
150 - def __call__(self, atom1, atom2, atom3, **kw):
151 """float <- measureAngle(atom1, atom2, atom3 **kw) 152 \natom1 --- first atom 153 \natom2 --- second atom 154 \natom3 --- third atom""" 155 ats = self.vf.expandNodes(atom1) 156 if not len(ats): return 'ERROR' 157 atom1 = ats[0] 158 ats = self.vf.expandNodes(atom2) 159 if not len(ats): return 'ERROR' 160 atom2 = ats[0] 161 ats = self.vf.expandNodes(atom3) 162 if not len(ats): return 'ERROR' 163 atom3 = ats[0] 164 return apply(self.doitWrapper, (atom1, atom2, atom3), kw)
165 166 167
168 -class MeasureTorsion(MeasureAtomCommand):
169 """Compute the torsion between atom1, atom2, atom3, atom4.All coordinates are Cartesian; result is in degrees. 170 \nPackage : Pmv 171 \nModule : measureCommands 172 \nClass : MeasureTorsion 173 \nCommand : measureTorsion 174 \nSynopsis:\n 175 float <- measureTorsion(atom1, atom2, atom3, atom4, **kw) 176 \nRequired Argument:\n 177 atom1 --- first atom 178 \natom2 --- second atom 179 \natom3 --- third atom 180 \natom4 --- fourth atom 181 """ 182 183
184 - def torsion(self, x1, x2, x3, x4):
185 """ 186 Compute the torsion angle between x1, x2, x3, x4. 187 All coordinates are cartesian; result is in degrees. 188 Raises a ValueError if angle is not defined. 189 """ 190 from math import sqrt, acos 191 import Numeric 192 193 tang=0.0 194 assert x1.shape == (3, ) 195 assert x2.shape == (3, ) 196 assert x3.shape == (3, ) 197 assert x4.shape == (3, ) 198 199 a = x1-x2 200 b = x3-x2 201 c = self.vvmult(a, b) 202 203 a = x2-x3 204 b = x4-x3 205 d = self.vvmult(a, b) 206 207 dd=sqrt(Numeric.sum(c*c)) 208 de=sqrt(Numeric.sum(d*d)) 209 210 if dd<0.001 or de<0.001: 211 raise ValueError ( 'Torsion angle undefined, degenerate points') 212 213 vv = Numeric.dot(c, d) / (dd*de); 214 if vv<1.0: tang=vv 215 else: tang= 1.0 216 if tang<-1.0: tang=-1.0 217 tang = acos(tang) 218 tang = tang*57.296 219 220 b = self.vvmult(c, d) 221 if Numeric.dot(a, b) > 0.0: tang = -tang 222 return tang
223 224
225 - def doit(self, atom1, atom2, atom3, atom4):
226 c1 = self.getTransformedCoords(atom1) 227 c2 = self.getTransformedCoords(atom2) 228 c3 = self.getTransformedCoords(atom3) 229 c4 = self.getTransformedCoords(atom4) 230 return self.torsion(c1, c2, c3, c4)
231 232
233 - def __call__(self, atom1, atom2, atom3, atom4, **kw):
234 """float <--- measureTorsion(atom1, atom2, atom3, atom4, **kw) 235 \natom1 --- first atom 236 \natom2 --- second atom 237 \natom3 --- third atom 238 \natom4 --- fourth atom""" 239 ats = self.vf.expandNodes(atom1) 240 if not len(ats): return 'ERROR' 241 atom1 = ats[0] 242 ats = self.vf.expandNodes(atom2) 243 if not len(ats): return 'ERROR' 244 atom2 = ats[0] 245 ats = self.vf.expandNodes(atom3) 246 if not len(ats): return 'ERROR' 247 atom3 = ats[0] 248 ats = self.vf.expandNodes(atom4) 249 if not len(ats): return 'ERROR' 250 atom4 = ats[0] 251 return apply(self.doitWrapper, (atom1, atom2, atom3, atom4), kw)
252 253 254 from opengltk.OpenGL import GL 255
256 -class MeasureGUICommand(MeasureAtomCommand, MVAtomICOM):
257 """Base class for commands which provide measureCommands user-interface.Implements setLength_cb, userpref callback, setLength, guiCallback and stopICOM 258 \nPackage : Pmv 259 \nModule : measureCommands 260 \nClass : MeasureGUICommand 261 """ 262
263 - def __init__(self, func=None):
264 MVCommand.__init__(self, func) 265 MVAtomICOM.__init__(self) 266 self.atomList = [] 267 self.atomCenters = [] 268 self.labelStrs = [] 269 self.labelCenters = [] 270 self.lineVertices = [] 271 self.snakeLength = 4 272 self.continuousUpdate = 0
273 274
275 - def setLength_cb(self, name, oldval, newLength):
276 self.setLength(newLength)
277 278
279 - def continuousUpdate_cb(self, name, oldval, newval):
280 ## ehm = self.vf.GUI.ehm 281 if newval == 'yes': 282 self.continuousUpdate = 1 283 for event in ['<B2-Motion>', '<B3-Motion>', '<Shift-B3-Motion>']: 284 self.vf.GUI.addCameraCallback(event, self.update_cb) 285 ## if self.update_cb not in ehm.eventHandlers[event]: 286 ## ehm.AddCallback(event, self.update_cb) 287 else: 288 self.continuousUpdate = 0 289 for event in ['<B2-Motion>', '<B3-Motion>', '<Shift-B3-Motion>']: 290 self.vf.GUI.removeCameraCallback(event, self.update_cb)
291 ## if self.update_cb in ehm.eventHandlers[event]: 292 ## ehm.RemoveCallback(event, self.update_cb) 293 294
295 - def update_cb(self, event=None):
296 if not len(self.atomList): 297 return 298 vi = self.vf.GUI.VIEWER 299 if vi.redirectTransformToRoot: 300 return 301 if vi.currentObject==vi.rootObject: 302 return 303 self.update()
304 305
306 - def setLength(self, newLength):
307 assert newLength>0, 'can only set to values > 0' 308 self.snakeLength = newLength
309 310
311 - def guiCallback(self, event=None):
312 self.vf.setICOM(self, topCommand=0)
313 314
315 - def setupUndoBefore(self,ats):
316 self.addUndoCall((),{},self.name+'.undo')
317 318
319 - def undo(self,*args, **kw):
320 if len(self.lineVertices): 321 self.atomList = self.atomList[:-1] 322 self.update(forward=0)
323 324
325 - def onRemoveObjectFromViewer(self, mol):
326 lenAts = len(self.atomList) 327 #if cmd has no atoms on its list, nothing to do 328 if not lenAts: 329 return 330 #remove any atoms which are being deleted from viewer 331 self.atomList = AtomSet(self.atomList)-mol.allAtoms 332 #if some have been removed, do an update 333 if lenAts!=len(self.atomList): 334 self.update()
335 336
337 - def startICOM(self):
338 self.vf.setIcomLevel( Atom )
339 340
341 - def initICOM(self, modifier):
342 #when cmd becomes icom and user pref is set, register callback 343 if self.continuousUpdate: 344 # ehm = self.vf.GUI.ehm 345 for event in ['<B2-Motion>', '<B3-Motion>', '<Shift-B3-Motion>']: 346 self.vf.GUI.addCameraCallback(event, self.update_cb)
347 ## if ehm.eventHandlers.has_key(event) and self.update_cb not in \ 348 ## ehm.eventHandlers[event]: 349 ## ehm.AddCallback(event, self.update_cb) 350 351
352 - def stopICOM(self):
353 self.atomList = [] 354 self.atomCenters = [] 355 self.labelStrs = [] 356 self.labelCenters = [] 357 self.lineVertices = [] 358 self.spheres.Set(vertices=[]) 359 #self.spheres.Set(vertices=[], tagModified=False) 360 self.lines.Set(vertices=[], faces=[]) 361 #self.lines.Set(vertices=[], faces=[], tagModified=False) 362 self.labels.Set(vertices=[]) 363 #self.labels.Set(vertices=[], tagModified=False) 364 if hasattr(self, 'fan'): 365 self.arcNormals = [] 366 self.arcVectors = [] 367 self.arcCenters = [] 368 self.fan.Set(vertices=[], vnormals=[]) 369 #self.fan.Set(vertices=[], vnormals=[], tagModified=False) 370 self.vf.GUI.VIEWER.Redraw() 371 #when cmd stops being icom, remove callback 372 ## ehm = self.vf.GUI.ehm 373 for event in ['<B2-Motion>', '<B3-Motion>', '<Shift-B3-Motion>']: 374 self.vf.GUI.removeCameraCallback(event, self.update_cb)
375 ## if ehm.eventHandlers.has_key(event) and self.update_cb in \ 376 ## ehm.eventHandlers[event]: 377 ## ehm.RemoveCallback(event, self.update_cb) 378 379 380
381 -class MeasureDistanceGUICommand(MeasureGUICommand):
382 """Accumulates picked atoms. Draws lines between pairs of selected atoms, labeled by distance (color-coded yellow).Userpref 'measureDistanceSL' sets the 'snakeLength' which is how many distance measures can be displayed at the same time.When more than that number are measured, the first distance measured is no longer labeled. 383 \nPackage : Pmv 384 \nModule : measureCommands 385 \nClass : MeasureDistanceGUICommand 386 \nCommand : measureDistanceGC 387 \nSynopsis:\n 388 distance/None<---measureDistanceGC(atoms) 389 \nRequired Argument:\n 390 atoms --- atom(s) 391 """
392 - def __init__(self, func=None):
393 MeasureGUICommand.__init__(self, func=func) 394 self.flag = self.flag | self.objArgOnly
395
396 - def guiCallback(self, event=None):
397 MeasureGUICommand.guiCallback(self, event) 398 self.vf.setICOM(self.vf.measureDistanceCtrl, modifier='Control_L', topCommand=0)
399 400 # def stopICOM(self): 401 # MeasureGUICommand.stopICOM(self) 402 # self.vf.measureDistanceCtrl.spheres.Set(vertices=[]) 403 # self.measureDistanceCtrl.lines.Set(vertices=[], faces=[]) 404 # self.measureDistanceCtrl.labels.Set(vertices=[]) 405
406 - def onAddCmdToViewer(self):
407 if not self.vf.commands.has_key('setICOM'): 408 self.vf.loadCommand('interactiveCommands', 'setICOM', 'Pmv', 409 topCommand=0) 410 if not self.vf.commands.has_key('measureDistance'): 411 self.vf.loadCommand('measureCommands', 'measureDistance', 412 'Pmv', topCommand=0) 413 if not self.vf.commands.has_key('measureDistanceCntr'): 414 self.vf.loadCommand('measureCommands', 'measureDistanceCtrl', 415 'Pmv', topCommand=0) 416 417 miscGeom = self.vf.GUI.miscGeom 418 measure_geoms = check_measure_geoms(self.vf.GUI) 419 self.masterGeom = Geom('measureDistGeom', shape=(0,0), 420 pickable=0, protected=True) 421 self.masterGeom.isScalable = 0 422 self.vf.GUI.VIEWER.AddObject(self.masterGeom, parent = measure_geoms) 423 self.lines = IndexedPolylines('distLine', materials = ((1,1,0),), 424 inheritMaterial=0, lineWidth=3, 425 stippleLines=1, protected=True) 426 self.labels = GlfLabels(name='distLabel', shape=(0,3), 427 inheritMaterial=0, materials = ((1,1,0),)) 428 self.spheres = Spheres(name='distSpheres', shape=(0,3), 429 inheritMaterial=0, radii=0.2, quality=15, 430 materials = ((1.,1.,0.),), protected=True) 431 for item in [self.lines, self.labels, self.spheres]: 432 self.vf.GUI.VIEWER.AddObject(item, parent=self.masterGeom) 433 434 doc = """Number of labeled distances displayed simultaneousely. 435 Valid values are integers>0""" 436 self.vf.userpref.add('measureDistanceSL', 4, 437 callbackFunc = [self.setLength_cb], 438 validateFunc = lambda x: x>0, doc=doc ) 439 #used after startICOM is invoked 440 doc = """Continuous update of distances if 'transformRoot only' is 441 turned off and viewer's current object is not Root.""" 442 choices = ['yes','no'] 443 self.vf.userpref.add('continuousUpdateDist', 'yes', choices, 444 callbackFunc = [self.continuousUpdate_cb], 445 doc=doc ) 446 self.snakeLength = 4
447 448
449 - def __call__(self, atoms, **kw):
450 """distance/None<---measureDistanceGC(atoms) 451 \natoms --- atom(s)""" 452 if type(atoms) is types.StringType: 453 self.nodeLogString = "'"+atoms+"'" 454 ats = self.vf.expandNodes(atoms) 455 if not len(ats): return 'ERROR' 456 return apply(self.doitWrapper, (ats,), kw)
457 458
459 - def doit(self, ats):
460 for at in ats: 461 lenAts = len(self.atomList) 462 if lenAts and lenAts%2!=0 and at==self.atomList[-1]: 463 continue 464 self.atomList.append(at) 465 if len(self.atomList)>self.snakeLength+1: 466 self.atomList = self.atomList[-self.snakeLength-1:] 467 self.update() 468 if len(self.labelStrs): 469 return float(self.labelStrs[-1])
470 471
472 - def update(self, forward=1, event=None):
473 if not len(self.atomList): 474 self.spheres.Set(vertices=[]) 475 #self.spheres.Set(vertices=[], tagModified=False) 476 self.labels.Set(vertices=[]) 477 #self.labels.Set(vertices=[], tagModified=False) 478 self.lines.Set(vertices=[]) 479 #self.lines.Set(vertices=[], tagModified=False) 480 return 481 self.lineVertices=[] 482 #each time have to recalculate lineVertices 483 for at in self.atomList: 484 c1 = self.getTransformedCoords(at) 485 self.lineVertices.append(tuple(c1)) 486 self.spheres.Set(vertices=self.lineVertices) 487 #self.spheres.Set(vertices=self.lineVertices, tagModified=False) 488 #setting spheres doesn't trigger redraw so do it explicitly 489 self.vf.GUI.VIEWER.Redraw() 490 #each time have to recalculate labelCenters and labelStrs 491 if len(self.lineVertices)>1: 492 self.labelCenters = [] 493 self.labelStrs = [] 494 numLabels = len(self.lineVertices)-1 495 for i in range(numLabels): 496 c0 = Numeric.array(self.lineVertices[i]) 497 c1 = Numeric.array(self.lineVertices[i+1]) 498 newCenter = tuple((c1 + c0)/2.0) 499 self.labelCenters.append(newCenter) 500 at1 = self.atomList[i] 501 at2 = self.atomList[i+1] 502 d = self.vf.measureDistance(at1, at2, topCommand=0) 503 dLabel = '%.3f' %d 504 self.labelStrs.append(dLabel) 505 labLimit = self.snakeLength 506 #correct length of labels here 507 if len(self.labelStrs)>labLimit: 508 self.labelCenters = self.labelCenters[-labLimit:] 509 self.labelStrs = self.labelStrs[-labLimit:] 510 self.lineVertices = self.lineVertices[-(labLimit+1):] 511 self.labels.Set(vertices=self.labelCenters,labels=self.labelStrs) 512 #tagModified=False) 513 self.lines.Set(vertices=self.lineVertices, type=GL.GL_LINE_STRIP, 514 faces=[ range(len(self.lineVertices)) ], freshape=1) 515 #tagModified=False) 516 517 elif len(self.lineVertices)==1 and len(self.labelCenters)==1: 518 #this fixes case of stepping back over 1st label 519 self.labels.Set(vertices=[]) 520 #self.labels.Set(vertices=[], tagModified=False) 521 self.lines.Set(vertices=[])
522 #self.lines.Set(vertices=[], tagModified=False) 523 524 525 MeasureDistanceGUICommandGUI = CommandGUI() 526 MeasureDistanceGUICommandGUI.addMenuCommand('menuRoot', 'Display', 'Distance', 527 cascadeName = 'Measure') 528
529 -class MeasureDistanceCtrlGUICommand(MeasureGUICommand):
530 """Accumulates picked atoms. Draws lines between pairs of selected atoms, labeled by distance (color-coded yellow).Userpref 'measureDistanceSL' sets the 'snakeLength' which is how many distance measures can be displayed at the same time.When more than that number are measured, the first distance measured is no longer labeled. 531 \nPackage : Pmv 532 \nModule : measureCommands 533 \nClass : MeasureDistanceGUICommand 534 \nCommand : measureDistanceGC 535 \nSynopsis:\n 536 distance/None<---measureDistanceGC(atoms) 537 \nRequired Argument:\n 538 atoms --- atom(s) 539 """
540 - def __init__(self, func=None):
541 MeasureGUICommand.__init__(self, func=func) 542 self.flag = self.flag | self.objArgOnly
543
544 - def onAddCmdToViewer(self):
545 if not self.vf.commands.has_key('setICOM'): 546 self.vf.loadCommand('interactiveCommands', 'setICOM', 'Pmv', 547 topCommand=0) 548 if not self.vf.commands.has_key('measureDistance'): 549 self.vf.loadCommand('measureCommands', 'measureDistance', 550 'Pmv', topCommand=0) 551 552 miscGeom = self.vf.GUI.miscGeom 553 measure_geoms = check_measure_geoms(self.vf.GUI) 554 self.masterGeom = Geom('measureDistGeomCtrl', shape=(0,0), 555 pickable=0 ) 556 self.masterGeom.isScalable = 0 557 self.vf.GUI.VIEWER.AddObject(self.masterGeom, parent = measure_geoms) 558 self.lines = IndexedPolylines('distLine', materials = ((1,1,0),), 559 inheritMaterial=0, lineWidth=3., 560 stippleLines=1) 561 self.labels = GlfLabels(name='distLabel', shape=(0,3), 562 inheritMaterial=0, materials = ((1,1,0),)) 563 self.spheres = Spheres(name='distSpheres', shape=(0,3), 564 inheritMaterial=0, radii=0.2, quality=15, 565 materials = ((1.,1.,0.),)) 566 for item in [self.lines, self.labels, self.spheres]: 567 self.vf.GUI.VIEWER.AddObject(item, parent=self.masterGeom)
568 569
570 - def __call__(self, atoms, **kw):
571 """distance/None<---measureDistanceGC(atoms) 572 \natoms --- atom(s)""" 573 if type(atoms) is types.StringType: 574 self.nodeLogString = "'"+atoms+"'" 575 ats = self.vf.expandNodes(atoms) 576 if not len(ats): return 'ERROR' 577 return apply(self.doitWrapper, (ats,), kw)
578 579
580 - def doit(self, ats):
581 for at in ats: 582 lenAts = len(self.atomList) 583 if lenAts and at==self.atomList[-1]: 584 continue 585 self.atomList.append(at) 586 self.update() 587 if len(self.labelStrs): 588 return float(self.labelStrs[-1])
589 590
591 - def update(self, forward=1, event=None):
592 if not len(self.atomList): 593 self.spheres.Set(vertices=[]) 594 #self.spheres.Set(vertices=[], tagModified=False) 595 self.labels.Set(vertices=[]) 596 #self.labels.Set(vertices=[], tagModified=False) 597 self.lines.Set(vertices=[]) 598 #self.lines.Set(vertices=[], tagModified=False) 599 return 600 self.lineVertices=[] 601 #each time have to recalculate lineVertices 602 for at in self.atomList: 603 c1 = self.getTransformedCoords(at) 604 self.lineVertices.append(tuple(c1)) 605 self.spheres.Set(vertices=self.lineVertices) 606 #self.spheres.Set(vertices=self.lineVertices, tagModified=False) 607 #setting spheres doesn't trigger redraw so do it explicitly 608 self.vf.GUI.VIEWER.Redraw() 609 #each time have to recalculate labelCenters and labelStrs 610 if len(self.lineVertices)>1: 611 self.labelCenters = [] 612 self.labelStrs = [] 613 self.faces = [] 614 numLabels = len(self.lineVertices)-1 615 for i in range(0,numLabels,2): 616 c0 = Numeric.array(self.lineVertices[i]) 617 c1 = Numeric.array(self.lineVertices[i+1]) 618 newCenter = tuple((c1 + c0)/2.0) 619 self.labelCenters.append(newCenter) 620 at1 = self.atomList[i] 621 at2 = self.atomList[i+1] 622 d = self.vf.measureDistance(at1, at2, topCommand=0) 623 dLabel = '%.3f' %d 624 self.labelStrs.append(dLabel) 625 self.faces.append([i,i+1]) 626 #correct length of labels here 627 self.labels.Set(vertices=self.labelCenters,labels=self.labelStrs) 628 #tagModified=False) 629 self.lines.Set(vertices=self.lineVertices, type=GL.GL_LINE_STRIP, 630 faces=self.faces, freshape=1) 631 #tagModified=False) 632 633 elif len(self.lineVertices)==1 and len(self.labelCenters)==1: 634 #this fixes case of stepping back over 1st label 635 self.labels.Set(vertices=[]) 636 #self.labels.Set(vertices=[], tagModified=False) 637 self.lines.Set(vertices=[])
638 #self.lines.Set(vertices=[], tagModified=False) 639 640
641 -class MeasureAngleGUICommand(MeasureGUICommand):
642 """Accumulates picked atoms.Draws fans, lines and labels labelling the angle between trios of selected atoms (color-coded orange).Userpref 'measureAngleSL' sets the 'snakeLength' which is how many angle measureDisplays can be seen at the same time.When more than that number are measured, the first angle measured is no longer labeled. 643 \nPackage : Pmv 644 \nModule : measureCommands 645 \nClass : MeasureAngleGUICommand 646 \nCommand : measureAngleGC 647 \nSynopsis:\n 648 angle/None<---measureAngleGC(atoms) 649 \nRequired Argument:\n 650 atoms --- atom(s) 651 \nangle --- returned when the number of atoms is a multiple of 3 652 """ 653
654 - def __init__(self, func=None):
655 MeasureGUICommand.__init__(self, func=func) 656 self.flag = self.flag | self.objArgOnly
657
658 - def onAddCmdToViewer(self):
659 from DejaVu.Arcs3D import Fan3D 660 from DejaVu.bitPatterns import pat3 661 self.arcNormals = [] 662 self.arcVectors = [] 663 self.arcCenters = [] 664 if not self.vf.commands.has_key('setICOM'): 665 self.vf.loadCommand('interactiveCommands', 'setICOM', 'Pmv', 666 topCommand=0) 667 if not self.vf.commands.has_key('measureAngle'): 668 self.vf.loadCommand('measureCommands', 'measureAngle', 'Pmv', 669 topCommand=0) 670 671 miscGeom = self.vf.GUI.miscGeom 672 measure_geoms = check_measure_geoms(self.vf.GUI) 673 674 self.masterGeom = Geom('measureAngleGeom',shape=(0,0), 675 pickable=0, protected=True) 676 self.masterGeom.isScalable = 0 677 self.vf.GUI.VIEWER.AddObject(self.masterGeom, parent = measure_geoms) 678 self.lines = IndexedPolylines('angleLine', materials = ((1,.5,0),), 679 inheritMaterial=0, lineWidth=3, 680 stippleLines=1, protected=True) 681 self.fan = Fan3D('angles', materials = ((1,.5,0),), culling=GL.GL_NONE, 682 inheritMaterial=0, stipplePolygons=1, radii=(1.,), 683 inheritStipplePolygons=0, backPolyMode=GL.GL_FILL) 684 self.fan.polygonstipple.Set(pattern=pat3) 685 #self.fan.polygonstipple.Set(pattern=pat3, tagModified=False) 686 #self.fan.RenderMode(GL.GL_FILL) 687 #self.fan.RenderMode(GL.GL_FILL, face=GL.GL_BACK) 688 self.labels = GlfLabels(name='angleLabel', shape=(0,3), 689 inheritMaterial=0, materials = ((1.,.5,0.),)) 690 self.spheres = Spheres(name='angleSpheres', shape=(0,3), 691 inheritMaterial=0, radii=0.2, quality=15, 692 materials = ((1.,.5,0.),), protected=True) 693 for item in [self.lines,self.labels, self.spheres, self.fan]: 694 self.vf.GUI.VIEWER.AddObject(item, parent=self.masterGeom) 695 doc = """Number of labeled angles displayed. 696 Valid values are integers>0""" 697 self.vf.userpref.add('measureAngleSL', 4, 698 callbackFunc = [self.setLength_cb], 699 validateFunc = lambda x: x>0, doc=doc) 700 #used after startICOM is invoked 701 doc = """Continuous update of angles if 'transformRoot only' is 702 turned off and viewer's current object is not Root.""" 703 choices = ['yes','no'] 704 self.vf.userpref.add('continuousUpdateAngle', 'yes', choices, 705 callbackFunc = [self.continuousUpdate_cb], 706 doc=doc ) 707 self.snakeLength = 4
708 709 710
711 - def __call__(self, atoms, **kw):
712 """angle/None<---measureAngleGC(atoms) 713 \natoms --- atom(s) 714 \nangle --- returned when the number of atoms is a multiple of 3""" 715 if type(atoms) is types.StringType: 716 self.nodeLogString = "'"+atoms+"'" 717 ats = self.vf.expandNodes(atoms) 718 if not len(ats): return 'ERROR' 719 return apply(self.doitWrapper, (ats,), kw)
720 721
722 - def doit(self, ats):
723 for at in ats: 724 lenAts = len(self.atomList) 725 if lenAts and lenAts%3!=0 and at==self.atomList[-1]: 726 continue 727 self.atomList.append(at) 728 l = len(self.atomList) 729 #for this command, reset after every 3 730 #wrap when len(atoms)=3*self.snakeLength+1 731 if l>3*self.snakeLength: 732 self.atomList = self.atomList[3:] 733 self.update() 734 if len(self.labelStrs) and len(self.atomList)%3==0: 735 return float(self.labelStrs[-1])
736 737
738 - def update(self,forward=1, event=None):
739 if not len(self.atomList): 740 self.spheres.Set(vertices=[]) 741 #self.spheres.Set(vertices=[], tagModified=False) 742 self.labels.Set(vertices=[]) 743 #self.labels.Set(vertices=[], tagModified=False) 744 self.lines.Set(vertices=[]) 745 #self.lines.Set(vertices=[], tagModified=False) 746 self.vf.GUI.VIEWER.Redraw() 747 return 748 limit = self.snakeLength 749 #each time have to recalculate lineVertices 750 self.lineVertices=[] 751 for at in self.atomList: 752 c1 = self.getTransformedCoords(at) 753 self.lineVertices.append(tuple(c1)) 754 #display spheres: 755 self.spheres.Set(vertices=self.lineVertices) 756 #self.spheres.Set(vertices=self.lineVertices, tagModified=False) 757 self.vf.GUI.VIEWER.Redraw() 758 #label with angle 759 #lines between spheres are only drawn when angle completed 760 #that is, len(ats)%3=0 761 if len(self.lineVertices)<3: 762 self.labels.Set(vertices=[]) 763 #self.labels.Set(vertices=[], tagModified=False) 764 self.fan.Set(vertices=[]) 765 #self.fan.Set(vertices=[], tagModified=False) 766 self.lines.Set(vertices=[]) 767 #self.lines.Set(vertices=[], tagModified=False) 768 else: 769 #should all of these be reset? 770 self.arcNormals=[] 771 self.arcVectors=[] 772 self.arcCenters=[] 773 self.labelCenters=[] 774 self.labelStrs=[] 775 #rebuild arcNormals, arcVectors, arcCenters 776 #labelCenters, labelStrs, 777 #this gets done lenATs/3 times 778 numItems = len(self.atomList)/3 779 for i in range(numItems): 780 at0 = self.atomList[i*3] 781 at1 = self.atomList[i*3+1] 782 at2 = self.atomList[i*3+2] 783 ang = self.vf.measureAngle(at0, at1, at2, topCommand=0) 784 v, n = self.normal(at0, at1, at2) 785 self.arcNormals.append(n) 786 #self.arcNormals = self.arcNormals[-limit:] 787 self.arcVectors.append(v) 788 #self.arcVectors = self.arcVectors[-limit:] 789 self.arcCenters.append(self.getTransformedCoords(at1)) 790 #self.arcCenters = self.arcCenters[-limit:] 791 angLabel = '%.3f' %ang 792 self.labelStrs.append(angLabel) 793 c0 = self.getTransformedCoords(at0) 794 c1 = self.getTransformedCoords(at2) 795 newcenter = tuple((c0+c1)/2.0) 796 self.labelCenters.append(newcenter) 797 #to reset labels, lines and fan, EACH TIME 798 self.labels.Set(vertices=self.labelCenters,labels=self.labelStrs) 799 #tagModified=False) 800 faces = range(numItems*3) 801 faces = Numeric.reshape(faces, (-1,3)) 802 self.lines.Set(vertices=self.lineVertices, type=GL.GL_LINE_STRIP, 803 faces=faces, freshape=1) 804 #faces=faces, freshape=1, tagModified=False) 805 self.fan.angles=map(float, self.labelStrs) 806 self.fan.vectors=self.arcVectors 807 self.fan.Set(vertices=self.arcCenters, vnormals=self.arcNormals)
808 #tagModified=False) 809 810
811 - def normal(self, at0, at1, at2):
812 c0 = self.getTransformedCoords(at0) 813 c1 = self.getTransformedCoords(at1) 814 c2 = self.getTransformedCoords(at2) 815 v1 = c1-c0 816 v2 = c1-c2 817 l1 = math.sqrt(Numeric.sum(v1*v1)) 818 l2 = math.sqrt(Numeric.sum(v2*v2)) 819 #FIXME 820 #protect against divide by 0 821 n = self.vvmult(v1/l1,v2/l2) 822 n = n/math.sqrt(Numeric.sum(n*n)) 823 return -v2/l2, n.astype('f')
824 825 826 827 MeasureAngleGUICommandGUI = CommandGUI() 828 MeasureAngleGUICommandGUI.addMenuCommand('menuRoot', 'Display', 'Angle', 829 cascadeName = 'Measure') 830 831 832
833 -class MeasureTorsionGUICommand(MeasureGUICommand):
834 """Label torsion between four atoms (color coded cyan) Accumulates picked atoms.Draws polygons and labels showing the torsion angle between groups of 4 selected atoms (color-coded cyan).Userpref 'measureTorsionSL' sets the 'snakeLength' which is how many torsion measureDisplays can be seen at the same time.When more than that number are measured, the first torsion measured is no longer labeled. 835 \nPackage : Pmv 836 \nModule : measureCommands 837 \nClass : MeasureTorsionGUICommand 838 \nCommand : measureTorsionGC 839 \nSynopsis:\n 840 torsion/None<---measureTorsionGC(atoms) 841 \nRequired Argument:\n 842 atoms --- the atom(s) 843 \ntorsion --- returned when the number of atoms is a multiple of 4 844 """ 845
846 - def __init__(self, func=None):
847 MeasureGUICommand.__init__(self, func=func) 848 self.flag = self.flag | self.objArgOnly
849
850 - def onAddCmdToViewer(self):
851 from DejaVu.bitPatterns import pat3 852 from DejaVu.IndexedPolygons import IndexedPolygons 853 if not self.vf.commands.has_key('setICOM'): 854 self.vf.loadCommand('interactiveCommands', 'setICOM', 'Pmv', 855 topCommand=0) 856 if not self.vf.commands.has_key('measureAngle'): 857 self.vf.loadCommand('measureCommands', 'measureAngle', 'Pmv', 858 topCommand=0) 859 860 miscGeom = self.vf.GUI.miscGeom 861 measure_geoms = check_measure_geoms(self.vf.GUI) 862 self.masterGeom = Geom('measureTorsionGeom',shape=(0,0), 863 pickable=0, protected=True) 864 self.masterGeom.isScalable = 0 865 self.vf.GUI.VIEWER.AddObject(self.masterGeom, parent = measure_geoms) 866 self.lines = IndexedPolygons('torsionLine', materials = ((0,1,1),), 867 culling=GL.GL_NONE,inheritStipplePolygons=0, 868 inheritMaterial=0, stipplePolygons=1, 869 backPolyMode=GL.GL_FILL, 870 frontPolyMode=GL.GL_FILL, protected=True,) 871 if self.vf.userpref['sharpColorBoundariesForMsms']['value'] == 'blur': 872 self.lines.Set(inheritSharpColorBoundaries=False, sharpColorBoundaries=False,) 873 self.lines.polygonstipple.Set(pattern=pat3) 874 #self.lines.polygonstipple.Set(pattern=pat3, tagModified=False) 875 #self.lines.RenderMode(GL.GL_FILL) 876 #self.lines.RenderMode(GL.GL_FILL, face=GL.GL_BACK) 877 self.labels = GlfLabels(name='torsionLabel', shape=(0,3), 878 inheritMaterial=0, materials = ((0,1,1),)) 879 self.spheres = Spheres(name='torsionSpheres', shape=(0,3), 880 inheritMaterial=0, radii=0.2, quality=15, 881 materials = ((0.,1.,1.),), protected=True) 882 for item in [self.lines, self.labels, self.spheres]: 883 self.vf.GUI.VIEWER.AddObject(item, parent=self.masterGeom) 884 doc = """Number of labeled torsions displayed. 885 Valid values are integers>0""" 886 self.vf.userpref.add('measureTorsionSL', 4, 887 callbackFunc=[self.setLength_cb], 888 validateFunc = lambda x: x>0, doc=doc) 889 #used after startICOM is invoked 890 doc = """Continuous update of torsions if 'transformRoot only' is 891 turned off and viewer's current object is not Root.""" 892 choices = ['yes','no'] 893 self.vf.userpref.add('continuousUpdateTorsion', 'yes', choices, 894 callbackFunc = [self.continuousUpdate_cb], 895 doc=doc ) 896 self.snakeLength = 4
897 898
899 - def __call__(self, atoms, **kw):
900 """torsion/None<-measureTorsionGC(atoms) 901 \natoms --- the atom(s) 902 \ntorsion --- returned when the number of atoms is a multiple of 4""" 903 if type(atoms) is types.StringType: 904 self.nodeLogString = "'"+atoms+"'" 905 ats = self.vf.expandNodes(atoms) 906 if not len(ats): return 'ERROR' 907 return apply(self.doitWrapper, (ats,), kw)
908 909
910 - def doit(self, ats):
911 for at in ats: 912 lenAts = len(self.atomList) 913 if lenAts and lenAts%4!=0 and at==self.atomList[-1]: 914 continue 915 self.atomList.append(at) 916 if len(self.atomList)>4*self.snakeLength: 917 self.atomList = self.atomList[4:] 918 self.update() 919 if len(self.labelStrs) and len(self.atomList)%4==0: 920 return float(self.labelStrs[-1])
921 922
923 - def update(self, forward=1, event=None):
924 if not len(self.atomList): 925 self.spheres.Set(vertices=[]) 926 #self.spheres.Set(vertices=[], tagModified=False) 927 self.labels.Set(vertices=[]) 928 #self.labels.Set(vertices=[], tagModified=False) 929 self.lines.Set(vertices=[]) 930 #self.lines.Set(vertices=[], tagModified=False) 931 self.vf.GUI.VIEWER.Redraw() 932 return 933 limit = self.snakeLength 934 #each time have to recalculate lineVertices 935 self.lineVertices=[] 936 for at in self.atomList: 937 c1 = self.getTransformedCoords(at) 938 self.lineVertices.append(tuple(c1)) 939 #display spheres: 940 self.spheres.Set(vertices=self.lineVertices) 941 #self.spheres.Set(vertices=self.lineVertices, tagModified=False) 942 self.vf.GUI.VIEWER.Redraw() 943 #label with torsion 944 #lines between spheres are only drawn when angle completed 945 #that is, len(ats)%4=0 946 if len(self.lineVertices)<4: 947 self.labels.Set(vertices=[]) 948 #self.labels.Set(vertices=[], tagModified=False) 949 self.lines.Set(vertices=[]) 950 #self.lines.Set(vertices=[], tagModified=False) 951 else: 952 #rebuild labels and polygons each time 953 self.labelCenters=[] 954 self.labelStrs=[] 955 #labelCenters, labelStrs, 956 #this gets done lenATs/4 times 957 numItems = len(self.atomList)/4 958 for i in range(numItems): 959 at0 = self.atomList[i*4] 960 at1 = self.atomList[i*4+1] 961 at2 = self.atomList[i*4+2] 962 at3 = self.atomList[i*4+3] 963 torsion = self.vf.measureTorsion(at0, at1, at2, at3, topCommand=0) 964 torsionLabel = '%.3f' %torsion 965 self.labelStrs.append(torsionLabel) 966 c0 = self.getTransformedCoords(at0) 967 c1 = self.getTransformedCoords(at3) 968 newcenter = tuple((c0+c1)/2.0) 969 self.labelCenters.append(newcenter) 970 #to reset labels, lines and fan, EACH TIME 971 self.labels.Set(vertices=self.labelCenters,labels=self.labelStrs) 972 #tagModified=False) 973 #if len(self.lineVertices)%4!=0: 974 #self.lineVertices = self.lineVertices[:numItems*4] 975 #only draw lines in groups of 4 976 #numItems*4 977 if len(self.atomList)%4==0: 978 faces = range(numItems*4) 979 faces = Numeric.reshape(faces, (-1,4)) 980 ###FIX THIS 981 ###on undo: if you have just wrapped, undoing the next pt 982 ###breaks because trying to set the vertices uses the old 983 ###faces 984 if not forward: 985 self.lines.Set(vertices=[], faces=[]) 986 #self.lines.Set(vertices=[], faces=[], tagModified=False) 987 self.lines.Set(vertices=self.lineVertices, faces=faces, 988 freshape=1) 989 #freshape=1, tagModified=False) 990 self.vf.GUI.VIEWER.Redraw() 991 else: 992 #this only works going forward: undo breaks here 993 if len(self.lines.faceSet.faces.array)>numItems: 994 faces = range((numItems+1)*4) 995 faces = Numeric.reshape(faces, (-1,4)) 996 if forward: 997 faces = faces[1:] 998 else: 999 faces = faces[:-1] 1000 self.lines.Set(faces=faces) 1001 self.vf.GUI.VIEWER.Redraw()
1002 1003 1004 1005 MeasureTorsionGUICommandGUI = CommandGUI() 1006 MeasureTorsionGUICommandGUI.addMenuCommand('menuRoot', 'Display', 'Torsion', 1007 cascadeName = 'Measure') 1008 1009 1010 commandList = [ 1011 {'name':'measureDistance', 'cmd':MeasureDistance(), 'gui':None}, 1012 {'name':'measureAngle', 'cmd':MeasureAngle(), 'gui':None}, 1013 {'name':'measureTorsion', 'cmd':MeasureTorsion(), 'gui':None}, 1014 {'name':'measureDistanceGC', 'cmd':MeasureDistanceGUICommand(), 1015 'gui':MeasureDistanceGUICommandGUI}, 1016 {'name':'measureDistanceCtrl', 'cmd':MeasureDistanceCtrlGUICommand(), 1017 'gui':None}, 1018 {'name':'measureAngleGC', 'cmd':MeasureAngleGUICommand(), 1019 'gui':MeasureAngleGUICommandGUI}, 1020 {'name':'measureTorsionGC', 'cmd':MeasureTorsionGUICommand(), 1021 'gui':MeasureTorsionGUICommandGUI}, 1022 ] 1023 1024
1025 -def initModule(viewer):
1026 for dict in commandList: 1027 viewer.addCommand(dict['cmd'], dict['name'], dict['gui'])
1028