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

Source Code for Module Pmv.mvCommand

  1  ############################################################################# 
  2  # 
  3  # Author: Michel F. SANNER 
  4  # 
  5  # Copyright: M. Sanner TSRI 2000 
  6  # 
  7  ############################################################################# 
  8   
  9  # $Header: /opt/cvs/python/packages/share1.5/Pmv/mvCommand.py,v 1.41 2007/05/01 17:44:36 sargis Exp $ 
 10  # 
 11  # $Id: mvCommand.py,v 1.41 2007/05/01 17:44:36 sargis Exp $ 
 12  # 
 13   
 14  import types, Numeric, warnings 
 15   
 16  from ViewerFramework.VFCommand import Command, CommandGUI, \ 
 17       InteractiveCmdCaller, ActiveObject 
 18  from MolKit.tree import TreeNode, TreeNodeSet 
 19  from MolKit.molecule import Molecule, Atom, BondSet 
 20  from MolKit.protein import Protein, Chain, Residue 
 21  from mglutil.util.uniq import uniq3 
 22   
 23  # used by BindGeomToMolecularFragment 
 24  from mglutil.gui.InputForm.Tk.gui import InputFormDescr 
 25  import Pmw, Tkinter, string 
 26  from MolKit.molecule import AtomSet, Atom 
 27   
28 -class MVInteractiveCmdCaller(InteractiveCmdCaller):
29 """subclass InteractiveCmdCaller to add the concept of selection level 30 This object holds a dictionary of commands and a level. Both are active 31 objects. 32 """ 33
34 - def __init__(self, vf, level=Molecule):
35 apply( InteractiveCmdCaller.__init__, (self, vf) ) 36 self.level = ActiveObject(level) 37 self.levelColors = { 38 'Atom':(1.,1.,0.), 39 'Residue':(0.,1.,0.), 40 'Chain':(0.,1.,1.), 41 'Molecule':(1.,0.,0.), 42 'Protein':(1.,0.,0.), 43 } 44 if self.vf.hasGui: 45 for c in self.vf.GUI.VIEWER.cameras: 46 c.selectionColor = self.levelColors[level.__name__]
47 48
49 - def execCmd(self, objects):
50 # objects is an AtomSet 51 if len(objects)==0: return 52 if len(self.commands.value)==0: return 53 54 # Add a check in case objects is not a TreeNodeSet, i.e geometry. 55 if isinstance(objects, TreeNodeSet) and \ 56 not isinstance(objects, BondSet) : 57 # change objects from atom to current selected level 58 typ = self.level.value 59 if typ in [Molecule, Protein]: 60 nodes = objects.findType(Molecule) 61 if nodes is None: 62 nodes = objects.findType(Protein) 63 else: 64 nodes = objects.findType(typ) 65 nodes = nodes.uniq() 66 67 # if we are not at the Atom level we have to create the 68 # pickedInstances for each node 69 pickedInstances = {} 70 for n in nodes: 71 pickedInstances[n] = [] 72 if self.level.value != Atom: 73 for atom in objects: 74 p = atom.parent 75 while p and not isinstance(p,self.level.value): 76 p = p.parent 77 if p: 78 pickedInstances[n].extend( atom.pickedInstances ) 79 # now set the pickedInstances attribute for all nodes 80 for n in nodes: 81 n.pickedInstances = pickedInstances[n] 82 83 else: 84 nodes = objects 85 if nodes: 86 InteractiveCmdCaller.execCmd(self, nodes)
87 88
89 - def setLevel(self, Klass, KlassSet=None):
90 if Klass==self.vf.ICmdCaller.level.value: 91 return self.vf.ICmdCaller.level.value 92 self.vf.ICmdCaller.level.Set(Klass) 93 if self.vf.hasGui: 94 for c in self.vf.GUI.VIEWER.cameras: 95 c.selectionColor = self.levelColors[Klass.__name__]
96 97 98 from ViewerFramework.VFCommand import ICOM 99
100 -class MVICOM(ICOM):
101 """Specialize ViewerFramework.VFCommand.ICOM class for a molecular viewer 102 """ 103 pass
104 105 106
107 -class MVAtomICOM(MVICOM):
108 """Specialize ViewerFramework.VFCommand.ICOM class for a molecular viewer. 109 use this mixin class if 110 """ 111
112 - def getObjects(self, pick):
113 """to be implemented by sub-class""" 114 return self.vf.findPickedAtoms(pick)
115 116 117
118 -class MVBondICOM(MVICOM):
119 """Specialize ViewerFramework.VFCommand.ICOM class for a molecular viewer. 120 use this mixin class if 121 """ 122
123 - def getObjects(self, pick):
124 return self.vf.findPickedBonds(pick)
125 126 127
128 -class MVCommand(Command):
129 """ 130 Base class for command objects for a molecule viewer. 131 132 Base class for command objects for a molecule viewer 133 This command class derives from the ViewerFramework.VFCommand.Command class. 134 Classes derived from that class can be added to a MoleculeViewer using the 135 addCommand method. 136 To write a command the programmer has to create a class MyCmd derived from the 137 MVCommand class and should overwrite the following methods when relevant. 138 139 Information on how to write an inputForm is also available at 140 http://www.scripps.edu/~sanner/software/python/inputform/tableOfContent.html 141 142 __init__(self, func=None) 143 The constructor has to be overwritten to set the self.flag attribute 144 properly. 145 self.objArgOnly is turned on when the doit only has one required 146 argument which is the current selection. 147 This will automatically make this command a Picking command 148 self.negateKw is turned on when the doit method takes a boolean 149 flag negate which makes this command undoable. 150 151 see Pmv.colorCommands.color command class for an example 152 153 __call__(self, *args, **kw): 154 Entrypoint to the command from the command line. It overloads 155 calling the object (command) as a function, which enables calling 156 an instance of a command as a method of a viewer once it it has 157 been loaded. 158 159 Typically, this method checks the arguments supplied and calls the 160 doitWrapper method with the arguments supplied. 161 162 This method needs to have the same signature than the doit. 163 A documentation string supplying the following information will be 164 displayed in a tooltip when calling the command from the python 165 idle shell. 166 This documentation string should provide the synopsis of the 167 command and a description of the arguments. 168 169 See Pmv.colorCommands.py, Pmv.displayCommands.py for good example 170 of documentation string. 171 172 guiCallback(self, event=None, *args, **kw): 173 This method is bound by default as the callback of the GUI item 174 associated with the command. 175 It is the entrypoint of the command through the GUI. 176 177 It typically creates an inputform allowing the user to specify any 178 argument required for the command. 179 The command method showForm is typically called with the name of 180 the form to be created and a bunch of optional argument described 181 later. 182 Once all the parameters are known, the doitWrapper method is 183 called to carry out the command. 184 185 The old behavior was to call self.vf.getUserInput. The showForm method 186 is a command method and replace the getUserInput method of the 187 ViewerFramework. Although some commands still implement the 188 getUserInput mechanism. 189 190 191 buildFormDescr(self, formName): 192 This method typically creates the inputform descriptor used by the 193 guiCallback method to get user input for the command. 194 The formName is a string which will be used as a key in the 195 cmdForms dictionary to store the form information. 196 This method is called by self.showForm 197 This method returns an instance of an InputFormDescr which is the 198 object describing a inputform in ViewerFramework. 199 More information can be found in mglutil/gui/InputForm/Tk/gui.py 200 201 For an example see: 202 Pmv.secondaryStructureCommands.py module 203 Almost all the PMV command implement an inputform. 204 205 doit(self, *args, **kw): 206 This method does the actual work. It should not implement any 207 functionality that should be available outside the application 208 for scripting purposes. This method should call such functions or 209 object. 210 211 setUpUndo(self, *args, **kw): 212 Typically this method should be implemented if the command is 213 undoable. It should have the same signature than the doit and the 214 __call__ method. 215 216 See displayCommands and colorCommands for a good example 217 218 Of course, one is free to overwrite any of these methods and for instance 219 rename doit using a more appropriate name. But, when doing so, 220 the programmer has to overwrite both __call__ and guiCallback to call 221 the right method to carry out the work. 222 223 Besides these methods which are required to be implemented in a command there 224 are the following optional methods: 225 strArg(self, arg): 226 Method to turn a command argument into a string. Used by the log 227 method to generate a log string for the command. 228 This method can be overwritten or extended by classes subclassing 229 Command in order to handle properly instances of objects defined 230 by the application when not handled properly by the base class. 231 This method has been overwritten to be 'Molecule' aware. 232 If you need to handle an argument a particular way this method should 233 be overwritten. 234 235 checkDependencies(): 236 This method called when command is loaded. It typically checks for dependencies and 237 if all the dependencies are not found the command won't be loaded. 238 See msmsCommands for an example 239 240 onAddCmdToViewer(): 241 (previously initCommand) 242 onAddCmdToViewer is called once when a command is loaded into a 243 Viewer. 244 Typically, this method : 245 takes care of dependencies (i.e. load other commands that 246 might be required) 247 creates/initializes variables. 248 see Pmv.traceCommands.py, Pmv.secondaryStructureCommands.py etc... 249 250 customizeGUI(self): 251 (obsolete) 252 method allowing to modify the GUI associated with a command 253 254 onAddObjectToViewer(self, obj): 255 (previously named initGeom) 256 When a command is loaded that implements an onAddObjectToViewer 257 function,this function id called for every object present in the 258 application. 259 Once the command is loaded, this function will be called for every 260 new object added to the application. 261 In general, onAddObjectToViewer is used by a command to add a new 262 geometry to the geometry container of an object. 263 It is also where picking events and building arrays of colors specific 264 to each geometry can be registered. 265 266 See secondaryStructureCommands, msmsCommadns etc.. for an example 267 268 onRemoveObjectFromViewer(self, obj): 269 In python if all references to an object are not deleted, memory 270 space occupied by that object even after its deletion is not freed. 271 272 This function will be called for every object removed (deleted) from 273 the application. 274 When references to an object are created inside a command which are 275 not related to the geomContainer of the object, in order to prevent 276 memory leak this command has to implement an onRemoveObjectFromViewer 277 to delete those references. 278 See secondaryStructureCommands for an example 279 280 281 The following methods are helper methods. 282 log(self, *args, **kw): 283 Method to log a command. args provides a list of positional 284 arguments and kw is a dictionary of named arguments. This method 285 loops over both args and kw and builds a string representation of 286 their values. When a class is passed as one the arguments, an 287 additional command is logged to load that class. 288 This method also sets the command's lastCmdLog member. 289 290 showForm(self, *args, **kw): 291 292 If the inputForm object associated with the given formName already 293 exists than it just deiconify the forms unless the force argument is 294 set to true. 295 Otherwise, showForm calls buildFormDescr with the given 296 formName, then it creates an instance of InputForm with the given 297 parameters and stores this object in the cmdForms dictionary, where 298 the key is the formName and the value the InputForm object. 299 (see the showForm documentation string for the description of the 300 arguments) 301 showForm will return the dictionary containing the values of the 302 widgets if modal or blocking other it will return the form object. 303 304 If a command generates geometries to be displayed in the camera, it is 305 expected to create the geometry objects and add them to the appropriate 306 GeometryContainer. 307 308 A Python module implementing commands should implement the following at the 309 end of the file so the commands are loadable in the application using 310 the browseCommands command. 311 312 commandList -- which is a list of dictionaries containing the following: 313 'name': command name (string) used as an alias to invoke 314 the command from the commandline. 315 'cmd' : Command Class 316 'gui' : Typically is a commandGUI object but can be None 317 if no GUI is associated with the Command 318 319 An initModule method 320 def initModule(viewer): 321 for dict in commandList: 322 viewer.addCommand(dict['cmd'], dict['name'], dict['gui']) 323 324 This method will be used by the browseCommands command to load the 325 given module into the application but also to determine which 326 modules implement commands. 327 """
328 - def __init__(self, func=None):
329 self.nodeLogString = None 330 Command.__init__(self, func)
331 332
333 - def guiCallback(self,event=None, log=None, redraw=None):
334 kw = {} 335 if log!=None: kw['log']=log 336 if redraw!=None: kw['redraw']=redraw 337 args, kw = apply( self.getArguments, (), kw) 338 if not kw.has_key('redraw'): kw['redraw']=1 339 if not kw.has_key('log'): kw['log']=1 340 if event: 341 return apply( self.doitWrapper, (event,)+args, kw ) 342 else: 343 return apply( self.doitWrapper, args, kw )
344
345 - def _strArg(self, arg):
346 """ 347 Method to turn a command argument into a string for logging purposes 348 Add support for TreeNodes and TreeNodeSets 349 """ 350 if type(arg)==types.InstanceType: 351 if issubclass(arg.__class__, TreeNode): 352 return '"' + arg.full_name() + '", ', None 353 354 if issubclass(arg.__class__, TreeNodeSet): 355 stringRepr = arg.getStringRepr() 356 if stringRepr: 357 return '"' + stringRepr + '", ', None 358 else: 359 name = "" 360 mols, elems = self.vf.getNodesByMolecule(arg) 361 for elem in elems: 362 name = name + elem.full_name() +';' 363 return '"' + name + '", ', None 364 365 return Command._strArg(self, arg)
366 367 ## if issubclass(arg.__class__, TreeNode) or \ 368 ## issubclass(arg.__class__, TreeNodeSet): 369 ## # 'self.getSelection()' will be used instead of the 370 ## # the arg.full_name() when : 371 ## # - expandNodeLogString userpref is set to False 372 ## # - the command objArgOnly is True 373 ## # - arg is the current selection 374 ## if not self.vf.userpref['expandNodeLogString']['value'] and \ 375 ## self.flag & self.objArgOnly and \ 376 ## self.vf.selection is arg: 377 ## return 'self.getSelection(), ', None 378 ## else: 379 ## # full_name can only create smart (i.e. short string repr) 380 ## # within a single molecule, so we break args by molecule 381 ## name = "" 382 ## mols, elems = self.vf.getNodesByMolecule(arg) 383 ## for elem in elems: 384 ## name = name + elem.full_name() +';' 385 ## return '"' + name + '", ', None 386 ## else: 387 ## return Command.strArg(self, arg) 388 ## else: 389 ## return Command.strArg(self, arg) 390 391 ## def buildLogArgList(self, args, kw): 392 ## """build and return the log string representing the arguments 393 ## a list of python statments called before is also built. This list 394 ## has to be exec'ed to make sure the log can be played back. 395 ## The first argument is treated differently to cover the cases of 396 ## the expansion of nodes. If the attribute nodeLogString is set then 397 ## this string is used to build the log otherwise call strArg.""" 398 ## if self.vf is None: return 399 ## argString = '' 400 ## args, kw = self.getLogArgs( args, kw ) 401 ## before = [] 402 ## if len(args)>0: 403 ## arg = args[0] 404 ## # In the case the __call__ was called with a string 405 ## # we don't want it to be replaced by self.getSelection 406 ## if not self.nodeLogString is None and \ 407 ## type(self.nodeLogString) is types.StringType: 408 ## s, bef = self.nodeLogString +",", None 409 ## self.nodeLogString = None 410 ## else: 411 ## s, bef = self.strArg(arg) 412 ## argString = argString + s 413 ## if bef is not None: before.append(bef) 414 415 ## for arg in args[1:]: 416 ## s, bef = self.strArg(arg) 417 ## argString = argString + s 418 ## if bef is not None: before.append(bef) 419 ## for name,value in kw.items(): 420 ## s, bef = self.strArg(value) 421 ## argString = argString + '%s=%s'%(name, s) 422 ## if bef is not None: before.append(bef) 423 ## return '('+argString[:-2]+')', before # remove last ", " 424 425
426 -class MVCommandGUI(CommandGUI):
427 """Base class for GUI objects associated with MVCommands. 428 """ 429 pass
430 431 432
433 -class MVPrintNodeNames(MVCommand, MVAtomICOM):
434 """Print the name of molecular fragments. The name is a colon separated 435 list of string mol:chain:residue:atom. If the geoemtry depicting the fragments 436 have multiple instances a list of (geom,instance) tuples is printed after the 437 moelcular fragment name. 438 """ 439
440 - def __init__(self, func=None):
441 MVCommand.__init__(self, func) 442 MVAtomICOM.__init__(self) 443 self.flag = self.flag | self.objArgOnly
444 445
446 - def doit(self, nodes):
447 nodes = self.vf.expandNodes(nodes) 448 for n in nodes: 449 msgBase = n.full_name() 450 for instance in n.pickedInstances: 451 if max(instance[1])>0: 452 geomNames = instance[0].fullName.split('|') 453 msg = msgBase+' instance: ' + \ 454 str(zip(geomNames,instance[1])) 455 self.vf.message( msg ) 456 else: 457 self.vf.message( msgBase )
458 459
460 - def __call__(self, nodes, topCommand=0, **kw):
461 # we do not want this command to log or undo itself 462 if type(nodes) is types.StringType: 463 self.nodeLogString = "'"+nodes+"'" 464 465 if not kw.has_key('topCommand'): kw['topCommand'] = topCommand 466 apply( self.doitWrapper, (nodes,), kw )
467 468 469
470 -class MVCenterOnNodes(MVCommand, MVAtomICOM):
471 """Set the pivot point (i.e. center of rotation) of the entire scene to 472 the geometric center of the picked atoms. this command is aware of instance 473 matrices. 474 """
475 - def __init__(self, func=None):
476 MVCommand.__init__(self, func) 477 MVAtomICOM.__init__(self) 478 self.flag = self.flag | self.objArgOnly
479 480
481 - def doit(self, nodes):
482 if len(nodes)==0: 483 return 'ERROR' # prevent logging if nothing was picked 484 vt = self.vf.transformedCoordinatesWithInstances(nodes) 485 g = [0,0,0] 486 i = 0 487 for v in vt: 488 g[0] += v[0] 489 g[1] += v[1] 490 g[2] += v[2] 491 i+=1 492 g[0] = g[0]/i 493 g[1] = g[1]/i 494 g[2] = g[2]/i 495 496 vi = self.vf.GUI.VIEWER 497 root = vi.rootObject 498 self.vf.centerGeom( root, g, topCommand=0, log=1, setupUndo=1)
499 500
501 - def __call__(self, nodes, **kw):
502 # we do not want this command to log or undo itself 503 kw['topCommand']=0 504 kw['busyIdle']=1 505 if type(nodes) is types.StringType: 506 self.nodeLogString = "'"+nodes+"'" 507 apply( self.doitWrapper, (nodes,), kw )
508 509 510 from DejaVu.colorTool import TkColor 511
512 -class MVSetIcomLevel(MVCommand):
513
514 - def setupUndoBefore(self, Klass, KlassSet=None):
515 if not (self.vf.ICmdCaller.level.value in [Molecule, Protein] and \ 516 Klass in [Molecule, Protein]) and \ 517 self.vf.ICmdCaller.level.value != Klass: 518 self.addUndoCall( (self.vf.ICmdCaller.level.value,), 519 {'KlassSet':None}, self.name )
520 521
522 - def doit(self, Klass, KlassSet=None):
523 #print "in MVSetIComLevel" 524 if Klass is Protein: Klass = Molecule 525 self.vf.ICmdCaller.setLevel(Klass, KlassSet=None) 526 if hasattr(self.vf,'ICOMbar'): 527 col =self.vf.ICmdCaller.levelColors[Klass.__name__] 528 c = (col[0]/1.5,col[1]/1.5,col[2]/1.5) 529 self.vf.ICOMbar.LevelOption.setvalue(Klass.__name__) 530 self.vf.ICOMbar.LevelOption._menubutton.configure( 531 background = TkColor(c), 532 activebackground = TkColor(col))
533 534 #self.vf.GUI.pickLabel.configure(background = TkColor(c)) 535 536
537 - def __call__(self, Klass, KlassSet=None, **kw):
538 """None <- setIcomLevel(Klass, KlassSet=None, **kw) 539 set the current IcomLevel level 540 """ 541 kw['KlassSet'] = KlassSet 542 apply( self.doitWrapper, (Klass,), kw)
543 544
545 -class MVSetSelectionLevel(MVCommand):
546
547 - def __init__(self):
548 MVCommand.__init__(self) 549 self.levelDict={"Molecule":Molecule, 550 "Protein":Molecule, 551 "Chain":Chain, 552 "Residue":Residue, 553 "Atom":Atom} 554 self.levelColors = { 555 'Atom':'yellow', 556 'Residue':'green', 557 'Chain':'cyan', 558 'Molecule':'red', 559 'Protein':'red', 560 }
561 562
563 - def onAddCmdToViewer(self):
564 if self.vf.hasGui: 565 self.levelVar = Tkinter.StringVar()
566
567 - def setupUndoBefore(self, Klass, KlassSet=None):
568 if not (self.vf.selectionLevel in [Molecule, Protein] and \ 569 Klass in [Molecule, Protein]) and \ 570 self.vf.selectionLevel!= Klass: 571 if len(self.vf.selection)!=0: 572 # if there is a selection that will expand we add undoing 573 # the selection expansion to the undo stack 574 str1 = apply( self.logString, 575 (self.vf.selectionLevel,), 576 {'KlassSet':None, 'topCommand':0} ) 577 undoString = str1 + ';'\ 578 'self.select('+ \ 579 '"' + self.vf.getSelection().full_name() + \ 580 '", only=1, topCommand=0 ) ' 581 self.vf.undo.addEntry(undoString, self.name) 582 else: 583 self.addUndoCall( (self.vf.selectionLevel,), 584 {'KlassSet':None}, self.name )
585 586
587 - def doit(self, Klass, KlassSet=None):
588 if type(Klass)==types.StringType: 589 if Klass in self.levelDict.keys(): 590 Klass = self.levelDict[Klass] 591 else: 592 msg = Klass + "string does not map to a valid level" 593 self.warningMsg(msg) 594 return "ERROR" 595 if Klass is Protein: 596 Klass = Molecule 597 if len(self.vf.selection): 598 self.vf.selection = self.vf.selection.findType(Klass).uniq() 599 self.vf.selectionLevel = Klass 600 if self.vf.hasGui: 601 col = self.vf.ICmdCaller.levelColors[Klass.__name__] 602 c = (col[0]/1.5,col[1]/1.5,col[2]/1.5) 603 self.vf.GUI.pickLabel.configure(background = TkColor(c)) 604 msg = '%d %s(s)' % ( len(self.vf.selection), 605 self.vf.selectionLevel.__name__ ) 606 self.vf.GUI.pickLabel.configure( text=msg ) 607 if hasattr(self.vf, 'select'): 608 self.vf.select.updateSelectionIcons() 609 self.levelVar.set(self.vf.selectionLevel.__name__)
610 611 612
613 - def Close_cb(self, event=None):
614 self.form.withdraw()
615 616
617 - def guiCallback(self, event=None):
618 if not hasattr(self, 'ifd'): 619 self.buildForm() 620 else: 621 self.form.deiconify() 622 selLevel = self.vf.selectionLevel 623 if selLevel != TreeNodeSet: 624 level = selLevel.__name__ 625 self.levelVar.set(level)
626 627
628 - def setLevel_cb(self, event=None):
629 self.doitWrapper(self.levelVar.get())
630 631
632 - def __call__(self, Klass, KlassSet=None, **kw):
633 """selectionLevel <- setSelectionLevel(Klass, KlassSet=None, **kw) 634 set the current selection level and promotes current selection to 635 this level 636 """ 637 kw['KlassSet'] = KlassSet 638 apply( self.doitWrapper, (Klass,), kw)
639 640
641 - def buildForm(self):
642 ifd = self.ifd = InputFormDescr(title = "Selection level:") 643 levels = ['Molecule', 'Chain', 'Residue', 'Atom'] 644 levelLabels = ['Molecule ', 'Chain ', 645 'Residue ', 'Atom '] 646 self.levelVar.set("Molecule") 647 for level, levlabel in zip(levels, levelLabels): 648 ifd.append({'name':level, 649 'widgetType': Tkinter.Radiobutton, 650 'wcfg':{'text':levlabel, 651 'variable':self.levelVar, 652 'value':level, 653 'justify':'left', 654 'activebackground':self.levelColors[level], 655 'selectcolor':self.levelColors[level], 656 'command':self.setLevel_cb}, 657 'gridcfg':{'sticky':'we'}}) 658 ifd.append({'name':'dismiss', 659 'widgetType':Tkinter.Button, 660 'defaultValue':1, 661 'wcfg':{'text':'Dismiss', 662 'command':self.Close_cb}, 663 'gridcfg':{'sticky':'we'} 664 }) 665 self.form = self.vf.getUserInput(self.ifd, modal=0, blocking=0) 666 self.form.root.protocol('WM_DELETE_WINDOW',self.Close_cb)
667 668 669 670 MVSetSelectionLevelGUI = CommandGUI() 671 MVSetSelectionLevelGUI.addMenuCommand('menuRoot', 'Select','Set Selection Level', 672 separatorBelow=1) 673