ReferenceError: StructRNA of type Object has been removed between modal and execute

Hi, I want to create locator and move it with my mouse, and eventually correct its position with floatProperty. But I get error in execute : ReferenceError: StructRNA of type Object has been removed.
When I put the locator object in a dict, Blender (2.8) gives me : <bpy_struct, Object invalid>
What did I miss ?

import bpy
from bpy.props import IntProperty, FloatProperty

class ModalOperator(bpy.types.Operator):
    """Move an object with the mouse, example"""
    bl_idname = "object.modal_operator"
    bl_label = "Simple Modal Operator"
    bl_options = {"REGISTER","UNDO"}

    x_translation = FloatProperty()
    loc = {}
    def execute(self, context):
        print ( self.loc["start"] )
        print (self.locator.name)
        self.locator.location.x += self.x_translation
        return {'FINISHED'}

    def modal(self, context, event):
        if event.type == 'MOUSEMOVE':
            self.x_translation = (self.first_mouse_x - event.mouse_x) * 0.01
            context.object.location.x = self.first_value + self.x_translation

        elif event.type == 'LEFTMOUSE':
            return {'FINISHED'}

        elif event.type in {'RIGHTMOUSE', 'ESC'}:
            context.object.location.x = self.first_value
            return {'CANCELLED'}

        return {'RUNNING_MODAL'}

    def invoke(self, context, event):
        self.loc = {}
        bpy.ops.object.empty_add(type='ARROWS')
        context.active_object.name = "locator_start"
        self.locator = context.active_object
        self.loc["start"] = context.active_object
        print (self.locator.name)
        if context.object:
            self.first_mouse_x = event.mouse_x
            self.first_value = context.object.location.x

            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(ModalOperator)


def unregister():
    bpy.utils.unregister_class(ModalOperator)


if __name__ == "__main__":
    register()

I and welcome on BA ! :slight_smile:


Your problem comes from this fact : When you launch the Modal Operator, in runs invoke() and then modal() on your mouse move. Then it’s finished.
If you then set the self.x_translation with the FloatProperty, operator is run again (a “last time” we could say) in the execute(). It means at this point consider that nothing exists, so the created arrow is deleted. That’s why in the execute() function self.locator is a pointer to an unexisting object.

To conclude, you should create the arrow as well in the execute.
In order not to double the code for creating it, you could use a get_locator() function.
Here is an example :

import bpy
from bpy.props import IntProperty, FloatProperty

def get_locator():
    bpy.ops.object.empty_add(type='ARROWS')
    bpy.context.active_object.name = "locator_start"
    locator = bpy.context.active_object
    return locator

class ModalOperator(bpy.types.Operator):
    """Move an object with the mouse, example"""
    bl_idname = "object.modal_operator"
    bl_label = "Simple Modal Operator"
    bl_options = {"REGISTER","UNDO"}

    x_translation = FloatProperty()
    loc = {}
    def execute(self, context):
        locator = get_locator()
        locator.location.x += self.first_value + self.x_translation
        return {'FINISHED'}

    def modal(self, context, event):
        if event.type == 'MOUSEMOVE':
            self.x_translation = (self.first_mouse_x - event.mouse_x) * 0.01
            context.object.location.x = self.first_value + self.x_translation

        elif event.type == 'LEFTMOUSE':
            return {'FINISHED'}

        elif event.type in {'RIGHTMOUSE', 'ESC'}:
            context.object.location.x = self.first_value
            return {'CANCELLED'}

        return {'RUNNING_MODAL'}

    def invoke(self, context, event):
        self.locator = get_locator()
        self.first_mouse_x = event.mouse_x
        self.first_value = context.object.location.x
        context.window_manager.modal_handler_add(self)
        return {'RUNNING_MODAL'}

def register():
    bpy.utils.register_class(ModalOperator)


def unregister():
    bpy.utils.unregister_class(ModalOperator)


if __name__ == "__main__":
    register()

NB: Why did your line 14 crashed :
print (self.locator.name)
and not the 13 :
print (self.loc["start"])
?
Indeed, self.loc["start"] and self.locator are exactly the same broken pointer, but print is able to print a non existing pointer. It printed “<bpy_struct, Object invalid>”
But the line 14 tries to access self.locator.name and this is impossible

1 Like

Perfect! Thank you very much for your explanation and solution ! :smiley:
Some precision given by rjg on stackexchange :

When the modal operator finishes execution, either by returning FINISHED or CANCELED the Empty object still exists. What causes the object to be deleted is the adjusting of the FloatProperty which is treated as an undo of the operations performed by the invoke and modal functions, followed by the call to execute . Hence the objects created before will be removed before execute is run. That is also why bl_options = {"REGISTER", "UNDO"} is used, otherwise the Adjust Last Operation panel wouldn’t be displayed.