def __init__(self, parent, shareWidget, useStencilBuffer):
        """
        If shareWidget is specified, useStencilBuffer is ignored: set it in the widget you're sharing with.
        """

        if shareWidget:
            self.shareWidget = shareWidget  #bruce 051212
            glformat = shareWidget.format()
            QGLWidget.__init__(self, glformat, parent, shareWidget)
            if not self.isSharing():
                print "Request of display list sharing is failed."
                return
        else:
            glformat = QGLFormat()
            if (useStencilBuffer):
                glformat.setStencil(True)
                # set gl format to request stencil buffer
                # (needed for mouseover-highlighting of objects of general
                #  shape in depositMode.bareMotion) [bruce 050610]

            QGLWidget.__init__(self, glformat, parent)

        # Current view attributes (sometimes saved in or loaded from
        #  the currently displayed part or its mmp file):

        # rotation
        self.quat = Q(1, 0, 0, 0)

        # point of view (i.e. negative of center of view)
        self.pov = V(0.0, 0.0, 0.0)

        # half-height of window in Angstroms (reset by certain view-changing operations)
        self.scale = float(env.prefs[startup_GLPane_scale_prefs_key])

        # zoom factor
        self.zoomFactor = 1.0

        self.trackball = Trackball(10, 10)

        self._functions_to_call_when_gl_context_is_current = []

        # piotr 080714: Defined this attribute here in case
        # chunk.py accesses it in ThumbView.
        self.lastNonReducedDisplayMode = default_display_mode
        return
    def __init__(self, parent, shareWidget, useStencilBuffer):
        """
        #doc
        
        @note: If shareWidget is specified, useStencilBuffer is ignored:
               set it in the widget you're sharing with.
        """
        if shareWidget:
            self.shareWidget = shareWidget #bruce 051212
            glformat = shareWidget.format()
            QGLWidget.__init__(self, glformat, parent, shareWidget)
            if not self.isSharing():
                assert 0, "%r refused to share GL display list namespace " \
                          "with %r" % (self, shareWidget)
                return
        else:
            glformat = QGLFormat()
            if (useStencilBuffer):
                glformat.setStencil(True)
                    # set gl format to request stencil buffer
                    # (needed for mouseover-highlighting of objects of general
                    #  shape in BuildAtoms_Graphicsmode.bareMotion) [bruce 050610]

            if (self.useMultisample):
                # use full scene anti-aliasing on hardware that supports it
                # (note: setting this True works around bug 2961 on some systems)
                glformat.setSampleBuffers(True)
            
            QGLWidget.__init__(self, glformat, parent)
            pass

        self.glprefs = drawing_globals.glprefs
            # future: should be ok if this differs for different glpanes,
            # even between self and self.shareWidget. AFAIK, the refactoring
            # I'm doing yesterday and today means this would work fine,
            # or at least it does most of what would be required for that.
            # [bruce 090304]
        
        self._initialize_view_attributes()

        # Initial value of depth "constant" (changeable by prefs.)
        self.DEPTH_TWEAK = DEPTH_TWEAK_UNITS * DEPTH_TWEAK_VALUE

        self.trackball = Trackball(10, 10)

        self._functions_to_call_when_gl_context_is_current = []

        # piotr 080714: Defined this attribute here in case
        # chunk.py accesses it in ThumbView. 
        self.lastNonReducedDisplayMode = default_display_mode
        
        # piotr 080807
        # Most recent quaternion to be used in animation timer.
        self.last_quat = None

        self.transforms = [] ### TODO: clear this at start of frame, complain if not clear
            # stack of current transforms (or Nodes that generate them)
            # [bruce 090220]
            # Note: this may be revised once we have TransformNode,
            # e.g. we might push their dt and st separately;
            # note we might need to push a "slot" for them if they might
            # change before being popped, or perhaps even if they might
            # change between the time pushed and the time later used
            # (by something that collects them from here in association
            #  with a ColorSortedDisplayList).
        
        return
Exemple #3
0
    def __init__(self, parent, shareWidget, useStencilBuffer):
        """
        #doc
        
        @note: If shareWidget is specified, useStencilBuffer is ignored:
               set it in the widget you're sharing with.
        """
        if shareWidget:
            self.shareWidget = shareWidget  #bruce 051212
            glformat = shareWidget.format()
            QGLWidget.__init__(self, glformat, parent, shareWidget)
            if not self.isSharing():
                assert 0, "%r refused to share GL display list namespace " \
                          "with %r" % (self, shareWidget)
                return
        else:
            glformat = QGLFormat()
            if (useStencilBuffer):
                glformat.setStencil(True)
                # set gl format to request stencil buffer
                # (needed for mouseover-highlighting of objects of general
                #  shape in BuildAtoms_Graphicsmode.bareMotion) [bruce 050610]

            if (self.useMultisample):
                # use full scene anti-aliasing on hardware that supports it
                # (note: setting this True works around bug 2961 on some systems)
                glformat.setSampleBuffers(True)

            QGLWidget.__init__(self, glformat, parent)
            pass

        self.glprefs = drawing_globals.glprefs
        # future: should be ok if this differs for different glpanes,
        # even between self and self.shareWidget. AFAIK, the refactoring
        # I'm doing yesterday and today means this would work fine,
        # or at least it does most of what would be required for that.
        # [bruce 090304]

        self._initialize_view_attributes()

        # Initial value of depth "constant" (changeable by prefs.)
        self.DEPTH_TWEAK = DEPTH_TWEAK_UNITS * DEPTH_TWEAK_VALUE

        self.trackball = Trackball(10, 10)

        self._functions_to_call_when_gl_context_is_current = []

        # piotr 080714: Defined this attribute here in case
        # chunk.py accesses it in ThumbView.
        self.lastNonReducedDisplayMode = default_display_mode

        # piotr 080807
        # Most recent quaternion to be used in animation timer.
        self.last_quat = None

        self.transforms = [
        ]  ### TODO: clear this at start of frame, complain if not clear
        # stack of current transforms (or Nodes that generate them)
        # [bruce 090220]
        # Note: this may be revised once we have TransformNode,
        # e.g. we might push their dt and st separately;
        # note we might need to push a "slot" for them if they might
        # change before being popped, or perhaps even if they might
        # change between the time pushed and the time later used
        # (by something that collects them from here in association
        #  with a ColorSortedDisplayList).

        return
class GLPane_minimal(QGLWidget, GLPane_drawingset_methods, object): #bruce 070914
    """
    Mostly a stub superclass, just so GLPane and ThumbView can have a common
    superclass.

    TODO:
    They share a lot of code, which ought to be merged into this superclass.
    Once that happens, it might as well get renamed.
    """

    # bruce 070920 added object superclass to our subclass GLPane;

    # bruce 080220 moved object superclass from GLPane to this class.
    # Purpose is to make this a new-style class so as to allow
    # defining a python property in any subclass.

    # bruce 090219 added GLPane_drawingset_methods mixin superclass
    # (for draw_csdl method; ok to ignore pylint warning about not
    #  calling its __init__ method (it doesn't have one);
    #  requires calling _before_drawing_csdls and
    #  _after_drawing_csdls in all our subclass draw-like methods;
    #  to help with this the mixin provides methods
    #  _call_func_that_draws_model and
    #  _call_func_that_draws_objects)
    
    # note: every subclass defines .assy as an instance variable or property
    # (as of bruce 080220), but we can't define a default value or property
    # for that here, since some subclasses define it in each way
    # (and we'd have to know which way to define a default value correctly).

    # class constants

    SIZE_FOR_glSelectBuffer = 10000
        # guess, probably overkill, seems to work, no other value was tried
    
    # default values of instance variables:

    shareWidget = None

    is_animating = False #bruce 080219 fix bug 2632 (unverified)

    initialised = False

    _resize_counter = 0

    drawing_phase = '?' # overridden in GLPane_rendering_methods, but needed for
        # debug prints that might happen in any class of GLPane [bruce 090220]

    # default values of subclass-specific constants

    permit_draw_bond_letters = True #bruce 071023

    permit_shaders = False #bruce 090224
        # default False until shaders can work with more than one glpane
        # (set to true in the main GLPane)

    _general_appearance_change_indicator = 0 #bruce 090306
        # note: strictly speaking, ThumbView should update this like GLPane does,
        # but that's difficult (it has no part or assy in general),
        # and not very important (cosmetic bug when user changes certain prefs,
        # worked around by changing what's shown in the ThumbView).

    useMultisample = env.prefs[enableAntiAliasing_prefs_key]

    glprefs = None

    def __init__(self, parent, shareWidget, useStencilBuffer):
        """
        #doc
        
        @note: If shareWidget is specified, useStencilBuffer is ignored:
               set it in the widget you're sharing with.
        """
        if shareWidget:
            self.shareWidget = shareWidget #bruce 051212
            glformat = shareWidget.format()
            QGLWidget.__init__(self, glformat, parent, shareWidget)
            if not self.isSharing():
                assert 0, "%r refused to share GL display list namespace " \
                          "with %r" % (self, shareWidget)
                return
        else:
            glformat = QGLFormat()
            if (useStencilBuffer):
                glformat.setStencil(True)
                    # set gl format to request stencil buffer
                    # (needed for mouseover-highlighting of objects of general
                    #  shape in BuildAtoms_Graphicsmode.bareMotion) [bruce 050610]

            if (self.useMultisample):
                # use full scene anti-aliasing on hardware that supports it
                # (note: setting this True works around bug 2961 on some systems)
                glformat.setSampleBuffers(True)
            
            QGLWidget.__init__(self, glformat, parent)
            pass

        self.glprefs = drawing_globals.glprefs
            # future: should be ok if this differs for different glpanes,
            # even between self and self.shareWidget. AFAIK, the refactoring
            # I'm doing yesterday and today means this would work fine,
            # or at least it does most of what would be required for that.
            # [bruce 090304]
        
        self._initialize_view_attributes()

        # Initial value of depth "constant" (changeable by prefs.)
        self.DEPTH_TWEAK = DEPTH_TWEAK_UNITS * DEPTH_TWEAK_VALUE

        self.trackball = Trackball(10, 10)

        self._functions_to_call_when_gl_context_is_current = []

        # piotr 080714: Defined this attribute here in case
        # chunk.py accesses it in ThumbView. 
        self.lastNonReducedDisplayMode = default_display_mode
        
        # piotr 080807
        # Most recent quaternion to be used in animation timer.
        self.last_quat = None

        self.transforms = [] ### TODO: clear this at start of frame, complain if not clear
            # stack of current transforms (or Nodes that generate them)
            # [bruce 090220]
            # Note: this may be revised once we have TransformNode,
            # e.g. we might push their dt and st separately;
            # note we might need to push a "slot" for them if they might
            # change before being popped, or perhaps even if they might
            # change between the time pushed and the time later used
            # (by something that collects them from here in association
            #  with a ColorSortedDisplayList).
        
        return

    def _initialize_view_attributes(self): #bruce 090220 split this out
        """
        Initialize the current view attributes
        """
        # note: these are sometimes saved in or loaded from
        # the currently displayed part or its mmp file

        # rotation
        self.quat = Q(1, 0, 0, 0)

        # point of view (i.e. negative of center of view)
        self.pov = V(0.0, 0.0, 0.0)

        # half-height of window in Angstroms (reset by certain view-changing operations)
        self.scale = float(env.prefs[startup_GLPane_scale_prefs_key])
        
        # zoom factor
        self.zoomFactor = 1.0
            # Note: I believe (both now, and in a comment dated 060829 which is
            # now being removed from GLPane.py) that nothing ever sets
            # self.zoomFactor to anything other than 1.0, though there is code
            # which could do this in theory. I think zoomFactor was added as
            # one way to implement zoomToArea, but another way (changing scale)
            # was chosen, which makes zoomFactor useless. Someday we should
            # consider removing it, unless we think it might be useful for
            # something in the future. [bruce 080910 comment]
        return
    
    # define properties which return model-space vectors
    # corresponding to various directions relative to the screen
    # (can be used during drawing or when handling mouse events)
    #
    # [bruce 080912 turned these into properties, and optimized;
    #  before that, this was done by __getattr__ in each subclass]

    def __right(self, _q = V(1, 0, 0)):
        return self.quat.unrot(_q)
    right = property(__right)

    def __left(self, _q = V(-1, 0, 0)):
        return self.quat.unrot(_q)
    left = property(__left)

    def __up(self, _q = V(0, 1, 0)):
        return self.quat.unrot(_q)
    up = property(__up)

    def __down(self, _q = V(0, -1, 0)):
        return self.quat.unrot(_q)
    down = property(__down)

    def __out(self, _q = V(0, 0, 1)):
        return self.quat.unrot(_q)
    out = property(__out)
    
    def __lineOfSight(self, _q = V(0, 0, -1)):
        return self.quat.unrot(_q)
    lineOfSight = property(__lineOfSight)

    def get_angle_made_with_screen_right(self, vec):
        """
        Returns the angle (in degrees) between screen right direction
        and the given vector. It returns positive angles (between 0 and
        180 degrees) if the vector lies in first or second quadrants
        (i.e. points more up than down on the screen). Otherwise the
        angle returned is negative.
        """
        #Ninad 2008-04-17: This method was added AFTER rattlesnake rc2.
        #bruce 080912 bugfix: don't give theta the wrong sign when
        # dot(vec, self.up) < 0 and dot(vec, self.right) == 0.
        vec = norm(vec)        
        theta = angleBetween(vec, self.right)
        if dot(vec, self.up) < 0:
            theta = - theta
        return theta

    def __get_vdist(self):
        """
        Recompute and return the desired distance between
        eyeball and center of view.
        """
        #bruce 070920 revised; bruce 080912 moved from GLPane into GLPane_minimal
        # Note: an old comment from elsewhere in GLPane claims:
            # bruce 041214 comment: some code assumes vdist is always 6.0 * self.scale
            # (e.g. eyeball computations, see bug 30), thus has bugs for aspect < 1.0.
            # We should have glpane attrs for aspect, w_scale, h_scale, eyeball,
            # clipping planes, etc, like we do now for right, up, etc. ###e
        # I don't know if vdist could ever have a different value,
        # or if we still have aspect < 1.0 bugs due to some other cause.
        return 6.0 * self.scale

    vdist = property(__get_vdist)

    def eyeball(self): #bruce 060219
        ##e should call this to replace equivalent formulae in other places
        """
        Return the location of the eyeball in model coordinates.

        @note: this is not meaningful except when using perspective projection.
        """
        ### REVIEW: whether this is correct for tall aspect ratio GLPane.
        # See also the comment in __get_vdist, above, mentioning bug 30.
        # There is related code in writepovfile which computes a camera position
        # which corrects vdist when aspect < 1.0:
        ##    # Camera info
        ##    vdist = cdist
        ##    if aspect < 1.0:
        ##            vdist = cdist / aspect
        ##    eyePos = vdist * glpane.scale * glpane.out - glpane.pov
        # [bruce comment 080912]
        #bruce 0809122 moved this from GLPane into GLPane_minimal,
        # and made region selection code call it for the first time.
        return self.quat.unrot(V(0, 0, self.vdist)) - self.pov

    def __repr__(self):
        return "<%s at %#x>" % (self.__class__.__name__.split('.')[-1], id(self))

    def initializeGL(self):
        """
        Called once by Qt when the OpenGL context is first ready to use.
        """
        #bruce 080912 merged this from subclass methods, guessed docstring
        self._setup_lighting()
        
        glShadeModel(GL_SMOOTH)
        glEnable(GL_DEPTH_TEST)
        glEnable(GL_CULL_FACE)
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        
        if not self.isSharing():
            self._setup_display_lists()
        return

    def resizeGL(self, width, height):
        """
        Called by QtGL when the drawing window is resized.
        """
        #bruce 080912 moved this from GLPane into GLPane_minimal

        self._resize_counter += 1 #bruce 080922
        
        self.width = width
        self.height = height

        glViewport(0, 0, self.width, self.height)
            # example of using a smaller viewport:
            ## glViewport(10, 15, (self.width - 10)/2, (self.height - 15)/3)

        # modify width and height for trackball
        # (note: this was done in GLPane but not in ThumbView until 080912)
        if width < 300:
            width = 300
        if height < 300:
            height = 300
        self.trackball.rescale(width, height)
        
        self.initialised = True
        
        return

    def __get_aspect(self):
        #bruce 080912 made this a property, moved to this class
        return (self.width + 0.0) / (self.height + 0.0)
    
    aspect = property(__get_aspect)
    
    def _setup_projection(self, glselect = False):
        ### WARNING: This is not actually private! TODO: rename it.
        """
        Set up standard projection matrix contents using various attributes of
        self (aspect, vdist, scale, zoomFactor).  Also reads the current OpenGL
        viewport bounds in window coordinates.
        
        (Warning: leaves matrixmode as GL_PROJECTION.)
        
        @param glselect: False (default) normally, or a 4-tuple
               (format not documented here) to prepare for GL_SELECT picking by
               calling gluPickMatrix().

        If you are really going to draw in the pick window (for GL_RENDER and
        glReadPixels picking, rather than GL_SELECT picking), don't forget to
        set the glViewport *after* calling _setup_projection.  Here's why:

           gluPickMatrix needs to know the current *whole-window* viewport, in
           order to set up a projection matrix to map a small portion of it to
           the clipping boundaries for GL_SELECT.

           From the gluPickMatrix doc page:
             viewport:
               Specifies the current viewport (as from a glGetIntegerv call).
             Description:
               gluPickMatrix creates a projection matrix that can be used to
               restrict drawing to a small region of the viewport.

           In the graphics pipeline, the clipper actually works in homogeneous
           coordinates, clipping polygons to the {X,Y}==+-W boundaries.  This
           saves the work of doing the homogeneous division: {X,Y}/W==+-1.0,
           (and avoiding problems when W is zero for points on the eye plane in
           Z,) but it comes down to the same thing as clipping to X,Y==+-1 in
           orthographic.

           So the projection matrix decides what 3D model-space planes map to
           +-1 in X,Y.  (I think it maps [near,far] to [0,1] in Z, because
           they're not clipped symmetrically.)

           Then glViewport sets the hardware transform that determines where the
           +-1 square of clipped output goes in screen pixels within the window.

           Normally you don't actually draw pixels while picking in GL_SELECT
           mode because the pipeline outputs hits after the clipping stage, so
           gluPickMatrix only reads the viewport and sets the projection matrix.
        """
        #bruce 080912 moved this from GLPane into GLPane_minimal
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()

        scale = self.scale * self.zoomFactor
        near, far = self.near, self.far

        aspect = self.aspect
        vdist = self.vdist

        if glselect:
            x, y, w, h = glselect
            gluPickMatrix(
                x, y,
                w, h,
                glGetIntegerv( GL_VIEWPORT ) #k is this arg needed? it might be the default...
            )

        if self.ortho:
            glOrtho( - scale * aspect, scale * aspect,
                     - scale,          scale,
                     vdist * near, vdist * far )
        else:
            glFrustum( - scale * near * aspect, scale * near * aspect,
                       - scale * near,          scale * near,
                       vdist * near, vdist * far)
        return

    def _setup_modelview(self):
        """
        set up modelview coordinate system
        """
        #bruce 080912 moved this from GLPane into GLPane_minimal
        # note: it's not yet used in ThumbView, but maybe it could be.
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        glTranslatef( 0.0, 0.0, - self.vdist)
            # translate coordinate system for drawing, away from eye
            # (-Z direction) by vdist. This positions center of view
            # in eyespace.
        q = self.quat
        glRotatef( q.angle * 180.0 / math.pi, q.x, q.y, q.z)
            # rotate coordinate system for drawing by trackball quat
            # (this rotation is around the center of view, since that is what
            #  is positioned at drawing coordsystem's current origin,
            #  by the following code)
        glTranslatef( self.pov[0], self.pov[1], self.pov[2])
            # translate model (about to be drawn) to bring its center of view
            # (cov == - pov) to origin of coordinate system for drawing.
            # We translate by -cov to bring model.cov to origin.
        return
    
    def _setup_lighting(self):
        # note: in subclass GLPane, as of 080911 this is defined in
        # its mixin superclass GLPane_lighting_methods
        assert 0, "subclass must override this"
        
    def _setup_display_lists(self): # bruce 071030
        """
        This needs to be called during __init__ if a new display list context
        is being used.

        WARNING: the functions this calls store display list names in
        global variables (as of 071030); until this is cleaned up, only the
        most recently set up display list context will work with the
        associated drawing functions in the same modules.
        """
        # TODO: warn if called twice (see docstring for why)
        setup_drawer()
        setup_draw_grid_lines()
        return

    # ==

    def model_is_valid(self): #bruce 080117
        """
        Subclasses should override this to return a boolean
        saying whether their model is currently valid for drawing.

        Subclass implementations of paintGL are advised to call this
        at the beginning and return immediately if it's false.
        """
        return True #bruce 080913 False -> True (more useful default)

    # ==

    def is_sphere_visible(self, center, radius): # piotr 080331
        """
        Frustum culling test. Subclasses should override it
        to disable drawing objects outside of the view frustum.
        """
        return True

    # ==

    def is_lozenge_visible(self, pos1, pos2, radius): # piotr 080402
        """
        Frustum culling test for lozenge-shaped objects. Subclasses should 
        override it to disable drawing objects outside of the view frustum.
        """
        return True

    # ==

    def _call_whatever_waits_for_gl_context_current(self): #bruce 071103
        """
        For whatever functions have been registered to be called (once)
        when our GL context is next current, call them now (and then discard them).

        Note: subclasses wishing to support self.call_when_glcontext_is_next_current
        MUST call this at some point during their paintGL method
        (preferably before doing any drawing in that method, though this is
        not at present guaranteed). Subclasses not wishing to support that
        should override it to discard functions passed to it immediately.
        """
        functions = self._functions_to_call_when_gl_context_is_current
        self._functions_to_call_when_gl_context_is_current = []
        for func in functions:
            try:
                func()
            except:
                print_compact_traceback(
                    "bug: %r._call_whatever_waits_for_gl_context_current "
                    "ignoring exception in %r: " % \
                    (self, func) )
            continue
        return

    def call_when_glcontext_is_next_current(self, func): #bruce 071103
        """
        Call func at the next convenient time when we know our OpenGL context
        is current.

        (Subclasses are permitted to call func immediately if they happen
        to know their gl context is current right when this is called.
        Conversely, subclasses are permitted to never call func, though
        that will defeat the optimizations this is used for, such as
        deallocating OpenGL display lists which are no longer needed.)
        """
        self._functions_to_call_when_gl_context_is_current.append( func)
        return

    # ==

    def should_draw_valence_errors(self):
        """
        Return a boolean to indicate whether valence error
        indicators (of any kind) should ever be drawn in self.
        (Each specific kind may also be controlled by a prefs
        setting, checked independently by the caller. As of 070914
        there is only one kind, drawn by class Atom.)
        """
        return False

    def get_drawLevel(self, assy_or_part):
        """
        Get the recommended sphere drawingLevel to use for drawing
        assy_or_part in self.

        @param assy_or_part: an Assembly, or its current .part, a Part
        """
        #bruce 090306 split out of ChunkDrawer, optimized for shaders
        #bruce 090309 revised
        if self.permit_shaders and \
           self.glprefs.sphereShader_desired() and \
           drawing_globals.sphereShader_available():
            # drawLevel doesn't matter (not used by shaders),
            # so return a constant value to avoid accessing assy.drawLevel,
            # and therefore (I hope) avoid recomputing the number of atoms
            # in assy. But in case of bugs in which this ends up being used,
            # let this constant be the usual level "2".
            # [todo: use a symbolic constant, probably there is one already]
            return 2
        else:
            return assy_or_part.drawLevel # this might recompute it
            # (if that happens and grabs the pref value, I think this won't
            #  subscribe our Chunks' display list to it, since we're called
            #  outside the begin/ends for those, and that's good, since they
            #  include this in havelist instead, which avoids some unneeded
            #  redrawing, e.g. if pref changed and changed back while
            #  displaying a different Part. [bruce 060215])
        pass

    # ==

    def setDepthRange_setup_from_debug_pref(self):
        self.DEPTH_TWEAK = DEPTH_TWEAK_UNITS * \
                    debug_pref("GLPane: depth tweak", DEPTH_TWEAK_CHOICE)
        return

    def setDepthRange_Normal(self):
        glDepthRange(0.0 + self.DEPTH_TWEAK, 1.0) # args are near, far
        return

    def setDepthRange_Highlighting(self):
        glDepthRange(0.0, 1.0 - self.DEPTH_TWEAK)
        return
    
    # ==

    # REVIEW:
    # the following "Undo view" methods probably don't work in subclasses other
    # than GLPane. It might make sense to have them here, but only if they are
    # refactored a bit, e.g. so that self.animateToView works in other
    # subclassses even if it doesn't animate. Ultimately it might be better
    # to refactor them a lot and/or move them out of this class hierarchy
    # entirely. [bruce 080912 comment]
    
    def current_view_for_Undo(self, assy): #e shares code with saveNamedView
        """
        Return the current view in this glpane (which we assume is showing
        this assy), with additional attributes saved along with the view by Undo
        (i.e. the index of the current selection group).
        (The assy arg is used for multiple purposes specific to Undo.)

        @warning: present implem of saving current Part (using its index in MT)
                  is not suitable for out-of-order Redo.
        """
        # WARNING: not reviewed for use in subclasses which don't
        # have and draw a .assy attribute, though by passing assy into this method,
        # we remove any obvious bug from that. [bruce 080220 comment]

        oldc = assy.all_change_indicators()

        namedView = NamedView(assy, "name", self.scale, self.pov, self.zoomFactor, self.quat)

        newc = assy.all_change_indicators()
        assert oldc == newc

        namedView.current_selgroup_index = assy.current_selgroup_index()
            # storing this on the namedView is a kluge, but should be safe

        return namedView # ideally would not return a Node but just a
            # "view object" with the same 4 elements in it as passed to NamedView

    def set_view_for_Undo(self, assy, namedView):
        """
        Restore the view (and the current Part) to what was saved by
        current_view_for_Undo.

        @warning: present implem of saving current Part (using its index in MT)
                  is not suitable for out-of-order Redo.

        @warning: might not gl_update, assume caller does so [#k obs warning?]
        """
        # shares code with NamedView.set_view; might be very similar to some GLPane method, too
        ## compare to NamedView.set_view (which passes animate = True) -- not sure if we want
        # to animate in this case [we do, for A8],
        # but if we do, we might have to do that at a higher level in the call chain
        restore_view = env.prefs[undoRestoreView_prefs_key] #060314
        restore_current_part = True # always do this no matter what
        ## restore_mode?? nah (not for A7 anyway; unclear what's best in long run)
        if restore_view:
            if type(namedView) == type(""):
                self._initialize_view_attributes()
                    #bruce 090220 revision to remove copied code; not fully
                    # equivalent to prior code (sets scale from prefs rather
                    # than to 10.0) but I think that's ok, since I think this
                    # functionality (Undo changing the view) is only cosmetic.
            else:
                self.animateToView(namedView.quat, namedView.scale, namedView.pov,
                                   namedView.zoomFactor, animate = False)
                    # if we want this to animate, we probably have to move that
                    # higher in the call chain and do it after everything else
        if restore_current_part:
            if type(namedView) == type(""):
                if env.debug():
                    print "debug: fyi: cys == '' still happens"
                        # does it? ###@@@ 060314 remove if seen, or if not seen
                current_selgroup_index = 0
            else:
                current_selgroup_index = namedView.current_selgroup_index
            sg = assy.selgroup_at_index(current_selgroup_index)
            assy.set_current_selgroup(sg)
                #e how might that interact with setting the selection?
                # Hopefully, not much, since selection (if any) should be inside sg.
        #e should we update_parts?
        return

    pass # end of class
Exemple #5
0
class GLPane_minimal(QGLWidget, GLPane_drawingset_methods,
                     object):  #bruce 070914
    """
    Mostly a stub superclass, just so GLPane and ThumbView can have a common
    superclass.

    TODO:
    They share a lot of code, which ought to be merged into this superclass.
    Once that happens, it might as well get renamed.
    """

    # bruce 070920 added object superclass to our subclass GLPane;

    # bruce 080220 moved object superclass from GLPane to this class.
    # Purpose is to make this a new-style class so as to allow
    # defining a python property in any subclass.

    # bruce 090219 added GLPane_drawingset_methods mixin superclass
    # (for draw_csdl method; ok to ignore pylint warning about not
    #  calling its __init__ method (it doesn't have one);
    #  requires calling _before_drawing_csdls and
    #  _after_drawing_csdls in all our subclass draw-like methods;
    #  to help with this the mixin provides methods
    #  _call_func_that_draws_model and
    #  _call_func_that_draws_objects)

    # note: every subclass defines .assy as an instance variable or property
    # (as of bruce 080220), but we can't define a default value or property
    # for that here, since some subclasses define it in each way
    # (and we'd have to know which way to define a default value correctly).

    # class constants

    SIZE_FOR_glSelectBuffer = 10000
    # guess, probably overkill, seems to work, no other value was tried

    # default values of instance variables:

    shareWidget = None

    is_animating = False  #bruce 080219 fix bug 2632 (unverified)

    initialised = False

    _resize_counter = 0

    drawing_phase = '?'  # overridden in GLPane_rendering_methods, but needed for
    # debug prints that might happen in any class of GLPane [bruce 090220]

    # default values of subclass-specific constants

    permit_draw_bond_letters = True  #bruce 071023

    permit_shaders = False  #bruce 090224
    # default False until shaders can work with more than one glpane
    # (set to true in the main GLPane)

    _general_appearance_change_indicator = 0  #bruce 090306
    # note: strictly speaking, ThumbView should update this like GLPane does,
    # but that's difficult (it has no part or assy in general),
    # and not very important (cosmetic bug when user changes certain prefs,
    # worked around by changing what's shown in the ThumbView).

    useMultisample = env.prefs[enableAntiAliasing_prefs_key]

    glprefs = None

    def __init__(self, parent, shareWidget, useStencilBuffer):
        """
        #doc
        
        @note: If shareWidget is specified, useStencilBuffer is ignored:
               set it in the widget you're sharing with.
        """
        if shareWidget:
            self.shareWidget = shareWidget  #bruce 051212
            glformat = shareWidget.format()
            QGLWidget.__init__(self, glformat, parent, shareWidget)
            if not self.isSharing():
                assert 0, "%r refused to share GL display list namespace " \
                          "with %r" % (self, shareWidget)
                return
        else:
            glformat = QGLFormat()
            if (useStencilBuffer):
                glformat.setStencil(True)
                # set gl format to request stencil buffer
                # (needed for mouseover-highlighting of objects of general
                #  shape in BuildAtoms_Graphicsmode.bareMotion) [bruce 050610]

            if (self.useMultisample):
                # use full scene anti-aliasing on hardware that supports it
                # (note: setting this True works around bug 2961 on some systems)
                glformat.setSampleBuffers(True)

            QGLWidget.__init__(self, glformat, parent)
            pass

        self.glprefs = drawing_globals.glprefs
        # future: should be ok if this differs for different glpanes,
        # even between self and self.shareWidget. AFAIK, the refactoring
        # I'm doing yesterday and today means this would work fine,
        # or at least it does most of what would be required for that.
        # [bruce 090304]

        self._initialize_view_attributes()

        # Initial value of depth "constant" (changeable by prefs.)
        self.DEPTH_TWEAK = DEPTH_TWEAK_UNITS * DEPTH_TWEAK_VALUE

        self.trackball = Trackball(10, 10)

        self._functions_to_call_when_gl_context_is_current = []

        # piotr 080714: Defined this attribute here in case
        # chunk.py accesses it in ThumbView.
        self.lastNonReducedDisplayMode = default_display_mode

        # piotr 080807
        # Most recent quaternion to be used in animation timer.
        self.last_quat = None

        self.transforms = [
        ]  ### TODO: clear this at start of frame, complain if not clear
        # stack of current transforms (or Nodes that generate them)
        # [bruce 090220]
        # Note: this may be revised once we have TransformNode,
        # e.g. we might push their dt and st separately;
        # note we might need to push a "slot" for them if they might
        # change before being popped, or perhaps even if they might
        # change between the time pushed and the time later used
        # (by something that collects them from here in association
        #  with a ColorSortedDisplayList).

        return

    def _initialize_view_attributes(self):  #bruce 090220 split this out
        """
        Initialize the current view attributes
        """
        # note: these are sometimes saved in or loaded from
        # the currently displayed part or its mmp file

        # rotation
        self.quat = Q(1, 0, 0, 0)

        # point of view (i.e. negative of center of view)
        self.pov = V(0.0, 0.0, 0.0)

        # half-height of window in Angstroms (reset by certain view-changing operations)
        self.scale = float(env.prefs[startup_GLPane_scale_prefs_key])

        # zoom factor
        self.zoomFactor = 1.0
        # Note: I believe (both now, and in a comment dated 060829 which is
        # now being removed from GLPane.py) that nothing ever sets
        # self.zoomFactor to anything other than 1.0, though there is code
        # which could do this in theory. I think zoomFactor was added as
        # one way to implement zoomToArea, but another way (changing scale)
        # was chosen, which makes zoomFactor useless. Someday we should
        # consider removing it, unless we think it might be useful for
        # something in the future. [bruce 080910 comment]
        return

    # define properties which return model-space vectors
    # corresponding to various directions relative to the screen
    # (can be used during drawing or when handling mouse events)
    #
    # [bruce 080912 turned these into properties, and optimized;
    #  before that, this was done by __getattr__ in each subclass]

    def __right(self, _q=V(1, 0, 0)):
        return self.quat.unrot(_q)

    right = property(__right)

    def __left(self, _q=V(-1, 0, 0)):
        return self.quat.unrot(_q)

    left = property(__left)

    def __up(self, _q=V(0, 1, 0)):
        return self.quat.unrot(_q)

    up = property(__up)

    def __down(self, _q=V(0, -1, 0)):
        return self.quat.unrot(_q)

    down = property(__down)

    def __out(self, _q=V(0, 0, 1)):
        return self.quat.unrot(_q)

    out = property(__out)

    def __lineOfSight(self, _q=V(0, 0, -1)):
        return self.quat.unrot(_q)

    lineOfSight = property(__lineOfSight)

    def get_angle_made_with_screen_right(self, vec):
        """
        Returns the angle (in degrees) between screen right direction
        and the given vector. It returns positive angles (between 0 and
        180 degrees) if the vector lies in first or second quadrants
        (i.e. points more up than down on the screen). Otherwise the
        angle returned is negative.
        """
        #Ninad 2008-04-17: This method was added AFTER rattlesnake rc2.
        #bruce 080912 bugfix: don't give theta the wrong sign when
        # dot(vec, self.up) < 0 and dot(vec, self.right) == 0.
        vec = norm(vec)
        theta = angleBetween(vec, self.right)
        if dot(vec, self.up) < 0:
            theta = -theta
        return theta

    def __get_vdist(self):
        """
        Recompute and return the desired distance between
        eyeball and center of view.
        """
        #bruce 070920 revised; bruce 080912 moved from GLPane into GLPane_minimal
        # Note: an old comment from elsewhere in GLPane claims:
        # bruce 041214 comment: some code assumes vdist is always 6.0 * self.scale
        # (e.g. eyeball computations, see bug 30), thus has bugs for aspect < 1.0.
        # We should have glpane attrs for aspect, w_scale, h_scale, eyeball,
        # clipping planes, etc, like we do now for right, up, etc. ###e
        # I don't know if vdist could ever have a different value,
        # or if we still have aspect < 1.0 bugs due to some other cause.
        return 6.0 * self.scale

    vdist = property(__get_vdist)

    def eyeball(self):  #bruce 060219
        ##e should call this to replace equivalent formulae in other places
        """
        Return the location of the eyeball in model coordinates.

        @note: this is not meaningful except when using perspective projection.
        """
        ### REVIEW: whether this is correct for tall aspect ratio GLPane.
        # See also the comment in __get_vdist, above, mentioning bug 30.
        # There is related code in writepovfile which computes a camera position
        # which corrects vdist when aspect < 1.0:
        ##    # Camera info
        ##    vdist = cdist
        ##    if aspect < 1.0:
        ##            vdist = cdist / aspect
        ##    eyePos = vdist * glpane.scale * glpane.out - glpane.pov
        # [bruce comment 080912]
        #bruce 0809122 moved this from GLPane into GLPane_minimal,
        # and made region selection code call it for the first time.
        return self.quat.unrot(V(0, 0, self.vdist)) - self.pov

    def __repr__(self):
        return "<%s at %#x>" % (self.__class__.__name__.split('.')[-1],
                                id(self))

    def initializeGL(self):
        """
        Called once by Qt when the OpenGL context is first ready to use.
        """
        #bruce 080912 merged this from subclass methods, guessed docstring
        self._setup_lighting()

        glShadeModel(GL_SMOOTH)
        glEnable(GL_DEPTH_TEST)
        glEnable(GL_CULL_FACE)
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()

        if not self.isSharing():
            self._setup_display_lists()
        return

    def resizeGL(self, width, height):
        """
        Called by QtGL when the drawing window is resized.
        """
        #bruce 080912 moved this from GLPane into GLPane_minimal

        self._resize_counter += 1  #bruce 080922

        self.width = width
        self.height = height

        glViewport(0, 0, self.width, self.height)
        # example of using a smaller viewport:
        ## glViewport(10, 15, (self.width - 10)/2, (self.height - 15)/3)

        # modify width and height for trackball
        # (note: this was done in GLPane but not in ThumbView until 080912)
        if width < 300:
            width = 300
        if height < 300:
            height = 300
        self.trackball.rescale(width, height)

        self.initialised = True

        return

    def __get_aspect(self):
        #bruce 080912 made this a property, moved to this class
        return (self.width + 0.0) / (self.height + 0.0)

    aspect = property(__get_aspect)

    def _setup_projection(self, glselect=False):
        ### WARNING: This is not actually private! TODO: rename it.
        """
        Set up standard projection matrix contents using various attributes of
        self (aspect, vdist, scale, zoomFactor).  Also reads the current OpenGL
        viewport bounds in window coordinates.
        
        (Warning: leaves matrixmode as GL_PROJECTION.)
        
        @param glselect: False (default) normally, or a 4-tuple
               (format not documented here) to prepare for GL_SELECT picking by
               calling gluPickMatrix().

        If you are really going to draw in the pick window (for GL_RENDER and
        glReadPixels picking, rather than GL_SELECT picking), don't forget to
        set the glViewport *after* calling _setup_projection.  Here's why:

           gluPickMatrix needs to know the current *whole-window* viewport, in
           order to set up a projection matrix to map a small portion of it to
           the clipping boundaries for GL_SELECT.

           From the gluPickMatrix doc page:
             viewport:
               Specifies the current viewport (as from a glGetIntegerv call).
             Description:
               gluPickMatrix creates a projection matrix that can be used to
               restrict drawing to a small region of the viewport.

           In the graphics pipeline, the clipper actually works in homogeneous
           coordinates, clipping polygons to the {X,Y}==+-W boundaries.  This
           saves the work of doing the homogeneous division: {X,Y}/W==+-1.0,
           (and avoiding problems when W is zero for points on the eye plane in
           Z,) but it comes down to the same thing as clipping to X,Y==+-1 in
           orthographic.

           So the projection matrix decides what 3D model-space planes map to
           +-1 in X,Y.  (I think it maps [near,far] to [0,1] in Z, because
           they're not clipped symmetrically.)

           Then glViewport sets the hardware transform that determines where the
           +-1 square of clipped output goes in screen pixels within the window.

           Normally you don't actually draw pixels while picking in GL_SELECT
           mode because the pipeline outputs hits after the clipping stage, so
           gluPickMatrix only reads the viewport and sets the projection matrix.
        """
        #bruce 080912 moved this from GLPane into GLPane_minimal
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()

        scale = self.scale * self.zoomFactor
        near, far = self.near, self.far

        aspect = self.aspect
        vdist = self.vdist

        if glselect:
            x, y, w, h = glselect
            gluPickMatrix(
                x,
                y,
                w,
                h,
                glGetIntegerv(
                    GL_VIEWPORT
                )  #k is this arg needed? it might be the default...
            )

        if self.ortho:
            glOrtho(-scale * aspect, scale * aspect, -scale, scale,
                    vdist * near, vdist * far)
        else:
            glFrustum(-scale * near * aspect, scale * near * aspect,
                      -scale * near, scale * near, vdist * near, vdist * far)
        return

    def _setup_modelview(self):
        """
        set up modelview coordinate system
        """
        #bruce 080912 moved this from GLPane into GLPane_minimal
        # note: it's not yet used in ThumbView, but maybe it could be.
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        glTranslatef(0.0, 0.0, -self.vdist)
        # translate coordinate system for drawing, away from eye
        # (-Z direction) by vdist. This positions center of view
        # in eyespace.
        q = self.quat
        glRotatef(q.angle * 180.0 / math.pi, q.x, q.y, q.z)
        # rotate coordinate system for drawing by trackball quat
        # (this rotation is around the center of view, since that is what
        #  is positioned at drawing coordsystem's current origin,
        #  by the following code)
        glTranslatef(self.pov[0], self.pov[1], self.pov[2])
        # translate model (about to be drawn) to bring its center of view
        # (cov == - pov) to origin of coordinate system for drawing.
        # We translate by -cov to bring model.cov to origin.
        return

    def _setup_lighting(self):
        # note: in subclass GLPane, as of 080911 this is defined in
        # its mixin superclass GLPane_lighting_methods
        assert 0, "subclass must override this"

    def _setup_display_lists(self):  # bruce 071030
        """
        This needs to be called during __init__ if a new display list context
        is being used.

        WARNING: the functions this calls store display list names in
        global variables (as of 071030); until this is cleaned up, only the
        most recently set up display list context will work with the
        associated drawing functions in the same modules.
        """
        # TODO: warn if called twice (see docstring for why)
        setup_drawer()
        setup_draw_grid_lines()
        return

    # ==

    def model_is_valid(self):  #bruce 080117
        """
        Subclasses should override this to return a boolean
        saying whether their model is currently valid for drawing.

        Subclass implementations of paintGL are advised to call this
        at the beginning and return immediately if it's false.
        """
        return True  #bruce 080913 False -> True (more useful default)

    # ==

    def is_sphere_visible(self, center, radius):  # piotr 080331
        """
        Frustum culling test. Subclasses should override it
        to disable drawing objects outside of the view frustum.
        """
        return True

    # ==

    def is_lozenge_visible(self, pos1, pos2, radius):  # piotr 080402
        """
        Frustum culling test for lozenge-shaped objects. Subclasses should 
        override it to disable drawing objects outside of the view frustum.
        """
        return True

    # ==

    def _call_whatever_waits_for_gl_context_current(self):  #bruce 071103
        """
        For whatever functions have been registered to be called (once)
        when our GL context is next current, call them now (and then discard them).

        Note: subclasses wishing to support self.call_when_glcontext_is_next_current
        MUST call this at some point during their paintGL method
        (preferably before doing any drawing in that method, though this is
        not at present guaranteed). Subclasses not wishing to support that
        should override it to discard functions passed to it immediately.
        """
        functions = self._functions_to_call_when_gl_context_is_current
        self._functions_to_call_when_gl_context_is_current = []
        for func in functions:
            try:
                func()
            except:
                print_compact_traceback(
                    "bug: %r._call_whatever_waits_for_gl_context_current "
                    "ignoring exception in %r: " % \
                    (self, func) )
            continue
        return

    def call_when_glcontext_is_next_current(self, func):  #bruce 071103
        """
        Call func at the next convenient time when we know our OpenGL context
        is current.

        (Subclasses are permitted to call func immediately if they happen
        to know their gl context is current right when this is called.
        Conversely, subclasses are permitted to never call func, though
        that will defeat the optimizations this is used for, such as
        deallocating OpenGL display lists which are no longer needed.)
        """
        self._functions_to_call_when_gl_context_is_current.append(func)
        return

    # ==

    def should_draw_valence_errors(self):
        """
        Return a boolean to indicate whether valence error
        indicators (of any kind) should ever be drawn in self.
        (Each specific kind may also be controlled by a prefs
        setting, checked independently by the caller. As of 070914
        there is only one kind, drawn by class Atom.)
        """
        return False

    def get_drawLevel(self, assy_or_part):
        """
        Get the recommended sphere drawingLevel to use for drawing
        assy_or_part in self.

        @param assy_or_part: an Assembly, or its current .part, a Part
        """
        #bruce 090306 split out of ChunkDrawer, optimized for shaders
        #bruce 090309 revised
        if self.permit_shaders and \
           self.glprefs.sphereShader_desired() and \
           drawing_globals.sphereShader_available():
            # drawLevel doesn't matter (not used by shaders),
            # so return a constant value to avoid accessing assy.drawLevel,
            # and therefore (I hope) avoid recomputing the number of atoms
            # in assy. But in case of bugs in which this ends up being used,
            # let this constant be the usual level "2".
            # [todo: use a symbolic constant, probably there is one already]
            return 2
        else:
            return assy_or_part.drawLevel  # this might recompute it
            # (if that happens and grabs the pref value, I think this won't
            #  subscribe our Chunks' display list to it, since we're called
            #  outside the begin/ends for those, and that's good, since they
            #  include this in havelist instead, which avoids some unneeded
            #  redrawing, e.g. if pref changed and changed back while
            #  displaying a different Part. [bruce 060215])
        pass

    # ==

    def setDepthRange_setup_from_debug_pref(self):
        self.DEPTH_TWEAK = DEPTH_TWEAK_UNITS * \
                    debug_pref("GLPane: depth tweak", DEPTH_TWEAK_CHOICE)
        return

    def setDepthRange_Normal(self):
        glDepthRange(0.0 + self.DEPTH_TWEAK, 1.0)  # args are near, far
        return

    def setDepthRange_Highlighting(self):
        glDepthRange(0.0, 1.0 - self.DEPTH_TWEAK)
        return

    # ==

    # REVIEW:
    # the following "Undo view" methods probably don't work in subclasses other
    # than GLPane. It might make sense to have them here, but only if they are
    # refactored a bit, e.g. so that self.animateToView works in other
    # subclassses even if it doesn't animate. Ultimately it might be better
    # to refactor them a lot and/or move them out of this class hierarchy
    # entirely. [bruce 080912 comment]

    def current_view_for_Undo(self, assy):  #e shares code with saveNamedView
        """
        Return the current view in this glpane (which we assume is showing
        this assy), with additional attributes saved along with the view by Undo
        (i.e. the index of the current selection group).
        (The assy arg is used for multiple purposes specific to Undo.)

        @warning: present implem of saving current Part (using its index in MT)
                  is not suitable for out-of-order Redo.
        """
        # WARNING: not reviewed for use in subclasses which don't
        # have and draw a .assy attribute, though by passing assy into this method,
        # we remove any obvious bug from that. [bruce 080220 comment]

        oldc = assy.all_change_indicators()

        namedView = NamedView(assy, "name", self.scale, self.pov,
                              self.zoomFactor, self.quat)

        newc = assy.all_change_indicators()
        assert oldc == newc

        namedView.current_selgroup_index = assy.current_selgroup_index()
        # storing this on the namedView is a kluge, but should be safe

        return namedView  # ideally would not return a Node but just a
        # "view object" with the same 4 elements in it as passed to NamedView

    def set_view_for_Undo(self, assy, namedView):
        """
        Restore the view (and the current Part) to what was saved by
        current_view_for_Undo.

        @warning: present implem of saving current Part (using its index in MT)
                  is not suitable for out-of-order Redo.

        @warning: might not gl_update, assume caller does so [#k obs warning?]
        """
        # shares code with NamedView.set_view; might be very similar to some GLPane method, too
        ## compare to NamedView.set_view (which passes animate = True) -- not sure if we want
        # to animate in this case [we do, for A8],
        # but if we do, we might have to do that at a higher level in the call chain
        restore_view = env.prefs[undoRestoreView_prefs_key]  #060314
        restore_current_part = True  # always do this no matter what
        ## restore_mode?? nah (not for A7 anyway; unclear what's best in long run)
        if restore_view:
            if type(namedView) == type(""):
                self._initialize_view_attributes()
                #bruce 090220 revision to remove copied code; not fully
                # equivalent to prior code (sets scale from prefs rather
                # than to 10.0) but I think that's ok, since I think this
                # functionality (Undo changing the view) is only cosmetic.
            else:
                self.animateToView(namedView.quat,
                                   namedView.scale,
                                   namedView.pov,
                                   namedView.zoomFactor,
                                   animate=False)
                # if we want this to animate, we probably have to move that
                # higher in the call chain and do it after everything else
        if restore_current_part:
            if type(namedView) == type(""):
                if env.debug():
                    print "debug: fyi: cys == '' still happens"
                    # does it? ###@@@ 060314 remove if seen, or if not seen
                current_selgroup_index = 0
            else:
                current_selgroup_index = namedView.current_selgroup_index
            sg = assy.selgroup_at_index(current_selgroup_index)
            assy.set_current_selgroup(sg)
            #e how might that interact with setting the selection?
            # Hopefully, not much, since selection (if any) should be inside sg.
        #e should we update_parts?
        return

    pass  # end of class