Ejemplo n.º 1
0
 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
Ejemplo n.º 2
0
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