Bug related to the Gizmo API?

I’m working on addon which uses Gizmo API and have found a bug. I don’t know how these are related but…

Here is sample code (complete sample, can be added to the Blender as an addon and tested):

# Licensing information

bl_info = {
    "name": "Gizmo button 2d test",
    "version": (0, 1),
    "blender": (2, 80, 0),
    "description": "test gizmo button 2d functionality, report problem...",
    "location": "View3D",
    "warning": "",
    "category": "3D View"
}

import bpy

from bpy.props import (IntProperty, EnumProperty, BoolProperty)
from bpy.types import (AddonPreferences, GizmoGroup, Operator)

class GizmoButton2D(GizmoGroup):
    """ test gizmo button 2d """
    bl_idname = "view3d.gizmo_button_2d"
    bl_label = "Test button 2d"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'WINDOW'
    bl_options = {'PERSISTENT', 'SCALE'}
    
    @classmethod
    def poll(cls, context):
        if context.object != None:
            return True
        return False
    
    def draw_prepare(self, context):
            self.foo_gizmo.matrix_basis[0][3] = 100
            self.foo_gizmo.matrix_basis[1][3] = 100

    def setup(self, context):
        mpr = self.gizmos.new("GIZMO_GT_button_2d")
        mpr.target_set_operator("transform.rotate")
        mpr.icon = 'OUTLINER_OB_CAMERA'
        mpr.draw_options = {'BACKDROP', 'OUTLINE'}
        mpr.alpha = 0.0
        mpr.color_highlight = 0.8, 0.8, 0.8
        mpr.alpha_highlight = 0.2
        mpr.scale_basis = (80 * 0.35) / 2 # same as buttons defined in C
        self.foo_gizmo = mpr

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

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

When this addon is activated in preferences in the Operator properties window (as an example used Move op - I’ve moved islands in the UV editor) content is downscaled as in this screenshot:

When the addon is deactivated everything is OK:

As far as I’ve found in my experiments, any operation with Gizmo (even seemingly completely innocent self.foo_gizmo.hide = True in the GizmoGroup.draw_prepare() function will give this effect!)

I’m having problems with the gimzo api, too! It’s a different problem than yours, though. But I wonder if they’re related since we’re both having problems with gizmo stuff around the same time.

Everything was fine for me with build 2019-06-19 18:29 (Hash: d30f72dfd8ac).

Then I downloaded 2019-06-28 22:08 (Hash: 648e8a1f1d4a) and the operator I’ve been writing for weeks, completely broke.

I was invoking bpy.ops.gizmogroup.gizmo_tweak() from within my operator which is supposed to return ‘FINISHED’ if executed over a gizmo. But suddenly it started returning {‘PASS_THROUGH’, ‘CANCELLED’} with the latest build.

You can reproduce it:


In the keymap look up ‘gizmo’ and under Generic Gizmo Maybe Drag set the event type to Nothing.

import bpy
from bpy.props import IntProperty

def in_threshold(x_pos_1, x_pos_2, y_pos_1, y_pos_2, thresh):
     
    return ((x_pos_1 - thresh) <= x_pos_2 <= (x_pos_1 + thresh) and
           (y_pos_1 - thresh) <= y_pos_2 <= (y_pos_1 + thresh))
           
class GizmoOperator(bpy.types.Operator):
    """Gizmo Tweak Example"""
    bl_idname = "object.gizmo_operator"
    bl_label = "Gizmo Tweak Operator"

    first_mouse_x: IntProperty()
    first_mouse_y: IntProperty()
    tweak_thresh = bpy.context.preferences.inputs.drag_threshold_mouse
    exit = False
    count = 0

    def modal(self, context, event):

        if self.exit:
            return {'FINISHED'}
        
        elif event.type == 'LEFTMOUSE':
            return {'FINISHED'}

        elif event.type in {'RIGHTMOUSE', 'ESC'}:
            context.object.location.x = self.first_value
            return {'CANCELLED'}
        
        # Wait for mouse to move beyond 'tweak threshold"
        elif not in_threshold(self.first_mouse_x, event.mouse_x, 
                            self.first_mouse_y, event.mouse_y, 
                            self.tweak_thresh):
                           
            # Gizmo_Tweak will return 'PASS_THROUGH', 'CANCELLED' the first time
            # and 'FINISHED' the second time.
            if event.type == 'MOUSEMOVE':
                self.count += 1
                try_gizmo = bpy.ops.gizmogroup.gizmo_tweak('INVOKE_DEFAULT')
                #print(try_gizmo)
                print("TRY #:", self.count, 'FINISHED' in try_gizmo)
                if 'FINISHED' in try_gizmo:
                    self.exit = True
                
            return {'PASS_THROUGH'}

        return {'RUNNING_MODAL'}

    def invoke(self, context, event):
        self.tweak_thresh = bpy.context.preferences.inputs.drag_threshold_mouse
        
        if context.object:
            self.first_mouse_x = event.mouse_x
            self.first_mouse_y = event.mouse_y

            active = bpy.context.active_object
            select = bpy.context.selected_objects

            # Modify the selection in some way
            for o in select:
                o.select_set(True)
                view_layer = bpy.context.view_layer
                view_layer.objects.active = active

            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(GizmoOperator)


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


if __name__ == "__main__":
    register()

Run this sample code and assign it a keymap.

image

It’s easiest to just turn off the Active Move Tool and stick it in a new slot underneath.

Open the System Console. Now click and drag on the gizmo.

You should see this:

TRY #: 1 False
TRY #: 2 True

It fails the first try, and works on the second. Weirdly, if you comment out this section:

 for o in select:
      o.select_set(True)
      view_layer = bpy.context.view_layer
      view_layer.objects.active = active

It works on the first try again.

At first I thought modifying the selection must be causing the problem, but if you invoke bpy.ops.gizmogroup.gizmo_tweak() immediately after the selection code, it works on the first try. It’s only if you modify the selection then try to invoke gizmo_tweak() once you’re running modal that it starts failing again.

I have no idea what’s going on, but this breaks my operator beyond repair, which is weeks of lost work. I’m really bummed.

Do you think the two issues could be related at all? Everything worked just fine on the previous build.

@at0 Which build were you using when the bug started?

This was committed by @ideasman42 on the same day I downloaded the build that started giving me problems:
https://developer.blender.org/rB9bcab8050f44e5bd36a0715811ee0355e4b221b6

Actually, a whole bunch of stuff was done to gizmos on the 27th, too.

I have no idea if they’re related or not.

And this one, too, specifically to Generic Gizmo Maybe Drag by @mont29:

https://developer.blender.org/rBc8034993ff5ed6a4fa667bb16eb042114beb2f68

Edit:

I built Blender myself, ran it in debug, and tried my repro steps above and Blender threw an exception:

>	blender.exe!issue_debug_notification(const wchar_t * const message) Line 28	C++
 	blender.exe!__acrt_report_runtime_error(const wchar_t * message) Line 154	C++
 	blender.exe!abort() Line 61	C++
 	blender.exe!gizmo_tweak_invoke(bContext * C, wmOperator * op, const wmEvent * event) Line 593	C
 	blender.exe!wm_operator_invoke(bContext * C, wmOperatorType * ot, wmEvent * event, PointerRNA * properties, ReportList * reports, const bool poll_only, bool use_last_properties) Line 1438	C
 	blender.exe!wm_operator_call_internal(bContext * C, wmOperatorType * ot, PointerRNA * properties, ReportList * reports, const short context, const bool poll_only, wmEvent * event) Line 1685	C
 	blender.exe!WM_operator_call_py(bContext * C, wmOperatorType * ot, short context, PointerRNA * properties, ReportList * reports, const bool is_undo) Line 1785	C
 	blender.exe!pyop_call(_object * UNUSED_self, _object * args) Line 267	C
 	[External Code]	
 	blender.exe!bpy_class_call(bContext * C, PointerRNA * ptr, FunctionRNA * func, ParameterList * parms) Line 8295	C
 	blender.exe!rna_operator_modal_cb(bContext * C, wmOperator * op, const wmEvent * event) Line 1378	C
 	blender.exe!wm_handler_operator_call(bContext * C, ListBase * handlers, wmEventHandler * handler_base, wmEvent * event, PointerRNA * properties) Line 2186	C
 	blender.exe!wm_handlers_do_intern(bContext * C, wmEvent * event, ListBase * handlers) Line 2897	C
 	blender.exe!wm_handlers_do(bContext * C, wmEvent * event, ListBase * handlers) Line 2945	C
 	blender.exe!wm_event_do_handlers(bContext * C) Line 3307	C
 	blender.exe!WM_main(bContext * C) Line 420	C
 	blender.exe!main(int argc, const unsigned char * * UNUSED_argv_c) Line 502	C
 	[External Code]

I don’t even have to set Generic Gizmo Maybe Drag to ‘Nothing’. I can map the operator to right-click press and it still throws the error.

If I comment out the selection modification, no error is thrown.

@at0 Sorry man, I kind of hijacked your thread. I just tested your code on the build before I started having problems and the text is getting miniaturized whenever the add-on is enabled, and returns to normal size when you disable the add-on. So this problem looks like it existed before the commits I linked to earlier, which is before I started having problems with my code.

I did notice though, that once you disable and re-enable the add-on I couldn’t seem to get the text to miniaturize again.

Have you had any progress on this? Did you submit a bug report?

Also, if anyone could help test my issue I’d really appreciate it. I can’t find a work-around and I can’t move forward the way it is now.

Hi! Never mind. In fact the problem with miniaturized controls was fixed just before Blender RC1 has become available. Have you tested your code for issues in RC1 and RC2?

@at0 Awesome! I’m glad your bug is fixed!

I did try RC1 and my problem was still there, unfortunately. I submitted a bug but it hasn’t been looked at yet so I’ll give it a shot on RC2.

While I was waiting I did figure out that gizmo_tweak() was failing in modal because objects.active was modified through script during invoke. I still don’t understand why that causes tweak to fail and do think it’s a bug, but it’s probably not something that will cause problems for most people. So my guess is that fixing it will be a low priority for the devs.

With that in mind I completely re-wrote my operator working around that new limitation. It was a big pain in the butt but I finally got it working again.