2
$\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$
1
  • $\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, 2019 at 10:35

2 Answers 2

5
$\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.

$\endgroup$
3
  • $\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, 2019 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, 2019 at 1:44
  • $\begingroup$ It's been awhile and this has been the only thing I've found that works in 2024. Does anyone know of any new developments? The reason why I'm asking is because GizmoGroups appear to be removed and added when you do things like: Move windows, invoke operators, press Ctrl-Z for undo. This is going to cause some weirdness with my addon if I turn things on and off automatically. $\endgroup$
    – TedMilker
    Jan 28 at 21:18
3
$\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.

$\endgroup$
1
  • 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, 2019 at 11:24

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .