0
$\begingroup$

I wrote the code as below.

import bpy

bl_info = {
    "name": "TestModal",
    "author": "Taichi",
    "version": (1, 0),
    "blender": (2, 80, 0),
    "location": "3DView",
    "description": "",
    "warning": "",
    "support": "COMMUNITY",
    "wiki_url": "",
    "tracker_url": "",
    "category": "Animation"
}

class TestModal_PT_Panel(bpy.types.Panel):
    blidname = "TestModal_PT_Panel"
    bl_label = "TestModal Panel"
    bl_category = "TestModal"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    
    def draw(self,context):
        layout = self.layout
        
        row = layout.row()
        row.operator('wm.modal_testmodal_operator',text ='Start')
        
        row = layout.row()
        row.operator('wm.modal_testmodal_operator',text ='Stop')
        
class TestModal_OT_Operator(bpy.types.Operator):
    bl_idname = "wm.modal_testmodal_operator"
    bl_label = "Modal TestModal Operator"
    _timer = None

    def modal(self, context, event):
        if event.type in {'ESC'}:
            self.cancel(context)
            return{'CANCELLED'}

        if event.type=='TIMER':
            try:
                print("TestModal")
            except:
                pass
        return{'PASS_THROUGH'}

    def execute(self, context):
        wm = context.window_manager
        if TestModal_OT_Operator._timer is None:
            TestModal_OT_Operator._timer = wm.event_timer_add(0.1, window=context.window)
            wm.modal_handler_add(self)
        return {'RUNNING_MODAL'}

    def cancel(self, context):
        wm = context.window_manager
        self.realtimeAnimFlag = 0
        wm.event_timer_remove(self._timer)
        TestModal_OT_Operator._timer = None

classes = (TestModal_PT_Panel,TestModal_OT_Operator)
register,unregister = bpy.utils.register_classes_factory(classes)

if __name__ == "__main__":
    register()

This code just prints "Test Modal" 10 times a second when you press the "Start" button.

enter image description here

I want to cancel ModalOperator when I press the Stop button.

I haven't written the code to set multiple buttons and I'm not sure how to access functions etc. from buttons. Also, I don't know how to access ModalOperator Cancel.

Even now, I can press the Esc key on the keyboard to cancel, but how can I cancel it with the button?

I am using Blender 2.81.

Addendum: I tried the following code according to the answer but got an error.

import bpy

bl_info = {
    "name": "TestModal",
    "author": "Taichi",
    "version": (1, 0),
    "blender": (2, 80, 0),
    "location": "3DView",
    "description": "",
    "warning": "",
    "support": "COMMUNITY",
    "wiki_url": "",
    "tracker_url": "",
    "category": "Animation"
}

class TestModal_PT_Panel(bpy.types.Panel):
    blidname = "TestModal_PT_Panel"
    bl_label = "TestModal Panel"
    bl_category = "TestModal"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"

    def draw(self,context):
        layout = self.layout
        lmadd = context.scene.lm_addon
        vis_box = layout.box()
        if not lmadd.vis_run:
            vis_box.operator(TestModal_OT_Operator.bl_idname, text='Visualize')
        else:
            vis_box.prop(lmadd, 'vis_run', text="Stop", toggle=True)
            
class TestModal_OT_Operator(bpy.types.Operator):
    bl_idname = "wm.modal_testmodal_operator"
    bl_label = "Modal TestModal Operator"
    _timer = None

    def modal(self, context, event):
        if event.type in {'ESC'}:
            self.cancel(context)
            return{'CANCELLED'}

        if event.type=='TIMER':
            lm_addon.vis_run = context.scene.lm_addon.vis_run
            if not lm_addon.vis_run:
                lm_addon.vis_run = False
                return {"FINISHED"}
            else:
                print("testModal")
        return{'PASS_THROUGH'}

    def execute(self, context):
        context.scene.lm_addon.vis_run = True
        wm = context.window_manager
        if TestModal_OT_Operator._timer is None:
            TestModal_OT_Operator._timer = wm.event_timer_add(0.1, window=context.window)
            wm.modal_handler_add(self)
        return {'RUNNING_MODAL'}

    def cancel(self, context):
        wm = context.window_manager
        self.realtimeAnimFlag = 0
        wm.event_timer_remove(self._timer)
        TestModal_OT_Operator._timer = None


classes = (TestModal_PT_Panel,TestModal_OT_Operator)

def register():
    from bpy.utils import register_class
    for cls in classes:
        register_class(cls)
    bpy.types.Scene.lm_addon.vis_run = bpy.props.BoolProperty(
        name="modal running",
        description="determined whether running landmark visualizer",
        default=False,
    )
    
def unregister():
    from bpy.utils import unregister_class
    for cls in classes:
        unregister_class(cls)
    del bpy.types.Scene.lm_addon.vis_run

if __name__ == "__main__":
    register()

Error message is below:

AttributeError:type object 'Scene' has no attribute 'lm_addon'
$\endgroup$
6
  • $\begingroup$ I'm not sure I understood. Could you explain the context in which you need to use this? $\endgroup$
    – mugnozzo
    Commented Aug 31, 2020 at 12:34
  • $\begingroup$ If you continue to loop ModalOperator after pressing Start button, the process becomes heavy, so you have to stop it. You can stop it by pressing the Esc key, but add-on users may not notice the Esc key's presence. So I want to be able to stop the ModalOperator with the Stop button. $\endgroup$
    – taichi
    Commented Aug 31, 2020 at 13:11
  • 1
    $\begingroup$ You don't need to follow the same name lm_addon. The bpy.context.scene is needed, that's the scene in Blender, you can register any attribute to your scene. In your addon, you will need a Boolean Property to store your operator running state. It will be access by both operator and UI panel $\endgroup$
    – HikariTW
    Commented Sep 2, 2020 at 1:41
  • 1
    $\begingroup$ Basic python syntax error. Cannot assign "foo.bar" as a property in one hit. As with variable names they cannot contain a period "." Either make a property group "foo" with member "bar" or use something like type.Scene.foo_bar = BoolProp(...) Suggest this could be re-closed as a duplicate. $\endgroup$
    – batFINGER
    Commented Sep 2, 2020 at 6:30
  • $\begingroup$ @DuarteFarrajotaRamos ♦ believe this can be re-closed as duplicate. $\endgroup$
    – batFINGER
    Commented Sep 3, 2020 at 15:33

1 Answer 1

1
$\begingroup$

A hacky method is to have a Boolean property outside your operator and let the modal constantly check that properties. And let the panel link to the properties which user can see it and toggle the state of the Boolean.

A sample snippet As below:

# There was a group property. Only for example.
bpy.types.Scene.vis_run = BoolProperty(
    name="running visualize",
    description="determined whether running landmark visualizer",
    default=False,
)

class mesh_OT_visualize(bpy.types.Operator):
    bl_idname = "mesh.lm_vis"
    bl_label = "Draw Landmarks"
    bl_options = {'REGISTER'}

    def invoke(self, context, event):
        # Mark the modal running tag
        context.scene.vis_run = True
        return {"RUNNING_MODAL"}

    def modal(self, context, event):
        # Get whether user change the state of run tag
        vis_run= context.scene.vis_run
        if not vis_run:
            vis_run = False
            return {"FINISHED"}
        return {"PASS_THROUGH"}


class LandmarkPanel(bpy.types.Panel):
    bl_idname = "VIEW3D_PT_landmark"
    bl_label = "Landmark Panel"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"

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

        vis_box = layout.box()
        if not vis_run:
            vis_box.operator(mesh_OT_visualize.bl_idname, text='Visualize')
        else:
            vis_box.prop(context.scene, 'vis_run', text="Stop", toggle=True)

$\endgroup$
5
  • 1
    $\begingroup$ Or maybe just use a simple class attribute which share among all operator instance and make a stop operator to actual change the class attribute. Not sure which one will be the better idea $\endgroup$
    – HikariTW
    Commented Sep 1, 2020 at 5:33
  • $\begingroup$ Thank you for answering my question! But when I try to enable this addon, I get the error "AttributeError:type object 'Scene' has no attribute 'lm_addon' ". What is "lm_addon"? Is it possible to write including parts such as register? $\endgroup$
    – taichi
    Commented Sep 1, 2020 at 11:27
  • 1
    $\begingroup$ @taichi This snippet is not a minimum work sample addon but literal a snippet. lm_addon is a random GroupProperties and it contain a bool property vis_run. You don't need group thing. You can directly assign a bool property to bpy.types.Scene. eg. bpy.types.Scene.my_bool = BoolProperty() and then you access this property in any bpy.context.scene -> context.scene.my_bool $\endgroup$
    – HikariTW
    Commented Sep 1, 2020 at 11:53
  • 1
  • $\begingroup$ Should I do this? bpy.types.Scene.lm_addon = bpy.props.GroupProperties(name="GroupProps") bpy.types.Scene.lm_addon.vis_run = bpy.props.BoolProperty(....) But,I got this error AttributeError:module 'bpy.props' has no attribute 'GroupProperties' $\endgroup$
    – taichi
    Commented Sep 1, 2020 at 21:18

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