WorkSpaceTool - with widget/gizmo - that hover highlights geo on mousemove

What I try to do - is to draw gizmo circle - similar to how sculpt brushes circle preview works.
There is no good example how to attach gizmo to WorkspaceTool - where Gizmo would be drawn at ray_cast(from_camera) based on 2d mouse position.
Gizmo has modal(context, event) - method - where in theory I could run raycasting - but this method is only executed on gizmo highlight/move with LMB - so it cant be used like I would usually do - on mouse_move event.
Since there is no way to read events (eg mouse position) in Gizmo then how can we the raycasting from camera on selected mesh geo?

You can use the test_select callback which gets the mouselocation as an argument.
https://docs.blender.org/api/current/bpy.types.Gizmo.html#bpy.types.Gizmo.test_select

This is how the snap utilities line addon does it, so i recommend to check out it’s source to see how it works.

2 Likes

That looks promising, but after using test_select method, it is not called after invoking gizmo on viewport navigation - and other people are having same issue.
It but looks like only @mano-wii managed to make test_select() work in SnapUtils addon, but its complicated to reverse engineer. Any ideas what are test_select() method requirements, to make it run on mouse hover after enabling gizmo?

Indeed the test_select method runs only to check if the gizmo is hovered. Once the gizmo is activated the invoke and modal methods get executed. It should be possible to update a gizmo property from both the test_select and modal methods and access that prop from the draw function if thats what you’re trying to do.

However i’m not sure if that makes alot of sense. When you simply want to draw a circle at the cursor position why and how would you want to interact with that circle?

I just try to make brush circle outline in python (similar to how sculpt brush circle works). I already made circle around cursor using gpu module but since the my tool is made as WorkspaceTool, then I thought using gizmo would be better choice.
Anyway maybe I’m doing something wrong, but test_select is not executed even once. Here is part of the code responsible for gizmo (I assumed I would use ray_cast in test_select() to center gizmo under mouse):

class MyCustomShapeWidget(bpy.types.Gizmo):
    bl_idname = "VIEW3D_GT_Circle_Gizmo"
    __slots__ = ()

    def _update_offset_matrix(self):
        # self.matrix_offset.col[3][2] = self.target_get_value("offset") / -10.0
        print("Gizmo update offset matrix")

    def draw(self, context):
        print("Gizmo draw")
        # self._update_offset_matrix()
        mat = context.active_object.matrix_world
        self.draw_preset_circle(mat, axis='POS_Z', select_id=-1)

    def draw_select(self, context, select_id):
        print(f"Gizmo draw select {select_id}")
        # self._update_offset_matrix()
        mat = context.active_object.matrix_world
        self.draw_preset_circle(mat, axis='POS_Z', select_id=select_id)

    def setup(self):
        print("Gizmo setup")
        self.use_draw_hover = True

    def invoke(self, context, event):
        print("Gizmo invoke")
        return {'RUNNING_MODAL'}

    def exit(self, context, cancel):
        print("gizmo Exit")

    def test_select(self, context, location):
        print('Gizmo test_select !!!!!!!!!!!!!!!!!!!')
        print(location)
        # self.gz.matrix_basis =
        self.mouse_2d_co = location
        # self.prev_hit_loc = self.hit_loc

        # self.draw_cursor_co, self.hit_loc = raycast(context, self.mouse_2d_co, self.snap_surface_BVHT, self.draw_cursor_co)
        context.area.tag_redraw()
        # return -1

    def modal(self, context, event, tweak):
        print('Gizmo modal')
        return {'RUNNING_MODAL'}


class BrushCircleGizmoGroup(bpy.types.GizmoGroup):
    bl_idname = "MESH_GGT_brush_circle"
    bl_label = "Side of Plane Gizmo"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'WINDOW'
    bl_options = {'3D', 'SELECT'}


    @staticmethod
    def my_view_orientation(context):
        rv3d = context.space_data.region_3d
        view_inv = rv3d.view_matrix.to_3x3()
        return view_inv.normalized()

    @classmethod
    def poll(cls, context):
        if not context.mode != 'EDIT_MESH':
            print("unlink delayed")
            wm = context.window_manager
            wm.gizmo_group_type_unlink_delayed(BrushCircleGizmoGroup.bl_idname)
            return False
        return True


    def setup(self, context):
        print('GizmoGroup Setup')
        gz = self.gizmos.new("VIEW3D_GT_Circle_Gizmo")
        # gz.draw_options = {'ANGLE_START_Y'}

        # gz.use_draw_value = True
        gz.use_draw_hover = True
        gz.select = True
        # gz.is_highlight = True

        gz.color = 0.8, 0.8, 0.8
        gz.alpha = 0.5

        gz.color_highlight = 1.0, 1.0, 1.0
        gz.alpha_highlight = 1.0

        self.gizmo_dial = gz

    def draw_prepare(self, context):
        print('GizmoGroup draw_prepare')
        view_inv = self.my_view_orientation(context)

        self.view_inv = view_inv
        self.rotate_axis = view_inv[2].xyz
        self.rotate_up = view_inv[1].xyz

        self.gizmo_dial.matrix_basis = context.active_object.matrix_world

As far as i understand there are two ways of handling the selection of gizmos:

  • by using draw_select()
    This will draw the selection area of a gizmo into a selection texture which can later be sampled by blender to determine if the gizmo is hovered
  • by using test_select()
    Where you can do a check on your own based on the current mouse location and then directly return the selection state of the gizmo

In your code snippet you’ve defined both functions which doesn’t really make sense. I got it working by simply deleting the draw_select method. Does that work for you?

1 Like

Tanks hlorus! That was it - removing draw_select() activated test_select() - and now I see 2d mouse coordinates. The docs are poorly written, so I would not figured it out…

2 Likes