Bug. Wrong value for bpy.context.area.spaces.active in modal

Scripting in Blender with Python, and working on the API

Moderators: jesterKing, stiv

Post Reply
fnunnari
Posts: 0
Joined: Wed Sep 11, 2013 6:10 pm

Bug. Wrong value for bpy.context.area.spaces.active in modal

Post by fnunnari »

Hello everybody,

I just found a buggy/weird behaviour in writing a modal Operator.
I'm writing a modal Operator invoked through a timed callback.
My aim is to retrieve in real-time the 3DView currently activated by the user. It means retrieving the area that is currently under the mouse pointer.

The operator can be activated from any area using the alt+shift+C shortcut.

There is a problem in the modal execution.
Even if the user moves the mouse across the screen, the Space instance returned by bpy.context.area.spaces.active is always the same! The one active at the moment of the shortcut stroke.

At beginning I thought it was a desired behaviour: maybe the context is "frozen" at the moment of first invocation.
But a couple of hints suggested me the contrary:
- The list of "selected_objects" is correctly updated
- If I switch to another screen (e.g., from Scripting to Default), while the operator is active, everything starts to behave correctly!

HOW TO REPRODUCE THE BUG:
- File -> New
- Switch to Scripting screen
- Paste the provided script into a new text area
- Run it
- Move the mouse in an area not "grabbing" the keytypes, like the 3D View or the Properties
- Press shift+alt+c
- The console will print every 2 secs the active space
- Regardless of mouse movements, the active space will remain the one active at the moment of shortcut press
- Object selection is printed correctly
- Switch to a different screen (e.g. Default or Animation)
- The active space will be correctly identified
- Switch back to Scripting
- The active space will be AGAIN WRONGLY identified


Developing on MaxOSX 10.8.4.
Behave the same in 2.66a and 2.68a

The script follows:

Code: Select all

# Skeleton template to hook on a timer callback

# Press alt+fhift+c to activate. Same to end.

# Modal listening method taken from Screencast Key Status Tool
# http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/3D_interaction/Screencast_Key_Status_Tool

import bpy
import time

# properties used by the script
def init_properties():
    #scene = bpy.types.Scene
    wm = bpy.types.WindowManager

    # Runstate initially always set to False
    # note: it is not stored in the Scene, but in window manager:
    wm.global_timed_callback_status = bpy.props.BoolProperty(default=False)


# removal of properties when script is disabled
def clear_properties():
    wm = bpy.context.window_manager
    if p in wm:
        del wm["global_timed_callback_status"]
    
    
class GlobalTimedCallbackOperator(bpy.types.Operator):
    bl_idname = "view3d.global_timed_callback"
    bl_label = "Global Timed callback"
    bl_description = "Template for global timed callback"

    _timer = None

    @staticmethod
    def handle_add(self, context):
        GlobalTimedCallbackOperator._timer = context.window_manager.event_timer_add(2, context.window)

    @staticmethod
    def handle_remove(context):
        if GlobalTimedCallbackOperator._timer is not None:
            context.window_manager.event_timer_remove(GlobalTimedCallbackOperator._timer)
        GlobalTimedCallbackOperator._timer = None


    def invoke(self, context, event):
        print("Global Timed Callback Invoked on area type " + context.area.type)

        # Test for first invocation
        if context.window_manager.global_timed_callback_status is False:
            # operator is called for the first time, start everything
            print("Global Timed Callback First call")
            self.modal_call_counter = 0
                
            context.window_manager.global_timed_callback_status = True
            GlobalTimedCallbackOperator.handle_add(self, context)
            context.window_manager.modal_handler_add(self)
            return {'RUNNING_MODAL'}
        else:
            # operator is called again, stop timer
            print("Global Timed Callback stop")
            context.window_manager.global_timed_callback_status = False
            return {'CANCELLED'}


    def modal(self, context, event):

        print("===> " + event.type + " --- modal_call_counter="+ str(self.modal_call_counter) )

        area = bpy.context.area
        if(area==None):
            return {'PASS_THROUGH'}
        print("Area="+str(area.type) +"\t" +str(area))
            
        space = area.spaces.active
        print("Active Space: " + space.type + "\t" + str(space))
            
        print("# selected objs: " + str(len(bpy.context.selected_objects)), end="")
        for obj in bpy.context.selected_objects:
            print(" " + obj.name, end="")
        print("")
        self.modal_call_counter += 1
        
        if event.type == 'TIMER':
            # DO THE REAL JOB
            return {'PASS_THROUGH'}
        else:
            print("modal event.type=" + event.type)
            

        if not context.window_manager.global_timed_callback_status:
            # stop script
            GlobalTimedCallbackOperator.handle_remove(context)
            return {'CANCELLED'}

        return {'PASS_THROUGH'}


    def cancel(self, context):
        if context.window_manager.global_timed_callback_status:
            GlobalTimedCallbackOperator.handle_remove(context)
            #context.window_manager.screencast_keys_keys = False
        return {'CANCELLED'}




# List of classes to add/remove
classes = [GlobalTimedCallbackOperator]

# store keymaps here to access after registration
addon_keymaps = []


def register():
    # Init global scene properties
    init_properties()

    # Register classes    
    for c in classes:
        bpy.utils.register_class(c)

    # Init key shortcuts control
    wm = bpy.context.window_manager
    kc = wm.keyconfigs.addon
    if kc:
        km = kc.keymaps.new(name='Window', space_type='EMPTY', region_type='WINDOW')
        kmi = km.keymap_items.new('view3d.global_timed_callback', 'C', 'PRESS', shift=True, alt=True)
        addon_keymaps.append((km, kmi))
    print("Registered")


def unregister():
    # in case its enabled
    GlobalTimedCallbackOperator.handle_remove(bpy.context)

    for c in classes:
        bpy.utils.unregister_class(c)

    # handle the keymapÇ
    for km, kmi in addon_keymaps:
        km.keymap_items.remove(kmi)
    addon_keymaps.clear()

    clear_properties()


if __name__ == "__main__":
    register()

Post Reply