Help understanding screen (or depsgraph) updates

Hello community,

I am trying to write a simple modal operator that blends objects from it’s current position to the position of the left or right keyframe. Something I use all the time when animating.

However there are some things I just don’t understand:

  1. fcurve.keyframe_points.insert does not seem to update the scene. However updating the key directly with key.co.y does? Check lines 40/61. Without them, the view is not updated until the operator has finished AND i deselect something.

  2. When I remove keys I need to deselect them before, otherwise they still exist until I deselect them. If I instead move them, they are not deleted but are greyed out, still work normally though. Check 56ff

  3. When I confirm the operator and try to modify values afterwards using the nice ui in the bottom left of the 3d view, blender crashes. Crash report at the end of this post. Some properties seem to be deleted, but why?

bl_info = {
    "name": "CL Anim Ops",
    "author" : "Christoph Lendenfeld",
    "blender": (2, 81, 0),
    "category": "User",
}


import bpy


class BlendToNeighbour(bpy.types.Operator):
    """Blend current position to keyframes on each side"""      # Use this as a tooltip for menu items and buttons.
    bl_idname = "object.blend_to_neighbour"        # Unique identifier for buttons and menu items to reference.
    bl_label = "blend to neighbour"    # Display name in the interface.
    bl_options = {'REGISTER', 'UNDO'}  # Enable undo for the operator.

    #overshoot : bpy.props.BoolProperty(name = 'Overshoot', description = 'Allow values out of -1/1 range')
    delta : bpy.props.FloatProperty(name = 'Blend Value', description = 'Amount of blending', min = -1, max = 1)
    leftKey : bpy.props.IntProperty(name = 'Left Key', description= 'Left keyframe neighbour of current time', default = -64000)
    rightKey : bpy.props.IntProperty(name = 'Right Key', description= 'Right keyframe neighbour of current time', default = 64000)



    # calculate delta, interpolate and apply values here
    def execute(self, context):        # execute() is called when running the operator.
        for fcurveInfo in self.fcurveInfos:
            fcurve = fcurveInfo.fcurve
            leftValue = fcurve.evaluate(self.leftKey)
            rightValue = fcurve.evaluate(self.rightKey)
            centerValue = fcurveInfo.startValue

            blendValue = 0
            if self.delta < 0:
                blendValue = (abs(self.delta) * leftValue) + ((1 - abs(self.delta)) * centerValue)
            else:
                blendValue = (self.delta * rightValue) + ((1 - self.delta) * centerValue)

            fcurveInfo.fcurve.keyframe_points.insert(self.currentFrame, blendValue)
            fcurveInfo.key.co.y = blendValue    # TODO this makes the scene update???
            
        return {'FINISHED'}            # Lets Blender know the operator finished successfully.


    # called every frame?
    # that's the interactive part
    def modal(self, context, event):
        if event.type == 'MOUSEMOVE':  # Apply
            self.delta = max(min((event.mouse_x - self.startPosX) / 100, 1),-1) 
            self.execute(context)
        elif event.type == 'LEFTMOUSE':  # Confirm
            return {'FINISHED'}
        elif event.type in {'RIGHTMOUSE', 'ESC'}:  # Cancel
            # delete key if it didn't exist before, or reset key to default position
            for fcurveInfo in self.fcurveInfos:
                fcurveInfo.key.select_control_point = False     # TODO keys are not deleted as long as they are selected...
                if fcurveInfo.newKey:
                    fcurveInfo.fcurve.keyframe_points.remove(fcurveInfo.key)
                else:
                    fcurveInfo.fcurve.keyframe_points.insert(self.currentFrame, fcurveInfo.startValue)
                    fcurveInfo.key.co.y = fcurveInfo.startValue     # TODO update scene
            return {'CANCELLED'}

        return {'RUNNING_MODAL'}


    # called by blender before calling execute
    # add the modal handler, which makes it interactive
    def invoke(self, context, event):
        self.startPosX = event.mouse_x
        self.currentFrame = context.scene.frame_current
        self.fcurveInfos = []

        if context.mode == 'OBJECT':
            self.selection = context.selected_objects
        
        if context.mode == 'POSE':
            self.selection = context.selected_pose_bones

        if not self.selection:
            self.info('Nothing selected')
            return {'FINISHED'}

        for obj in self.selection:
            if not obj.animation_data:
                continue
            if not obj.animation_data.action:
                continue
            for fcurve in obj.animation_data.action.fcurves:
                startValue = fcurve.evaluate(self.currentFrame)
                newKey = self.findClosestFrames(fcurve)
                key = fcurve.keyframe_points.insert(self.currentFrame, startValue)
                self.fcurveInfos.append(FCurveInfo(fcurve, key, startValue, newKey))

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


    def info(self, info):
        self.report({'INFO'}, str(info))


    def findClosestFrames(self, fcurve):
        newKey = True   # False if current position already has a key
        for key in fcurve.keyframe_points:
            if key.co.x > self.leftKey and key.co.x < self.currentFrame:
                self.leftKey = key.co.x
            elif key.co.x < self.rightKey and key.co.x > self.currentFrame:
                self.rightKey = key.co.x
            elif key.co.x == self.currentFrame:
                newKey = False

        return newKey


# container for fcurves and keys
class FCurveInfo():
    def __init__(self, fcurve, key, startValue, newKey):
        self.fcurve = fcurve
        self.key = key
        self.startValue = startValue
        self.newKey = newKey    # bool that determines if the key was newly created






classes = (
    BlendToNeighbour,
)

def register():
    from bpy.utils import register_class
    for cls in classes:
        register_class(cls)

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



# Blender 2.81 (sub 16), Commit date: 2019-11-20 14:27, Hash 26bd5ebd42e3
bpy.ops.anim.keyframe_insert_menu(type='Location')  # Operator
bpy.ops.transform.translate(value=(-2.62383, -2.69766e-07, 2.0078), orient_type='GLOBAL', orient_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)), orient_matrix_type='GLOBAL', mirror=True, use_proportional_edit=False, proportional_edit_falloff='SMOOTH', proportional_size=1, use_proportional_connected=False, use_proportional_projected=False)  # Operator
bpy.ops.transform.translate(value=(3.68477, -2.97356e-07, 2.21315), orient_type='GLOBAL', orient_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)), orient_matrix_type='GLOBAL', mirror=True, use_proportional_edit=False, proportional_edit_falloff='SMOOTH', proportional_size=1, use_proportional_connected=False, use_proportional_projected=False)  # Operator
bpy.ops.action.interpolation_type(type='BEZIER')  # Operator
bpy.ops.object.blend_to_neighbour(delta=0.34, leftKey=20, rightKey=45)  # Operator
bpy.data.window_managers["WinMan"].(null) = 0.37  # Property