| 1 | from chimera.baseDialog import ModelessDialog
|
|---|
| 2 |
|
|---|
| 3 | # This class is only needed for the GUI. The SortableTable
|
|---|
| 4 | # API is much simpler if column values are attributes (like
|
|---|
| 5 | # "frequency", "index" and "active")
|
|---|
| 6 | class _NormalModeProxy:
|
|---|
| 7 |
|
|---|
| 8 | def __init__(self, modes, index, active):
|
|---|
| 9 | self.modes = modes
|
|---|
| 10 | self.index = index
|
|---|
| 11 | self.frequency = str(self.mode().frequency)
|
|---|
| 12 | self.active = active
|
|---|
| 13 |
|
|---|
| 14 | def mode(self):
|
|---|
| 15 | return self.modes[self.index]
|
|---|
| 16 |
|
|---|
| 17 | # This class is needed to interface with MovieDialog
|
|---|
| 18 | class NormalModeTraj:
|
|---|
| 19 | def __len__(self):
|
|---|
| 20 | return len(self.molecule.coordSets)
|
|---|
| 21 | def __getitem__(self, key):
|
|---|
| 22 | return None
|
|---|
| 23 |
|
|---|
| 24 | class NormalModesTableDialog(ModelessDialog):
|
|---|
| 25 | """Display MMTK normal modes by reconstructing a Chimera
|
|---|
| 26 | (trajectory) model from the MMTK universe and one particular
|
|---|
| 27 | normal mode."""
|
|---|
| 28 |
|
|---|
| 29 | buttons = ( "Show", "Quit" )
|
|---|
| 30 | title = "Normal Modes"
|
|---|
| 31 | oneshot = True
|
|---|
| 32 |
|
|---|
| 33 | def __init__(self, modes, *args, **kw):
|
|---|
| 34 | # We can override window title with
|
|---|
| 35 | # self.title = XXX
|
|---|
| 36 | # if we only knew what XXX should be.
|
|---|
| 37 | # Create proxy normal mode objects to simplify use
|
|---|
| 38 | # with SortableTable.
|
|---|
| 39 | self.modeData = [ _NormalModeProxy(modes, i, False)
|
|---|
| 40 | for i in range(len(modes)) ]
|
|---|
| 41 | # Save modes. Currently we only use it to get
|
|---|
| 42 | # at the universe.
|
|---|
| 43 | self.modes = modes
|
|---|
| 44 | ModelessDialog.__init__(self, *args, **kw)
|
|---|
| 45 | self._createMovieDialog()
|
|---|
| 46 |
|
|---|
| 47 | def fillInUI(self, parent):
|
|---|
| 48 | from CGLtk.Table import SortableTable
|
|---|
| 49 | import Pmw
|
|---|
| 50 | # scaleWidget allows user to scale the displacement
|
|---|
| 51 | # vector for display purposes.
|
|---|
| 52 | self.scaleWidget = Pmw.Counter(parent,
|
|---|
| 53 | labelpos = 'w',
|
|---|
| 54 | label_text = 'Scale factor:',
|
|---|
| 55 | label_justify = 'right',
|
|---|
| 56 | entryfield_value = '1.0',
|
|---|
| 57 | datatype = {'counter':'real'},
|
|---|
| 58 | entryfield_validate = {'validator':'real',
|
|---|
| 59 | 'min' : '1.0', 'max' : '100.0'},
|
|---|
| 60 | increment=1.0)
|
|---|
| 61 | self.scaleWidget.pack(expand=False, fill="x")
|
|---|
| 62 | t = SortableTable(parent)
|
|---|
| 63 | t.pack(expand=True, fill="both")
|
|---|
| 64 | # addColumn arguments are: (column title, attribute associated
|
|---|
| 65 | # with column, layout format). format=None means use default
|
|---|
| 66 | # display string. format=bool means display using checkbutton
|
|---|
| 67 | # that can be used to change the value of the variable.
|
|---|
| 68 | t.addColumn("Mode", "index", format="%d")
|
|---|
| 69 | t.addColumn("Frequency", "frequency", format=None)
|
|---|
| 70 | t.addColumn("Active", "active", format=bool)
|
|---|
| 71 | t.setData(self.modeData)
|
|---|
| 72 | t.launch()
|
|---|
| 73 | self.modesTable = t
|
|---|
| 74 |
|
|---|
| 75 | def Show(self):
|
|---|
| 76 | from chimera import UserError
|
|---|
| 77 | # For now, only allow one mode to be selected
|
|---|
| 78 | which = None
|
|---|
| 79 | for nmp in self.modeData:
|
|---|
| 80 | if not nmp.active:
|
|---|
| 81 | continue
|
|---|
| 82 | if which is not None:
|
|---|
| 83 | raise UserError("Only one mode may be selected")
|
|---|
| 84 | else:
|
|---|
| 85 | which = nmp
|
|---|
| 86 | if which is None:
|
|---|
| 87 | raise UserError("Please select normal mode to show.")
|
|---|
| 88 | sf = float(self.scaleWidget.get())
|
|---|
| 89 | self._updateTrajectory(which, sf)
|
|---|
| 90 |
|
|---|
| 91 | def Quit(self):
|
|---|
| 92 | if self.movieDialog:
|
|---|
| 93 | self.movieDialog.Quit()
|
|---|
| 94 | self.movieDialog = None
|
|---|
| 95 | self.Close()
|
|---|
| 96 |
|
|---|
| 97 | def _createMovieDialog(self):
|
|---|
| 98 | # First create the reference molecule
|
|---|
| 99 | from MMTK2Molecule import convert
|
|---|
| 100 | m, self._mmtk2chimera = convert(self.modes.universe,
|
|---|
| 101 | defaultCS=1)
|
|---|
| 102 |
|
|---|
| 103 | # Next we need to add the extra coordinate sets showing
|
|---|
| 104 | # normal mode extreme positions. The trajectory consists
|
|---|
| 105 | # of 4 coordinate sets. CS 1 was created above and is
|
|---|
| 106 | # the reference coordinate set. CS 2 and 4 are the two
|
|---|
| 107 | # extreme positions while CS 3 is another reference.
|
|---|
| 108 | # When played back, you can see the periodic motion.
|
|---|
| 109 | # Initially, we set CS 2 and CS 4 to be reference also.
|
|---|
| 110 | cs2 = m.newCoordSet(2) # + displacement
|
|---|
| 111 | cs3 = m.newCoordSet(3) # reference
|
|---|
| 112 | cs4 = m.newCoordSet(4) # - displacement
|
|---|
| 113 | for a in m.atoms:
|
|---|
| 114 | p = a.coord()
|
|---|
| 115 | a.setCoord(p, cs2)
|
|---|
| 116 | a.setCoord(p, cs3)
|
|---|
| 117 | a.setCoord(p, cs4)
|
|---|
| 118 |
|
|---|
| 119 | # Finally we need to create the MovieDialog for the
|
|---|
| 120 | # trajectory we just made
|
|---|
| 121 | ensemble = NormalModeTraj()
|
|---|
| 122 | ensemble.name = "Normal mode"
|
|---|
| 123 | keys = m.coordSets.keys()
|
|---|
| 124 | ensemble.startFrame = min(keys)
|
|---|
| 125 | ensemble.endFrame = max(keys)
|
|---|
| 126 | ensemble.molecule = m
|
|---|
| 127 | from Movie.gui import MovieDialog
|
|---|
| 128 | self.movieDialog = MovieDialog(ensemble)
|
|---|
| 129 |
|
|---|
| 130 | def _updateTrajectory(self, nmp, sf):
|
|---|
| 131 | # Assume that the trajectory has been created and has
|
|---|
| 132 | # exactly 4 coordinate sets. See above. We update
|
|---|
| 133 | # only the two non-reference coordinate sets.
|
|---|
| 134 | from chimera import Vector
|
|---|
| 135 | mode = nmp.mode()
|
|---|
| 136 | m = self.movieDialog.ensemble.molecule
|
|---|
| 137 | cs1 = m.findCoordSet(1) # reference
|
|---|
| 138 | cs2 = m.findCoordSet(2) # + displacement
|
|---|
| 139 | cs4 = m.findCoordSet(4) # - displacement
|
|---|
| 140 | for ma in self.modes.universe.atomList():
|
|---|
| 141 | x, y, z = mode[ma] * 10 * sf
|
|---|
| 142 | v = Vector(x, y, z)
|
|---|
| 143 | a = self._mmtk2chimera[ma]
|
|---|
| 144 | p = a.coord(cs1)
|
|---|
| 145 | a.setCoord(p + v, cs2)
|
|---|
| 146 | a.setCoord(p - v, cs4)
|
|---|