예제 #1
0
    def _capture_saved_bg_image(self):
        """
        """
        # TODO: investigate better ways to do this, which don't involve the CPU.
        # For example, frame buffer objects, or "render to texture":
        # - by glCopyTexImage2D,
        #   http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=36
        #   (which can do color -- I don't know about depth),
        # - or by more platform-specific ways, e.g. pbuffer.
        # [bruce 081002]

        print "_capture_saved_bg_image", self._print_data()
        sys.stdout.flush()

        if 1:
            from OpenGL.GL import glFlush, glFinish
            glFlush(
            )  # might well be needed, based on other code in NE1; not enough by itself
            glFinish()  # try this too if needed
        w = _trim(self.width)
        h = _trim(self.height)

        # grab the color image part
        image = glReadPixels(0, 0, w, h, _GL_FORMAT_FOR_COLOR,
                             GL_UNSIGNED_BYTE)
        self._cached_bg_color_image = image

        # grab the depth part
        ## image = glReadPixels( 0, 0, w, h, GL_DEPTH_COMPONENT, _GL_TYPE_FOR_DEPTH )
        image = glReadPixelsf(0, 0, w, h, GL_DEPTH_COMPONENT)  #####
        self._cached_bg_depth_image = image
        print "grabbed depth at 0,0:", image[0][0]  ######

        return
예제 #2
0
    def _capture_saved_bg_image(self):
        """
        """
        # TODO: investigate better ways to do this, which don't involve the CPU.
        # For example, frame buffer objects, or "render to texture":
        # - by glCopyTexImage2D,
        #   http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=36
        #   (which can do color -- I don't know about depth),
        # - or by more platform-specific ways, e.g. pbuffer.
        # [bruce 081002]
        
        print "_capture_saved_bg_image", self._print_data()
        sys.stdout.flush()
        
        if 1:
            from OpenGL.GL import glFlush, glFinish
            glFlush() # might well be needed, based on other code in NE1; not enough by itself
            glFinish() # try this too if needed
        w = _trim(self.width)
        h = _trim(self.height)
        
        # grab the color image part
        image = glReadPixels( 0, 0, w, h, _GL_FORMAT_FOR_COLOR, GL_UNSIGNED_BYTE )
        self._cached_bg_color_image = image
        
        # grab the depth part
        ## image = glReadPixels( 0, 0, w, h, GL_DEPTH_COMPONENT, _GL_TYPE_FOR_DEPTH )
        image = glReadPixelsf(0, 0, w, h, GL_DEPTH_COMPONENT) #####
        self._cached_bg_depth_image = image
        print "grabbed depth at 0,0:", image[0][0]######

        return
예제 #3
0
    def select(self, wX, wY):
        """
        Use the OpenGL picking/selection to select any object. Return the
        selected object, otherwise, return None. Restore projection and
        modelview matrices before returning.
        """
        ### NOTE: this code is similar to (and was copied and modified from)
        # GLPane_highlighting_methods.do_glselect_if_wanted, but also differs
        # in significant ways (too much to make it worth merging, unless we
        # decide to merge the differing algorithms as well). It's one of
        # several instances of hit-test code that calls glRenderMode.
        # [bruce 060721/080917 comment]
        wZ = glReadPixelsf(wX, wY, 1, 1, GL_DEPTH_COMPONENT)
        gz = wZ[0][0]

        if gz >= GL_FAR_Z:  # Empty space was clicked
            return None

        pxyz = A(gluUnProject(wX, wY, gz))
        pn = self.out
        pxyz -= 0.0002 * pn
        # Note: if this runs before the model is drawn, this can have an
        # exception "OverflowError: math range error", presumably because
        # appropriate state for gluUnProject was not set up. That doesn't
        # normally happen but can happen due to bugs (no known open bugs
        # of that kind).
        # Sometimes our drawing area can become "stuck at gray",
        # and when that happens, the same exception can occur from this line.
        # Could it be that too many accidental mousewheel scrolls occurred
        # and made the scale unreasonable? (To mitigate, we should prevent
        # those from doing anything unless we have a valid model, and also
        # reset that scale when loading a new model (latter is probably
        # already done, but I didn't check). See also the comments
        # in def wheelEvent.) [bruce 080220 comment]
        dp = -dot(pxyz, pn)

        # Save projection matrix before it's changed.
        glMatrixMode(GL_PROJECTION)
        glPushMatrix()

        current_glselect = (wX, wY, 1, 1)
        self._setup_projection(glselect=current_glselect)

        glSelectBuffer(self.SIZE_FOR_glSelectBuffer)
        glRenderMode(GL_SELECT)
        glInitNames()
        glMatrixMode(GL_MODELVIEW)
        # Save model view matrix before it's changed.
        glPushMatrix()

        # Draw model using glRenderMode(GL_SELECT) as set up above
        try:
            glClipPlane(GL_CLIP_PLANE0, (pn[0], pn[1], pn[2], dp))
            glEnable(GL_CLIP_PLANE0)
            self._drawModel_using_DrawingSets()
        except:
            #bruce 080917 fixed predicted bugs in this except clause (untested)
            print_compact_traceback(
                "exception in ThumbView._drawModel_using_DrawingSets() during GL_SELECT; ignored; restoring matrices: "
            )
            glDisable(GL_CLIP_PLANE0)
            glMatrixMode(GL_PROJECTION)
            glPopMatrix()
            glMatrixMode(GL_MODELVIEW)
            glPopMatrix()
            glRenderMode(GL_RENDER)
            return None
        else:
            glDisable(GL_CLIP_PLANE0)
            # Restore model/view matrix
            glPopMatrix()

        # Restore projection matrix and set matrix mode to Model/View
        glMatrixMode(GL_PROJECTION)
        glPopMatrix()
        glMatrixMode(GL_MODELVIEW)

        glFlush()

        hit_records = list(glRenderMode(GL_RENDER))
        ## print "%d hits" % len(hit_records)
        for (near, far, names) in hit_records:
            ## print "hit record: near, far, names:", near, far, names
            # note from testing: near/far are too far apart to give actual depth,
            # in spite of the 1-pixel drawing window (presumably they're vertices
            # taken from unclipped primitives, not clipped ones).
            ### REVIEW: this just returns the first candidate object found.
            # The clip plane may restrict the set of candidates well enough to
            # make sure that's the right one, but this is untested and unreviewed.
            # (And it's just my guess that that was Huaicai's intention in
            #  setting up clipping, since it's not documented. I'm guessing that
            #  the plane is just behind the hitpoint, but haven't confirmed this.)
            # [bruce 080917 comment]
            if names:
                name = names[-1]
                assy = self.assy
                obj = assy and assy.object_for_glselect_name(name)
                #k should always return an obj
                return obj
        return None  # from ThumbView.select
예제 #4
0
    def select(self, wX, wY):
        """
        Use the OpenGL picking/selection to select any object. Return the
        selected object, otherwise, return None. Restore projection and 
        modelview matrices before returning.
        """
        ### NOTE: this code is similar to (and was copied and modified from)
        # GLPane_highlighting_methods.do_glselect_if_wanted, but also differs
        # in significant ways (too much to make it worth merging, unless we
        # decide to merge the differing algorithms as well). It's one of
        # several instances of hit-test code that calls glRenderMode.
        # [bruce 060721/080917 comment]
        wZ = glReadPixelsf(wX, wY, 1, 1, GL_DEPTH_COMPONENT)
        gz = wZ[0][0]
        
        if gz >= GL_FAR_Z: # Empty space was clicked
            return None
        
        pxyz = A(gluUnProject(wX, wY, gz))
        pn = self.out
        pxyz -= 0.0002 * pn
            # Note: if this runs before the model is drawn, this can have an
            # exception "OverflowError: math range error", presumably because
            # appropriate state for gluUnProject was not set up. That doesn't
            # normally happen but can happen due to bugs (no known open bugs
            # of that kind).
            # Sometimes our drawing area can become "stuck at gray",
            # and when that happens, the same exception can occur from this line.
            # Could it be that too many accidental mousewheel scrolls occurred
            # and made the scale unreasonable? (To mitigate, we should prevent
            # those from doing anything unless we have a valid model, and also
            # reset that scale when loading a new model (latter is probably
            # already done, but I didn't check). See also the comments
            # in def wheelEvent.) [bruce 080220 comment]
        dp = - dot(pxyz, pn)
        
        # Save projection matrix before it's changed.
        glMatrixMode(GL_PROJECTION)
        glPushMatrix()
        
        current_glselect = (wX, wY, 1, 1) 
        self._setup_projection(glselect = current_glselect) 
        
        glSelectBuffer(self.SIZE_FOR_glSelectBuffer)
        glRenderMode(GL_SELECT)
        glInitNames()
        glMatrixMode(GL_MODELVIEW)
        # Save model view matrix before it's changed.
        glPushMatrix()

        # Draw model using glRenderMode(GL_SELECT) as set up above 
        try:
            glClipPlane(GL_CLIP_PLANE0, (pn[0], pn[1], pn[2], dp))
            glEnable(GL_CLIP_PLANE0)
            self._drawModel_using_DrawingSets()
        except:
            #bruce 080917 fixed predicted bugs in this except clause (untested)
            print_compact_traceback("exception in ThumbView._drawModel_using_DrawingSets() during GL_SELECT; ignored; restoring matrices: ")
            glDisable(GL_CLIP_PLANE0)
            glMatrixMode(GL_PROJECTION)
            glPopMatrix()
            glMatrixMode(GL_MODELVIEW)
            glPopMatrix()
            glRenderMode(GL_RENDER)
            return None
        else:
            glDisable(GL_CLIP_PLANE0)
            # Restore model/view matrix
            glPopMatrix()
        
        # Restore projection matrix and set matrix mode to Model/View
        glMatrixMode(GL_PROJECTION)
        glPopMatrix()
        glMatrixMode(GL_MODELVIEW)
        
        glFlush()
        
        hit_records = list(glRenderMode(GL_RENDER))
        ## print "%d hits" % len(hit_records)
        for (near, far, names) in hit_records:
            ## print "hit record: near, far, names:", near, far, names
            # note from testing: near/far are too far apart to give actual depth,
            # in spite of the 1-pixel drawing window (presumably they're vertices
            # taken from unclipped primitives, not clipped ones).
            ### REVIEW: this just returns the first candidate object found.
            # The clip plane may restrict the set of candidates well enough to
            # make sure that's the right one, but this is untested and unreviewed.
            # (And it's just my guess that that was Huaicai's intention in
            #  setting up clipping, since it's not documented. I'm guessing that
            #  the plane is just behind the hitpoint, but haven't confirmed this.)
            # [bruce 080917 comment]
            if names:
                name = names[-1]
                assy = self.assy
                obj = assy and assy.object_for_glselect_name(name)
                    #k should always return an obj
                return obj
        return None # from ThumbView.select
예제 #5
0
    def do_glselect_if_wanted(self):  #bruce 070919 split this out
        """
        Do the glRenderMode(GL_SELECT) drawing, and/or the glname-color
        drawing for shader primitives, used to guess which object
        might be under the mouse, for one drawing frame,
        if desired for this frame. Report results by storing candidate
        mouseover objects in self.glselect_dict. 
        
        The depth buffer is initially clear, and must be clear
        when we're done as well.

        @note: does not do related individual object depth/stencil 
               buffer drawing -- caller must do that on some or all
               of the objects we store into self.glselect_dict.
        """
        if self.glselect_wanted:  # note: this will be reset below.
            ###@@@ WARNING: The original code for this, here in GLPane, has been duplicated and slightly modified
            # in at least three other places (search for glRenderMode to find them). This is bad; common code
            # should be used. Furthermore, I suspect it's sometimes needlessly called more than once per frame;
            # that should be fixed too. [bruce 060721 comment]
            wX, wY, self.targetdepth = self.glselect_wanted  # wX, wY is the point to do the hit-test at
            # targetdepth is the depth buffer value to look for at that point, during ordinary drawing phase
            # (could also be used to set up clipping planes to further restrict hit-test, but this isn't yet done)
            # (Warning: targetdepth could in theory be out of date, if more events come between bareMotion
            #  and the one caused by its gl_update, whose paintGL is what's running now, and if those events
            #  move what's drawn. Maybe that could happen with mousewheel events or (someday) with keypresses
            #  having a graphical effect. Ideally we'd count intentional redraws, and disable this picking in that case.)
            self.wX, self.wY = wX, wY
            self.glselect_wanted = 0
            pwSize = 1  # Pick window size.  Russ 081128: Was 3.
            # Bruce: Replace 3, 3 with 1, 1? 5, 5? not sure whether this will
            # matter...  in principle should have no effect except speed.
            # Russ: For glname rendering, 1x1 is better because it doesn't
            # have window boundary issues.  We get the coords of a single
            # pixel in the window for the mouse position.

            #bruce 050615 for use by nodes which want to set up their own projection matrix.
            self.current_glselect = (wX, wY, pwSize, pwSize)
            self._setup_projection(glselect=self.current_glselect
                                   )  # option makes it use gluPickMatrix

            # Russ 081209: Added.
            debugPicking = debug_pref("GLPane: debug mouseover picking?",
                                      Choice_boolean_False,
                                      prefs_key=True)

            if self.enabled_shaders():
                # TODO: optimization: find an appropriate place to call
                # _compute_frustum_planes. [bruce 090105 comment]

                # Russ 081122: There seems to be no way to access the GL name
                # stack in shaders. Instead, for mouseover, draw shader
                # primitives with glnames as colors in glRenderMode(GL_RENDER),
                # then read back the pixel color (glname) and depth value.

                # Temporarily replace the full-size viewport with a little one
                # at the mouse location, matching the pick matrix location.
                # Otherwise, we will draw a closeup of that area into the whole
                # window, rather than a few pixels. (This wasn't needed when we
                # only used GL_SELECT rendering mode here, because that doesn't
                # modify the frame buffer -- it just returns hits by graphics
                # primitives when they are inside the clipping boundaries.)
                #
                # (Don't set the viewport *before* _setup_projection(), since
                #  that method needs to read the current whole-window viewport
                #  to set up glselect. See explanation in its docstring.)

                savedViewport = glGetIntegerv(GL_VIEWPORT)
                glViewport(wX, wY, pwSize, pwSize)  # Same as current_glselect.

                # First, clear the pixel RGBA to zeros and a depth of 1.0 (far),
                # so we won't confuse a color with a glname if there are
                # no shader primitives drawn over this pixel.
                saveDepthFunc = glGetInteger(GL_DEPTH_FUNC)
                glDepthFunc(GL_ALWAYS)
                glWindowPos3i(wX, wY, 1)  # Note the Z coord.
                gl_format, gl_type = GL_RGBA, GL_UNSIGNED_BYTE
                glDrawPixels(pwSize, pwSize, gl_format, gl_type, (0, 0, 0, 0))
                glDepthFunc(
                    saveDepthFunc)  # needed, though we'll change it again

                # We must be in glRenderMode(GL_RENDER) (as usual) when this is called.
                # Note: _setup_projection leaves the matrix mode as GL_PROJECTION.
                glMatrixMode(GL_MODELVIEW)
                shaders = self.enabled_shaders()
                try:
                    # Set flags so that we will use glnames-as-color mode
                    # in shaders, and draw only shader primitives.
                    # (Ideally we would also draw all non-shader primitives
                    #  as some other color, unequal to all glname colors
                    #  (or derived from a fake glname for that purpose),
                    #  in order to obscure shader primitives where appropriate.
                    #  This is intended to be done but is not yet implemented.
                    #  [bruce 090105 addendum])
                    for shader in shaders:
                        shader.setPicking(True)
                    self.set_drawing_phase("glselect_glname_color")

                    for stereo_image in self.stereo_images_to_draw:
                        self._enable_stereo(stereo_image)
                        try:
                            self._do_graphicsMode_Draw(
                                for_mouseover_highlighting=True)
                            # note: we can't disable depth writing here,
                            # since we need it to make sure the correct
                            # shader object comes out on top, or is
                            # obscured by a DL object. Instead, we'll
                            # clear the depth buffer again (at this pixel)
                            # below. [bruce 090105]
                        finally:
                            self._disable_stereo()
                except:
                    print_compact_traceback(
                        "exception in or around _do_graphicsMode_Draw() during glname_color;"
                        "drawing ignored; restoring modelview matrix: ")
                    # REVIEW: what does "drawing ignored" mean, in that message? [bruce 090105 question]
                    glMatrixMode(GL_MODELVIEW)
                    self._setup_modelview(
                    )  ### REVIEW: correctness of this is unreviewed!
                    # now it's important to continue, at least enough to restore other gl state
                    pass
                for shader in shaders:
                    shader.setPicking(False)
                self.set_drawing_phase('?')

                # Restore the viewport.
                glViewport(savedViewport[0], savedViewport[1],
                           savedViewport[2], savedViewport[3])

                # Read pixel value from the back buffer and re-assemble glname.
                glFinish()  # Make sure the drawing has completed.
                # REVIEW: is this glFinish needed? [bruce 090105 comment]
                rgba = glReadPixels(wX, wY, 1, 1, gl_format, gl_type)[0][0]
                pixZ = glReadPixelsf(wX, wY, 1, 1, GL_DEPTH_COMPONENT)[0][0]

                # Clear our depth pixel to 1.0 (far), so we won't mess up the
                # subsequent call of preDraw_glselect_dict.
                # (The following is not the most direct way, but it ought to work.
                #  Note that we also clear the color pixel, since (being a glname)
                #  it has no purpose remaining in the color buffer -- either it's
                #  changed later, or, if not, that's a bug, but we'd rather have
                #  it black than a random color.) [bruce 090105 bugfix]
                glDepthFunc(GL_ALWAYS)
                glWindowPos3i(wX, wY, 1)  # Note the Z coord.
                glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
                gl_format, gl_type = GL_RGBA, GL_UNSIGNED_BYTE
                glDrawPixels(pwSize, pwSize, gl_format, gl_type, (0, 0, 0, 0))
                glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
                glDepthFunc(saveDepthFunc)

                # Comes back sign-wrapped, in spite of specifying UNSIGNED_BYTE.
                def us(b):
                    if b < 0:
                        return 256 + b
                    return b

                bytes = tuple([us(b) for b in rgba])
                ##glname = (bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3])
                ## Temp fix: Ignore the last byte, which always comes back 255 on Windows.
                glname = (bytes[0] << 16 | bytes[1] << 8 | bytes[2])
                if debugPicking:
                    print("shader mouseover xy %d %d, " % (wX, wY) +
                          "rgba bytes (0x%x, 0x%x, 0x%x, 0x%x), " % bytes +
                          "Z %f, glname 0x%x" % (pixZ, glname))
                    pass

                ### XXX This ought to be better-merged with the DL selection below.
                if glname:
                    obj = self.object_for_glselect_name(glname)
                    if debugPicking:
                        print "shader mouseover glname=%r, obj=%r." % (glname,
                                                                       obj)
                    if obj is None:
                        # REVIEW: does this happen for mouse over a non-shader primitive?
                        # [bruce 090105 question]

                        #### Note: this bug is common. Guess: we are still drawing
                        # ordinary colors for some primitives and/or for the
                        # background, and they are showing up here and confusing
                        # us. To help debug this, print the color too. But testing
                        # shows it's not so simple -- e.g. for rung bonds it happens
                        # where shader sphere and cylinder overlap, but not on either
                        # one alone; for strand bonds it also happens on the bonds alone
                        # (tested in Build DNA, in or not in Insert DNA).
                        # [bruce 090218]
                        #
                        # Update: Since it's so common, I need to turn it off by default.
                        # Q: is the situation safe?
                        # A: if a color looks like a real glname by accident,
                        # we'll get some random candidate object -- perhaps a killed one
                        # or from a different Part or even a closed assy --
                        # and try to draw it. That doesn't sound very safe. Unfortunately
                        # there is no perfect way to filter selobjs for safety, in the
                        # current still-informal Selobj_API. The best approximation is
                        # selobj_still_ok, and it should always say yes for the usual kinds,
                        # so I'll add it as a check in the 'else' clause below.
                        # [bruce 090311]
                        if debug_flags.atom_debug:
                            print "bug: object_for_glselect_name returns None for glname %r (color %r)" % (
                                glname, bytes)
                    else:
                        if self.graphicsMode.selobj_still_ok(obj):
                            #bruce 090311 added condition, explained above
                            self.glselect_dict[id(obj)] = obj
                        else:
                            # This should be rare but possible. Leave it on briefly and see
                            # if it's ever common. If possible, gate it by atom_debug before
                            # the release. [bruce 090311]
                            print "fyi: glname-color selobj %r rejected since not selobj_still_ok" % obj
                        pass
                    pass
                pass

            if self._use_frustum_culling:
                self._compute_frustum_planes()
                # piotr 080331 - the frustum planes have to be setup after the
                # projection matrix is setup. I'm not sure if there may
                # be any side effects - see the comment below about
                # possible optimization.
            glSelectBuffer(self.SIZE_FOR_glSelectBuffer)
            # Note: this allocates a new select buffer,
            # and glRenderMode(GL_RENDER) returns it and forgets it,
            # so it's required before *each* call of glRenderMode(GL_SELECT) +
            # glRenderMode(GL_RENDER), not just once to set the size.
            # Ref: http://pyopengl.sourceforge.net/documentation/opengl_diffs.html
            # [bruce 080923 comment]
            glInitNames()

            # REVIEW: should we also set up a clipping plane just behind the
            # hit point, as (I think) is done in ThumbView, to reduce the
            # number of candidate objects? This might be a significant
            # optimization, though I don't think it eliminates the chance
            # of having multiple candidates. [bruce 080917 comment]

            glRenderMode(GL_SELECT)
            glMatrixMode(GL_MODELVIEW)
            try:
                self.set_drawing_phase('glselect')  #bruce 070124
                for stereo_image in self.stereo_images_to_draw:
                    self._enable_stereo(stereo_image)
                    try:
                        self._do_graphicsMode_Draw(
                            for_mouseover_highlighting=True)
                    finally:
                        self._disable_stereo()
            except:
                print_compact_traceback(
                    "exception in or around _do_graphicsMode_Draw() during GL_SELECT; "
                    "ignored; restoring modelview matrix: ")
                glMatrixMode(GL_MODELVIEW)
                self._setup_modelview(
                )  ### REVIEW: correctness of this is unreviewed!
                # now it's important to continue, at least enough to restore other gl state

            self._frustum_planes_available = False  # piotr 080331
            # just to be safe and not use the frustum planes computed for
            # the pick matrix
            self.set_drawing_phase('?')
            self.current_glselect = False
            # REVIEW: On systems with no stencil buffer, I think we'd also need
            # to draw selobj here in highlighted form (in case that form is
            # bigger than when it's not highlighted), or (easier & faster)
            # just always pretend it passes the hit test and add it to
            # glselect_dict -- and, make sure to give it "first dibs" for being
            # the next selobj. I'll implement some of this now (untested when
            # no stencil buffer) but not yet all. [bruce 050612]
            selobj = self.selobj
            if selobj is not None:
                self.glselect_dict[id(selobj)] = selobj
                # (review: is the following note correct?)
                # note: unneeded, if the func that looks at this dict always
                # tries selobj first (except for a kluge near
                # "if self.glselect_dict", commented on below)
            glFlush()
            hit_records = list(glRenderMode(GL_RENDER))
            if debugPicking:
                print "DLs %d hits" % len(hit_records)
            for (near, far,
                 names) in hit_records:  # see example code, renderpass.py
                ## print "hit record: near, far, names:", near, far, names
                # e.g. hit record: near, far, names: 1439181696 1453030144 (1638426L,)
                # which proves that near/far are too far apart to give actual depth,
                # in spite of the 1- or 3-pixel drawing window (presumably they're vertices
                # taken from unclipped primitives, not clipped ones).
                del near, far
                if 1:
                    # partial workaround for bug 1527. This can be removed once that bug (in drawer.py)
                    # is properly fixed. This exists in two places -- GLPane.py and modes.py. [bruce 060217]
                    if names and names[-1] == 0:
                        print "%d(g) partial workaround for bug 1527: removing 0 from end of namestack:" % env.redraw_counter, names
                        names = names[:-1]
                if names:
                    # For now, we only use the last element of names,
                    # though (as of long before 080917) it is often longer:
                    # - some code pushes the same name twice (directly and
                    #   via ColorSorter) (see 060725 debug print below);
                    # - chunks push a name even when they draw atoms/bonds
                    #   which push their own names (see 080411 comment below).
                    #
                    # Someday: if we ever support "name/subname paths" we'll
                    # probably let first name interpret the remaining ones.
                    # In fact, if nodes change projection or viewport for
                    # their kids, and/or share their kids, they'd need to
                    # push their own names on the stack, so we'd know how
                    # to redraw the kids, or which ones are meant when they
                    # are shared.
                    if debug_flags.atom_debug and len(
                            names) > 1:  # bruce 060725
                        if len(names) == 2 and names[0] == names[1]:
                            if not env.seen_before(
                                    "dual-names bug"
                            ):  # this happens for Atoms (colorsorter bug??)
                                print "debug (once-per-session message): why are some glnames duplicated on the namestack?", names
                        else:
                            # Note: as of sometime before 080411, this became common --
                            # I guess that chunks (which recently acquired glselect names)
                            # are pushing their names even while drawing their atoms and bonds.
                            # I am not sure if this can cause any problems -- almost surely not
                            # directly, but maybe the nestedness of highlighted appearances could
                            # violate some assumptions made by the highlight code... anyway,
                            # to reduce verbosity I need to not print this when the deeper name
                            # is that of a chunk, and there are exactly two names. [bruce 080411]
                            if len(names) == 2 and \
                               isinstance( self.object_for_glselect_name(names[0]), self.assy.Chunk ):
                                if not env.seen_before(
                                        "nested names for Chunk"):
                                    print "debug (once-per-session message): nested glnames for a Chunk: ", names
                            else:
                                print "debug fyi: len(names) == %d (names = %r)" % (
                                    len(names), names)
                    obj = self.object_for_glselect_name(
                        names[-1])  #k should always return an obj
                    if obj is None:
                        print "bug: object_for_glselect_name returns None for name %r at end of namestack %r" % (
                            names[-1], names)
                    else:
                        self.glselect_dict[id(obj)] = obj
                        # note: outside of this method, one of these will be
                        # chosen to be saved as self.selobj and rerendered
                        # in "highlighted" form
                        ##if 0:
                        ##    # this debug print was useful for debugging bug 2945,
                        ##    # and when it happens it's usually a bug,
                        ##    # but not always:
                        ##    # - it's predicted to happen for ChunkDrawer._renderOverlayText
                        ##    # - and whenever we're using a whole-chunk display style
                        ##    # so we can't leave it in permanently. [bruce 081211]
                        ##    if isinstance( obj, self.assy.Chunk ):
                        ##        print "\n*** namestack topped with a chunk:", obj
                    pass
                continue  # next hit_record
            #e maybe we should now sort glselect_dict by "hit priority"
            # (for depth-tiebreaking), or at least put selobj first.
            # (or this could be done lower down, where it's used.)
            # [I think we do this now...]

        return  # from do_glselect_if_wanted
예제 #6
0
    def check_target_depth(
            self,
            candidate,
            fudge,
            debug_prefix=None):  #bruce 050609; revised 050702, 070115
        """
        [private helper method]
           [required arg fudge is the fudge factor in threshhold test]
           WARNING: docstring is obsolete -- no newpicked anymore, retval details differ: ###@@@
        Candidate is an object which drew at the mouse position during GL_SELECT drawing mode
        (using the given gl_select name), and which (1) has now noticed this, via its entry in self.glselect_dict
        (which was made when GL_SELECT mode was exited; as of 050609 this is in the same paintGL call as we're in now),
        and (2) has already drawn into the depth buffer during normal rendering (or an earlier rendering pass).
        (It doesn't matter whether it's already drawn into the color buffer when it calls this method.)
           We should read pixels from the depth buffer (after glFlush)
        to check whether it has just reached self.targetdepth at the appropriate point,
        which would mean candidate is the actual newly picked object.
           If so, record this fact and return True, else return False.
        We might quickly return False (checking nothing) if we already returned True in the same pass,
        or we might read pixels anyway for debugging or assertions.
           It's possible to read a depth even nearer than targetdepth, if the drawing passes round
        their coordinates differently (as the use of gluPickMatrix for GL_SELECT is likely to do),
        or if events between the initial targetdepth measurement and this redraw tell any model objects to move.
        Someday we should check for this.
        """
        glFlush()  # In theory, glFinish might be needed here;
        # in practice, I don't know if even glFlush is needed.
        # [bruce 070921 comment]
        wX, wY = self.wX, self.wY
        wZ = glReadPixelsf(wX, wY, 1, 1, GL_DEPTH_COMPONENT)
        newdepth = wZ[0][0]
        targetdepth = self.targetdepth
        ### possible change: here we could effectively move selobj forwards
        # (to give it an advantage over other objects)...
        # but we'd need to worry about scales of coord systems in doing that...
        # due to that issue it is probably easier to fix this solely when
        # drawing it, instead.
        if newdepth <= targetdepth + fudge:
            # test passes -- return candidate below

            # note: condition uses fudge factor in case of roundoff errors
            # (hardcoded as 0.0001 before 070115) or slight differences in
            # highlighted vs plain form of object (e.g. due to inconsistent
            # orientation of polygonal approximation to a cylinder)
            #
            # [bruce 050702: 0.000001 was not enough! 0.00003 or more was needed, to properly highlight some bonds
            #  which became too hard to highlight after today's initial fix of bug 715-1.]
            # [update, bruce 070921: fyi: one reason this factor is needed is the shorten_tubes flag used to
            #  highlight some bonds, which changes the cylinder scaling, and conceivably (due solely to
            #  roundoff errors) the precise axis direction, and thus the precise cross-section rotation
            #  around the axis. Another reason was a bug in bond_drawer which I fixed today, so the
            #  necessary factor may now be smaller, but I didn't test this.]
            #
            #e could check for newdepth being < targetdepth - 0.002 (error), but best
            # to just let caller do that (NIM), since we would often not catch this error anyway,
            # since we're turning into noop on first success
            # (no choice unless we re-cleared depth buffer now, which btw we could do... #e).
            if debug_prefix:
                counter = env.redraw_counter
                print "%s (%d): target depth %r reached by %r at %r" % \
                      (debug_prefix, counter, targetdepth, candidate, newdepth)
                if newdepth > targetdepth:
                    print "  (too deep by %r, but fudge factor is %r)" % \
                          (newdepth - targetdepth, fudge)
                elif newdepth < targetdepth:
                    print "  (in fact, object is nearer than targetdepth by %r)" % \
                          (targetdepth - newdepth,)
                pass
            return candidate
            # caller should not call us again without clearing depth buffer,
            # otherwise we'll keep returning every object even if its true
            # depth is too deep
        if debug_prefix:
            counter = env.redraw_counter
            print "%s (%d): target depth %r NOT reached by %r at %r" % \
                  (debug_prefix, counter, targetdepth, candidate, newdepth)
            print "  (too deep by %r, but fudge factor is only %r)" % \
                  (newdepth - targetdepth, fudge)
        return None
예제 #7
0
    def leftUp(self, event):
        """
        Erase the final rubber band window and do zoom if user indeed draws a
        rubber band window.
        """
        # bugs 1190, 1818 wware 4/05/2006 - sometimes Qt neglects to call
        # leftDown before this
        if not hasattr(self, "pWxy") or not hasattr(self, "firstDraw"):
            return
        cWxy = (event.pos().x(), self.glpane.height - event.pos().y())
        zoomX = (abs(cWxy[0] - self.pWxy[0]) + 0.0) / (self.glpane.width + 0.0)
        zoomY = (abs(cWxy[1] - self.pWxy[1]) + 0.0) / (self.glpane.height + 0.0)

        # The rubber band window size can be larger than that of glpane.
        # Limit the zoomFactor to 1.0
        zoomFactor = min(max(zoomX, zoomY), 1.0)

        # Huaicai: when rubber band window is too small,
        # like a double click, a single line rubber band, skip zoom
        DELTA = 1.0E-5
        if self.pWxy[0] == cWxy[0] or self.pWxy[1] == cWxy[1] \
                or zoomFactor < DELTA:

            self.command.command_Done()
            return

        # Erase the last rubber-band window
        rbwcolor = self.command.rbwcolor
        drawrectangle(self.pStart, self.pPrev, self.glpane.up,
                      self.glpane.right, rbwcolor)
        glFlush()
        self.glpane.swapBuffers()

        winCenterX = (cWxy[0] + self.pWxy[0]) / 2.0
        winCenterY = (cWxy[1] + self.pWxy[1]) / 2.0
        winCenterZ = \
            glReadPixelsf(int(winCenterX), int(winCenterY), 1, 1,
                          GL_DEPTH_COMPONENT)

        assert winCenterZ[0][0] >= 0.0 and winCenterZ[0][0] <= 1.0
        if winCenterZ[0][0] >= GL_FAR_Z:  # window center touches nothing
            p1 = A(gluUnProject(winCenterX, winCenterY, 0.005))
            p2 = A(gluUnProject(winCenterX, winCenterY, 1.0))

            los = self.glpane.lineOfSight
            k = dot(los, -self.glpane.pov - p1) / dot(los, p2 - p1)

            zoomCenter = p1 + k*(p2-p1)

        else:
            zoomCenter = \
                A(gluUnProject(winCenterX, winCenterY, winCenterZ[0][0]))
        self.glpane.pov = V(-zoomCenter[0], -zoomCenter[1], -zoomCenter[2])

        # The following are 2 ways to do the zoom, the first one
        # changes view angles, the 2nd one change viewing distance
        # The advantage for the 1st one is model will not be clipped by
        #  near or back clipping planes, and the rubber band can be
        # always shown. The disadvantage: when the view field is too
        # small, a selection window may be actually act as a single pick.
        # rubber band window will not look as rectangular any more.
        #zf = self.glpane.getZoomFactor() # [note: method does not exist]
        #zoomFactor = pow(zoomFactor, 0.25)
        #zoomFactor *= zf
        #self.glpane.setZoomFactor(zoomFactor) # [note: method does not exist]

        # Change viewing distance to do zoom. This works better with
        # mouse wheel, since both are changing viewing distance, and
        # it's not too bad of model being clipped, since the near/far clip
        # plane change as scale too.
        self.glpane.scale *= zoomFactor

        self.command.command_Done()
        return
예제 #8
0
    def select(self, wX, wY):
        """
        Use the OpenGL picking/selection to select any object. Return the
        selected object, otherwise, return None. Restore projection and 
        model/view matrices before returning.
        """
        ####@@@@ WARNING: The original code for this, in GLPane, has been duplicated and slightly modified
        # in at least three other places (search for glRenderMode to find them). This is bad; common code
        # should be used. Furthermore, I suspect it's sometimes needlessly called more than once per frame;
        # that should be fixed too. [bruce 060721 comment]
        wZ = glReadPixelsf(wX, wY, 1, 1, GL_DEPTH_COMPONENT)
        gz = wZ[0][0]

        if gz >= GL_FAR_Z:  ##Empty space was clicked
            return None

        pxyz = A(gluUnProject(wX, wY, gz))
        pn = self.out
        pxyz -= 0.0002 * pn
        # Note: if this runs before the model is drawn, this can have an
        # exception "OverflowError: math range error", presumably because
        # appropriate state for gluUnProject was not set up. That doesn't
        # normally happen but can happen due to bugs (no known open bugs
        # of that kind).
        # Sometimes our drawing area can become "stuck at gray",
        # and when that happens, the same exception can occur from this line.
        # Could it be that too many accidental mousewheel scrolls occurred
        # and made the scale unreasonable? (To mitigate, we should prevent
        # those from doing anything unless we have a valid model, and also
        # reset that scale when loading a new model (latter is probably
        # already done, but I didn't check).) [bruce 080220 comment]
        dp = -dot(pxyz, pn)

        # Save projection matrix before it's changed.
        glMatrixMode(GL_PROJECTION)
        glPushMatrix()

        current_glselect = (wX, wY, 1, 1)
        self._setup_projection(glselect=current_glselect)

        glSelectBuffer(self.glselectBufferSize)
        glRenderMode(GL_SELECT)
        glInitNames()
        glMatrixMode(GL_MODELVIEW)
        # Save model view matrix before it's changed.
        glPushMatrix()
        try:
            glClipPlane(GL_CLIP_PLANE0, (pn[0], pn[1], pn[2], dp))
            glEnable(GL_CLIP_PLANE0)
            self.drawModel()
            glDisable(GL_CLIP_PLANE0)
        except:
            print_compact_traceback("exception in mode.Draw() during GL_SELECT; ignored; restoring modelview matrix: ")
            glPopMatrix()
            glRenderMode(GL_RENDER)
            return None
        else:
            # Restore model/view matrix
            glPopMatrix()

        # Restore project matrix and set matrix mode to Model/View
        glMatrixMode(GL_PROJECTION)
        glPopMatrix()
        glMatrixMode(GL_MODELVIEW)

        glFlush()

        hit_records = list(glRenderMode(GL_RENDER))
        if debug_flags.atom_debug and 0:
            print "%d hits" % len(hit_records)
        for (near, far, names) in hit_records:  # see example code, renderpass.py
            if debug_flags.atom_debug and 0:
                print "hit record: near, far, names:", near, far, names
                # e.g. hit record: near, far, names: 1439181696 1453030144 (1638426L,)
                # which proves that near/far are too far apart to give actual depth,
                # in spite of the 1-pixel drawing window (presumably they're vertices
                # taken from unclipped primitives, not clipped ones).
            if names:
                name = names[-1]
                assy = self.assy
                obj = assy and assy.object_for_glselect_name(name)
                # k should always return an obj
                return obj
        return None  # from ThumbView.select
예제 #9
0
    def maybeTip(self, helpEvent):
        """
        Determines if this tooltip should be displayed. The tooltip will be displayed at
        helpEvent.globalPos() if an object is highlighted and the mouse hasn't moved for
        some period of time, called the "wake up delay" period, which is a user pref
        (not yet implemented in the Preferences dialog) currently set to 1 second.

        maybeTip() is called by GLPane.timerEvent() whenever the cursor is not moving to
        determine if the tooltip should be displayed.

        @param helpEvent: a QHelpEvent constructed by the caller
        @type helpEvent: QHelpEvent
        """
        # docstring used to also say:
        ## For more details about this member, see Qt documentation on QToolTip.maybeTip().
        # but this is unclear to me (since this class does not inherit from
        # QToolTip), so I removed it. [bruce 081208]

        debug = debug_pref("GLPane: graphics debug tooltip?",
                           Choice_boolean_False,
                           prefs_key = True )

        glpane = self.glpane
        selobj = glpane.selobj

        if debug:
            # russ 080715: Graphics debug tooltip.
            # bruce 081208/081211: revised, moved out of _getToolTipText,
            # made debug_pref.
            # note: we don't use glpane.MousePos since it's not reliable --
            # only some graphicsModes store it, and only in mouse press events.

            # Note: double buffering applies only to the color buffer,
            # not the stencil or depth buffers, which have only one copy.
            # The setting of GL_READ_BUFFER should have no effect on
            # glReadPixelsf from those buffers.
            # [bruce 081211 comment, based on Russ report of OpenGL doc]

            pos = helpEvent.pos()
            wX = pos.x()
            wY = glpane.height - pos.y() #review: off by 1??
            wZ = glReadPixelsf(wX, wY, 1, 1, GL_DEPTH_COMPONENT)[0][0]
            stencil = glReadPixelsi(wX, wY, 1, 1, GL_STENCIL_INDEX)[0][0]
            savebuff = glGetInteger(GL_READ_BUFFER)
            whichbuff = {GL_FRONT:"front", GL_BACK:"back"}.get(savebuff, "unknown")
            redraw_counter = env.redraw_counter
            # Pixel data is sign-wrapped, in spite of specifying unsigned_byte.
            def us(b):
                if b < 0:
                    return 256 + b
                else:
                    return b
            def pixvals(buff):
                glReadBuffer(buff)
                gl_format, gl_type = GL_RGBA, GL_UNSIGNED_BYTE
                rgba = glReadPixels( wX, wY, 1, 1, gl_format, gl_type )[0][0]
                return (
                    "rgba %u, %u, %u, %u" %
                    (us(rgba[0]), us(rgba[1]), us(rgba[2]), us(rgba[3]))
                 )
            def redifnot(v1, v2, text):
                if v1 != v2:
                    return redmsg(text)
                else:
                    return text
            front_pixvals = pixvals(GL_FRONT)
            back_pixvals = pixvals(GL_BACK)
            glReadBuffer(savebuff)      # restore the saved value
            tipText = (
                "env.redraw = %d; selobj = %s<br>" % (redraw_counter, quote_html(str(selobj)),) +
                    # note: sometimes selobj is an instance of _UNKNOWN_SELOBJ_class... relates to testmode bug from renderText
                    # (confirmed that renderText zaps stencil and that that alone causes no bug in other graphicsmodes)
                    # TODO: I suspect this can be printed even during rendering... need to print glpane variables
                    # which indicate whether we're doing rendering now, e.g. current_glselect, drawing_phase;
                    # also modkeys (sp?), glselect_wanted
                "mouse position (xy): %d, %d<br>" % (wX, wY,) +
                "depth %f, stencil %d<br>" % (wZ, stencil) +
                redifnot(whichbuff, "back",
                         "current read buffer: %s<br>" % whichbuff ) +
                redifnot(glpane.glselect_wanted, 0,
                         "glselect_wanted: %s<br>" % (glpane.glselect_wanted,) ) +
                redifnot(glpane.current_glselect, False,
                         "current_glselect: %s<br>" % (glpane.current_glselect,) ) +
                redifnot(glpane.drawing_phase, "?",
                         "drawing_phase: %s<br>" % (glpane.drawing_phase,) ) +
                "front: " + front_pixvals + "<br>" +
                redifnot(back_pixvals, front_pixvals,
                         "back:  " + back_pixvals )
             )
            global _last_tipText
            if tipText != _last_tipText:
                print
                print tipText
                _last_tipText = tipText
            pass # use tipText below

        else:

            # <motionlessCursorDuration> is the amount of time the cursor (mouse) has been motionless.
            motionlessCursorDuration = time.time() - glpane.cursorMotionlessStartTime

            # Don't display the tooltip yet if <motionlessCursorDuration> hasn't exceeded the "wake up delay".
            # The wake up delay is currently set to 1 second in prefs_constants.py. Mark 060818.
            if motionlessCursorDuration < env.prefs[dynamicToolTipWakeUpDelay_prefs_key]:
                self.toolTipShown = False
                return

            # If an object is not currently highlighted, don't display a tooltip.
            if not selobj:
                return

            # If the highlighted object is a singlet,
            # don't display a tooltip for it.
            if isinstance(selobj, Atom) and (selobj.element is Singlet):
                return

            if self.toolTipShown:
                # The tooltip is already displayed, so return.
                # Do not allow tip() to be called again or it will "flash".
                return

            tipText = self._getToolTipText()

            pass

        # show the tipText

        if not tipText:
            tipText = ""
            # This makes sure that dynamic tip is not displayed when
            # the highlightable object is 'unknown' to the dynamic tip class.
            # (From QToolTip.showText doc: "If text is empty the tool tip is hidden.")

        showpos = helpEvent.globalPos()

        if debug:
            # show it a little lower to avoid the cursor obscuring the tooltip.
            # (might be useful even when not debugging, depending on the cursor)
            # [bruce 081208]
            showpos = showpos + QPoint(0, 10)

        QToolTip.showText(showpos, tipText)  #@@@ ninad061107 works fine but need code review

        self.toolTipShown = True
예제 #10
0
    def leftUp(self, event):
        """
        Erase the final rubber band window and do zoom if user indeed draws a
        rubber band window.
        """
        # bugs 1190, 1818 wware 4/05/2006 - sometimes Qt neglects to call
        # leftDown before this
        if not hasattr(self, "pWxy") or not hasattr(self, "firstDraw"):
            return
        cWxy = (event.pos().x(), self.glpane.height - event.pos().y())
        zoomX = (abs(cWxy[0] - self.pWxy[0]) + 0.0) / (self.glpane.width + 0.0)
        zoomY = (abs(cWxy[1] - self.pWxy[1]) + 0.0) / (self.glpane.height +
                                                       0.0)

        # The rubber band window size can be larger than that of glpane.
        # Limit the zoomFactor to 1.0
        zoomFactor = min(max(zoomX, zoomY), 1.0)

        # Huaicai: when rubber band window is too small,
        # like a double click, a single line rubber band, skip zoom
        DELTA = 1.0E-5
        if self.pWxy[0] == cWxy[0] or self.pWxy[1] == cWxy[1] \
                or zoomFactor < DELTA:
            self.command.Done()
            return

        # Erase the last rubber-band window
        rbwcolor = self.command.rbwcolor
        drawrectangle(self.pStart, self.pPrev, self.glpane.up,
                      self.glpane.right, rbwcolor)
        glFlush()
        self.glpane.swapBuffers()

        winCenterX = (cWxy[0] + self.pWxy[0]) / 2.0
        winCenterY = (cWxy[1] + self.pWxy[1]) / 2.0
        winCenterZ = \
            glReadPixelsf(int(winCenterX), int(winCenterY), 1, 1,
                          GL_DEPTH_COMPONENT)

        assert winCenterZ[0][0] >= 0.0 and winCenterZ[0][0] <= 1.0
        if winCenterZ[0][0] >= GL_FAR_Z:  # window center touches nothing
            p1 = A(gluUnProject(winCenterX, winCenterY, 0.005))
            p2 = A(gluUnProject(winCenterX, winCenterY, 1.0))

            los = self.glpane.lineOfSight
            k = dot(los, -self.glpane.pov - p1) / dot(los, p2 - p1)

            zoomCenter = p1 + k * (p2 - p1)

        else:
            zoomCenter = \
                A(gluUnProject(winCenterX, winCenterY, winCenterZ[0][0]))
        self.glpane.pov = V(-zoomCenter[0], -zoomCenter[1], -zoomCenter[2])

        # The following are 2 ways to do the zoom, the first one
        # changes view angles, the 2nd one change viewing distance
        # The advantage for the 1st one is model will not be clipped by
        #  near or back clipping planes, and the rubber band can be
        # always shown. The disadvantage: when the view field is too
        # small, a selection window may be actually act as a single pick.
        # rubber ban window will not look as rectanglular any more.
        #zf = self.glpane.getZoomFactor()
        #zoomFactor = pow(zoomFactor, 0.25)
        #zoomFactor *= zf
        #self.glpane.setZoomFactor(zoomFactor)

        # Change viewing distance to do zoom. This works better with
        # mouse wheel, since both are changing viewing distance, and
        # it's not too bad of model being clipped, since the near/far clip
        # plane change as scale too.
        self.glpane.scale *= zoomFactor

        self.command.Done(exit_using_done_or_cancel_button=False)
        return
예제 #11
0
    def do_glselect_if_wanted(self):  # bruce 070919 split this out
        """
        Do the glRenderMode(GL_SELECT) drawing, and/or the glname-color
        drawing for shader primitives, used to guess which object
        might be under the mouse, for one drawing frame,
        if desired for this frame. Report results by storing candidate
        mouseover objects in self.glselect_dict. 
        
        The depth buffer is initially clear, and must be clear
        when we're done as well.

        @note: does not do related individual object depth/stencil 
               buffer drawing -- caller must do that on some or all
               of the objects we store into self.glselect_dict.
        """
        if self.glselect_wanted:  # note: this will be reset below.
            ###@@@ WARNING: The original code for this, here in GLPane, has been duplicated and slightly modified
            # in at least three other places (search for glRenderMode to find them). This is bad; common code
            # should be used. Furthermore, I suspect it's sometimes needlessly called more than once per frame;
            # that should be fixed too. [bruce 060721 comment]
            wX, wY, self.targetdepth = self.glselect_wanted  # wX, wY is the point to do the hit-test at
            # targetdepth is the depth buffer value to look for at that point, during ordinary drawing phase
            # (could also be used to set up clipping planes to further restrict hit-test, but this isn't yet done)
            # (Warning: targetdepth could in theory be out of date, if more events come between bareMotion
            #  and the one caused by its gl_update, whose paintGL is what's running now, and if those events
            #  move what's drawn. Maybe that could happen with mousewheel events or (someday) with keypresses
            #  having a graphical effect. Ideally we'd count intentional redraws, and disable this picking in that case.)
            self.wX, self.wY = wX, wY
            self.glselect_wanted = 0
            pwSize = 1  # Pick window size.  Russ 081128: Was 3.
            # Bruce: Replace 3, 3 with 1, 1? 5, 5? not sure whether this will
            # matter...  in principle should have no effect except speed.
            # Russ: For glname rendering, 1x1 is better because it doesn't
            # have window boundary issues.  We get the coords of a single
            # pixel in the window for the mouse position.

            # bruce 050615 for use by nodes which want to set up their own projection matrix.
            self.current_glselect = (wX, wY, pwSize, pwSize)
            self._setup_projection(glselect=self.current_glselect)  # option makes it use gluPickMatrix

            # Russ 081209: Added.
            debugPicking = debug_pref("GLPane: debug mouseover picking?", Choice_boolean_False, prefs_key=True)

            if self.enabled_shaders():
                # TODO: optimization: find an appropriate place to call
                # _compute_frustum_planes. [bruce 090105 comment]

                # Russ 081122: There seems to be no way to access the GL name
                # stack in shaders. Instead, for mouseover, draw shader
                # primitives with glnames as colors in glRenderMode(GL_RENDER),
                # then read back the pixel color (glname) and depth value.

                # Temporarily replace the full-size viewport with a little one
                # at the mouse location, matching the pick matrix location.
                # Otherwise, we will draw a closeup of that area into the whole
                # window, rather than a few pixels. (This wasn't needed when we
                # only used GL_SELECT rendering mode here, because that doesn't
                # modify the frame buffer -- it just returns hits by graphics
                # primitives when they are inside the clipping boundaries.)
                #
                # (Don't set the viewport *before* _setup_projection(), since
                #  that method needs to read the current whole-window viewport
                #  to set up glselect. See explanation in its docstring.)

                savedViewport = glGetIntegerv(GL_VIEWPORT)
                glViewport(wX, wY, pwSize, pwSize)  # Same as current_glselect.

                # First, clear the pixel RGBA to zeros and a depth of 1.0 (far),
                # so we won't confuse a color with a glname if there are
                # no shader primitives drawn over this pixel.
                saveDepthFunc = glGetInteger(GL_DEPTH_FUNC)
                glDepthFunc(GL_ALWAYS)
                glWindowPos3i(wX, wY, 1)  # Note the Z coord.
                gl_format, gl_type = GL_RGBA, GL_UNSIGNED_BYTE
                glDrawPixels(pwSize, pwSize, gl_format, gl_type, (0, 0, 0, 0))
                glDepthFunc(saveDepthFunc)  # needed, though we'll change it again

                # We must be in glRenderMode(GL_RENDER) (as usual) when this is called.
                # Note: _setup_projection leaves the matrix mode as GL_PROJECTION.
                glMatrixMode(GL_MODELVIEW)
                shaders = self.enabled_shaders()
                try:
                    # Set flags so that we will use glnames-as-color mode
                    # in shaders, and draw only shader primitives.
                    # (Ideally we would also draw all non-shader primitives
                    #  as some other color, unequal to all glname colors
                    #  (or derived from a fake glname for that purpose),
                    #  in order to obscure shader primitives where appropriate.
                    #  This is intended to be done but is not yet implemented.
                    #  [bruce 090105 addendum])
                    for shader in shaders:
                        shader.setPicking(True)
                    self.set_drawing_phase("glselect_glname_color")

                    for stereo_image in self.stereo_images_to_draw:
                        self._enable_stereo(stereo_image)
                        try:
                            self._do_graphicsMode_Draw(for_mouseover_highlighting=True)
                            # note: we can't disable depth writing here,
                            # since we need it to make sure the correct
                            # shader object comes out on top, or is
                            # obscured by a DL object. Instead, we'll
                            # clear the depth buffer again (at this pixel)
                            # below. [bruce 090105]
                        finally:
                            self._disable_stereo()
                except:
                    print_compact_traceback(
                        "exception in or around _do_graphicsMode_Draw() during glname_color;"
                        "drawing ignored; restoring modelview matrix: "
                    )
                    # REVIEW: what does "drawing ignored" mean, in that message? [bruce 090105 question]
                    glMatrixMode(GL_MODELVIEW)
                    self._setup_modelview()  ### REVIEW: correctness of this is unreviewed!
                    # now it's important to continue, at least enough to restore other gl state
                    pass
                for shader in shaders:
                    shader.setPicking(False)
                self.set_drawing_phase("?")

                # Restore the viewport.
                glViewport(savedViewport[0], savedViewport[1], savedViewport[2], savedViewport[3])

                # Read pixel value from the back buffer and re-assemble glname.
                glFinish()  # Make sure the drawing has completed.
                # REVIEW: is this glFinish needed? [bruce 090105 comment]
                rgba = glReadPixels(wX, wY, 1, 1, gl_format, gl_type)[0][0]
                pixZ = glReadPixelsf(wX, wY, 1, 1, GL_DEPTH_COMPONENT)[0][0]

                # Clear our depth pixel to 1.0 (far), so we won't mess up the
                # subsequent call of preDraw_glselect_dict.
                # (The following is not the most direct way, but it ought to work.
                #  Note that we also clear the color pixel, since (being a glname)
                #  it has no purpose remaining in the color buffer -- either it's
                #  changed later, or, if not, that's a bug, but we'd rather have
                #  it black than a random color.) [bruce 090105 bugfix]
                glDepthFunc(GL_ALWAYS)
                glWindowPos3i(wX, wY, 1)  # Note the Z coord.
                glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
                gl_format, gl_type = GL_RGBA, GL_UNSIGNED_BYTE
                glDrawPixels(pwSize, pwSize, gl_format, gl_type, (0, 0, 0, 0))
                glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
                glDepthFunc(saveDepthFunc)

                # Comes back sign-wrapped, in spite of specifying UNSIGNED_BYTE.
                def us(b):
                    if b < 0:
                        return 256 + b
                    return b

                bytes = tuple([us(b) for b in rgba])
                ##glname = (bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3])
                ## Temp fix: Ignore the last byte, which always comes back 255 on Windows.
                glname = bytes[0] << 16 | bytes[1] << 8 | bytes[2]
                if debugPicking:
                    print (
                        "shader mouseover xy %d %d, " % (wX, wY)
                        + "rgba bytes (0x%x, 0x%x, 0x%x, 0x%x), " % bytes
                        + "Z %f, glname 0x%x" % (pixZ, glname)
                    )
                    pass

                ### XXX This ought to be better-merged with the DL selection below.
                if glname:
                    obj = self.object_for_glselect_name(glname)
                    if debugPicking:
                        print "shader mouseover glname=%r, obj=%r." % (glname, obj)
                    if obj is None:
                        # REVIEW: does this happen for mouse over a non-shader primitive?
                        # [bruce 090105 question]

                        #### Note: this bug is common. Guess: we are still drawing
                        # ordinary colors for some primitives and/or for the
                        # background, and they are showing up here and confusing
                        # us. To help debug this, print the color too. But testing
                        # shows it's not so simple -- e.g. for rung bonds it happens
                        # where shader sphere and cylinder overlap, but not on either
                        # one alone; for strand bonds it also happens on the bonds alone
                        # (tested in Build DNA, in or not in Insert DNA).
                        # [bruce 090218]
                        #
                        # Update: Since it's so common, I need to turn it off by default.
                        # Q: is the situation safe?
                        # A: if a color looks like a real glname by accident,
                        # we'll get some random candidate object -- perhaps a killed one
                        # or from a different Part or even a closed assy --
                        # and try to draw it. That doesn't sound very safe. Unfortunately
                        # there is no perfect way to filter selobjs for safety, in the
                        # current still-informal Selobj_API. The best approximation is
                        # selobj_still_ok, and it should always say yes for the usual kinds,
                        # so I'll add it as a check in the 'else' clause below.
                        # [bruce 090311]
                        if debug_flags.atom_debug:
                            print "bug: object_for_glselect_name returns None for glname %r (color %r)" % (
                                glname,
                                bytes,
                            )
                    else:
                        if self.graphicsMode.selobj_still_ok(obj):
                            # bruce 090311 added condition, explained above
                            self.glselect_dict[id(obj)] = obj
                        else:
                            # This should be rare but possible. Leave it on briefly and see
                            # if it's ever common. If possible, gate it by atom_debug before
                            # the release. [bruce 090311]
                            print "fyi: glname-color selobj %r rejected since not selobj_still_ok" % obj
                        pass
                    pass
                pass

            if self._use_frustum_culling:
                self._compute_frustum_planes()
                # piotr 080331 - the frustum planes have to be setup after the
                # projection matrix is setup. I'm not sure if there may
                # be any side effects - see the comment below about
                # possible optimization.
            glSelectBuffer(self.SIZE_FOR_glSelectBuffer)
            # Note: this allocates a new select buffer,
            # and glRenderMode(GL_RENDER) returns it and forgets it,
            # so it's required before *each* call of glRenderMode(GL_SELECT) +
            # glRenderMode(GL_RENDER), not just once to set the size.
            # Ref: http://pyopengl.sourceforge.net/documentation/opengl_diffs.html
            # [bruce 080923 comment]
            glInitNames()

            # REVIEW: should we also set up a clipping plane just behind the
            # hit point, as (I think) is done in ThumbView, to reduce the
            # number of candidate objects? This might be a significant
            # optimization, though I don't think it eliminates the chance
            # of having multiple candidates. [bruce 080917 comment]

            glRenderMode(GL_SELECT)
            glMatrixMode(GL_MODELVIEW)
            try:
                self.set_drawing_phase("glselect")  # bruce 070124
                for stereo_image in self.stereo_images_to_draw:
                    self._enable_stereo(stereo_image)
                    try:
                        self._do_graphicsMode_Draw(for_mouseover_highlighting=True)
                    finally:
                        self._disable_stereo()
            except:
                print_compact_traceback(
                    "exception in or around _do_graphicsMode_Draw() during GL_SELECT; "
                    "ignored; restoring modelview matrix: "
                )
                glMatrixMode(GL_MODELVIEW)
                self._setup_modelview()  ### REVIEW: correctness of this is unreviewed!
                # now it's important to continue, at least enough to restore other gl state

            self._frustum_planes_available = False  # piotr 080331
            # just to be safe and not use the frustum planes computed for
            # the pick matrix
            self.set_drawing_phase("?")
            self.current_glselect = False
            # REVIEW: On systems with no stencil buffer, I think we'd also need
            # to draw selobj here in highlighted form (in case that form is
            # bigger than when it's not highlighted), or (easier & faster)
            # just always pretend it passes the hit test and add it to
            # glselect_dict -- and, make sure to give it "first dibs" for being
            # the next selobj. I'll implement some of this now (untested when
            # no stencil buffer) but not yet all. [bruce 050612]
            selobj = self.selobj
            if selobj is not None:
                self.glselect_dict[id(selobj)] = selobj
                # (review: is the following note correct?)
                # note: unneeded, if the func that looks at this dict always
                # tries selobj first (except for a kluge near
                # "if self.glselect_dict", commented on below)
            glFlush()
            hit_records = list(glRenderMode(GL_RENDER))
            if debugPicking:
                print "DLs %d hits" % len(hit_records)
            for (near, far, names) in hit_records:  # see example code, renderpass.py
                ## print "hit record: near, far, names:", near, far, names
                # e.g. hit record: near, far, names: 1439181696 1453030144 (1638426L,)
                # which proves that near/far are too far apart to give actual depth,
                # in spite of the 1- or 3-pixel drawing window (presumably they're vertices
                # taken from unclipped primitives, not clipped ones).
                del near, far
                if 1:
                    # partial workaround for bug 1527. This can be removed once that bug (in drawer.py)
                    # is properly fixed. This exists in two places -- GLPane.py and modes.py. [bruce 060217]
                    if names and names[-1] == 0:
                        print "%d(g) partial workaround for bug 1527: removing 0 from end of namestack:" % env.redraw_counter, names
                        names = names[:-1]
                if names:
                    # For now, we only use the last element of names,
                    # though (as of long before 080917) it is often longer:
                    # - some code pushes the same name twice (directly and
                    #   via ColorSorter) (see 060725 debug print below);
                    # - chunks push a name even when they draw atoms/bonds
                    #   which push their own names (see 080411 comment below).
                    #
                    # Someday: if we ever support "name/subname paths" we'll
                    # probably let first name interpret the remaining ones.
                    # In fact, if nodes change projection or viewport for
                    # their kids, and/or share their kids, they'd need to
                    # push their own names on the stack, so we'd know how
                    # to redraw the kids, or which ones are meant when they
                    # are shared.
                    if debug_flags.atom_debug and len(names) > 1:  # bruce 060725
                        if len(names) == 2 and names[0] == names[1]:
                            if not env.seen_before("dual-names bug"):  # this happens for Atoms (colorsorter bug??)
                                print "debug (once-per-session message): why are some glnames duplicated on the namestack?", names
                        else:
                            # Note: as of sometime before 080411, this became common --
                            # I guess that chunks (which recently acquired glselect names)
                            # are pushing their names even while drawing their atoms and bonds.
                            # I am not sure if this can cause any problems -- almost surely not
                            # directly, but maybe the nestedness of highlighted appearances could
                            # violate some assumptions made by the highlight code... anyway,
                            # to reduce verbosity I need to not print this when the deeper name
                            # is that of a chunk, and there are exactly two names. [bruce 080411]
                            if len(names) == 2 and isinstance(self.object_for_glselect_name(names[0]), self.assy.Chunk):
                                if not env.seen_before("nested names for Chunk"):
                                    print "debug (once-per-session message): nested glnames for a Chunk: ", names
                            else:
                                print "debug fyi: len(names) == %d (names = %r)" % (len(names), names)
                    obj = self.object_for_glselect_name(names[-1])  # k should always return an obj
                    if obj is None:
                        print "bug: object_for_glselect_name returns None for name %r at end of namestack %r" % (
                            names[-1],
                            names,
                        )
                    else:
                        self.glselect_dict[id(obj)] = obj
                        # note: outside of this method, one of these will be
                        # chosen to be saved as self.selobj and rerendered
                        # in "highlighted" form
                        ##if 0:
                        ##    # this debug print was useful for debugging bug 2945,
                        ##    # and when it happens it's usually a bug,
                        ##    # but not always:
                        ##    # - it's predicted to happen for ChunkDrawer._renderOverlayText
                        ##    # - and whenever we're using a whole-chunk display style
                        ##    # so we can't leave it in permanently. [bruce 081211]
                        ##    if isinstance( obj, self.assy.Chunk ):
                        ##        print "\n*** namestack topped with a chunk:", obj
                    pass
                continue  # next hit_record
            # e maybe we should now sort glselect_dict by "hit priority"
            # (for depth-tiebreaking), or at least put selobj first.
            # (or this could be done lower down, where it's used.)
            # [I think we do this now...]

        return  # from do_glselect_if_wanted
예제 #12
0
    def check_target_depth(self, candidate, fudge, debug_prefix=None):  # bruce 050609; revised 050702, 070115
        """
        [private helper method]
           [required arg fudge is the fudge factor in threshhold test]
           WARNING: docstring is obsolete -- no newpicked anymore, retval details differ: ###@@@
        Candidate is an object which drew at the mouse position during GL_SELECT drawing mode
        (using the given gl_select name), and which (1) has now noticed this, via its entry in self.glselect_dict
        (which was made when GL_SELECT mode was exited; as of 050609 this is in the same paintGL call as we're in now),
        and (2) has already drawn into the depth buffer during normal rendering (or an earlier rendering pass).
        (It doesn't matter whether it's already drawn into the color buffer when it calls this method.)
           We should read pixels from the depth buffer (after glFlush)
        to check whether it has just reached self.targetdepth at the appropriate point,
        which would mean candidate is the actual newly picked object.
           If so, record this fact and return True, else return False.
        We might quickly return False (checking nothing) if we already returned True in the same pass,
        or we might read pixels anyway for debugging or assertions.
           It's possible to read a depth even nearer than targetdepth, if the drawing passes round
        their coordinates differently (as the use of gluPickMatrix for GL_SELECT is likely to do),
        or if events between the initial targetdepth measurement and this redraw tell any model objects to move.
        Someday we should check for this.
        """
        glFlush()  # In theory, glFinish might be needed here;
        # in practice, I don't know if even glFlush is needed.
        # [bruce 070921 comment]
        wX, wY = self.wX, self.wY
        wZ = glReadPixelsf(wX, wY, 1, 1, GL_DEPTH_COMPONENT)
        newdepth = wZ[0][0]
        targetdepth = self.targetdepth
        ### possible change: here we could effectively move selobj forwards
        # (to give it an advantage over other objects)...
        # but we'd need to worry about scales of coord systems in doing that...
        # due to that issue it is probably easier to fix this solely when
        # drawing it, instead.
        if newdepth <= targetdepth + fudge:
            # test passes -- return candidate below

            # note: condition uses fudge factor in case of roundoff errors
            # (hardcoded as 0.0001 before 070115) or slight differences in
            # highlighted vs plain form of object (e.g. due to inconsistent
            # orientation of polygonal approximation to a cylinder)
            #
            # [bruce 050702: 0.000001 was not enough! 0.00003 or more was needed, to properly highlight some bonds
            #  which became too hard to highlight after today's initial fix of bug 715-1.]
            # [update, bruce 070921: fyi: one reason this factor is needed is the shorten_tubes flag used to
            #  highlight some bonds, which changes the cylinder scaling, and conceivably (due solely to
            #  roundoff errors) the precise axis direction, and thus the precise cross-section rotation
            #  around the axis. Another reason was a bug in bond_drawer which I fixed today, so the
            #  necessary factor may now be smaller, but I didn't test this.]
            #
            # e could check for newdepth being < targetdepth - 0.002 (error), but best
            # to just let caller do that (NIM), since we would often not catch this error anyway,
            # since we're turning into noop on first success
            # (no choice unless we re-cleared depth buffer now, which btw we could do... #e).
            if debug_prefix:
                counter = env.redraw_counter
                print "%s (%d): target depth %r reached by %r at %r" % (
                    debug_prefix,
                    counter,
                    targetdepth,
                    candidate,
                    newdepth,
                )
                if newdepth > targetdepth:
                    print "  (too deep by %r, but fudge factor is %r)" % (newdepth - targetdepth, fudge)
                elif newdepth < targetdepth:
                    print "  (in fact, object is nearer than targetdepth by %r)" % (targetdepth - newdepth,)
                pass
            return candidate
            # caller should not call us again without clearing depth buffer,
            # otherwise we'll keep returning every object even if its true
            # depth is too deep
        if debug_prefix:
            counter = env.redraw_counter
            print "%s (%d): target depth %r NOT reached by %r at %r" % (
                debug_prefix,
                counter,
                targetdepth,
                candidate,
                newdepth,
            )
            print "  (too deep by %r, but fudge factor is only %r)" % (newdepth - targetdepth, fudge)
        return None
예제 #13
0
    def maybeTip(self, helpEvent):
        """
        Determines if this tooltip should be displayed. The tooltip will be displayed at
        helpEvent.globalPos() if an object is highlighted and the mouse hasn't moved for 
        some period of time, called the "wake up delay" period, which is a user pref
        (not yet implemented in the Preferences dialog) currently set to 1 second.
        
        maybeTip() is called by GLPane.timerEvent() whenever the cursor is not moving to 
        determine if the tooltip should be displayed.

        @param helpEvent: a QHelpEvent constructed by the caller
        @type helpEvent: QHelpEvent
        """
        # docstring used to also say:
        ## For more details about this member, see Qt documentation on QToolTip.maybeTip().
        # but this is unclear to me (since this class does not inherit from
        # QToolTip), so I removed it. [bruce 081208]

        debug = debug_pref("GLPane: graphics debug tooltip?",
                           Choice_boolean_False,
                           prefs_key = True )

        glpane = self.glpane
        selobj = glpane.selobj

        if debug:
            # russ 080715: Graphics debug tooltip.
            # bruce 081208/081211: revised, moved out of _getToolTipText,
            # made debug_pref.
            # note: we don't use glpane.MousePos since it's not reliable --
            # only some graphicsModes store it, and only in mouse press events.

            # Note: double buffering applies only to the color buffer,
            # not the stencil or depth buffers, which have only one copy.
            # The setting of GL_READ_BUFFER should have no effect on
            # glReadPixelsf from those buffers.
            # [bruce 081211 comment, based on Russ report of OpenGL doc]
            
            pos = helpEvent.pos()
            wX = pos.x()
            wY = glpane.height - pos.y() #review: off by 1??
            wZ = glReadPixelsf(wX, wY, 1, 1, GL_DEPTH_COMPONENT)[0][0]
            stencil = glReadPixelsi(wX, wY, 1, 1, GL_STENCIL_INDEX)[0][0]
            savebuff = glGetInteger(GL_READ_BUFFER)
            whichbuff = {GL_FRONT:"front", GL_BACK:"back"}.get(savebuff, "unknown")
            redraw_counter = env.redraw_counter
            # Pixel data is sign-wrapped, in spite of specifying unsigned_byte.
            def us(b):
                if b < 0:
                    return 256 + b
                else:
                    return b
            def pixvals(buff):
                glReadBuffer(buff)
                gl_format, gl_type = GL_RGBA, GL_UNSIGNED_BYTE
                rgba = glReadPixels( wX, wY, 1, 1, gl_format, gl_type )[0][0]
                return (
                    "rgba %u, %u, %u, %u" %
                    (us(rgba[0]), us(rgba[1]), us(rgba[2]), us(rgba[3]))
                 )
            def redifnot(v1, v2, text):
                if v1 != v2:
                    return redmsg(text)
                else:
                    return text
            front_pixvals = pixvals(GL_FRONT)
            back_pixvals = pixvals(GL_BACK)
            glReadBuffer(savebuff)      # restore the saved value
            tipText = (
                "env.redraw = %d; selobj = %s<br>" % (redraw_counter, quote_html(str(selobj)),) +
                    # note: sometimes selobj is an instance of _UNKNOWN_SELOBJ_class... relates to testmode bug from renderText
                    # (confirmed that renderText zaps stencil and that that alone causes no bug in other graphicsmodes)
                    # TODO: I suspect this can be printed even during rendering... need to print glpane variables
                    # which indicate whether we're doing rendering now, e.g. current_glselect, drawing_phase;
                    # also modkeys (sp?), glselect_wanted
                "mouse position (xy): %d, %d<br>" % (wX, wY,) +
                "depth %f, stencil %d<br>" % (wZ, stencil) +
                redifnot(whichbuff, "back",
                         "current read buffer: %s<br>" % whichbuff ) +
                redifnot(glpane.glselect_wanted, 0,
                         "glselect_wanted: %s<br>" % (glpane.glselect_wanted,) ) + 
                redifnot(glpane.current_glselect, False,
                         "current_glselect: %s<br>" % (glpane.current_glselect,) ) + 
                redifnot(glpane.drawing_phase, "?",
                         "drawing_phase: %s<br>" % (glpane.drawing_phase,) ) +
                "front: " + front_pixvals + "<br>" +
                redifnot(back_pixvals, front_pixvals,
                         "back:  " + back_pixvals )
             )
            global _last_tipText
            if tipText != _last_tipText:
                print
                print tipText
                _last_tipText = tipText
            pass # use tipText below

        else:
            
            # <motionlessCursorDuration> is the amount of time the cursor (mouse) has been motionless.
            motionlessCursorDuration = time.time() - glpane.cursorMotionlessStartTime
            
            # Don't display the tooltip yet if <motionlessCursorDuration> hasn't exceeded the "wake up delay".
            # The wake up delay is currently set to 1 second in prefs_constants.py. Mark 060818.
            if motionlessCursorDuration < env.prefs[dynamicToolTipWakeUpDelay_prefs_key]:
                self.toolTipShown = False
                return
            
            # If an object is not currently highlighted, don't display a tooltip.
            if not selobj:
                return
            
            # If the highlighted object is a singlet, 
            # don't display a tooltip for it.
            if isinstance(selobj, Atom) and (selobj.element is Singlet):
                return
                
            if self.toolTipShown:
                # The tooltip is already displayed, so return. 
                # Do not allow tip() to be called again or it will "flash".
                return
        
            tipText = self._getToolTipText()

            pass

        # show the tipText
        
        if not tipText:            
            tipText = "" 
            # This makes sure that dynamic tip is not displayed when
            # the highlightable object is 'unknown' to the dynamic tip class.
            # (From QToolTip.showText doc: "If text is empty the tool tip is hidden.")

        showpos = helpEvent.globalPos()

        if debug:
            # show it a little lower to avoid the cursor obscuring the tooltip.
            # (might be useful even when not debugging, depending on the cursor)
            # [bruce 081208]
            showpos = showpos + QPoint(0, 10)
        
        QToolTip.showText(showpos, tipText)  #@@@ ninad061107 works fine but need code review
               
        self.toolTipShown = True
예제 #14
0
    def select(self, wX, wY):
        """
        Use the OpenGL picking/selection to select any object. Return the
        selected object, otherwise, return None. Restore projection and 
        model/view matrices before returning.
        """
        ####@@@@ WARNING: The original code for this, in GLPane, has been duplicated and slightly modified
        # in at least three other places (search for glRenderMode to find them). This is bad; common code
        # should be used. Furthermore, I suspect it's sometimes needlessly called more than once per frame;
        # that should be fixed too. [bruce 060721 comment]
        wZ = glReadPixelsf(wX, wY, 1, 1, GL_DEPTH_COMPONENT)
        gz = wZ[0][0]

        if gz >= GL_FAR_Z:  ##Empty space was clicked
            return None

        pxyz = A(gluUnProject(wX, wY, gz))
        pn = self.out
        pxyz -= 0.0002 * pn
        # Note: if this runs before the model is drawn, this can have an
        # exception "OverflowError: math range error", presumably because
        # appropriate state for gluUnProject was not set up. That doesn't
        # normally happen but can happen due to bugs (no known open bugs
        # of that kind).
        # Sometimes our drawing area can become "stuck at gray",
        # and when that happens, the same exception can occur from this line.
        # Could it be that too many accidental mousewheel scrolls occurred
        # and made the scale unreasonable? (To mitigate, we should prevent
        # those from doing anything unless we have a valid model, and also
        # reset that scale when loading a new model (latter is probably
        # already done, but I didn't check).) [bruce 080220 comment]
        dp = -dot(pxyz, pn)

        #Save projection matrix before it's changed.
        glMatrixMode(GL_PROJECTION)
        glPushMatrix()

        current_glselect = (wX, wY, 1, 1)
        self._setup_projection(glselect=current_glselect)

        glSelectBuffer(self.glselectBufferSize)
        glRenderMode(GL_SELECT)
        glInitNames()
        glMatrixMode(GL_MODELVIEW)
        # Save model view matrix before it's changed.
        glPushMatrix()
        try:
            glClipPlane(GL_CLIP_PLANE0, (pn[0], pn[1], pn[2], dp))
            glEnable(GL_CLIP_PLANE0)
            self.drawModel()
            glDisable(GL_CLIP_PLANE0)
        except:
            print_compact_traceback(
                "exception in mode.Draw() during GL_SELECT; ignored; restoring modelview matrix: "
            )
            glPopMatrix()
            glRenderMode(GL_RENDER)
            return None
        else:
            # Restore model/view matrix
            glPopMatrix()

        #Restore project matrix and set matrix mode to Model/View
        glMatrixMode(GL_PROJECTION)
        glPopMatrix()
        glMatrixMode(GL_MODELVIEW)

        glFlush()

        hit_records = list(glRenderMode(GL_RENDER))
        if debug_flags.atom_debug and 0:
            print "%d hits" % len(hit_records)
        for (near, far,
             names) in hit_records:  # see example code, renderpass.py
            if debug_flags.atom_debug and 0:
                print "hit record: near, far, names:", near, far, names
                # e.g. hit record: near, far, names: 1439181696 1453030144 (1638426L,)
                # which proves that near/far are too far apart to give actual depth,
                # in spite of the 1-pixel drawing window (presumably they're vertices
                # taken from unclipped primitives, not clipped ones).
            if names:
                name = names[-1]
                assy = self.assy
                obj = assy and assy.object_for_glselect_name(name)
                #k should always return an obj
                return obj
        return None  # from ThumbView.select