Gizmo Invoke on Hover?

Still not a lot in the way of documentation of the Gizmo API and the templates are minimal.

I am trying to make a gizmo invoke when hovering the mouse over it. Can it be done?

Alternatively, another solution would be a generic operator invoked when the mouse enters a specific part of a region.

How?

1 Like

Same here, whant to know more about gizmos.

So I have a “working” solution, but I want to play around with it a bit before posting a demo. Basically what I do is give myself a GizmoGroup with the PERSISTENT option, a Gizmo in that group, and an Operator.

Store whatever state it is you want in either the gizmo or its group depending on what you are doing. These seem to stay alive along with whatever space/editor you are working in.

The methods in the gizmo look something like this (code is incomplete obviously):

class GIZMO_GT_test(Gizmo):	
	gm = {}
	
	def draw(self, context):
		pass

	def test_select(self, context, location):
		if 0: # Put your mouse hover condition here
			c = {
				"window": context.window,
				"screen": context.screen,
				"area": context.area,
				"region": context.region,
				"blend_data": context.blend_data
			}
			def op():
				bpy.ops.gizmo.test(
					c, "INVOKE_DEFAULT",
					key=self.as_pointer()
				)
				return None
			# Run "timer" "immediately"
			bpy.app.timers.register(op)
			# Store reference back to self
			self.gm[self.as_pointer()] = self
		return -1

	def setup(self):
		pass

	# Not used
	def invoke(self, context, event):
		return {"PASS_THROUGH"}

	# This must be defined or left click input will be blocked
	# and Blender will crash after a script reload.
	def modal(self, context, event, tweak):
		return {"BITE_ME"}

Then the operator is something like this:

class GIZMO_OT_test(Operator):
	bl_idname = "gizmo.test"
	# ...

	key: IntProperty()

	def invoke(self, context, event):
		self.gz = GIZMO_GT_test.gm.get(self.key, None)
		if self.gz == None:
			return {"CANCELLED"}
		context.window_manager.modal_handler_add(self)
		return {"RUNNING_MODAL"}

	def modal(self, context, event):
		# Reference self.gz here...
		# Handle events, exit on mouse leave, etc.
		# Remove ref from GIZMO_GT_test.gm when done.
		return {"RUNNING_MODAL"}

The entire key here is that test_select is called on mouse events in the region for which the gizmo is a part of. This appears to be the only way to get mouse events outside of an already running modal operator. I tried this in old Blender 2.79 and earlier and that was extremely difficult.

The problem is that test_select is considered a “draw function” so you can’t call operators inside of it or Blender will complain. To get around that we have to use the new timer api to differ a function call to something outside of this “draw function”. In this case a temp function called op() that will in turn call our operator. Registering a timer function with no value seems to call it “as soon as possible” which is fast enough in this case, and returning None in that function unregisters it.

The next problem here is that the operator call does not like the context inside of the timer function. To avoid this, we declare the context override dict outside of the timer function so the correct references are stored.

Finally a reference to the gizmo is stored in a class map with its pointer as the key. The operator takes an int value for this same key. When the operator is invoked, the key it was passed is used to get the reference to the gizmo out of the class map. When done the operator can remove that entry in the map.


I am not familiar with a lot of the API yet, but much of the documentation is still incomplete. Simply put, I wanted to run an operator when the mouse enters specific coords of a region. The gizmo API seemed like a good start because of test_select. Unfortunately, it seems like gizmos are only invoked on left clicking by default, and not sure how to change that.

test_select is listed in the python API as returning int [0, inf] but the references in the source code suggest only checks to see if its equal to -1 or not. If only there was an option to return some other specific value to invoke it.

That would be a much better solution than this fuster cluck I came up with. Because of this, I am not going to mark this post as a “solution”. Hope someone can come up with a better solution. It would be greatly appreciated.


*sigh* The draw function on the gizmos appears to be behind nodes and links if used in the node editor, so the global draw callback must be used should you want your gizmo on top of the nodes and links. That is another rant for another time…

As an improvement to the above, I decided to not pass a key to the operator, and instead use the space pointer as the key and store a weakref to the gizmo in the GizmoGroup setup method. This way the operator can get the gizmo, but so can the global draw callback, which I also need because there does not appear to be a way to set the gizmo’s draw order (POST_PIXEL in my case).

In the end I may have to find a solution to this problem with ctypes again, due to the Python API always falling short. Maybe look at the window manager and event structs and see if there is some way I can add my own event handlers.

For now, this pile of hack will have to do.

1 Like

Hi man,
Searching for informations about gizmo , found your post,
have you any tutorial / material to share?

@SynaGl0w
hi… some news about this? I’m struggling with gizmos and your experiments can illuminate the things.