summaryrefslogtreecommitdiff
path: root/cad/src/dna/model/DnaGroup.py
blob: c547ebcc6a6182095cfe3289b29cb73762ea62f5 (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
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
# Copyright 2007-2008 Nanorex, Inc.  See LICENSE file for details.
"""
DnaGroup.py - ...

@author: Bruce
@version: $Id$
@copyright: 2007-2008 Nanorex, Inc.  See LICENSE file for details.
"""

from foundation.Group import Group

from utilities.constants import gensym

from utilities.icon_utilities import imagename_to_pixmap

from dna.updater.dna_updater_globals import _f_DnaGroup_for_homeless_objects_in_Part

from utilities import debug_flags

from utilities.debug import print_compact_stack


class DnaGroup(Group):
    """
    Model object which packages together some Dna Segments, Dna Strands,
    and other objects needed to represent all their PAM atoms and markers.

    Specific kinds of Group member contents include:
    - DnaStrands (optionally inside Groups)
    - DnaSegments (ditto)
    - Groups (someday might be called Blocks when they occur in this context;
       note that class Block is deprecated and should not be used for these)
    - DnaMarkers (a kind of Jig, always inside an owning
      DnaStrand or DnaSegment)
    - specialized chunks for holding PAM atoms:
      - DnaAxisChunk (these live inside the DnaSegments they belong to)
      - DnaStrandChunk (these live inside their DnaStrands)

    As other attributes:
    - whatever other properties the user needs to assign, which are not
      covered by the member nodes or superclass attributes. [nim?]
    """

    # The iconPath specifies path(string) of an icon that represents the
    # objects of this class
    iconPath = "modeltree/DNA.png"
    hide_iconPath = "modeltree/DNA-hide.png"

    # This should be a tuple of classifications that appear in
    # files_mmp._GROUP_CLASSIFICATIONS, most general first.
    # See comment in class Group for more info. [bruce 080115]
    _mmp_group_classifications = ('DnaGroup',)

    # Open/closed state of the Dna Group in the Model Tree --
    # default closed.
    open = False

    autodelete_when_empty = True
        # (but only if current command permits that for this class --
        #  see comment near Group.autodelete_when_empty for more info,
        #  and implems of Command.keep_empty_group)

    def node_icon(self, display_prefs):
        """
        Model Tree node icon for the dna group node
        @see: Group.all_content_is_hidden()
        """
        del display_prefs # unused

        if self.all_content_is_hidden():
            return imagename_to_pixmap( self.hide_iconPath)
        else:
            return imagename_to_pixmap( self.iconPath)


    def make_DnaStrandOrSegment_for_marker(self, controlling_marker): # review: wholechain arg needed? @@@
        """
        The given DnaMarker is either newly made to control a wholechain,
        or old but newly controlling it; but it has no DnaStrandOrSegment.

        Make and return a new DnaStrand or DnaSegment
        (ask marker what class to use)
        inside self (review: inside some Group inside self?),
        perhaps making use of info in controlling_marker
        to help decide how to initialize some of its attributes.

        (Assume calling code will later move all chunks
        and markers from marker's wholechain into the new DnaStrandOrSegment,
        and will store references to it as needed
        into controlling_marker and/or its wholechain,
        so don't do those things here.)
        """
        assert not self.killed(), \
               "self must not be killed in %r.make_DnaStrandOrSegment_for_marker" % self
        class1 = controlling_marker.DnaStrandOrSegment_class()
        name = gensym(class1.__name__.split('.')[-1], self.assy)
            ###STUB -- should use class constant prefix
            # todo: sensible name? (if we split a seg, is name related to old seg; if so how?)
        assy = controlling_marker.assy # it's a Jig so it has one
        obj = class1(name, assy, None) # note: these args are for Group.__init__
        self.addchild(obj) # note: this asserts self.assy is not None
            # (but it can't assert not self.killed(), see its comment for why)
        return obj

    # Note: some methods below this point are examples or experiments or stubs,
    # and are likely to be revised significantly or replaced.
    # [bruce 080115 comment]

    # example method:
    def get_segments(self):
        """
        Return a list of all our DnaSegment objects.
        """
        return self.get_topmost_subnodes_of_class(self.assy.DnaSegment)

    def addSegment(self, segment):
        """
        Adds a new segment object for this dnaGroup.

        @param segment: The DnaSegment to be added to this DnaGroup object
        @type: B{DnaSegment}
        """
        self.addchild(segment)

    def getProps(self):
        """
        Method to support Dna duplex editing. see Group.__init__ for
        a comment

        THIS IS THE DEFAULT IMPLEMENTATION. TO BE MODIFIED
        """
        #Should it supply the Dna Segment list (children) and then add
        #individual segments when setProps is called??
        # [probably not; see B&N email discussion from when this comment was added]
        if self.editCommand:
            props = ()
            return props

    def setProps(self, props):
        """
        Method  to support Dna duplex editing. see Group.__init__ for
        a comment
        THIS IS THE DEFAULT IMPLEMENTATION. TO BE MODIFIED
        """
        #Should it accept the Dna Segment list and then add individual segments?
        pass

    def edit(self):
        """
        @see: Group.edit()
        """
        commandSequencer = self.assy.w.commandSequencer
        commandSequencer.userEnterCommand('BUILD_DNA', always_update = True)
        currentCommand = commandSequencer.currentCommand
        assert currentCommand.commandName == 'BUILD_DNA'
        currentCommand.editStructure(self)

    def getDnaSequence(self, format = 'CSV'):
        """
        Return the complete Dna sequence information string (i.e. all strand
        sequences) in the specified format.

        @return: The Dna sequence string
        @rtype: string

        """
        if format == 'CSV':#comma separated values.
            separator = ','
        dnaSequenceString = ''
        strandList = self.getStrands()

        for strand in strandList:
            dnaSequenceString = dnaSequenceString + strand.name + separator
            strandSequenceString = str(strand.getStrandSequence())
            if strandSequenceString:
                strandSequenceString = strandSequenceString.upper()
                dnaSequenceString = dnaSequenceString + strandSequenceString

            dnaSequenceString = dnaSequenceString + "\n"

        return dnaSequenceString


    def getStrands(self):
        """
        Returns a list of strands inside a DnaGroup object

        @return: A list containing all the strand objects
                 within self.
        @rtype: list

        @see: B{BuildDna_PropertyManager.updateStrandListWidget()}
        @see: B{BuildDna_PropertyManager._currentSelectionParams}
        """

        #TO BE REVISED. As of 2008-01-17, it uses isinstance check for
        #Chunk and some additional things to find out a list of strands inside
        # a DnaGroup -- Ninad 2008-01-17

        if self.assy is None:
            #This is to avoid possible bugs if group gets deleted. But print
            #traceback so that we know about this bug. This could happen due to
            #insufficient command sequencer stack. Temporary fix for bug 2699
            print_compact_stack("bug: self.assy is None for DnaGroup %s."%self)
            return ()

        strandList = []

        def filterStrands(node):
            if isinstance(node, self.assy.DnaStrand) and not node.isEmpty():
                strandList.append(node)
            elif isinstance(node, self.assy.Chunk) and node.isStrandChunk():
                if node.parent_node_of_class(self.assy.DnaStrand) is None:
                    strandList.append(node)

        self.apply2all(filterStrands)

        return strandList

    def getAxisChunks(self):
        """
        Returns a list of Axis chunks inside a DnaGroup object

        @return: A list containing all the axis chunks
                 within self.
        @rtype: list
        """
        if self.assy is None:
            #This is to avoid possible bugs if group gets deleted. But print
            #traceback so that we know about this bug. This could happen due to
            #insufficient command sequencer stack. Temporary fix for bug 2699
            print_compact_stack("bug: self.assy is None for DnaGroup %s."%self)
            return ()

        #TO BE REVISED. It uses isinstance check for
        #Chunk and some additional things to find out a list of strands inside
        # a DnaGroup -- Ninad 2008-02-02

        axisChunkList = []
        def filterAxisChunks(node):
            if isinstance(node, self.assy.Chunk) and node.isAxisChunk():
                axisChunkList.append(node)

        self.apply2all(filterAxisChunks)

        return axisChunkList

    def isEmpty(self):
        """
        Returns True if there are no axis or strand chunks as its members
        (Returns True even when there are empty DnaSegment objects inside)

        TODO: It doesn't consider other possibilitis such as hairpins .
        In general it relies on what getAxisChunks and getStrands returns
        (which in turn use 'Chunk' object to determine these things.)
        This method must be revised in the near future in a fully functional
        dna data model
        @see: BuildDna_EditCommand._finalizeStructure where this test is used.
        """
        #May be for the short term, we can use self.getAtomList()? But that
        #doesn't ensure if the DnaGroup always has atom of type either
        #'strand' or 'axis' .
        if len(self.getStrands()) == 0 and len(self.getAxisChunks()) == 0:
            return True
        else:
            return False


    def getSelectedStrands(self):
        """
        Returns a list of selected strands of the DnaGroup
        @return: A list containing the selected strand objects
                 within self.
        @rtype: list
        """
        selectedStrandList = []
        for strand in self.getStrands():
            if strand.picked:
                selectedStrandList.append(strand)

        return selectedStrandList

    def getSelectedSegments(self):
        """
        Returns a list of segments whose all members are selected.
        @return: A list containing the selected strand objects
                 within self.
        @rtype: list
        """
        #TODO: This is a TEMPORARY KLUDGE  until Dna model is fully functional.
        #Must be revised. Basically it returns a list of DnaSegments whose
        #all members are selected.
        #See BuildDna_PropertyManager._currentSelectionParams() where it is used
        #-- Ninad 2008-01-18
        segmentList = self.get_segments()

        selectedSegmentList = []

        for segment in segmentList:

            pickedNodes = []
            unpickedNodes = []

            def func(node):
                if isinstance(node, self.assy.Chunk):
                    if not node.picked:
                        unpickedNodes.append(node)
                    else:
                        pickedNodes.append(node)

            segment.apply2all(func)

            if len(unpickedNodes) == 0 and pickedNodes:
                selectedSegmentList.append(segment)

        return selectedSegmentList

    def getAtomList(self):
        """
        Return a list of all atoms cotained within this DnaGroup
        """
        atomList = []
        def func(node):
            if isinstance(node, self.assy.Chunk):
                atomList.extend(node.atoms.itervalues())

        self.apply2all(func)
        return atomList

    def draw_highlighted(self, glpane, color):
        """
        Draw the strand and axis chunks as highlighted. (Calls the related
        methods in the chunk class)
        @param: GLPane object
        @param color: The highlight color
        @see: Chunk.draw_highlighted()
        @see: SelectChunks_GraphicsMode.draw_highlightedChunk()
        @see: SelectChunks_GraphicsMode._get_objects_to_highlight()
        """
        for c in self.getStrands():
            c.draw_highlighted(glpane, color)
        for c in self.getAxisChunks():
            c.draw_highlighted(glpane, color)


    pass # end of class DnaGroup

# ==

def find_or_make_DnaGroup_for_homeless_object(node):
    """
    All DNA objects found outside of a DnaGroup during one run of the dna updater
    in one Part should be put into one new DnaGroup at the end of that Part.
    This is a fallback, since it only happens if we didn't sanitize DnaGroups
    when reading a file, or due to bugs in Dna-related user ops,
    or a user running an ordinary op on DNA that our UI is supposed to disallow.
    So don't worry much about prettiness, just correctness,
    though don't gratuitously discard info.

    The hard part is "during one run of the dna updater". We'll let it make a
    global dict from Part to this DnaGroup, and discard it after every run
    (so no need for this dict to be weak-keyed).

    If we have to guess the Part, we'll use the node's assy's current Part
    (but complain, since this probably indicates a bug).
    """
    part = node.part or node.assy.part
    assert part
    if not node.part:
        print "likely bug: %r in %r has no .part" % \
              (node, node.assy)
    try:
        res = _f_DnaGroup_for_homeless_objects_in_Part[part]
    except KeyError:
        res = None
    if res:
        # found one -- make sure it's still ok
        # maybe: also make sure it's still in the same part, assy, etc.
        # (not urgent, now that this only lasts for one run of the updater)
        if res.killed():
            # discard it, after complaining [bruce 080218 bugfix for when this
            # got killed by user; works, but now should never happen due to the
            # better fix of calling clear_updater_run_globals() at start and end
            # of every updater run]
            print "\nBUG: _f_DnaGroup_for_homeless_objects_in_Part[%r] " \
                  "found %r which is killed -- discarding it" % \
                  (part, res)
            res = None
    if not res:
        res = _make_DnaGroup_for_homeless_objects_in_Part(part)
        _f_DnaGroup_for_homeless_objects_in_Part[part] = res
    return res

def _make_DnaGroup_for_homeless_objects_in_Part(part):
    # not needed, done in addnode: part.ensure_toplevel_group()
    assy = part.assy #k
    name = gensym("DnaGroup", assy) #bruce 080407 remove "fallback", pass assy
    dad = None
    dnaGroup = DnaGroup(name, assy, dad) # same args as for Group.__init__
    part.addnode(dnaGroup)
    if debug_flags.DEBUG_DNA_UPDATER:
        print "dna_updater: made new dnaGroup %r" % dnaGroup, \
              "(bug or unfixed mmp file)"
    return dnaGroup

# end