Trigger Action through mouseclick?

I would like to automatically run code whenever the user left clicks the mouse.
How could I do that?

Can you provide your code? It’s best to see if there aren’t operators that can interfere with the modal operation, usually you can call the operation before running modal.

I just need to retrieve the cursor position (bpy.context.scene.cursor_location), store it into a global list, and update some geometry’s position from it.

You’ll have to define an operator and set it in the keymap, or use a modal operator.

I think I have to use a modal operator, to catch mouseclicks.
I would like to update the position of object1, whenever the user presses the left mouse button and thus moves the cursor on the surface of object2.

Something like the following should be executed by the modal operator:

object1.location = bpy.context.scene.cursor_location

This would move object1 on the surface of object2, to the location of the cursor.

I played a bit with the Modal example in the Text editor, but could not make it work (the object is just updated once, then never again). The example moves an object by the mouse, so it should be pretty close to what I need:

class ModalOperator(bpy.types.Operator):
“”“Move an object with the mouse, example”""

How could I do that, or where is really good description of “Modal” or examples?

Many thanks for help

This is my 1st try:

A sphere is selected and I run the script.
Then when I leftclick somewhere, the sphere is moved to the previous cursor position (why?).
And after that it does not update the position any more through left mouse clicks - why?

I just wrote this into def modal:

if event.type == ‘LEFTMOUSE’:
context.object.location = bpy.context.scene.cursor_location

import bpy
from bpy.props import IntProperty, FloatProperty


class ModalOperator(bpy.types.Operator):
    """Move an object with the mouse, example"""
    bl_idname = "object.modal_operator"
    bl_label = "Simple Modal Operator"

    first_mouse_x = IntProperty()
    first_value = FloatProperty()

    def modal(self, context, event):
        if event.type == 'LEFTMOUSE':
            context.object.location = bpy.context.scene.cursor_location

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

        return {'RUNNING_MODAL'}

    def invoke(self, context, event):
        if context.object:
            self.first_mouse_x = event.mouse_x
            self.first_value = context.object.location.x

            context.window_manager.modal_handler_add(self)
            return {'RUNNING_MODAL'}
        else:
            self.report({'WARNING'}, "No active object, could not finish")
            return {'CANCELLED'}


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


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


if __name__ == "__main__":
    register()

    # test call
    bpy.ops.object.modal_operator('INVOKE_DEFAULT')

I think you are successfully catching all click events. After that you reset the objects location. But returning to ‘RUNNING_MODAL’ afterwards seems to cancel the click - which stops blender from executing its own left-click-tasks: in this case repositioning the 3d cursor. If you return ‘PASS_THROUGH’ (and by this pass the event on) instead of ‘RUNNING_MODAL’ this seems to be working just fine.


import bpy
from bpy.props import IntProperty, FloatProperty


class ModalOperator(bpy.types.Operator):
    """Move an object with the mouse, example"""
    bl_idname = "object.modal_operator"
    bl_label = "Simple Modal Operator"

    first_mouse_x = IntProperty()
    first_value = FloatProperty()

    def modal(self, context, event):
        if event.type == 'LEFTMOUSE':
            context.object.location = bpy.context.scene.cursor_location

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

        return {'PASS_THROUGH'}
        #return {'RUNNING_MODAL'}
        

    def invoke(self, context, event):
        if context.object:
            self.first_mouse_x = event.mouse_x
            self.first_value = context.object.location.x

            context.window_manager.modal_handler_add(self)
            return {'RUNNING_MODAL'}
        else:
            self.report({'WARNING'}, "No active object, could not finish")
            return {'CANCELLED'}


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


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


if __name__ == "__main__":
    register()

    # test call
    bpy.ops.object.modal_operator('INVOKE_DEFAULT')

WOW Helge, that was the reason.

THANK YOU!
Works wonderfully!

Now I will try to make it position a different sphere after each click. I still don’t understand the Modal concept very well - is there a good documentation somewhere?
I will have to count the mouseclicks (in a global variable?), so e.g. after click 5 it will update the position of “Sphere5”, and after click 6 it will update the position of “Sphere6”.

[QUOTE=Helge;3093523]I think you are successfully catching all click events. After that you reset the objects location. But returning to ‘RUNNING_MODAL’ afterwards seems to cancel the click - which stops blender from executing its own left-click-tasks: in this case repositioning the 3d cursor. If you return ‘PASS_THROUGH’ (and by this pass the event on) instead of ‘RUNNING_MODAL’ this seems to be working just fine.

I am making some progress now, I ve seen I can nicely access the objects from within modal, like
bpy.data.objects[spherename].select = True

I added self.clickcounter += 1 to the modal operator.

And then the positioning of the indexed spheres in:

if ((event.type == ‘LEFTMOUSE’) and (event.value == ‘RELEASE’)):

Thanks for your help again

I m still struggling a bit: What is the best way to move a modal operator from the Text Editor, where it executes perfectly, to the python console, to run it from there (and remove all whitespace/tabs for proper indenting).
When I try that then it does not execute properly any more: why??:

I make just the bare minimum changes so it executes with no error, it does not do anything, just
says: {‘PASS_THROUGH’}

Ok, solved: just a formatting error when moving code from the Text-editor to the Python console.

Nice to see things are working out well!
So far I’ve only looked into blender python scripting a very few times. So (unfortunately) I don’t know any good modal-resources I could recommend. (Yesterday, I tried to find a link to a description regarding that PASS_THROUGH issue - but eventually gave up on that plan. :wink:

Btw. some time ago I started working on something quite similar to your plan. (Placing the 3d cursor on a mesh and then moving another object to that place based on the nearest faces normal vector.) This looked quite promising but lately I didn’t have the time to get back to that. At least I learned a few things, so that is ok with me. :wink: