1
$\begingroup$

I am coding an addon, where a specific feature draws a small 2D overlay into the viewport via the draw handlers. While this works fine, I want this overlay to react when the user hovers the mouse over it.

I know, that getting the mouse position and reacting to it is possible by using modal operators. However, continuously running modal operators block the autosave function and users will not be able to have autosave running when they use the addon.

So, is there an alternative way, which allows to:

  1. Track the mouse position while it moves?
  2. Call a context.region.tag_redraw() for a SpaceView3D to update the viewport and redraw the 2D overlay?

Edit: Unfortunately, a custom tool suggested below is no option, because I need the addon feature to run continuously without blocking other Blender features (like other tools, etc.).

$\endgroup$

2 Answers 2

2
$\begingroup$

I've done a similar feature by creating a tool in the toolbar. While the tool is active, it uses its own keymap which allows setting an operator on the mouse move event.

class draw_tool(WorkSpaceTool):
    ...
    bl_keymap = (
         ....
        ("mesh.preview_operator",
         {"type": 'MOUSEMOVE', "value": 'ANY'},
         {"properties": [("add_new", True)]}),
        ("mesh.preview_operator",
         {"type": 'MOUSEMOVE', "ctrl": True, "value": 'ANY'},
         {"properties": [("add_new", False), ("tool_color", (1, 0, 0))]}),
         ....
    )

In operator, as you understand, it is easy to make changes in overlay.

$\endgroup$
8
  • $\begingroup$ Thank you, that sounds interesting! I never worked with keymaps, but I will try it. Two questions though: 1. Will this create a button in the toolbar or will it still be possible to invoke the tool from a custom button in the addon panel? 2. Will it be possible to have this tool activated all the time while the user uses another tool? $\endgroup$
    – reg.cs
    Jun 23, 2022 at 11:41
  • $\begingroup$ This is a tool in the toolbar. You cannot run two different tools at the same time. In my case it works well, I select the tool, and it shows where the block will be pasted $\endgroup$
    – Crantisz
    Jun 23, 2022 at 11:42
  • $\begingroup$ Yes, I was afraid of that. Then, unfortunately, this is not an option for my use case. I want this to be continuously running without blocking any other functionality. $\endgroup$
    – reg.cs
    Jun 23, 2022 at 11:43
  • $\begingroup$ You may try to set up a keymap for the entire 3D view then $\endgroup$
    – Crantisz
    Jun 23, 2022 at 11:45
  • $\begingroup$ I will check this. Do you know any good resources to get started with keymaps (except the Blender Python API documentation)? Or, even better: could you provide a simple code example by editing your answer? $\endgroup$
    – reg.cs
    Jun 23, 2022 at 11:48
0
$\begingroup$

Based on the comments by @Crantisz, I was able to solve the issue. A keymap based on the MOUSEMOVE event enables mouse hover events for test regions without using modal operators (and, thus, without blocking Blender's autosave function).

Here is a working example (tested in Blender 3.0):

import bpy

# global variables for the keymap handling
keyconfigs_addon = None
keymap_view3d = None
keymap_view3d_items = None

class EVENTKEYMAP_OT_mouse_move(bpy.types.Operator):
    """ This operator checks for mouseover events """
    bl_idname = "wm.mouse_over_event"
    bl_label = "Mouseover Event"

    # DEFINE REGION OF INTEREST
    # define a region in which something should happen if the mouse enters it
    test_region_x = 0
    test_region_y = 0
    test_region_width = 512
    test_region_height = 512

    # a function that tests if the mouse is over the test region
    def is_mouse_over(self, mouse_x, mouse_y):

        if (mouse_x > self.test_region_x and mouse_x < self.test_region_x + self.test_region_width) and (mouse_y > self.test_region_y and mouse_y < self.test_region_y + self.test_region_height):
            return True

    def invoke(self, context, event):

        # make sure the operator is called under valid conditions
        if (context is None) or (event is None) or (not context is None and context.region is None):
            return {'PASS_THROUGH'}

        # mouse position in current region
        mouse_x = event.mouse_x - context.region.x
        mouse_y = event.mouse_y - context.region.y

        # if mouse is over the test region
        if self.is_mouse_over(mouse_x, mouse_y):

            # DO SOMETHING HERE TO REACT TO THE EVENT
            print("MOUSE CURSOR IS IN THE TEST REGION!")

            # redraw the area (only required, if you draw something in the test region)
            # context.area.tag_redraw()

            # important to pass the event through, so Blenders internal event
            # processing is not blocked
            return {'PASS_THROUGH'}

        else:

            # DO SOMETHING HERE TO REACT TO THE EVENT
            print("MOUSE CURSOR IS NOT THE TEST REGION!")

            # redraw the area (only required, if you draw something in the test region)
            # context.area.tag_redraw()

            # important to pass the event through, so Blenders internal event
            # processing is not blocked
            return {'PASS_THROUGH'}

def register():

    # register the keymap operator
    bpy.utils.register_class(EVENTKEYMAP_OT_mouse_move)

    # add a global keymap for the VIEW_3D regions
    keyconfigs_addon = bpy.context.window_manager.keyconfigs.addon
    if keyconfigs_addon:
        keymap_view3d = keyconfigs_addon.keymaps.new(name="3D View", space_type='VIEW_3D')
        keymap_view3d_items = keymap_view3d.keymap_items.new("wm.mouse_over_event", 'MOUSEMOVE', 'ANY')

def unregister():

    # remove the keymap
    if keymap: keymap_view3d.keymap_items.remove(keymap_view3d_items)

    # unregister the keymap operator
    bpy.utils.unregister_class(EVENTKEYMAP_OT_mouse_move)

# start the script
if __name__ == '__main__':
    register()
$\endgroup$

You must log in to answer this question.

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