show_contacts()   F
last analyzed

Complexity

Conditions 30

Size

Total Lines 186
Code Lines 113

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 30
eloc 113
nop 7
dl 0
loc 186
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like build.rna_tools.tools.PyMOL4RNA.libs.show_contacts.show_contacts() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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