1
|
|
|
#!/usr/bin/python |
2
|
|
|
|
3
|
|
|
""" |
4
|
|
|
PyMOL plugin that provides show_contacts command and GUI |
5
|
|
|
for highlighting good and bad polar contacts. |
6
|
|
|
|
7
|
|
|
Factored out of clustermols by Matthew Baumgartner. |
8
|
|
|
|
9
|
|
|
The advantage of this package is it requires many fewer dependencies. |
10
|
|
|
|
11
|
|
|
Modified: Marcin Magnus 2020 |
12
|
|
|
|
13
|
|
|
Source <https://pymolwiki.org/index.php/Pymol-script-repo> |
14
|
|
|
""" |
15
|
|
|
from __future__ import print_function |
16
|
|
|
|
17
|
|
|
import sys |
18
|
|
|
import os |
19
|
|
|
from pymol import cmd |
20
|
|
|
|
21
|
|
|
print("""show_contacts |
22
|
|
|
------------------------------------- |
23
|
|
|
|
24
|
|
|
_polar: good polar interactions according to PyMOL |
25
|
|
|
_polar_ok: compute possibly suboptimal polar interactions using the user specified distance |
26
|
|
|
_aa: acceptors acceptors |
27
|
|
|
_dd: donors donors |
28
|
|
|
|
29
|
|
|
_all is all ;-) above!""") |
30
|
|
|
|
31
|
|
|
DEBUG=1 |
32
|
|
|
|
33
|
|
|
def show_contacts(selection='*', selection2='*', |
34
|
|
|
result="contacts", |
35
|
|
|
cutoff=3.6, |
36
|
|
|
bigcutoff = 4.0, |
37
|
|
|
labels=False, |
38
|
|
|
SC_DEBUG = DEBUG): |
39
|
|
|
""" |
40
|
|
|
USAGE |
41
|
|
|
|
42
|
|
|
show_contacts selection, selection2, [result=contacts],[cutoff=3.6],[bigcutoff=4.0] |
43
|
|
|
|
44
|
|
|
Show various polar contacts, the good, the bad, and the ugly. |
45
|
|
|
|
46
|
|
|
Edit MPB 6-26-14: The distances are heavy atom distances, so I upped the default cutoff to 4.0 |
47
|
|
|
|
48
|
|
|
Returns: |
49
|
|
|
True/False - if False, something went wrong |
50
|
|
|
""" |
51
|
|
|
if SC_DEBUG > 4: |
52
|
|
|
print('Starting show_contacts') |
53
|
|
|
print('selection = "' + selection + '"') |
54
|
|
|
print('selection2 = "' + selection2 + '"') |
55
|
|
|
|
56
|
|
|
result = cmd.get_legal_name(result) |
57
|
|
|
|
58
|
|
|
#if the group of contacts already exist, delete them |
59
|
|
|
cmd.delete(result) |
60
|
|
|
|
61
|
|
|
# ensure only N and O atoms are in the selection |
62
|
|
|
all_don_acc1 = selection + " and (donor or acceptor)" |
63
|
|
|
all_don_acc2 = selection2 + " and (donor or acceptor)" |
64
|
|
|
|
65
|
|
|
if SC_DEBUG > 4: |
66
|
|
|
print('all_don_acc1 = "' + all_don_acc1 + '"') |
67
|
|
|
print('all_don_acc2 = "' + all_don_acc2 + '"') |
68
|
|
|
|
69
|
|
|
#if theses selections turn out not to have any atoms in them, pymol throws cryptic errors when calling the dist function like: |
70
|
|
|
#'Selector-Error: Invalid selection name' |
71
|
|
|
#So for each one, manually perform the selection and then pass the reference to the distance command and at the end, clean up the selections |
72
|
|
|
#the return values are the count of the number of atoms |
73
|
|
|
all1_sele_count = cmd.select('all_don_acc1_sele', all_don_acc1) |
74
|
|
|
all2_sele_count = cmd.select('all_don_acc2_sele', all_don_acc2) |
75
|
|
|
|
76
|
|
|
#print out some warnings |
77
|
|
|
if DEBUG > 3: |
78
|
|
|
if not all1_sele_count: |
79
|
|
|
print('Warning: all_don_acc1 selection empty!') |
80
|
|
|
if not all2_sele_count: |
81
|
|
|
print('Warning: all_don_acc2 selection empty!') |
82
|
|
|
|
83
|
|
|
######################################## |
84
|
|
|
allres = result + "_all" |
85
|
|
|
if all1_sele_count and all2_sele_count: |
86
|
|
|
#print(allres) |
87
|
|
|
#print(cmd.get_distance(allres, 'all_don_acc1_sele', 'all_don_acc2_sele', bigcutoff, mode = 0)) |
88
|
|
|
any = cmd.distance(allres, 'all_don_acc1_sele', 'all_don_acc2_sele', bigcutoff, mode = 0) |
89
|
|
|
# if any is 0 it seems that there is no distance! |
90
|
|
|
if any: |
91
|
|
|
cmd.set("dash_radius", "0.05", allres) |
92
|
|
|
if not labels: |
93
|
|
|
cmd.hide("labels", allres) |
94
|
|
|
else: |
95
|
|
|
# just do nothing and clena up |
96
|
|
|
print('no contacts') |
97
|
|
|
cmd.delete('all_don_acc1_sele') |
98
|
|
|
cmd.delete('all_don_acc2_sele') |
99
|
|
|
cmd.delete(result + "_all") |
100
|
|
|
return None |
101
|
|
|
######################################## |
102
|
|
|
# compute good polar interactions according to pymol |
103
|
|
|
polres = result + "_polar" |
104
|
|
|
if all1_sele_count and all2_sele_count: |
105
|
|
|
cmd.distance(polres, 'all_don_acc1_sele', 'all_don_acc2_sele', cutoff, mode = 2) #hopefully this checks angles? Yes |
106
|
|
|
#cmd.set("dash_color", "marine", allres) |
107
|
|
|
#cmd.set('dash_gap', '0') |
108
|
|
|
cmd.set("dash_radius","0.2", polres) #"0.126" |
109
|
|
|
#cmd.set("dash_color", "marine", allres) |
110
|
|
|
if not labels: |
111
|
|
|
cmd.hide("labels", polres) |
112
|
|
|
|
113
|
|
|
######################################## |
114
|
|
|
# When running distance in mode=2, the cutoff parameter is ignored if set higher then the default of 3.6 |
115
|
|
|
# so set it to the passed in cutoff and change it back when you are done. |
116
|
|
|
old_h_bond_cutoff_center = cmd.get('h_bond_cutoff_center') # ideal geometry |
117
|
|
|
old_h_bond_cutoff_edge = cmd.get('h_bond_cutoff_edge') # minimally acceptable geometry |
118
|
|
|
cmd.set('h_bond_cutoff_center', bigcutoff) |
119
|
|
|
cmd.set('h_bond_cutoff_edge', bigcutoff) |
120
|
|
|
|
121
|
|
|
# compute possibly suboptimal polar interactions using the user specified distance |
122
|
|
|
pol_ok_res = result + "_polar_ok" |
123
|
|
|
if all1_sele_count and all2_sele_count: |
124
|
|
|
cmd.distance(pol_ok_res, 'all_don_acc1_sele', 'all_don_acc2_sele', bigcutoff, mode = 2) |
125
|
|
|
cmd.set("dash_radius", "0.06", pol_ok_res) |
126
|
|
|
if not labels: |
127
|
|
|
cmd.hide("labels", pol_ok_res) |
128
|
|
|
|
129
|
|
|
#now reset the h_bond cutoffs |
130
|
|
|
cmd.set('h_bond_cutoff_center', old_h_bond_cutoff_center) |
131
|
|
|
cmd.set('h_bond_cutoff_edge', old_h_bond_cutoff_edge) |
132
|
|
|
|
133
|
|
|
|
134
|
|
|
######################################## |
135
|
|
|
|
136
|
|
|
onlyacceptors1 = selection + " and (acceptor and !donor)" |
137
|
|
|
onlyacceptors2 = selection2 + " and (acceptor and !donor)" |
138
|
|
|
onlydonors1 = selection + " and (!acceptor and donor)" |
139
|
|
|
onlydonors2 = selection2 + " and (!acceptor and donor)" |
140
|
|
|
|
141
|
|
|
#perform the selections |
142
|
|
|
onlyacceptors1_sele_count = cmd.select('onlyacceptors1_sele', onlyacceptors1) |
143
|
|
|
onlyacceptors2_sele_count = cmd.select('onlyacceptors2_sele', onlyacceptors2) |
144
|
|
|
onlydonors1_sele_count = cmd.select('onlydonors1_sele', onlydonors1) |
145
|
|
|
onlydonors2_sele_count = cmd.select('onlydonors2_sele', onlydonors2) |
146
|
|
|
|
147
|
|
|
#print out some warnings |
148
|
|
|
if SC_DEBUG > 2: |
149
|
|
|
if not onlyacceptors1_sele_count: |
150
|
|
|
print('Warning: onlyacceptors1 selection empty!') |
151
|
|
|
if not onlyacceptors2_sele_count: |
152
|
|
|
print('Warning: onlyacceptors2 selection empty!') |
153
|
|
|
if not onlydonors1_sele_count: |
154
|
|
|
print('Warning: onlydonors1 selection empty!') |
155
|
|
|
if not onlydonors2_sele_count: |
156
|
|
|
print('Warning: onlydonors2 selection empty!') |
157
|
|
|
|
158
|
|
|
# acceptors acceptors |
159
|
|
|
accres = result+"_aa" |
160
|
|
|
if onlyacceptors1_sele_count and onlyacceptors2_sele_count: |
161
|
|
|
aa_dist_out = cmd.distance(accres, 'onlyacceptors1_sele', 'onlyacceptors2_sele', cutoff, 0) |
162
|
|
|
if aa_dist_out < 0: |
163
|
|
|
print('\n\nCaught a pymol selection error in acceptor-acceptor selection of show_contacts') |
164
|
|
|
print('accres:', accres) |
165
|
|
|
print('onlyacceptors1', onlyacceptors1) |
166
|
|
|
print('onlyacceptors2', onlyacceptors2) |
167
|
|
|
return False |
168
|
|
|
cmd.set("dash_color","red",accres) |
169
|
|
|
cmd.set("dash_radius","0.125",accres) |
170
|
|
|
if not labels: |
171
|
|
|
cmd.hide("labels", accres) |
172
|
|
|
|
173
|
|
|
######################################## |
174
|
|
|
# donors donors |
175
|
|
|
donres = result+"_dd" |
176
|
|
|
if onlydonors1_sele_count and onlydonors2_sele_count: |
177
|
|
|
dd_dist_out = cmd.distance(donres, 'onlydonors1_sele', 'onlydonors2_sele', cutoff, 0) |
178
|
|
|
|
179
|
|
|
#try to catch the error state |
180
|
|
|
if dd_dist_out < 0: |
181
|
|
|
print('\n\nCaught a pymol selection error in dd selection of show_contacts') |
182
|
|
|
print('donres:', donres) |
183
|
|
|
print('onlydonors1', onlydonors1) |
184
|
|
|
print('onlydonors2', onlydonors2) |
185
|
|
|
print("cmd.distance('" + donres + "', '" + onlydonors1 + "', '" + onlydonors2 + "', " + str(cutoff) + ", 0)") |
186
|
|
|
return False |
187
|
|
|
|
188
|
|
|
cmd.set("dash_color","red",donres) |
189
|
|
|
cmd.set("dash_radius","0.125",donres) |
190
|
|
|
if not labels: |
191
|
|
|
cmd.hide("labels", donres) |
192
|
|
|
|
193
|
|
|
########################################################## |
194
|
|
|
##### find the buried unpaired atoms of the receptor ##### |
195
|
|
|
########################################################## |
196
|
|
|
|
197
|
|
|
#initialize the variable for when CALC_SASA is False |
198
|
|
|
unpaired_atoms = '' |
199
|
|
|
|
200
|
|
|
## Group |
201
|
|
|
print(allres) # contacts_all |
202
|
|
|
cmd.group(result,"%s %s %s %s %s %s" % (polres, allres, accres, donres, pol_ok_res, unpaired_atoms)) |
203
|
|
|
|
204
|
|
|
## Clean up the selection objects |
205
|
|
|
#if the show_contacts debug level is high enough, don't delete them. |
206
|
|
|
if SC_DEBUG < 5: |
207
|
|
|
cmd.delete('all_don_acc1_sele') |
208
|
|
|
cmd.delete('all_don_acc2_sele') |
209
|
|
|
cmd.delete('onlyacceptors1_sele') |
210
|
|
|
cmd.delete('onlyacceptors2_sele') |
211
|
|
|
cmd.delete('onlydonors1_sele') |
212
|
|
|
cmd.delete('onlydonors2_sele') |
213
|
|
|
|
214
|
|
|
cmd.disable('contacts_all') |
215
|
|
|
cmd.disable('contacts_polar_ok') |
216
|
|
|
cmd.disable('contacts_aa') |
217
|
|
|
cmd.disable('contacts_dd') |
218
|
|
|
return True |
219
|
|
|
|
220
|
|
|
cmd.extend('contacts', show_contacts) #contacts to avoid clashing with cluster_mols version |
221
|
|
|
|
222
|
|
|
|
223
|
|
|
|
224
|
|
|
|
225
|
|
|
|
226
|
|
|
################################################################################# |
227
|
|
|
########################### Start of pymol plugin code ########################## |
228
|
|
|
################################################################################# |
229
|
|
|
|
230
|
|
|
|
231
|
|
|
about_text = '''show_contacts was factored out of the much more full-featured cluster_mols |
232
|
|
|
by Dr. Matt Baumgartner (https://pymolwiki.org/index.php/Cluster_mols). It provides |
233
|
|
|
an easy way to highlight polar contacts (and clashes) between two selections without |
234
|
|
|
requiring the installation of additional dependencies. |
235
|
|
|
''' |
236
|
|
|
|
237
|
|
|
class Show_Contacts: |
238
|
|
|
''' Tk version of the Plugin GUI ''' |
239
|
|
|
def __init__(self, app): |
240
|
|
|
parent = app.root |
241
|
|
|
self.parent = parent |
242
|
|
|
|
243
|
|
|
self.app = app |
244
|
|
|
|
245
|
|
|
import Pmw |
246
|
|
|
|
247
|
|
|
############################################################################################ |
248
|
|
|
### Open a window with options to select to loaded objects ### |
249
|
|
|
############################################################################################ |
250
|
|
|
|
251
|
|
|
self.select_dialog = Pmw.Dialog(parent, |
252
|
|
|
buttons = ('Ok','Cancel'), |
253
|
|
|
title = 'Show Contacts Plugin', |
254
|
|
|
command = self.button_pressed ) |
255
|
|
|
|
256
|
|
|
self.select_dialog.withdraw() |
257
|
|
|
|
258
|
|
|
|
259
|
|
|
#allow the user to select from objects already loaded in pymol |
260
|
|
|
self.select_object_combo_box = Pmw.ComboBox(self.select_dialog.interior(), |
261
|
|
|
scrolledlist_items=[], |
262
|
|
|
labelpos='w', |
263
|
|
|
label_text='Select loaded object:', |
264
|
|
|
listbox_height = 2, |
265
|
|
|
dropdown=True) |
266
|
|
|
self.select_object_combo_box2 = Pmw.ComboBox(self.select_dialog.interior(), |
267
|
|
|
scrolledlist_items=[], |
268
|
|
|
labelpos='w', |
269
|
|
|
label_text='Select loaded object:', |
270
|
|
|
listbox_height = 2, |
271
|
|
|
dropdown=True) |
272
|
|
|
self.select_object_combo_box.grid(column=1, row=0) |
273
|
|
|
self.select_object_combo_box2.grid(column=2, row=0) |
274
|
|
|
self.populate_ligand_select_list() |
275
|
|
|
self.select_dialog.show() |
276
|
|
|
|
277
|
|
|
|
278
|
|
|
|
279
|
|
|
def button_pressed(self, result): |
280
|
|
|
if hasattr(result,'keycode'): |
281
|
|
|
if result.keycode == 36: |
282
|
|
|
print('keycode:', result.keycode) |
283
|
|
|
elif result == 'Ok' or result == 'Exit' or result == None: |
284
|
|
|
s1 = self.select_object_combo_box.get() |
285
|
|
|
s2 = self.select_object_combo_box2.get() |
286
|
|
|
show_contacts(s1,s2,'%s_%s'%(s1,s2)) |
287
|
|
|
self.select_dialog.withdraw() |
288
|
|
|
elif result == 'Cancel' or result == None: |
289
|
|
|
self.select_dialog.withdraw() |
290
|
|
|
|
291
|
|
|
|
292
|
|
|
|
293
|
|
|
|
294
|
|
|
def populate_ligand_select_list(self): |
295
|
|
|
''' Go thourgh the loaded objects in PyMOL and add them to the selected list. ''' |
296
|
|
|
#get the loaded objects |
297
|
|
|
loaded_objects = _get_select_list() |
298
|
|
|
|
299
|
|
|
self.select_object_combo_box.clear() |
300
|
|
|
self.select_object_combo_box2.clear() |
301
|
|
|
|
302
|
|
|
for ob in loaded_objects: |
303
|
|
|
self.select_object_combo_box.insert('end', ob) |
304
|
|
|
self.select_object_combo_box2.insert('end', ob) |
305
|
|
|
|
306
|
|
|
|
307
|
|
|
def _get_select_list(): |
308
|
|
|
''' |
309
|
|
|
Get either a list of object names, or a list of chain selections |
310
|
|
|
''' |
311
|
|
|
loaded_objects = [name for name in cmd.get_names('all', 1) if '_cluster_' not in name] |
312
|
|
|
|
313
|
|
|
# if single object, try chain selections |
314
|
|
|
if len(loaded_objects) == 1: |
315
|
|
|
chains = cmd.get_chains(loaded_objects[0]) |
316
|
|
|
if len(chains) > 1: |
317
|
|
|
loaded_objects = ['{} & chain {}'.format(loaded_objects[0], chain) for chain in chains] |
318
|
|
|
|
319
|
|
|
return loaded_objects |
320
|
|
|
|
321
|
|
|
|
322
|
|
|
class Show_Contacts_Qt_Dialog(object): |
323
|
|
|
''' Qt version of the Plugin GUI ''' |
324
|
|
|
def __init__(self): |
325
|
|
|
from pymol.Qt import QtWidgets |
326
|
|
|
dialog = QtWidgets.QDialog() |
327
|
|
|
self.setupUi(dialog) |
328
|
|
|
self.populate_ligand_select_list() |
329
|
|
|
dialog.accepted.connect(self.accept) |
330
|
|
|
dialog.exec_() |
331
|
|
|
|
332
|
|
|
def accept(self): |
333
|
|
|
s1 = self.select_object_combo_box.currentText() |
334
|
|
|
s2 = self.select_object_combo_box2.currentText() |
335
|
|
|
show_contacts(s1, s2, '%s_%s' % (s1, s2)) |
336
|
|
|
|
337
|
|
|
def populate_ligand_select_list(self): |
338
|
|
|
loaded_objects = _get_select_list() |
339
|
|
|
|
340
|
|
|
self.select_object_combo_box.clear() |
341
|
|
|
self.select_object_combo_box2.clear() |
342
|
|
|
|
343
|
|
|
self.select_object_combo_box.addItems(loaded_objects) |
344
|
|
|
self.select_object_combo_box2.addItems(loaded_objects) |
345
|
|
|
|
346
|
|
|
if len(loaded_objects) > 1: |
347
|
|
|
self.select_object_combo_box2.setCurrentIndex(1) |
348
|
|
|
|
349
|
|
|
def setupUi(self, Dialog): |
350
|
|
|
# Based on auto-generated code from ui file |
351
|
|
|
from pymol.Qt import QtCore, QtWidgets |
352
|
|
|
Dialog.resize(400, 50) |
353
|
|
|
self.gridLayout = QtWidgets.QGridLayout(Dialog) |
354
|
|
|
label = QtWidgets.QLabel("Select loaded object:", Dialog) |
355
|
|
|
self.gridLayout.addWidget(label, 0, 0, 1, 1) |
356
|
|
|
self.select_object_combo_box = QtWidgets.QComboBox(Dialog) |
357
|
|
|
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) |
358
|
|
|
self.select_object_combo_box.setSizePolicy(sizePolicy) |
359
|
|
|
self.select_object_combo_box.setEditable(True) |
360
|
|
|
self.gridLayout.addWidget(self.select_object_combo_box, 0, 1, 1, 1) |
361
|
|
|
label = QtWidgets.QLabel("Select loaded object:", Dialog) |
362
|
|
|
self.gridLayout.addWidget(label, 1, 0, 1, 1) |
363
|
|
|
self.select_object_combo_box2 = QtWidgets.QComboBox(Dialog) |
364
|
|
|
self.select_object_combo_box2.setSizePolicy(sizePolicy) |
365
|
|
|
self.select_object_combo_box2.setEditable(True) |
366
|
|
|
self.gridLayout.addWidget(self.select_object_combo_box2, 1, 1, 1, 1) |
367
|
|
|
self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) |
368
|
|
|
self.buttonBox.setOrientation(QtCore.Qt.Horizontal) |
369
|
|
|
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) |
370
|
|
|
self.gridLayout.addWidget(self.buttonBox, 2, 0, 1, 2) |
371
|
|
|
self.buttonBox.accepted.connect(Dialog.accept) |
372
|
|
|
self.buttonBox.rejected.connect(Dialog.reject) |
373
|
|
|
|
374
|
|
|
|
375
|
|
|
def __init__(self): |
376
|
|
|
try: |
377
|
|
|
from pymol.plugins import addmenuitemqt |
378
|
|
|
addmenuitemqt('Show Contacts', Show_Contacts_Qt_Dialog) |
379
|
|
|
return |
380
|
|
|
except Exception as e: |
381
|
|
|
print(e) |
382
|
|
|
self.menuBar.addmenuitem('Plugin', 'command', 'Show Contacts', label = 'Show Contacts', command = lambda s=self : Show_Contacts(s)) |
383
|
|
|
|
384
|
|
|
|
385
|
|
|
|