summaryrefslogtreecommitdiff
path: root/cad/src/dna/commands/JoinStrands/ClickToJoinStrands_Command.py
blob: 93df75ebcc7e93131ddd68b0a5a4d61c25e5ca7e (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
# Copyright 2008 Nanorex, Inc.  See LICENSE file for details. 
"""
@author:    Ninad
@version:   $Id$
@copyright: 2008 Nanorex, Inc.  See LICENSE file for details.
@license:   GPL

History:
2008-10-22: Created to support single click join strand operation 
Example: while in this command, if user clicks anywhere on a strand, its 3'end
is joined to the nearest 5' end on the same dnasegment.

TODOs as of 2008-10-26: 
- Method _bond_two_strandAtoms needs refactoring and to be moved to a dna_helper
package. 
"""
import foundation.env as env
from dna.commands.JoinStrands.JoinStrands_PropertyManager import JoinStrands_PropertyManager
from commands.Select.Select_Command import Select_Command
from dna.commands.JoinStrands.ClickToJoinStrands_GraphicsMode import ClickToJoinStrands_GraphicsMode
import foundation.env as env
from utilities.prefs_constants import joinStrandsCommand_clickToJoinDnaStrands_prefs_key
from utilities.debug import print_compact_stack
from utilities.constants import CL_SUBCOMMAND
from model.bonds import bond_at_singlets
from geometry.NeighborhoodGenerator import NeighborhoodGenerator 
from utilities.prefs_constants import joinStrandsCommand_recursive_clickToJoinDnaStrands_prefs_key
from geometry.VQT import vlen
# == Command part

_superclass = Select_Command
class ClickToJoinStrands_Command(Select_Command): 
    """
    Command part for joining two strands. 
    @see: superclass B{BreakOrJoinStrands_Command} 
    """
    # class constants
    
    commandName = 'CLICK_TO_JOIN_STRANDS'
    featurename = "Click To Join Strands"   
    GraphicsMode_class = ClickToJoinStrands_GraphicsMode     
    
    FlyoutToolbar_class = None    
    command_should_resume_prevMode = False
    command_has_its_own_PM = False
                
    #class constants for the NEW COMMAND API -- 2008-07-30
    command_level = CL_SUBCOMMAND
    command_parent = 'JOIN_STRANDS'
       
               
    def command_update_state(self):
        """
        See superclass for documentation. 
        Note that this method is called only when self is the currentcommand on 
        the command stack. 
        @see: BuildAtomsFlyout.resetStateOfActions()
        @see: self.activateAtomsTool()
        """
        _superclass.command_update_state(self)
        
        #Make sure that the command Name is JOIN_STRANDS. (because subclasses 
        #of JoinStrands_Command might be using this method). 
        #As of 2008-10-23, if the checkbox 'Click on strand to join'
        #in the join strands PM is checked, NE1 will enter a command that 
        #implements different mouse behavior in its graphics mode and will stay
        #there. 
        if self.commandName == 'CLICK_TO_JOIN_STRANDS' and \
           not env.prefs[joinStrandsCommand_clickToJoinDnaStrands_prefs_key]:
            
            self.command_Done()
            
    def joinNeighboringStrands(self, strand, endChoice = 'THREE_PRIME_END'):
        """
        Join the 3 or 5' end of the given strand with a 5' or 3' (respt) end of 
        a neighboring strand, on the SAME DnaSegment. 
        
        @param strand: The DnaStrand whose 3' or 5' end will be joined with 
                       the 5' or 3' end base atom (respectively) on a 
                       neighboring strand of the same DnaSegment. 
        @type strand: DnaStrand              
        @see: self._bond_two_strandAtoms()
        @TODO: implement code when endChoice is 'Five prime end'. At the moment,
        it works only for the the 3' end atom of <strand>.
        
        @see: ClickToJoinStrands_GraphicsMode.chunkLeftUp()
        """
        
        if not isinstance(strand, self.assy.DnaStrand):
            print_compact_stack("bug: %s is not a DnaStrand instance"%strand)
            return
        
        bool_recursively_join_strands = env.prefs[joinStrandsCommand_recursive_clickToJoinDnaStrands_prefs_key]
        
        if endChoice == 'THREE_PRIME_END':
            current_strand = strand
            
            count = 0
            
            #This implements a NFR by Mark. Recursively join the DnaStrand's
            #3 prime end with the 5' end of a neighboring strand. 
            #This is SLOW for recursively joining strands.
            while(True):
                #If the 'recursivly join DnaStrand option is not checked
                #return immediately after the first interation (which joins
                #the two neighboring strands)
                if count == 1 and not bool_recursively_join_strands:
                    return
                                
                endAtom = current_strand.get_three_prime_end_base_atom()
                if endAtom is None:
                    return 
                
                #Now find the nearest five prime end atom on a strand of the 
                #*same* Dna segment. 
                axis_atom = endAtom.axis_neighbor()
                if axis_atom is None:
                    return 
                
                segment = current_strand.get_DnaSegment_with_content_atom(endAtom)
                
                if segment is None:
                    return 
                
                #find all five prime end base atoms contained by this DnaSegment.             
                raw_five_prime_ends = segment.get_all_content_five_prime_ends()
                
                def func(atm):
                    """
                    Returns true if the given atom's strand is not same as 
                    the 'strand' which is 'endAoms' parent DnaStrand 
                    
                    """
                    if atm.getDnaStrand() is current_strand:
                        return False
                    
                    return True
                
                #Following ensures that the three prime end of <strand> won't be 
                #bonded to the five prime end of the same strand. 
                five_prime_ends = filter(lambda atm: func(atm), raw_five_prime_ends)
                           
                #Max radius within which to search for the 'neighborhood' atoms 
                #(five prime ends) 
                maxBondLength = 10.0
                neighborHood = NeighborhoodGenerator(five_prime_ends, maxBondLength)
                
                pos = endAtom.posn()
                region_atoms = neighborHood.region(pos)
                            
                #minor optimization
                if not region_atoms:
                    if not bool_recursively_join_strands:
                        print_compact_stack(
                            "No five prime end atoms found" \
                            "within %f A radius of the "\
                            "strand's 3 prime end"%(maxBondLength))
                    return
                elif len(region_atoms) == 1:
                    five_prime_end_atom = region_atoms[0]
                else:          
                    lst = []
                    for atm in region_atoms:
                        length = vlen(pos - atm.posn())
                        lst.append((length, atm))
                    lst.sort()
                    
                    #Five prime end atom nearest to the 'endAtom' is the contained
                    #within the first tuple of the sorted list 'tpl'
                    length, five_prime_end_atom = lst[0]
                                         
                self._bond_two_strandAtoms(endAtom, five_prime_end_atom)
                self.assy.update_parts()
                
                current_strand = endAtom.getDnaStrand()
                count += 1
                
        elif endChoice == 'FIVE_PRIME_END':
            #NOT IMPLEMENTED AS OF 2008-10-26
            endAtom = strand.get_five_prime_end_base_atom()
            if endAtom is None:
                return
                
    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
        
        @see: self.joinNeighboringStrands() which calls this
        
        TODO 2008-10-26: This method is originally from 
        B_DNA_PAM3_SingleStrand class. It is modified further to account for 
        color change after bonding the two strand atoms. It needs to 
        be refactored and moved to a dna_helper package. [-- Ninad comment]
        """
        #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

        #Do the actual bonding        
        if bondPoint1 and bondPoint2:
            bond_at_singlets(bondPoint1, bondPoint2, move = False)
        else:
            print_compact_stack("Bug: unable to bond atoms %s and %s: " %
                                (atm1, atm2) )
            
            
        #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.assy.DnaStrand)
        
        #Temporary fix for bug 2829 that Damian reported. 
        #Bruce is planning to fix the underlying bug in the dna updater 
        #code. Once its fixed, The following block of code under 
        #"if DEBUG_BUG_2829" can be deleted -- Ninad 2008-05-01
        
        DEBUG_BUG_2829 = True
        
        if DEBUG_BUG_2829:            
            strandGroup2 = atm2.molecule.parent_node_of_class(
                self.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()