Page MenuHome

gizmo_tweak() does not return 'FINISHED' when invoked on gizmo
Open, Needs Triage by DeveloperPublic

Description

System Information
Operating system: Windows 10
Graphics card:

Blender Version
Broken: 2.80, 648e8a1f1d4a, master, 2019-06-28
Worked: 2.80, d30f72dfd8ac, master, 2019-06-19

Short description of error
bpy.ops.gizmogroup.gizmo_tweak() does not reliably return 'FINISHED' when invoked during a modal operator if bpy.context.view_layer.objects.active is set or modified in any way. Prior to 648e8a1f1d4a, running the following script in a modal operator (which invoked gizmo_tweak() on a MOUSEMOVE event) would return 'FINISHED' and would proceed to activate the gizmo, manipulating the object.
Now the same script, used in the same exact way returns {‘PASS_THROUGH’, ‘CANCELLED’}.

Exact steps for others to reproduce the error

  1. Execute the following script:
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'}:
            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
                
                ### Setting the active object is causing the problem
                ### If you comment out the following active object assignment, gizmo_tweak() correctly returns 'FINISHED' again

                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()
  1. Assign operator to a keymap (3D View Tool: Move to Mouse>Right>Press):

The operator is object.gizmo_operator
For this purpose just put it under 3D View Tool: Move> Mouse> Right> Press. Then, disable the Move operator

  1. Open the system console:
  1. Make the Active Tool the Move Tool. Now right-click>drag on the gizmo of a selected object:

This is what should print in the console:

TRY #: 1 False
TRY #: 2 True

gizmo_tweak() is returning {‘PASS_THROUGH’, ‘CANCELLED’} on the first pass of modal operation, then returning 'FINISHED' the second time through.

I built Blender myself and tried it in debug. The first pass which returns {‘PASS_THROUGH’, ‘CANCELLED’} raised the following 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]

If you comment out this line in the opersator script:

view_layer.objects.active = active

gizmo_tweak() will work on the first pass again.

I've tried setting active to None, making another scene object active, making the current active object not active then active again. It seems any attempt to set objects.active in any way before running modal causes gizmo_tweak() not to work on the first pass.

Lastly, if you invoke gizmo_tweak() immediately after assigning an active object before running modal, gizmo_tweak() will return 'FINISHED' as expected.

If what I'm seeing is intended (just confused because it worked fine before) is there a workaround that will allow active object assignment without causing gizmo_tweak() throw an exception?

Thanks!

Details

Type
Bug