Calling function when selection changes

Hi! I need to create a script that runs a function when the objects selection in scene changes.
I tried with an handler, that on each scene update compare the last selection with the new one, and if it is different it runs the function. I stored the selection in an array, but when I compare it with bpy.context.selected_objects it tells me that the array is referenced before assignemnt, even if the assignement is before it in the code.

Here’s the code so far. Probably with the handlers the things works different than usual.

import bpy

last_selection = []
sel_objs = bpy.context.selected_objects

def SelectAssembly():    
    print("ok")


def assembly_handler(scene):

    if sel_objs != last_selection:
        last_selection = sel_objs
        SelectAssembly()

bpy.app.handlers.scene_update_post.append(assembly_handler)

Any idea for this?
What I’m trying to achieve is that when I select an object, Blender selects all other objects with the same property. Any other ways to do this?

This error means that you have not declared the variable inside the local scope of the method.

But the truth is that you want to use the variable outside the scope of the method.

If you use actual variables outside of the method scope you must declare them as global.


def assembly_handler(scene):
	global last_selection, sel_objs
	...

Also since I need the same script for my personal project I took the time to create this example:


import bpy
import time


need_to_update = False
last_length = 0
handlers = bpy.app.handlers.scene_update_post


def select_assembly(scene):
	global last_length, need_to_update


	if last_length != len(bpy.context.selected_objects):
		need_to_update = True
		last_length = len(bpy.context.selected_objects)


	if need_to_update == True:
		print("Updated:", time.time())
		need_to_update = False


def add_handler(handlers, func):
	c = 0
	r = False
	for i in handlers:
		if i.__name__ == func.__name__:
			handlers.remove(handlers[c])
			r = True
		c += 1
	handlers.append(func)
	print(("Added" if r == False else "Replaced")+" Handler:", i.__name__)
add_handler(handlers, select_assembly)

Have fun. :slight_smile:

Have you considered to catch click events to determine possible selection changes?

import bpy


class SimpleOperator(bpy.types.Operator):
    """Tooltip"""
    bl_idname = "object.simple_operator"
    bl_label = "Simple Object Operator"

    counter = 0

    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        self.__class__.counter += 1
        self.report({'INFO'}, "Select %i" % self.__class__.counter)
        return {'FINISHED'}


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


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

    # handle the keymap
    wm = bpy.context.window_manager
    km = wm.keyconfigs.user.keymaps.new(name='3D View', space_type='VIEW_3D')

    kmi = km.keymap_items.new(SimpleOperator.bl_idname, 'SELECTMOUSE', 'PRESS', any=True)

    user_keymaps.append((km, kmi))


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

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


if __name__ == "__main__":
    register()


You great! You’ll see my script will be useful for many. I try it now!

<br>
<br>Yes, and even if I’m curious to see how to do that, I think that selection could change for other reasons than clicks, so I prefer to compare the selection itself instead of its lenght or when the click happens.

Can you explain me the purpose and the reasons behind the function add_handler?
Can I do the comparison of old and new selection in the handler def and calling the function in it like so?

def assembly_handler(scene):
         global last_selection, need_to_update
    
         if sel_objs != last_selection:
                 last_selection = sel_objs
                 SelectAssembly()

I made some corrections to the code, also now it’s far better and more simplified.

Can I do the comparison of old and new selection in the handler def and calling the function in it like so?

Good idea to compare the lists directly, it seems that it works, however you won’t need to use sel_objs at all since you can get the selected objects right from the context.


import bpy
import time


last_selection = []
sel_objs = bpy.context.selected_objects


"""
# OLD CODE
def assembly_handler(scene):
    global last_selection, sel_objs
    
    # At the very first loop this
    # will be valid because lists are not the same
    if sel_objs != last_selection:
        # Now lists are the same
        # the if loop will never run again
        last_selection = sel_objs
        print("Updated", time.time())
"""


def assembly_handler(scene):
    global last_selection
    if bpy.context.selected_objects != last_selection:
        last_selection = bpy.context.selected_objects
        print("Updated", time.time())
        
bpy.app.handlers.scene_update_post.clear()
bpy.app.handlers.scene_update_post.append(assembly_handler)

Can you explain me the purpose and the reasons behind the function add_handler?

There are various handlers in Blender, you can see more information about this here:
http://www.blender.org/api/blender_python_api_2_73_release/bpy.app.handlers.html?highlight=handler#module-bpy.app.handlers This way we can attach extra functions in specific parts of Blender’s lifecycle (i.e. at the end of scene update) and let them run automatically when needed.