summaryrefslogtreecommitdiff
path: root/cad/src/operations/ops_atoms.py
blob: 5fd30a739300779bde47ce18e2472584e0c329f6 (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
# Copyright 2004-2009 Nanorex, Inc.  See LICENSE file for details. 
"""
ops_atoms.py -- operations on the atoms and/or bonds inside a Part.
These operations generally create or destroy atoms, bondpoints, or real bonds.
Operations specific to single modes (Build, Crystal, Extrude) are not included here.

@version: $Id$
@copyright: 2004-2009 Nanorex, Inc.  See LICENSE file for details.

History:

bruce 050507 made this by collecting appropriate methods (by various authors)
from existing modules, from class Part and class basicMode.
"""

from utilities.Log import greenmsg, redmsg

from utilities.constants import SELWHAT_CHUNKS, SELWHAT_ATOMS
from utilities.constants import gensym

from platform_dependent.PlatformDependent import fix_plurals

from model.elements import Singlet
from model.chem import Atom

import foundation.env as env

class ops_atoms_Mixin:
    """
    Mixin class for providing these methods to class Part
    """
    
    def make_Atom_and_bondpoints(self, 
                                 elem, 
                                 pos, 
                                 atomtype = None, 
                                 Chunk_class = None ):
        """
        Create one unbonded atom, of element elem
        and (if supplied) the given atomtype
        (otherwise the default atomtype for elem),
        at position pos, in its own new chunk,
        with enough bondpoints to have no valence error.
    
        @param Chunk_class: constructor for the returned atom's new chunk
                            (self.assy.Chunk by default)
                            
        @return: one newly created Atom object, already placed into a new
                 chunk which has been added to the model using addnode
        """
        #bruce 041215 moved this from chunk.py to chem.py, and split part of it
        # into the new atom method make_bondpoints_when_no_bonds, to help fix bug 131.
        #bruce 050510 added atomtype option
        #bruce 080520 added Chunk_class option
        #bruce 090112 renamed oneUnbonded function and turned it into this method
        assy = self.assy
        if Chunk_class is None:
            Chunk_class = assy.Chunk
        chunk = Chunk_class(assy, 'bug') # name is reset below!
        atom = Atom(elem.symbol, pos, chunk)
        # bruce 041124 revised name of new chunk, was gensym('Chunk.');
        # no need for gensym since atom key makes the name unique, e.g. C1.
        atom.set_atomtype_but_dont_revise_singlets(atomtype) 
            # ok to pass None, type name, or type object; this verifies no change in elem
            # note, atomtype might well already be the value we're setting; 
            # if it is, this should do nothing
        ## chunk.name = "Chunk-%s" % str(atom)
        chunk.name = gensym("Chunk", assy) #bruce 080407 per Mark NFR desire
        atom.make_bondpoints_when_no_bonds() # notices atomtype
        assy.addnode(chunk) # REVIEW: same as self.addnode?
        return atom
        
    def modifyTransmute(self, elem, force = False, atomType = None): 
        """
        This method was originally a method of class mode and selectMode.
        Transmute selected atoms into <elem> and with an optional <atomType>. 
        <elem> is an element number that selected atoms will be transmuted to.
        <force>: boolean variable meaning keeping existing bond or not.
        <atomType>: the optional hybrid bond type if the element support hybrid. --Huaicai[9/1/05]
        """    
        # now change selected atoms to the specified element
        # [bruce 041215: this should probably be made available for any modes
        #  in which "selected atoms" are permitted, not just Select modes. #e]
        from model.elements import PeriodicTable
        if self.selatoms:
            dstElem = PeriodicTable.getElement(elem)
            for atm in self.selatoms.values():
                atm.Transmute(dstElem, force = force, atomtype=atomType)
                # bruce 041215 fix bug 131 by replacing low-level mvElement call
                # with new higher-level method Transmute. Note that singlets
                # can't be selected, so the fact that Transmute does nothing to
                # them is not (presently) relevant.
            #e status message?
            # (Presently a.Transmute makes one per "error or refusal".)
            self.o.gl_update()
            
        if self.selmols: #bruce 060720 elif -> if, in case both atoms and chunks can be selected someday
            dstElem = PeriodicTable.getElement(elem) #bruce 060720 fix typo dstElm -> dstElem to fix bug 2149
                # but we have to decide if we want the behavior this now gives us, of transmuting inside selected chunks.
            for mol in self.selmols[:]:
                for atm in mol.atoms.values():
                    atm.Transmute(dstElem, force = force, atomtype=atomType)
                        # this might run on some killed singlets; should be ok
            self.o.gl_update()
        
        return
    
    def modifyDeleteBonds(self):
        """
        Delete all bonds between selected and unselected atoms or chunks
        """
        cmd = greenmsg("Delete Bonds: ")
        
        if not self.selatoms and not self.selmols: # optimization, and different status msg
            msg = redmsg("Nothing selected")
            env.history.message(cmd + msg)
            return
        
        cutbonds = 0
        
        # Delete bonds between selected atoms and their neighboring atoms that are not selected.
        for a in self.selatoms.values():
            for b in a.bonds[:]:
                neighbor = b.other(a)
                if neighbor.element != Singlet:
                    if not neighbor.picked:
                        b.bust()
                        a.pick() # Probably not needed, but just in case...
                        cutbonds += 1

        # Delete bonds between selected chunks and chunks that are not selected.
        for mol in self.selmols[:]:
            # "externs" contains a list of bonds between this chunk and a different chunk
            for b in mol.externs[:]:
                # atom1 and atom2 are the connect atoms in the bond
                if int(b.atom1.molecule.picked) + int(b.atom2.molecule.picked) == 1: 
                    b.bust()
                    cutbonds += 1
                    
        msg = fix_plurals("%d bond(s) deleted" % cutbonds)
        env.history.message(cmd + msg)
        
        if self.selatoms and cutbonds:
            self.modifySeparate() # Separate the selected atoms into a new chunk
        else:
            self.w.win_update() #e do this in callers instead?
        return

    # change surface atom types to eliminate dangling bonds
    # a kludgey hack
    # bruce 041215 added some comments.
    def modifyPassivate(self):
        
        cmd = greenmsg("Passivate: ")
        
        if not self.selatoms and not self.selmols: # optimization, and different status msg
            msg = redmsg("Nothing selected")
            env.history.message(cmd + msg)
            return

        if self.selwhat == SELWHAT_CHUNKS:
            for m in self.selmols:
                m.Passivate(True) # arg True makes it work on all atoms in m
        else:
            assert self.selwhat == SELWHAT_ATOMS
            for m in self.molecules:
                m.Passivate() # lack of arg makes it work on only selected atoms
                # (maybe it could just iterate over selatoms... #e)
                
        # bruce 050511: remove self.changed (since done as needed in atom.Passivate) to fix bug 376
        ## self.changed() # could be much smarter
        self.o.gl_update()

    # add hydrogen atoms to each dangling bond
    # Changed this method to mirror what modifyDehydrogenate does.
    # It is more informative about the number of chunks modified, etc.
    # Mark 050124
    def modifyHydrogenate(self):
        """
        Add hydrogen atoms to bondpoints on selected chunks/atoms.
        """
        cmd = greenmsg("Hydrogenate: ")
        
        fixmols = {} # helps count modified mols for statusbar
        if self.selmols:
            counta = countm = 0
            for m in self.selmols:
                changed = m.Hydrogenate()
                if changed:
                    counta += changed
                    countm += 1
                    fixmols[id(m)] = m
            if counta:
                didwhat = "Added %d atom(s) to %d chunk(s)" \
                          % (counta, countm)
                if len(self.selmols) > countm:
                    didwhat += \
                        " (%d selected chunk(s) had no bondpoints)" \
                        % (len(self.selmols) - countm)
                didwhat = fix_plurals(didwhat)
            else:
                didwhat = "Selected chunks contain no bondpoints"    

        elif self.selatoms:
            count = 0
            for a in self.selatoms.values():
                ma = a.molecule
                for atm in a.neighbors():
                    matm = atm.molecule
                    changed = atm.Hydrogenate()
                    if changed:
                        count += 1
                        fixmols[id(ma)] = ma
                        fixmols[id(matm)] = matm
            if fixmols:
                didwhat = \
                    "Added %d atom(s) to %d chunk(s)" \
                    % (count, len(fixmols))
                didwhat = fix_plurals(didwhat)
                # Technically, we *should* say ", affected" instead of "from"
                # since the count includes mols of neighbors of
                # atoms we removed, not always only mols of atoms we removed.
                # Since that's rare, we word this assuming it didn't happen.
                # [#e needs low-pri fix to be accurate in that rare case;
                #  might as well deliver that as a warning, since that case is
                #  also "dangerous" in some sense.]
            else:
                didwhat = "No bondpoints on selected atoms"
        else:
            didwhat = redmsg("Nothing selected")

        if fixmols:
            self.changed()
            self.w.win_update()
        env.history.message(cmd + didwhat)
        return

    # Remove hydrogen atoms from each selected atom/chunk
    # (coded by Mark ~10/18/04; bugfixed/optimized/msgd by Bruce same day,
    #  and cleaned up (and perhaps further bugfixed) after shakedown changes
    #  on 041118.)
    def modifyDehydrogenate(self):
        """
        Remove hydrogen atoms from selected chunks/atoms.
        """
        cmd = greenmsg("Dehydrogenate: ")
        
        fixmols = {} # helps count modified mols for statusbar
        if self.selmols:
            counta = countm = 0
            for m in self.selmols:
                changed = m.Dehydrogenate()
                if changed:
                    counta += changed
                    countm += 1
                    fixmols[id(m)] = m
            if counta:
                didwhat = "Removed %d atom(s) from %d chunk(s)" \
                          % (counta, countm)
                if len(self.selmols) > countm:
                    didwhat += \
                        " (%d selected chunk(s) had no hydrogens)" \
                        % (len(self.selmols) - countm)
                didwhat = fix_plurals(didwhat)
            else:
                didwhat = "Selected chunks contain no hydrogens"
        elif self.selatoms:
            count = 0
            for a in self.selatoms.values():
                ma = a.molecule
                for atm in list(a.neighbors()) + [a]:
                    #bruce 041018 semantic change: added [a] as well
                    matm = atm.molecule
                    changed = atm.Dehydrogenate()
                    if changed:
                        count += 1
                        fixmols[id(ma)] = ma
                        fixmols[id(matm)] = matm
            if fixmols:
                didwhat = \
                    "Removed %d atom(s) from %d chunk(s)" \
                    % (count, len(fixmols))
                didwhat = fix_plurals(didwhat)
                # Technically, we *should* say ", affected" instead of "from"
                # since the count includes mols of neighbors of
                # atoms we removed, not always only mols of atoms we removed.
                # Since that's rare, we word this assuming it didn't happen.
                # [#e needs low-pri fix to be accurate in that rare case;
                #  might as well deliver that as a warning, since that case is
                #  also "dangerous" in some sense.]
            else:
                didwhat = "No hydrogens bonded to selected atoms"
        else:
            didwhat = redmsg("Nothing selected")
        if fixmols:
            self.changed() #e shouldn't we do this in lower-level methods?
            self.w.win_update()
        env.history.message(cmd + didwhat)
        return
    
    pass # end of class ops_atoms_Mixin

# end