How can I get a plane object under the mouse?

I am trying to make a object follow the mouse using a modal operator.
can the object track to view and have its depth closer to the viewport? I dont want it intersecting with other objects in scene when moving.

Let me know if it is possible! thank you

Here is what I have, the object follows the mouse, but intersects with objects in scene and doesnt rotate along with panning.

import bpy


from bpy_extras.view3d_utils import region_2d_to_location_3d




class ModalDrawOperator(bpy.types.Operator):
    """Make object follow"""
    bl_idname = "view3d.modal_operator"
    bl_label = "Simple Modal View3D Operator"

    def modal(self, context, event):
        context.area.tag_redraw()


        if event.type == 'MOUSEMOVE':
            x = event.mouse_region_x
            
            y= event.mouse_region_y
        #   self.mouse_path.append((x, y))
            obj = context.object
            if obj:
                loc = region_2d_to_location_3d(context.region, context.space_data.region_3d, (x,y,0), (0,0,0))
                obj.location = loc
                
               
        elif event.type == 'LEFTMOUSE':
           
            return {'FINISHED'}

        elif event.type in {'RIGHTMOUSE', 'ESC'}:
         
            return {'CANCELLED'}

        return {'PASS_THROUGH'}

    def invoke(self, context, event):
        if context.area.type == 'VIEW_3D':
       
       
           
           
            self.mouse_path = []

            context.window_manager.modal_handler_add(self)
            return {'RUNNING_MODAL'}
        else:
            self.report({'WARNING'}, "View3D not found, cannot run operator")
            return {'CANCELLED'}


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


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

if __name__ == "__main__":
    register()


Basically have an object under the mouse while tracking to view, no matter the panning,zooming in, etc.

Great idea !
did not found yet how to intersect without the game engine and rigid bodies, but…

I think you open a pandora box (in the good way) :

all of this below is of course if you agree :

I had in mind a “metaball painter” since a long time, but found only dead ends,until now.
With little modifications on your script, here is a first result with code and video below :

import bpy


from bpy_extras.view3d_utils import region_2d_to_location_3d




class metaball_painter(bpy.types.Operator):
    """Metaball painter"""
    bl_idname = "view3d.modal_operator"
    bl_label = "Metaball painter"

    def modal(self, context, event):
        context.area.tag_redraw()
        
        bpy.ops.object.metaball_add(type='BALL', radius=1, view_align=False, enter_editmode=False, location=(-0.588416, -0.261114, 1.00059), layers=(True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False))
        
        if event.type == 'MOUSEMOVE':
            x = event.mouse_region_x
            
            y= event.mouse_region_y
        #   self.mouse_path.append((x, y))
            obj = context.object
            if obj:
                loc = region_2d_to_location_3d(context.region, context.space_data.region_3d, (x,y,0), (0,0,0))
                obj.location = loc
                
               
        elif event.type == 'LEFTMOUSE':
           
            return {'FINISHED'}

        elif event.type in {'RIGHTMOUSE', 'ESC'}:
         
            return {'CANCELLED'}

        return {'PASS_THROUGH'}

    def invoke(self, context, event):
        if context.area.type == 'VIEW_3D':
       
       
           
           
            self.mouse_path = []

            context.window_manager.modal_handler_add(self)
            return {'RUNNING_MODAL'}
        else:
            self.report({'WARNING'}, "View3D not found, cannot run operator")
            return {'CANCELLED'}


def menu_item(self, context):
       self.layout.operator(metaball_painter.bl_idname, text="Metaball painter", icon="PLUGIN")

def register():
    bpy.utils.register_module(__name__)
    bpy.types.INFO_MT_mesh_add.append(menu_item)
    


def unregister():
    bpy.types.INFO_MT_mesh_add.remove(menu_item)
    

if __name__ == "__main__":
    register()



of course, there are still bugs,
but I think we can go further if you are okay.

for now, it paints automatically .
but in the future,
the best would be to start paint only with left button hold.
it could stop paint when left button off .

what do you think about it ?

That looks very useful, I can see this being used for drawing shapes.

What I was looking for, is something more like this:
I am trying to get the plane object to only move along the region 2d and not move around the scene. It seems to work, all it needs is to stay in position(scale) when zooming in and out.

import bpy
from bpy_extras.view3d_utils import region_2d_to_vector_3d, region_2d_to_location_3d

def view3d_find( return_area = False ):
  
    for area in bpy.context.window.screen.areas:
        if area.type == 'VIEW_3D':
            v3d = area.spaces[0]
            rv3d = v3d.region_3d
            for region in area.regions:
                if region.type == 'WINDOW':
                    if return_area: return region, rv3d, v3d, area
                    return region, rv3d, v3d
    return None, None

r3d, rv3d, v3d = view3d_find()



class ModalTimerOperator(bpy.types.Operator):
    '''Operator which runs its self from a timer.'''
    bl_idname = "wm.modal_timer_operator"
    bl_label = "Modal Timer Operator"

    _timer = None

    def modal(self, context, event):
        if event.type == 'ESC':
            return self.cancel(context)

        if event.type == 'TIMER':
            coord = event.mouse_region_x, event.mouse_region_y
            region = context.region
            rv3d = context.space_data.region_3d
            vec = region_2d_to_vector_3d(region, rv3d, coord)
            loc = region_2d_to_location_3d(region, rv3d, coord, vec)

            context.object.location = loc
            

            bpy.context.object.rotation_euler = rv3d.view_rotation.to_euler()
        return {'PASS_THROUGH'}

    def execute(self, context):
        context.window_manager.modal_handler_add(self)
        self._timer = context.window_manager.event_timer_add(0.02, context.window)
        return {'RUNNING_MODAL'}

    def cancel(self, context):
        context.window_manager.event_timer_remove(self._timer)
        return {'CANCELLED'}


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


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


if __name__ == "__main__":
    register()



hi!

just my 2 cents :

about the scale/stay in position when zooming in/out,
maybe you can try to trick by intercepting events, like WHEELUPMOUSE and WHEELDOWNMOUSE .

Something like (very rough) :
if WHEELUPMOUSE (or WHEELDOWNMOUSE )
then object.z (or whatever coordinate,property) + or - .

Besides, you probably already noticed in your script that the object’s orientation can be altered.

by example : add a monkey, or whatever.
run your script on this object
move.
the face looks down 90 degrees flip on the X axis facing the current 3d view.

unless you dit it on purpose,
I think you should init some rotation values before using the
bpy.context.object.rotation_euler = rv3d.view_rotation.to_euler()

still, your work is very promising,
and you got a golden nugget with so many possibilities.