3
$\begingroup$

In top-right part of 3D Viewport, the Navigation Gizmo has multiple components:

img

  1. Axis gizmo
  2. View gizmos

I have a custom operator which meant to be executed only when mouse pointer is above VIEW_3D WINDOW region, but areas with these gizmo groups should be an exception. (The problem is that I don't see how to determine the gizmos' area).

So how can I determine (using Python) whether the mouse pointer is hovering over the viewport navigation gizmos when the operator is called via F3?


Note:

According to Harry McKenzie's answer there is no direct way to get this info, but I'm open for workarounds. I have two ideas which haven't bring me the solution so far, but which could be worth to investigate further:

  1. There could be some operator poll returning False in the navigation gizmo area, while returning True in other parts of 3D Viewport WINDOW region.
  2. Maybe it's possible to retrieve gizmos' size and based on the size to calculate their coordinates(definitely possible for the Axis gizmo whose size is stored in bpy.context.preferences.view.gizmo_size_navigate_v3d, but I'm unable to find the size of View gizmos block yet)
$\endgroup$
2
  • 1
    $\begingroup$ Hello. Please note that the correct way to format keyboard shortcuts on this site is using <kbd> tags. I noticed you reverted my <kbd>F3</kbd> back to backticks. For reference, see Guidelines on the usage of StackExchange markup and style. $\endgroup$ Commented Nov 12 at 7:39
  • $\begingroup$ @HarryMcKenzie <kbd> is a great thing when shortcut should have the most attention, but in this case it was stealing attention from more important parts of the post, hence I made it less pronounced. Guidelines are a good thing, but they should not replace common sense. $\endgroup$ Commented Nov 12 at 14:03

1 Answer 1

4
$\begingroup$

The Navigation Gizmo is not part of Blender's RNA-based UI. It is drawn dynamically directly in the 3D Viewport using C++ code and the GPU, outside the standard data-access system. Unlike panels, buttons, or property sliders, which are registered in Blender's RNA and have defined property paths, the Navigation Gizmo is created dynamically by the viewport drawing engine (view3d_gizmo_navigate.cc) and exists only at runtime. It does not belong to any RNA struct like bpy.types.* and is not tied to stable data that Python can reference with as_pointer() or context.property, meaning it is not exposed by any Python API. Because of this, there is no way to retrieve or inspect it using Python, even with low-level ctypes memory access tricks. The gizmo simply does not exist in that part of Blender's data system.

One potential workaround is to approximate the position of the Navigation Gizmo using region coordinates and user preferences. This way, when you hover near the top-right corner of the 3D Viewport, the script can detect whether the mouse is over the Axis Gizmo or the View Gizmo buttons. The following code demonstrates this approach. It also accounts for the N-Panel being open or changes to the resolution scale under Edit > Preferences > Interface > Resolution Scale.

Run the modal operator with F3, then select "Detect Gizmo Hover", and move your mouse over the gizmos to see the output in the System Console. You may need to fine-tune the hard-coded multiplier values in axis_margin_x, axis_margin_y, view_margin_x, and view_margin_y.

import bpy

class VIEW3D_OT_detect_gizmo_hover(bpy.types.Operator):
    """Detect if mouse is hovering near the Navigation Gizmo"""
    bl_idname = "view3d.detect_gizmo_hover"
    bl_label = "Detect Gizmo Hover"
    bl_options = {'REGISTER'}

    _hover = None  # None = not hovering, "axis" or "view" = hovering type

    def modal(self, context, event):
        if event.type == 'MOUSEMOVE':
            region = context.region

            if not region:
                print("Left 3D View, stopping hover detector")
                return {'CANCELLED'}
            elif region.type != 'WINDOW':
                return {'PASS_THROUGH'}

            x, y = event.mouse_region_x, event.mouse_region_y
            sidebar_width = 0
            for r in context.area.regions:
                if r.type == 'UI':
                    sidebar_width = r.width
                    break

            size = bpy.context.preferences.view.gizmo_size_navigate_v3d
            dpi_scale = context.preferences.system.dpi / 72  # Blender base DPI

            axis_margin_x = size * dpi_scale  # Axis gizmo (square-ish)
            axis_margin_y = size * 1.7 * dpi_scale

            over_axis = (region.width - axis_margin_x - sidebar_width <= x <= region.width - sidebar_width) and \
                        (region.height - axis_margin_y <= y <= region.height)

            view_margin_x = size * 0.5 * dpi_scale  # View gizmo (slimmer rectangle below axis)
            view_margin_y = size * 3.3 * dpi_scale

            over_view = (region.width - view_margin_x - sidebar_width <= x <= region.width - sidebar_width) and \
                        (region.height - view_margin_y <= y <= region.height - axis_margin_y)

            new_hover = None
            if over_axis:
                new_hover = "axis"
            elif over_view:
                new_hover = "view"

            if new_hover != self._hover:
                self._hover = new_hover
                if self._hover == "axis":
                    print("Mouse entered Axis Gizmo area")
                elif self._hover == "view":
                    print("Mouse entered View Gizmo area")
                else:
                    print("Mouse left gizmo area")

        elif event.type in {'RIGHTMOUSE', 'ESC'}:
            print("Exiting hover detector")
            return {'CANCELLED'}

        return {'PASS_THROUGH'}

    def invoke(self, context, event):
        if context.area.type != 'VIEW_3D':
            self.report({'WARNING'}, "3D View not active")
            return {'CANCELLED'}

        context.window_manager.modal_handler_add(self)
        print("Hover detector running (move mouse in 3D Viewport)...")
        return {'RUNNING_MODAL'}

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

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

if __name__ == "__main__":
    register()
$\endgroup$
2
  • $\begingroup$ This workaround is mostly working, BUT testing different values for gizmo_size_navigate_v3d shows that view_gizmo size is not affected by this variable $\endgroup$ Commented Nov 12 at 14:40
  • 2
    $\begingroup$ Honestly you're going to have to jump through hoops to make it work if you're going to distribute your addon. I think you'd rather want to define your own gizmos and display it instead in the 3D viewport there and have your custom interaction then and there. $\endgroup$ Commented Nov 13 at 13:37

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.