3
$\begingroup$

I am looking for a code, that adds an uv sphere on a mesh when a mouse click happens. I saw the topic: Adding new object on mouse click? but there are no answers there. I guess a modal should be implemented on the LEFTMOUSE clicks, but I can't seem to find any simple examples and explanations.

import bpy
import bgl
import bmesh
from bpy_extras import view3d_utils
import mathutils
from 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


"""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)
    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 float(world_x[0]), float(world_y[0]), float(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
    mx, my = self.mx, self.my #got from modal

    # mouse coordinates relative to Blender interface
    view = get_viewport()
    gmx = view[0] + x
    gmy = view[1] + y

    if False:
        #c= bgl.Buffer(bgl.GL_UNSIGNED_BYTE, [3,1])
        #bgl.glReadPixels(x, y, 1, 1, bgl.GL_RGB, bgl.GL_FLOAT, c)
        #c= bgl.Buffer(bgl.GL_SHORT, [3,1])
        c = bgl.Buffer(bgl.GL_FLOAT, [3,1])
        bgl.glReadPixels(gmx, gmy,1,1,bgl.GL_RGB,bgl.GL_FLOAT,c);
        draw_square_follow_cursor(c, gmx, gmy)
    else:
        draw_uv_sphere(mx, my, 0.5)


def draw_uv_sphere(mx, my, s):
    ob = bpy.data.objects['Cube']
    v = ob.data.vertices[0].co
    mat = ob.matrix_world
    x,y,z = mouse_coords_to_3D_view(mx, my)
    mv = Vector((x, y, z))
    bpy.ops.mesh.primitive_uv_sphere_add(segments=42, ring_count=42, location=mat * mv, size=s)


def draw_square_follow_cursor(c, gmx, gmy):

    mouse3d = mouse_coords_to_3D_view(gmx, gmy)
    bgl.glEnable(bgl.GL_BLEND)
    bgl.glColor3f(0.0,0.0,255.0)
    bgl.glPointSize(30)
    bgl.glBegin(bgl.GL_POINTS)
    bgl.glVertex3f(*(mouse3d))
    bgl.glVertex3f(mouse3d[0], mouse3d[1], mouse3d[2])
    bgl.glVertex2f(gmx,gmy)
    bgl.glEnd()

    # restore opengl defaults
    bgl.glLineWidth(1)
    bgl.glDisable(bgl.GL_BLEND)
    bgl.glColor4f(0.0, 0.0, 0.0, 1.0)

def draw_corner_square(c):
    for area in bpy.context.screen.areas:
        if area.type=='VIEW_3D':
            X= area.x
            Y= area.y
    dist= 100 #distancia del punto al cursor
    mouse3d = mouse_coords_to_3D_view(X+dist,Y+dist)
    bgl.glEnable(bgl.GL_BLEND)
    bgl.glColor3f(c[0][0],c[1][0],c[2][0])
    bgl.glPointSize(30)
    bgl.glBegin(bgl.GL_POINTS)
    bgl.glVertex3f(mouse3d[0], mouse3d[1], mouse3d[2])
    bgl.glEnd()



    # restore opengl defaults
    bgl.glLineWidth(1)
    bgl.glDisable(bgl.GL_BLEND)
    bgl.glColor4f(0.0, 0.0, 0.0, 1.0)


def draw_square(c):
    bgl.glEnable(bgl.GL_BLEND)
    bgl.glColor3f(c[0][0],c[1][0],c[2][0])
    bgl.glBegin(bgl.GL_POLYGON)
    bgl.glVertex2f(-0.5, -0.5)
    bgl.glVertex2f(-0.5, 0.5)
    bgl.glVertex2f(0.5, 0.5)
    bgl.glVertex2f(0.5, -0.5)
    bgl.glEnd( )

    # restore opengl defaults
    bgl.glLineWidth(1)
    bgl.glDisable(bgl.GL_BLEND)
    bgl.glColor4f(0.0, 0.0, 0.0, 1.0)

class ModalDrawOperator(bpy.types.Operator):
    """Draw a point with the mouse"""
    bl_idname = "view3d.modal_operator"
    bl_label = "Simple Modal View3D Operator"


    def execute(self, context):
        draw_uv_sphere(self.mx, self.my, 0.5)
        return {'PASS_THROUGH'}

    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':
            self.mx, self.my= event.mouse_x, event.mouse_y
            self.execute(context)
            return {'PASS_THROUGH'}

        elif event.type in {'RIGHTMOUSE', 'ESC'}:
            if False:
                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.width = bpy.context.window.width
        self.height = 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()

Q: How can I add a new object on mouse click?

$\endgroup$

1 Answer 1

7
$\begingroup$

Similarly to This Answer The following adds a sphere on left mouse press. Needs to be invoked from a 3d view.

import bpy
from bpy_extras.view3d_utils import region_2d_to_vector_3d, region_2d_to_location_3d

class ModalTimerOperator(bpy.types.Operator):
    """Operator which runs its self from a timer"""
    bl_idname = "wm.modal_timer_operator"
    bl_label = "Add Sphere on Click"

    _timer = None

    def modal(self, context, event):
        if event.type in {'RIGHTMOUSE', 'ESC'}:
            self.cancel(context)
            return {'CANCELLED'}

        if event.type == 'LEFTMOUSE' and event.value == 'PRESS':
            # left click

            if context.area.type == 'VIEW_3D':
                region = context.region
                r3d = context.space_data.region_3d
                x, y = event.mouse_region_x, event.mouse_region_y
                view = region_2d_to_vector_3d(region, r3d, (x, y))
                loc = region_2d_to_location_3d(region, r3d, (x, y), view)
                bpy.ops.mesh.primitive_uv_sphere_add(location=loc)
        
        if event.type == 'TIMER':
            #print("timer")
            pass


        return {'PASS_THROUGH'}

    def execute(self, context):
        if context.area.type != 'VIEW_3D':
            print("Must use in a 3d region")
            return {'CANCELLED'}
        
        wm = context.window_manager
        self._timer = wm.event_timer_add(0.1, window=context.window)
        wm.modal_handler_add(self)
        return {'RUNNING_MODAL'}

    def cancel(self, context):
        wm = context.window_manager
        wm.event_timer_remove(self._timer)


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


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


if __name__ == "__main__":
    register()

    # test call
    bpy.ops.wm.modal_timer_operator()

# note code edited from text editor > templates > python > modal timer operator.
$\endgroup$
1
  • $\begingroup$ is this still supposed to be working in Blender 3.1? I get a ```AttributeError: 'NoneType' object has no attribute 'type'````error $\endgroup$
    – simone
    May 1, 2022 at 14:25

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .