1
$\begingroup$

For my addon in blender 2.8 I need to have a modal operator running while my custom tool in the new toolbar is selected, or at least know when it is selected/deselected so that I can invoke/stop a modal operator. I have not been able to find any way to do both these things. Is it possible with the current python API?

$\endgroup$
  • $\begingroup$ Would you mind post some code for how you start the operator and where you add the button to the panel? $\endgroup$ – HikariTW May 7 '19 at 10:35
2
$\begingroup$

This is a hack--but for now (in 2.80/2.81) you can find out when a custom tool is activated and deactivated with a custom gizmo group:

class MYADDONNAME_TOOL_mytool(bpy.types.WorkSpaceTool):
    bl_idname = "myaddonname.mytool"
    bl_space_type='VIEW_3D'
    bl_context_mode='OBJECT'
    bl_label = "My tool"
    bl_icon = "ops.transform.vertex_random"
    bl_widget = "MYADDONNAME_GGT_mytool_activated"

class MYADDONNAME_GGT_mytool_activated(bpy.types.GizmoGroup):
    bl_label = "(internal)"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'WINDOW'
    bl_options = {'3D'}

    @classmethod
    def poll(cls, context):
        return True

    def setup(self, context):
        print("My tool activated!")

    def __del__(self):
        print("My tool deactivated!")

bpy.utils.register_class(MYADDONNAME_GGT_mytool_activated)
bpy.utils.register_tool(MYADDONNAME_TOOL_mytool, separator=True)

This isn't enough to invoke a modal operator as you're asking for, because the setup() method gets called in a drawing/rendering context, not one where you can just call bpy.ops.whatever('INVOKE_DEFAULT'). But this might be a useful starting point.

| improve this answer | |
$\endgroup$
  • $\begingroup$ Thanks! This is exactly how I ended up doing it. I was able to solve the setup context problem by using bpy.app.timers.register to call a function which actually calls the modal operator. This is of course all very hackish, I hope there will be better options in the future :) $\endgroup$ – yggdrasil Nov 24 '19 at 12:20
  • $\begingroup$ Agree about the hackishness! In my addon I’m also using the GizmoGroup's draw_prepare() method to draw additional stuff, and my tool’s draw_cursor() method to get the mouse position and tag the area for a redraw… hacks upon hacks right now :-/ $\endgroup$ – adurdin Nov 25 '19 at 1:44
0
$\begingroup$

I was also finding a similar problem on it. But I figured out an strange way to achieve it by using BoolProperty.

Here is the sample code:

import bpy
from bpy.props import (PointerProperty, BoolProperty)
from bpy.utils import register_class
from bpy.utils import unregister_class

class MyCustomProperties(bpy.types.PropertyGroup):
    is_my_modal_run: BoolProperty(default=False)
    # Use an bool to save modal status

class SOME_OT_myops(bpy.types.Operator):
    bl_idname = "some.myops"
    bl_label = "Some OP"

    def execute(self, context):
        self.report({"INFO"}, "Stop!")
        return {'FINISHED'}

    def modal(self, context, event):
        # check bool status
        if not context.scene.someprop.is_my_modal_run:
            return self.execute(context)
        return {'PASS_THROUGH'}

    def invoke(self, context, event):
        context.window_manager.modal_handler_add(self)
        context.scene.someprop.is_my_modal_run = True
        # When started, mark the bool flag
        return {'RUNNING_MODAL'}

class SOME_PT_panel(bpy.types.Panel):
    """Landmark Panel"""
    bl_idname = "SOME_PT_panel"
    bl_label = "Some Panel"
    bl_category = "Something"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"

    def draw(self, context):
        layout = self.layout
        prop = context.scene.someprop

        # Make the UI button change depend on the bool flag
        if not prop.is_my_modal_run:
            layout.operator(SOME_OT_myops.bl_idname,\
                text='Start Running Modal')
        else:
            layout.prop(prop, 'is_my_modal_run',\
                text="Stop Running Modal",toggle = True)

clss = [MyCustomProperties,SOME_OT_myops,SOME_PT_panel]

def register():
    for cl in clss:
        register_class(cl)
    bpy.types.Scene.someprop = bpy.props.PointerProperty(\
        type = MyCustomProperties)

def unregister():
    for cl in clss[::-1]:
        unregister_class(cl)
    del bpy.types.Scene.someprop

if __name__ == "__main__":
    register()

There MUST BE a better way, but I end up with this solution working.

| improve this answer | |
$\endgroup$
  • 1
    $\begingroup$ Thanks for the answer. However in my case I'm not using a button in a panel but rather a button in the new blender 2.8 toolbar on the left, so a similiar solution unfortunately doesn't work. (I need to know when the toolbar button is deselected) $\endgroup$ – yggdrasil May 4 '19 at 11:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.