1
$\begingroup$

I've been creating an add-on for Blender but discovered a problem. For my add-on, I need to be able to get drivers to update in realtime while a Modal Operator is running. Since the add-on code is very long, I've created a demonstration script given below:

import bpy

from bpy.props import (PointerProperty,
                       FloatProperty
                       )
from bpy.types import (Panel,
                       Menu,
                       Operator,
                       PropertyGroup,
                       )

class SerialProperties(PropertyGroup):
    example_float: FloatProperty(
        name="User Input Float",
        description="Float to send to new item",
        default = 0.0
    )


class ModalOperator(bpy.types.Operator):
    bl_idname = "object.modal_operator"
    bl_label = "Simple Modal Operator"

    def __init__(self):
        print("Start")

    def __del__(self):
        print("End")

    def execute(self, context):
        context.object.location.x = self.value / 100.0
        return {'FINISHED'}

    def modal(self, context, event):
        if event.type == 'MOUSEMOVE':  # Apply
            self.value = event.mouse_x
            context.scene.serialtool.example_float = event.mouse_x
            self.execute(context)
        elif event.type == 'LEFTMOUSE':  # Confirm
            return {'FINISHED'}
        elif event.type in {'RIGHTMOUSE', 'ESC'}:  # Cancel
            context.object.location.x = self.init_loc_x
            context.scene.serialtool.example_float = self.init_loc_x
            return {'CANCELLED'}

        return {'RUNNING_MODAL'}

    def invoke(self, context, event):
        self.init_loc_x = context.object.location.x
        self.value = event.mouse_x
        self.execute(context)

        context.window_manager.modal_handler_add(self)
        return {'RUNNING_MODAL'}


class SERIAL_PT_MainPanel(Panel):
    bl_label = "Serial Panel"
    bl_idname = "SERIAL_PT_MainPanel"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = "Serial" #You can replace serial in this line with any other word you want
    #bl_context = "objectmode"

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

        layout.prop(serialtool, "example_float")
        layout.operator("object.modal_operator")

classes = ( #list all your classes here so you don't have to register them individually later
    ModalOperator,
    SerialProperties,
    SERIAL_PT_MainPanel, #Order matters here
)

def register():
    from bpy.utils import register_class
    for cls in classes:
        register_class(cls)
    bpy.types.Scene.serialtool = PointerProperty(type=SerialProperties)

def unregister():
    from bpy.utils import unregister_class
    for cls in reversed(classes):
        unregister_class(cls)
    del bpy.types.Scene.serialtool

# test call
#bpy.ops.object.modal_operator('INVOKE_DEFAULT')
if __name__ == "__main__":
    register()

For this demo, I'm using the custom property created by the script as the variable in a Driver used to drive the y Location of the Default cube in the default Blend file. However, the driver doesn't update correctly when the Modal Operator is running, as seen in the picture below: The Problem is on the right

As seen, the driven property (which for the demo is the y location of the cube) doesn't update at all, despite the driving property (the custom property created by the add-on) changing correctly. It stays at the same value it was before the Operator was run. I have no idea why this would be. When the custom property is changed manually (meaning through direct user input to the property itself), the driver functions normally. So the question: Why isn't the Modal Operator Updating the Driver?

$\endgroup$

1 Answer 1

1
$\begingroup$

I acted on a guess that what might be happening could count as a kind of very minor bug with the updating of properties. Based on that idea, I added a trivial update function to the custom property to try to force Blender to update the rest of the gui as well. With that addition, the code and driver now work completely as desired.

Here is the updated, working example.

import bpy

from bpy.props import (PointerProperty,
                       FloatProperty
                       )
from bpy.types import (Panel,
                       Menu,
                       Operator,
                       PropertyGroup,
                       )
def update_example(self, context):
    pass
    return None
class SerialProperties(PropertyGroup):
    example_float: FloatProperty(
        name="User Input Float",
        description="Float to send to new item",
        default = 0.0,
        update=update_example #This line is SUPER IMPORTANT because it forces Blender to update things including Drivers that rely on the property. If you didn't do that, the Drivers won't update.
    )


class ModalOperator(bpy.types.Operator):
    bl_idname = "object.modal_operator"
    bl_label = "Simple Modal Operator"

    def __init__(self):
        print("Start")

    def __del__(self):
        print("End")

    def execute(self, context):
        context.object.location.x = self.value / 100.0
        return {'FINISHED'}

    def modal(self, context, event):
        if event.type == 'MOUSEMOVE':  # Apply
            self.value = event.mouse_x
            context.scene.serialtool.example_float = event.mouse_x
            self.execute(context)
        elif event.type == 'LEFTMOUSE':  # Confirm
            return {'FINISHED'}
        elif event.type in {'RIGHTMOUSE', 'ESC'}:  # Cancel
            context.object.location.x = self.init_loc_x
            context.scene.serialtool.example_float = self.init_loc_x
            return {'CANCELLED'}

        return {'RUNNING_MODAL'}

    def invoke(self, context, event):
        self.init_loc_x = context.object.location.x
        self.value = event.mouse_x
        self.execute(context)

        context.window_manager.modal_handler_add(self)
        return {'RUNNING_MODAL'}


class SERIAL_PT_MainPanel(Panel):
    bl_label = "Serial Panel"
    bl_idname = "SERIAL_PT_MainPanel"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = "Serial" #You can replace serial in this line with any other word you want
    #bl_context = "objectmode"

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

        layout.prop(serialtool, "example_float")
        layout.operator("object.modal_operator")

classes = ( #list all your classes here so you don't have to register them individually later
    ModalOperator,
    SerialProperties,
    SERIAL_PT_MainPanel, #Order matters here
)

def register():
    from bpy.utils import register_class
    for cls in classes:
        register_class(cls)
    bpy.types.Scene.serialtool = PointerProperty(type=SerialProperties)

def unregister():
    from bpy.utils import unregister_class
    for cls in reversed(classes):
        unregister_class(cls)
    del bpy.types.Scene.serialtool

# test call
#bpy.ops.object.modal_operator('INVOKE_DEFAULT')
if __name__ == "__main__":
    register()
```
$\endgroup$

You must log in to answer this question.

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