summaryrefslogtreecommitdiff
path: root/cad/src/model/ExternalBondSet.py
blob: 193f5c6894a3edc5d7d14b60392ca78ca7f56fb4 (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
# Copyright 2008-2009 Nanorex, Inc.  See LICENSE file for details. 
"""
ExternalBondSet.py - keep track of external bonds, to optimize redraw

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

History:

bruce 080702 created

bruce 090211 making compatible with TransformNode, though that is unfinished
and not yet actually used.

(Update: as of 090225 TransformNode is abandoned.
However, I'm leaving some comments that refer to TransformNode in place
(in still-active files), since they also help point out the code which any 
other attempt to optimize rigid drags would need to modify. In those comments,
dt and st refer to dynamic transform and static transform, as used in
scratch/TransformNode.py.)

"""

# plan:
#
# initial test implem: do all bond drawing through this object,
# but do it just as often as right now, in the same way.
#
# see chunk._draw_external_bonds

# for now, we just maintain them but do nothing else with them,
# as of 080702 noon PT. update 080707: a debug_pref draws with them,
# false by default since predicted to be a slowdown for now. 
# Retesting this 090126, it seems to work. Put drawing code into
# ExternalBondSetDrawer, but not yet doing any caching there (CSDLs).
# When we do, not yet known how much inval/update code goes there vs. here.


from geometry.VQT import V, vlen

from graphics.model_drawing.ExternalBondSetDrawer import ExternalBondSetDrawer
    # todo: this import shouldn't be needed once we have the right
    # GraphicsRule architecture

_DEBUG_EBSET = False # ok to commit with True, until the debug_pref 
    # that calls us is on by default


class ExternalBondSet(object):
    """
    Know about all external bonds between two specific chunks,
    and cache info to speed their redrawing, including display lists.
    When the appearance or set of bonds change, we might be invalidated,
    or destroyed and remade, depending on client code.
    """
    def __init__(self, chunk1, chunk2):
        self.chunks = (chunk1, chunk2) # note: not private
            # note: our chunks are also called "our nodes",
            # since some of this code would work for them being any TransformNodes
            # WARNING: KLUGE: self.chunks might be reordered inside self.draw.
            # The order has no effect on anything except drawing,
            # and need not remain constant or relate to atom order in our bonds.
        # maybe todo: rename: _f_bonds
        self._bonds = {}
        self._drawer = ExternalBondSetDrawer(self) 
            # review: make on demand? GL context not current now...
            # hopefully ok, since it will only make DL on demand during .draw.
        return

    def __repr__(self):
        res = "<%s at %#x with %d bonds for %r>" % \
              (self.__class__.__name__.split('.')[-1],
               id(self),
               len(self._bonds),
               self.chunks # this is () if self is destroyed
              )
        return res
    
    def is_currently_bridging_dynamic_transforms(self):
        """
        @return: whether not all of our nodes share the same dynamic
                 transform object (counting None as such as object)
        @rtype: boolean
        """
        dt1 = self.chunks[0].dynamic_transform
        for chunk in self.chunks:
            if chunk.dynamic_transform is not dt1:
                return True
        return False

    def invalidate_distortion(self):
        """
        Called when any of our nodes' (chunks') dynamic transforms changes
        transform value (thus moving that node in space), if not all of our
        nodes share the same dynamic transform object, or when any of our nodes
        gets distorted internally (i.e. some of its atoms move, other than by
        all of them moving rigidly).
        """
        self.invalidate_display_lists()
        return
    
    def invalidate_display_lists(self):
        self._drawer.invalidate_display_lists()
        return
        
    def invalidate_display_lists_for_style(self, style): #bruce 090217
        """
        @see: documentation of same method in class Chunk
        """
        self._drawer.invalidate_display_lists_for_style(style)

    def other_chunk(self, chunk):
        """
        @see: Bond.other_chunk
        """
        c1, c2 = self.chunks
        if chunk is c1:
            return c2
        elif chunk is c2:
            return c1
        assert 0
        return
    
    def add_bond(self, bond):
        """
        Add this bond to self, if not already there.
        It must be a bond between our two chunks (not checked).
        Do appropriate invals within self.
        """
        # removed for speed: assert self._correct_bond(bond)
        if not self._bonds.has_key(id(bond)):
            # test is to avoid needless invalidation (important optim)
            self.invalidate_display_lists()
            self._bonds[id(bond)] = bond
            if _DEBUG_EBSET:
                print "added bond %r to %r" % (bond, self)
        return

    def empty(self): # todo: rename to is_empty
        return not self._bonds

    def remove_incorrect_bonds(self):
        """
        Some of our bonds may have been killed, or their atoms have changed,
        or their atom parents (chunks) have changed, so they are no longer
        correctly our members. Remove any such bonds, and do appropriate
        invals within self.
        """
        bad = []
        for bond in self._bonds.itervalues():
            if not self._correct_bond(bond):
                bad.append(bond)
        if bad:
            self.invalidate_display_lists()
            for bond in bad:
                del self._bonds[id(bond)]
                if _DEBUG_EBSET:
                    print "removed bond %r from %r" % (bond, self)
        return

    def _correct_bond(self, bond): # REVIEW: might need to speed this up.
        """
        Is bond, which once belonged in self, still ok to have in self?
        If not, it's because it was killed, or its atoms are not in both
        of self's chunks.
        """
        # bond must not be killed
        if bond.killed():
            return False
        if bond not in bond.atom1.bonds:
            # This ought to be checked by bond.killed, but isn't yet!
            # See comment therein. 
            
            # REVIEW: Hopefully it works now and bond.killed can be fixed to
            # check it. Conversely, if it doesn't work right, this routine
            # will probably have bugs of its own.
            
            # (Note: it'd be nice if that was faster, but there might be old
            # code that relies on looking at atoms of a killed bond, so we
            # can't just set the atoms to None. OTOH we don't want to waste
            # Undo time & space with a "killed flag" (for now).)
            return False
        # bond's atoms must (still) point to both of our chunks
        c1 = bond.atom1.molecule
        c2 = bond.atom2.molecule
        return (c1, c2) == self.chunks or (c2, c1) == self.chunks 
            # REVIEW: too slow due to == ? 
            # todo: optimize by sorting these when making bond?
            # (see also the kluge in draw, which can reverse them)
            # [bruce 090126]

    def destroy(self):
        if not self.chunks:
            return # permit repeated destroy
        if _DEBUG_EBSET:
            print "destroying %r" % self
        if self._drawer:
            self._drawer.destroy() # deallocate displists
            self._drawer = None
        for chunk in self.chunks:
            chunk._f_remove_ExternalBondSet(self)
        self.chunks = ()
        self._bonds = () # make len() still work
        ### TODO: other deallocations, e.g. of display lists
        return

    # ==

    # methods needed only for drawing
    
    def bounding_lozenge(self): #bruce 090306 unstubbed this
        # note: return this in abs coords, even after we have a
        # display list and permit relative motion.
        # todo: if this takes time to compute, cache it when
        # we redraw the display list, then
        # transform here into abs coords.
        chunk1, chunk2 = self.chunks
        c1, r1 = chunk1.bounding_sphere()
        c2, r2 = chunk2.bounding_sphere()
        return c1, c2, max(r1, r2)

    def bounding_sphere(self): #bruce 090306 unstubbed this; maybe never called
        # see comments in bounding_lozenge
        c1, c2, r = self.bounding_lozenge()
        return (c1 + c2)/2.0, r + vlen(c2 - c1)/2.0

    def should_draw_as_picked(self):
        """
        Should all the bonds in self be drawn as looking selected?

        @see: same named method in class Bond
        """
        # note: same-named method in class Bond has equivalent code
        # (after implem revised 090227)
        # warning: ChunkDrawer has an optim which depends on this
        # method's semantics; see its selColor assignment.
        return self.chunks[0].picked and self.chunks[1].picked

    def bondcolor(self): #bruce 090227
        """
        Return the color in which to draw all our bonds.
        """
        return self.chunks[0].drawing_color()
            # Note: this choice of bondcolor is flawed, because it depends
            # on which chunk is drawn first of the two that are connected.
            # (The reason it depends on that is because of a kluge in which
            #  we reorder the chunks to match that order, in self.draw().
            #  It would be better if it depended more directly on model tree
            #  order (the same, provided the chunks actually get drawn),
            #  or on something else (e.g. axis vs strand chunks).)
            # I'm leaving this behavior in place, but revising how it works
            # in order to avoid color changes or DL remake when highlighting
            # external bonds within ChunkDrawer.draw_highlighted.
            # [bruce 090227]

    def get_dispdef(self, glpane): #bruce 090227
        """
        Return the display style in which to draw all our bonds.
        """
        # [see comments in def bondcolor about choice of self.chunks[0]]
        # Note: if we ever decide to draw our bonds in both styles,
        # when our chunks disagree, we should revise this method's API
        # to return both disps to overdraw. (Or we might let it return
        # an opacity for each one, and also let users set a style like
        # that directly.)
        return self.chunks[0].get_dispdef(glpane)

    def draw(self, glpane, chunk, drawLevel, highlight_color = None):
        # todo: this method (and perhaps even our self._drawer attribute)
        # won't be needed once we have the right GraphicsRule architecture)
        if not self.chunks:
            # we've been destroyed (should not happen)
            return
        if chunk is not self.chunks[0] and highlight_color is None:
            # KLUGE; see self.bondcolor() comment for explanation.
            # This should happen at most once each time the model tree is
            # reordered (or the first time we're drawn), provided we don't
            # do it during ChunkDrawer.draw_highlighted (as we ensure by
            # testing highlight_color). Note that self (and therefore the
            # state of which chunk comes first) lasts as long as both chunks
            # remain alive. [bruce 090227]
            assert chunk is self.chunks[1]
            self.chunks = (self.chunks[1], self.chunks[0])
            pass
        self._drawer.draw(glpane, drawLevel, highlight_color)
    
    pass # end of class ExternalBondSet

# end