summaryrefslogtreecommitdiff
path: root/cad/src/dna/commands/MakeCrossovers/MakeCrossovers_Command.py
blob: 349709dfa93a38195b28f55bfe3d2d4c5e09d966 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
# Copyright 2008 Nanorex, Inc.  See LICENSE file for details. 
"""

@author: Ninad
@copyright: 2008 Nanorex, Inc.  See LICENSE file for details.
@version:$Id$

History:
2008-05-21 - 2008-06-01 Created and further refactored and modified
@See: ListWidgetItems_Command_Mixin, 
      ListWidgetItems_GraphicsMode_Mixin
      ListWidgetItems_PM_Mixin,
      CrossoverSite_Marker
      MakeCrossovers_GraphicsMode
      

TODO  2008-06-01 :
See class CrossoverSite_Marker for details
"""
import time
import foundation.changes as changes
from commands.SelectChunks.SelectChunks_Command import SelectChunks_Command
from dna.commands.MakeCrossovers.MakeCrossovers_GraphicsMode import MakeCrossovers_Graphicsmode
from dna.commands.MakeCrossovers.MakeCrossovers_PropertyManager import MakeCrossovers_PropertyManager
from model.bond_constants import find_bond
from model.bonds import bond_at_singlets
from utilities.debug import print_compact_traceback, print_compact_stack
from utilities.Log import orangemsg

from dna.commands.MakeCrossovers.ListWidgetItems_Command_Mixin import ListWidgetItems_Command_Mixin


MAXIMUM_ALLOWED_DNA_SEGMENTS_FOR_CROSSOVER = 8

_superclass = SelectChunks_Command
class MakeCrossovers_Command(SelectChunks_Command, 
                             ListWidgetItems_Command_Mixin
                             ):
    """
    
    """
    
    GraphicsMode_class = MakeCrossovers_Graphicsmode
    
    PM_class = MakeCrossovers_PropertyManager
    
    # class constants    
    commandName = 'MAKE_CROSSOVERS'
    featurename = "Make Crossovers"
    from utilities.constants import CL_SUBCOMMAND
    command_level = CL_SUBCOMMAND
    command_parent = 'BUILD_DNA'
    
    command_should_resume_prevMode = True 
    command_has_its_own_PM = True
    
    flyoutToolbar = None    
    
    
    def _getFlyoutToolBarActionAndParentCommand(self):
        """
        Overides superclass method. 
        @see: self.command_update_flyout()
        """
        flyoutActionToCheck = 'makeCrossoversAction'
        parentCommandName = None     
        return flyoutActionToCheck, parentCommandName

    def command_entered(self):
        #Set the initial segment list. The segments within this segment list
        #will be searched for the crossovers. 
        ListWidgetItems_Command_Mixin.command_entered(self) 
        
        _superclass.command_entered(self)
        
        selectedSegments = self.win.assy.getSelectedDnaSegments()        
        self.ensureSegmentListItemsWithinLimit(selectedSegments)                     
            
    def logMessage(self, type = 'DEFAULT'): 
        """
        Updates the PM message groupbox message with the one specified below. 
        """
        msg = ''
        if type == 'ADD_SEGMENTS_ACTIVATED':
            msg = """To add new PAM3 DNA segments to the list, click on the segment's
             axis. Multiple segments can be added at once by doing a 
            rectangular lasso selection in the 3D workspace."""
        elif type == 'REMOVE_SEGMENTS_ACTIVATED':
            msg = """To remove segments from the list, click on the segment's 
            axis. Multiple segments can be removed at once by doing a 
            rectangular lasso selection in the 3D workspace."""
        elif type == 'LEFT_DRAG_STARTED':
            msg = """Transparent green spheres (if present) around the atoms indicate 
            potential crossover sites."""
        elif type == 'LEFT_DRAG_FINISHED':
            msg = self.propMgr.defaultLogMessage
        elif type == 'WARNING_LIMIT_EXCEEDED':
            msg = "Only a maximum of <b> %d </b> DNA segments can be searched for "\
                "crossover sites. Segments not added to the list"%self.itemLimitForSegmentListWidget()
            msg = orangemsg(msg)
        elif type == 'WARNING_PAM5_SEGMENT_FOUND':
            msg = """Warning: One or more of the PAM5 DNA segments have been 
            removed from the segment list.Make Crossovers command is only 
            availble for PAM3 model."""
            msg = orangemsg(msg)
        else:
            msg = self.propMgr.defaultLogMessage
        
        self.propMgr.updateMessage(msg)
            
            
    def itemLimitForSegmentListWidget(self):
        """
        Maximum number of items allowed in the segment list widet.(For 
        performance reasons)
        """
        return MAXIMUM_ALLOWED_DNA_SEGMENTS_FOR_CROSSOVER
    
    def ensureSegmentListItemsWithinLimit(self, segments):
        """
        """
        #Following should never happen but added as a safety
        if len(self._structList) > self.itemLimitForSegmentListWidget():
            self.logMessage('WARNING_LIMIT_EXCEEDED')
            return
        
        #Optimization
        if len(self._structList) == 0 and \
           len(segments)<= self.itemLimitForSegmentListWidget():
            self.setSegmentList(segments)
            return
        
        raw_number_of_segments =  len(self._structList) + len(segments)
        if raw_number_of_segments <= self.itemLimitForSegmentListWidget():
            for segment in segments:
                self.addSegmentToSegmentList(segment)
            return
        
        #Check how many of the segments are already in self._structList. 
        #Filter the ones that are *NOT* in self._structList. This filtered 
        #segments are the segments to add to the list
        
        def func(segment):
            if segment not in self._structList:
                return True
            return False
        
        segments_to_add = filter(lambda seg: func(seg), segments)
        
        raw_number_of_segments = len(segments_to_add) + len(self._structList)
        
        if raw_number_of_segments <= self.itemLimitForSegmentListWidget():
            for segment in segments:
                self.addSegmentToSegmentList(segment)
            return
        else:
            self.logMessage('WARNING_LIMIT_EXCEEDED')
            

    def makeAllCrossovers(self):
        """
        Make all possible crossovers
        @see: self.makeCrossover()
        """
        crossoverPairs = self.graphicsMode.get_final_crossover_pairs()
        if crossoverPairs:
            for pairs in crossoverPairs:
                self.makeCrossover(pairs, 
                                   suppress_post_crossover_updates = True)                
                
        self.graphicsMode.clearDictionaries()
        
                    
    def makeCrossover(self, 
                      crossoverPairs, 
                      suppress_post_crossover_updates = False):
        """
        Make the crossover between the atoms of the crossover pairs. 
        @param crossoverPairs: A tuple of 4 atoms between which the crossover
               will be made. Note: As of 2008-06-03, this method assumes the 
               following  form: (atom1, neighbor1, atom2, neighbor2) 
               Where all atoms are PAM3 atoms. pair of atoms atom1 and neighbor1
               are sugar atoms bonded to each other
               (same for pair atom2, neighbor2)
               The bond between these atoms will be broken first and then the 
               atoms are bonded to the opposite atoms. 
               
        @type crossoverPair: tuple
        @param suppress_post_crossover_updates: After making a crossover, this 
        method calls its graphicsMode method to do some more updates (such 
        as updating the atoms dictionaries etc.) But if its a batch process, 
        (e.g. user is calling makeAllCrossovers, this update is not needed 
        after making an individual crossover. The caller then sets this flag to 
        true to tell this method to skip that update.
        @type suppress_post_crossover_updates: boolean
        @seE:self.makeAllCrossovers()
        """
        if len(crossoverPairs) != 4:
            print_compact_stack("Bug in making the crossover.len(crossoverPairs) != 4")
            return

        atm1, neighbor1, atm2, neighbor2 = crossoverPairs
        bond1 = find_bond(atm1, neighbor1)
        if bond1:
            bond1.bust()
        
        bond2 = find_bond(atm2, neighbor2)
        if bond2:
            bond2.bust()
        #Do we need to check if these pairs are valid (i.e.a 5' end atom is 
        #bonded to a 3' end atom.. I think its not needed as its done in 
        #self._bond_two_strandAtoms. 
        self._bond_two_strandAtoms(atm1, neighbor2)
        self._bond_two_strandAtoms(atm2, neighbor1)
        
        if not suppress_post_crossover_updates:
            self.graphicsMode.update_after_crossover_creation(crossoverPairs)
                
    
    def _bond_two_strandAtoms(self, atm1, atm2):
        """
        Bonds the given strand atoms (sugar atoms) together. To bond these atoms, 
        it always makes sure that a 3' bondpoint on one atom is bonded to 5'
        bondpoint on the other atom. 
                
        @param atm1: The first sugar atom of PAM3 (i.e. the strand atom) to be 
                     bonded with atm2. 
        @param atm2: Second sugar atom
        @Note: This method is copied from DnaDuplex.py
        
        """
        #Moved from B_Dna_PAM3_SingleStrand_Generator to here, to fix bugs like 
        #2711 in segment resizing-- Ninad 2008-04-14
        assert atm1.element.role == 'strand' and atm2.element.role == 'strand'
        #Initialize all possible bond points to None
                
        five_prime_bondPoint_atm1  = None
        three_prime_bondPoint_atm1 = None
        five_prime_bondPoint_atm2  = None
        three_prime_bondPoint_atm2 = None
        #Initialize the final bondPoints we will use to create bonds
        bondPoint1 = None
        bondPoint2 = None
        
        #Find 5' and 3' bondpoints of atm1 (BTW, as of 2008-04-11, atm1 is 
        #the new dna strandend atom See self._fuse_new_dna_with_original_duplex
        #But it doesn't matter here. 
        for s1 in atm1.singNeighbors():
            bnd = s1.bonds[0]            
            if bnd.isFivePrimeOpenBond():
                five_prime_bondPoint_atm1 = s1                
            if bnd.isThreePrimeOpenBond():
                three_prime_bondPoint_atm1 = s1
                
        #Find 5' and 3' bondpoints of atm2
        for s2 in atm2.singNeighbors():
            bnd = s2.bonds[0]
            if bnd.isFivePrimeOpenBond():
                five_prime_bondPoint_atm2 = s2
            if bnd.isThreePrimeOpenBond():
                three_prime_bondPoint_atm2 = s2
        #Determine bondpoint1 and bondPoint2 (the ones we will bond). See method
        #docstring for details.
        if five_prime_bondPoint_atm1 and three_prime_bondPoint_atm2:
            bondPoint1 = five_prime_bondPoint_atm1
            bondPoint2 = three_prime_bondPoint_atm2
        #Following will overwrite bondpoint1 and bondPoint2, if the condition is
        #True. Doesn't matter. See method docstring to know why.
        if three_prime_bondPoint_atm1 and five_prime_bondPoint_atm2:
            bondPoint1 = three_prime_bondPoint_atm1
            bondPoint2 = five_prime_bondPoint_atm2
            
        #Copied over from BuildAtoms_GraphicsMode._singletLeftUp_joinStrands()
        #The following fixes bug 2770 
        #Set the color of the whole dna strandGroup to the color of the
        #strand, whose bondpoint, is dropped over to the bondboint of the 
        #other strandchunk (thus joining the two strands together into
        #a single dna strand group) - Ninad 2008-04-09
        color = atm1.molecule.color 
        if color is None:
            color = atm1.element.color
        strandGroup1 = atm1.molecule.parent_node_of_class(self.win.assy.DnaStrand)
                   
        strandGroup2 = atm2.molecule.parent_node_of_class(
            self.win.assy.DnaStrand)                
        if strandGroup2 is not None:
            #set the strand color of strandGroup2 to the one for 
            #strandGroup1. 
            strandGroup2.setStrandColor(color)
            strandChunkList = strandGroup2.getStrandChunks()
            for c in strandChunkList:
                if hasattr(c, 'invalidate_ladder'):
                    c.invalidate_ladder()
        
        #Do the actual bonding        
        if bondPoint1 and bondPoint2:
            try:
                bond_at_singlets(bondPoint1, bondPoint2, move = False)
            except:
                print_compact_traceback("Bug: unable to bond atoms %s and %s"%(atm1, 
                                                                           atm2))
                
            if strandGroup1 is not None:
                strandGroup1.setStrandColor(color) 
                
    def updateCrossoverSites(self):
        """
        Calls the grraphics mode method that does the job of updating 
        all the crossover sites in the 3D workspace. 
        """
        self.graphicsMode.updateCrossoverSites()
           
        
    def updateExprsHandleDict(self): 
        self.graphicsMode.updateExprsHandleDict()
        
    def ensureValidSegmentList(self):
        """
        Ensures that the segments within the segment list being searched for 
        crossover sites have all valid segments (even empty list is considered
        as valid as of 2008-06-04)
        @see: MakeCrossversPropertyManager.model_changed()
        """
        pam5_segment_found = False
        for segment in self._structList:
            if not segment.is_PAM3_DnaSegment():
                pam5_segment_found = True                
                self._structList.remove(segment)
        if pam5_segment_found:
            self.logMessage('WARNING_PAM5_SEGMENT_FOUND')
            
        return pam5_segment_found