3D coordinates of the mouse

This script (below) allows us to see in real time the point’s coordinates where the mouse hits the activated object. It works well in “Object Mode”.
But in the “Edit Mode” the “Object ‘Cube’ has no mesh data to be used for ray casting”.
WHY? I need to use bmesh? What?



import bpy
from bpy_extras import view3d_utils
from bpy.props import FloatVectorProperty
             
def main(context, event, ray_max=1000.0):
    # get the context arguments
    scene = context.scene
    region = context.region
    rv3d = context.region_data
    coord = event.mouse_region_x, event.mouse_region_y


    # get the ray from the viewport and mouse
    view_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, coord)
    ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, coord)


    if rv3d.view_perspective == 'ORTHO':
        # move ortho origin back
        ray_origin = ray_origin - (view_vector * (ray_max / 2.0))


    ray_target = ray_origin + (view_vector * ray_max)


    def obj_ray_cast(obj, matrix):
        """Wrapper for ray casting that moves the ray into object space"""


        # get the ray relative to the object
        matrix_inv = matrix.inverted()
        ray_origin_obj = matrix_inv * ray_origin
        ray_target_obj = matrix_inv * ray_target


        # cast the ray
        hit, normal, face_index = obj.ray_cast(ray_origin_obj, ray_target_obj)


        if face_index != -1:
            return hit, normal, face_index
        else:
            return None, None, None


    # no need to loop through other objects since we are interested in the active object only
    obj = context.scene.objects.active
    matrix = obj.matrix_world.copy()
    if obj.type == 'MESH':
        hit, normal, face_index = obj_ray_cast(obj, matrix)
        if hit is not None:
            hit_world = matrix * hit
            return hit_world        
        else:
            return view_vector
    #return None
        
class ViewManoOperator(bpy.types.Operator):
    """using mouse events"""
    bl_idname = "view3d.mano_operator"
    bl_label = "View Mano Operator"


    hit = FloatVectorProperty(name="hit", size=3)
            
    def modal(self, context, event):
        if event.type == 'MOUSEMOVE':            
            self.hit = main(context, event)
            context.area.header_text_set("hit: %.4f %.4f %.4f" % tuple(self.hit))
            return {'RUNNING_MODAL'}


        elif event.type in {'LEFTMOUSE', 'RIGHTMOUSE', 'ESC'}:
            context.area.header_text_set()
            return {'CANCELLED'}


        return {'RUNNING_MODAL'} 


    def invoke(self, context, event):        
        context.window_manager.modal_handler_add(self)
        return {'RUNNING_MODAL'}


def register():
    bpy.utils.register_class(ViewManoOperator)


def unregister():
    bpy.utils.unregister_class(ViewManoOperator)


if __name__ == "__main__":
    register()

I decided to try something else: Get the 3D coordinates of the mouse through OpenGL.
The script is as follows:


import bpy
import bgl

class TestesOperator(bpy.types.Operator):
    """using mouse events"""
    bl_idname = "view3d.testes_operator"
    bl_label = "View testes Operator"    
  
    def invoke(self, context, event):


        self.viewport = bgl.Buffer(bgl.GL_INT, 4)
        bgl.glGetIntegerv(bgl.GL_VIEWPORT, self.viewport)
        #print(self.viewport)


        self.modelview = bgl.Buffer(bgl.GL_DOUBLE, [4, 4])
        bgl.glGetDoublev(bgl.GL_MODELVIEW_MATRIX, self.modelview)
        #print(self.modelview)


        self.projection = bgl.Buffer(bgl.GL_DOUBLE, [4, 4])
        bgl.glGetDoublev(bgl.GL_PROJECTION_MATRIX, self.projection)
        #print(self.projection)


        winX = event.mouse_region_x
        winY = self.viewport[3] - event.mouse_region_y
        winZ = bgl.Buffer(bgl.GL_FLOAT, [0.0])


        posX = bgl.Buffer(bgl.GL_DOUBLE, 1, [0.0])
        posY = bgl.Buffer(bgl.GL_DOUBLE, 1, [0.0])
        posZ = bgl.Buffer(bgl.GL_DOUBLE, 1, [1.0])


        bgl.glReadPixels(winX, winY, 1, 1, bgl.GL_DEPTH_COMPONENT, bgl.GL_FLOAT, winZ)
        bgl.gluUnProject( winX, winY, winZ, self.modelview, self.projection, self.viewport, posX, posY, posZ)
        print(posX, posY, posZ)
        return {'FINISHED'}


def register():
    bpy.utils.register_class(TestesOperator)


def unregister():
    bpy.utils.unregister_class(TestesOperator)


if __name__ == "__main__":
    register()

Apparently is correct but, in line:

...
bgl.gluUnProject( winX, winY, winZ, self.modelview, self.projection, self.viewport, posX, posY, posZ)
...

Blender reports:
“Traceback (most recent call last): File “…”, line 32, in invoke
TypeError: a float is required”

How I can fix this?

ray_cast() works in object mode only, I assume because the editmesh data is different and not flushed to the mesh datablock until mode switch (or by a call to ob.update_from_editmode). Perhaps ray casting is not possible on the editmesh data (normal updates?), or it’s simply not implemented to work in editmode too.

Your bgl code fails, because you hand a Buffer over to gluUnProject() for winZ, but it expects a float. You can fix it like this:

        bgl.gluUnProject( winX, winY, winZ[0], self.modelview, self.projection, self.viewport, posX, posY, posZ)
        print(posX[0], posY[0], posZ[0])

Thank @CoDEmanX, you’re the guy. Worked :smiley:

I edited the script and is as follows:


import bpy
from bpy.props import FloatVectorProperty
import bgl


class TestesOperator(bpy.types.Operator):
    """using mouse events"""
    bl_idname = "view3d.testes_operator"
    bl_label = "View testes Operator"


    hit = FloatVectorProperty(name="hit", size=3)


    def modal(self, context, event):
        context.area.tag_redraw()
        if event.type == 'MOUSEMOVE':                       
            self.modelview = bgl.Buffer(bgl.GL_DOUBLE, [4, 4])
            bgl.glGetDoublev(bgl.GL_MODELVIEW_MATRIX, self.modelview)
            #print(self.modelview)


            self.projection = bgl.Buffer(bgl.GL_DOUBLE, [4, 4])
            bgl.glGetDoublev(bgl.GL_PROJECTION_MATRIX, self.projection)
            #print(self.projection)


            self.viewport = bgl.Buffer(bgl.GL_INT, 4)
            bgl.glGetIntegerv(bgl.GL_VIEWPORT, self.viewport)
            #print(self.viewport)     


            winX = event.mouse_region_x
            #winY = event.mouse_region_y
            winY = self.viewport[3] - event.mouse_region_y
            winZ = bgl.Buffer(bgl.GL_FLOAT, [0.0])


            posX = bgl.Buffer(bgl.GL_DOUBLE, 1, [0.0])
            posY = bgl.Buffer(bgl.GL_DOUBLE, 1, [0.0])
            posZ = bgl.Buffer(bgl.GL_DOUBLE, 1, [1.0])


            bgl.glReadPixels(winX, winY, 1, 1, bgl.GL_DEPTH_COMPONENT, bgl.GL_FLOAT, winZ)
            bgl.gluUnProject(winX, winY, winZ[0], self.modelview, self.projection, self.viewport, posX, posY, posZ)
            #print(posX[0], posY[0], posZ[0])
            context.area.header_text_set("hit: %.4f %.4f %.4f" % tuple([posX[0], posY[0], posZ[0]]))
            #return {'FINISHED'}


        elif event.type in {'LEFTMOUSE', 'RIGHTMOUSE', 'ESC'}:
            context.area.header_text_set()
            return {'CANCELLED'}


        return {'RUNNING_MODAL'} 


    def invoke(self, context, event):
        if context.space_data.type == 'VIEW_3D':
            context.window_manager.modal_handler_add(self)
            return {'RUNNING_MODAL'}
        else:
            self.report({'WARNING'}, "Active space must be a View3d")
            return {'CANCELLED'}


def register():
    bpy.utils.register_class(TestesOperator)


def unregister():
    bpy.utils.unregister_class(TestesOperator)


if __name__ == "__main__":
    register()

The coordinates were not as I expected.
I followed this site hoping to get the coordinates of the 3D view:
http://nehe.gamedev.net/article/using_gluunproject/16013/

But instead, I get the screen coordinates and a Z coordinate with value -100 or 100. :frowning:
This is a limitation in Blender?

It seems it has something to do with such a “bgl.glEnable(bgl.GL_DEPTH)”

Maybe because there’s 2D drawing done by Blender, and the 3D information like depth does no exist anymore the moment you access it with your operator?

The moment I get the depth, this will be a big step. But I still cannot.

EDIT:
The malfunctioning reason is because the script takes the information (MODELVIEW_MATRIX, PROJECTION_MATRIX and VIEWPORT) of the whole Blender screen. And not only the 3D View of Blender. To fix this problem I have to specify the region for the 3D View.

In previous versions of Blender, the “# SPACEHANDLER.VIEW3D.EVENT” enabled the precise calculation of module bgl in View3D.
Eg: http://blenderclan.tuxfamily.org/html/modules/newbb/print.php?form=1&topic_id=8170&forum=2&order=ASC&start=0
In current versions of Blender, how can I enable the “# SPACEHANDLER.VIEW3D.EVENT”?

no idea, there’s an event object but haven’t heard of it in combination with view3d…

I did it!..finallly.


The secret is: bpy.types.SpaceView3D.draw_handler_add(draw_callback_px, args, ‘WINDOW’, ‘POST_VIEW’). no ‘POST_PIXEL’
The problem is that the point intersects the object and gets ugly


import bpy
import bgl

"""Functions for the mouse_coords_to_3D_view"""
def get_viewport():
    view = bgl.Buffer(bgl.GL_INT, 4)
    bgl.glGetIntegerv(bgl.GL_VIEWPORT, view)
    return view


def get_modelview_matrix():
    model_matrix = bgl.Buffer(bgl.GL_DOUBLE, [4, 4])
    bgl.glGetDoublev(bgl.GL_MODELVIEW_MATRIX, model_matrix)
    return model_matrix


def get_projection_matrix():
    proj_matrix = bgl.Buffer(bgl.GL_DOUBLE, [4, 4])
    bgl.glGetDoublev(bgl.GL_PROJECTION_MATRIX, proj_matrix)
    return proj_matrix


"""Function mouse_coords_to_3D_view"""
def mouse_coords_to_3D_view(x, y):    
    depth = bgl.Buffer(bgl.GL_FLOAT, [0.0])
    bgl.glReadPixels(x, y, 1, 1, bgl.GL_DEPTH_COMPONENT, bgl.GL_FLOAT, depth)
    #if (depth[0] != 1.0):
    world_x = bgl.Buffer(bgl.GL_DOUBLE, 1, [0.0])
    world_y = bgl.Buffer(bgl.GL_DOUBLE, 1, [0.0])
    world_z = bgl.Buffer(bgl.GL_DOUBLE, 1, [0.0])
    view1 = get_viewport()
    model = get_modelview_matrix()
    proj = get_projection_matrix ()   
    bgl.gluUnProject(x, y, depth[0], 
                     model, proj,
                     view1,
                     world_x, world_y, world_z)
    return world_x[0], world_y[0], world_z[0]


"""drawing point OpenGL in mouse_coords_to_3D_view"""
def draw_callback_px(self, context):
    # mouse coordinates relative to 3d view
    x, y = self.mouse_path
    
    # mouse coordinates relative to Blender interface
    view = get_viewport()
    gmx = view[0] + x
    gmy = view[1] + y
    
    # draw 3d mouse OpenGL point in the 3D View
    mouse3d = mouse_coords_to_3D_view(gmx, gmy)        
    bgl.glEnable(bgl.GL_BLEND)
    bgl.glColor4f(1.0, 0.8, 0.0, 1.0)
    bgl.glPointSize(30)    
    bgl.glBegin(bgl.GL_POINTS)
    bgl.glVertex3f(*(mouse3d))
    bgl.glEnd()
    
    context.area.header_text_set("hit: %.2f %.2f %.2f" % mouse3d)
    
class ModalDrawOperator(bpy.types.Operator):
    """Draw a point with the mouse"""
    bl_idname = "view3d.modal_operator"
    bl_label = "Simple Modal View3D Operator"   
    
    def modal(self, context, event):
        context.area.tag_redraw()


        if event.type == 'MOUSEMOVE':
            self.mouse_path = (event.mouse_region_x, event.mouse_region_y)            
                        
        elif event.type == 'LEFTMOUSE':
            bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
            context.area.header_text_set()
            return {'FINISHED'}


        elif event.type in {'RIGHTMOUSE', 'ESC'}:
            bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
            context.area.header_text_set()
            return {'CANCELLED'}


        return {'PASS_THROUGH'}


    def invoke(self, context, event):
        # the arguments we pass the the callback
        args = (self, context)
        # Add the region OpenGL drawing callback
        # draw in view space with 'POST_VIEW' and 'PRE_VIEW'
        self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px, args, 'WINDOW', 'POST_VIEW')
        self.mouse_path = []
        #self.wx = bpy.context.window.width
        #self.wy = bpy.context.window.height
        context.window_manager.modal_handler_add(self)
        return {'RUNNING_MODAL'}


def register():
    bpy.utils.register_class(ModalDrawOperator)


def unregister():
    bpy.utils.unregister_class(ModalDrawOperator)


if __name__ == "__main__":
    register()

Now I need to make a snap function

You could move your yellow rect closer to the virtual viewport camera maybe, or use POST_PIXEL but convert the coordinates over to screen space, see bpy_extras.view3d_utils.location_3d_to_region_2d()

Yeh, I will do. For now I’m solving a problem with the snap. Is working fine, the problem is that it snap to the points that are behind the mesh.
Is there any way to get a list of only the visible vertex? Like, get.postVertexID or something?


import bgl, bpy, bmesh, mathutilsfrom mathutils import Vector


"""Functions for the mouse_coords_to_3D_view"""
def get_viewport():
    view = bgl.Buffer(bgl.GL_INT, 4)
    bgl.glGetIntegerv(bgl.GL_VIEWPORT, view)
    return view


def get_modelview_matrix():
    model_matrix = bgl.Buffer(bgl.GL_DOUBLE, [4, 4])
    bgl.glGetDoublev(bgl.GL_MODELVIEW_MATRIX, model_matrix)
    return model_matrix


def get_projection_matrix():
    proj_matrix = bgl.Buffer(bgl.GL_DOUBLE, [4, 4])
    bgl.glGetDoublev(bgl.GL_PROJECTION_MATRIX, proj_matrix)
    return proj_matrix


def get_depth(x, y):
    depth = bgl.Buffer(bgl.GL_FLOAT, [0.0])
    bgl.glReadPixels(x, y, 1, 1, bgl.GL_DEPTH_COMPONENT, bgl.GL_FLOAT, depth)
    return depth


"""Function mouse_coords_to_3D_view"""


def mouse_coords_to_3D_view(x, y):    
    depth = get_depth(x, y)
    #if (depth[0] != 1.0):
    world_x = bgl.Buffer(bgl.GL_DOUBLE, 1, [0.0])
    world_y = bgl.Buffer(bgl.GL_DOUBLE, 1, [0.0])
    world_z = bgl.Buffer(bgl.GL_DOUBLE, 1, [0.0])
    view1 = get_viewport()
    model = get_modelview_matrix()
    proj = get_projection_matrix ()   
    bgl.gluUnProject(x, y, depth[0], 
                     model, proj,
                     view1,
                     world_x, world_y, world_z)
    return (world_x[0], world_y[0], world_z[0])


def coords_3D_to_2D(x, y, z):
    world_x = bgl.Buffer(bgl.GL_DOUBLE, 1, [0.0])
    world_y = bgl.Buffer(bgl.GL_DOUBLE, 1, [0.0])
    world_z = bgl.Buffer(bgl.GL_DOUBLE, 1, [0.0])
    view3 = get_viewport()
    model1 = get_modelview_matrix()
    proj1 = get_projection_matrix () 
    bgl.gluProject(x, y, z, model1, proj1, view3, world_x, world_y, world_z)
    return (world_x[0], world_y[0], world_z[0])


def close_point_range(location, range):
    obj = bpy.context.active_object
    bm = bmesh.from_edit_mesh(obj.data)
    vertices = bm.verts
        
    # Global coordinates ( Transform Matrix * local_coordinates )
    verts = [obj.matrix_world * vert.co for vert in vertices]
    verts2d = [coords_3D_to_2D(*coord) for coord in verts]
    #print(verts2d[1])
    #for vert in vertices:
        #verts = obj.matrix_world * vert.co
        #verts2d = coords_3D_to_2D(*verts)
    ## coordinates as tuples
    ## plain_verts = [vert.to_tuple() for vert in verts]


    # create a kd-tree from a mesh
    # 3d cursor relative to the object data    
    co_find = location * obj.matrix_world.inverted()
    #print(co_find)
    size = len(verts2d)
    kd = mathutils.kdtree.KDTree(size)
    for i, v in enumerate(verts2d):
        kd.insert(v, i)


    kd.balance()
    # Find points within a radius of the location
    for (co, index, dist) in kd.find_range(co_find, range):
        #print(co)
        return co
            
    return location   


"""drawing point OpenGL in mouse_coords_to_3D_view"""
def draw_callback_px(self, context):
    # mouse coordinates relative to 3d view
    x, y = self.mouse_path
    
    # mouse coordinates relative to Blender interface    
    view2 = get_viewport()
    gmx = view2[0] + x
    gmy = view2[1] + y
    
    # draw 3d mouse OpenGL point in the 3D View
    depth1 = get_depth(gmx, gmy)
    snap3d = close_point_range(Vector((gmx, gmy, 1.0)), 20)
    mouse3d = mouse_coords_to_3D_view(int(snap3d[0]), int(snap3d[1]))
    
    #print(snap3d)
    bgl.glEnable(bgl.GL_BLEND)
    bgl.glColor4f(1.0, 0.8, 0.0, 1.0)
    bgl.glPointSize(30)    
    bgl.glBegin(bgl.GL_POINTS)
    bgl.glVertex3f(*(mouse3d))
    bgl.glEnd()
    
    # restore opengl defaults
    bgl.glPointSize(1)
    bgl.glDisable(bgl.GL_BLEND)
    bgl.glColor4f(0.0, 0.0, 0.0, 1.0)
    
    context.area.header_text_set("hit: %.2f %.2f %.2f" % (mouse3d))
    
class ModalDrawOperator(bpy.types.Operator):
    """Draw a point with the mouse"""
    bl_idname = "view3d.modal_operator"
    bl_label = "Simple Modal View3D Operator"   
    
    def modal(self, context, event):
        context.area.tag_redraw()


        if event.type == 'MOUSEMOVE':
            self.mouse_path = (event.mouse_region_x, event.mouse_region_y)            
                        
        elif event.type == 'LEFTMOUSE':
            bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
            context.area.header_text_set()
            return {'FINISHED'}


        elif event.type in {'RIGHTMOUSE', 'ESC'}:
            bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
            context.area.header_text_set()
            return {'CANCELLED'}


        return {'PASS_THROUGH'}


    def invoke(self, context, event):
        # the arguments we pass the the callback
        args = (self, context)
        # Add the region OpenGL drawing callback
        # draw in view space with 'POST_VIEW' and 'PRE_VIEW'
        self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px, args, 'WINDOW', 'POST_VIEW')
        self.mouse_path = []
        #self.wx = bpy.context.window.width
        #self.wy = bpy.context.window.height
        context.window_manager.modal_handler_add(self)
        return {'RUNNING_MODAL'}


def register():
    bpy.utils.register_class(ModalDrawOperator)


def unregister():
    bpy.utils.unregister_class(ModalDrawOperator)


if __name__ == "__main__":
    register()



I worry you would need to convert all vertex locations to screen space to determine which are visible. Might be more efficient to use KDTree to find nearby vertices:

http://www.blender.org/api/blender_python_api_2_73_release/mathutils.kdtree.html

@CoDEmanX, This is a temporary solution. I understand your concern. I am trying to obtain the 2D coordinates of the vertices visible directly by OpenGL. This would reduce the calculations quite

But it seems that the version of OpenGL in Blender has no vertex array and display list

[URL="/u/CoDEmanX
@CoDEmanX, I discovered that the ray_cast can work in edit mode :). You only need to use the “Scene.ray_cast(start, end).”

But is disadvantageous because not updated editions. Bgl is even better