8
$\begingroup$

I'm searching an equivalent to the observer design pattern. I want to execute a script function every time an object position changes.

To achieve this there might be some options directly in blender UI, but if there is any possibilities to set up the observer using scripting, please provide both.

As an example I would like to do something like that in my script :

bpy.data.objects["cube"].location.onChange(myFunction)

def myFunction():
    #do stuff every time location is changed...

I am using Blender 2.67b

$\endgroup$
  • $\begingroup$ Are you talking about the Blender Game Engine, or the editor itself? $\endgroup$ – CharlesL Jun 12 '13 at 14:32
  • $\begingroup$ I'm not specifically talking about the game engine, but that could apply to it. See edited example. $\endgroup$ – user471 Jun 12 '13 at 16:03
8
$\begingroup$

It appears that this is not supported by blender. So here is my workaround.

I made a little script that will allow you to execute a function when the property of an object changes. I'm not used to blender modules so I didn't made a downloadable module out of it. But it's still pretty easy to set up.

Create the observer module

first, we need to execute a function every frame, this function will check if any property has changed and call the corresponding observer function.

  1. Add an empty to the scene.
  2. Add a pydriver to the empty (open the property panel (n) and then right clic on the location and select "Add Single Driver")
  3. Go in the graph window and open the "Drivers" display (clic on f-curve editor button in the bottom).
  4. Open the properties (n)
  5. Select the added driver in the list on the left.
  6. Remove any variable and modifier in the properties panel.
  7. Just below the "Scripted Expression" button there is the "Expr" field enter in it the following :

    update(frame)
    

    This will call our frameUpdate function at every frame change.

Now that we are setup

  1. create a new file in the text editor named

    observer.py
    
  2. check the "register" option (this will create a new module loaded automatically with .blend) and add the following to the file:


#author : Vincent Picard

import bpy
import bpy.app

#list of all the observers.
observers = []
print("test")
#This is to function to call in your scripts. use it to have your function called everytime an object property changes.
#object : Object to watch,
#property : property to be wathed.
#function to execute every time the property of the given object changes.
#function will be given this parameters :
#    object : the object.
#    value : new value of the property
#    old : old value of the property. 
#    frame : current frame.
def observe(object, property, function):
    observers.append(Observer(object,property,function))

#Called at every frame. This will call the function that are listening.
def frameUpdate(frame):
    for observer in observers:
        observer.update(frame)
    return 0.0


#make the function visible to scripted driver.
bpy.app.driver_namespace['update'] = frameUpdate

#Each time we call observe we create an observer object. 
class Observer:
    def __init__(self, object, property, function):
        #if the property object needs deep copy 
        try :
            self.oldValue = getattr(object, property).copy()
            self.newValue = getattr(object, property).copy()
        #if the property object doesn't need it (and don't have a copy method).
        except AttributeError:
            self.oldValue = getattr(object, property)
            self.newValue = getattr(object, property)

        self.object = object
        self.property = property
        self.function = function

    #Call the function if the object property changed.    
    def update(self, frame):
        #if the object needs deep copy 
        try :
            self.oldValue = self.newValue.copy()
            self.newValue = getattr(self.object, self.property).copy()
        #if the object doesn't need it (and don't have a copy method).
        except AttributeError:
            self.oldValue = self.newValue
            self.newValue = getattr(self.object, self.property)

        if self.oldValue != self.newValue:
            self.function(object = self.object, value = self.newValue, old = self.oldValue, frame = frame)

How to create an observer and use it.

  1. You now need to reopen you .blend, that way the "observer" module will be available.

You can now observe object properties and make a function to be call when it changes.

  1. Create your script file (example) :

    import bpy
    import observer
    
    #define the function you want to be executed. this one as an example simply print old and new value when they are changed.
    def updateAnimation(object, value, old, frame):
        print(value)
        print(old)
    
    #register your observer.   
    observer.observe(bpy.data.objects["Cube.031"], "location", updateAnimation)
    
  2. Run it, it will register your observer.

Potential Problems

do not run the observer.py file, if you do then it will stop working and you will need to reopen the .blend.

$\endgroup$
  • 2
    $\begingroup$ This is an amazing approach, thanks for sharing it! One could also use app handlers (scene_update_post), dunno about pros and cons. $\endgroup$ – CoDEmanX Feb 15 '14 at 1:11
  • $\begingroup$ I noticed that using the frame change handler (See blender app handlers) we could avoid the "Driver" hack in my solution. Which would make it way better/sexy. $\endgroup$ – user471 Mar 12 '14 at 3:02
4
$\begingroup$

to print all objects that have changed this code works for me:

def print_updated_objects(scene):
    updated_objects = []
    for o in scene.objects:
        if o.is_updated:
            updated_objects.append(o)

    if(len(updated_objects) > 0):
        print("updated objects: %s"%updated_objects[0])

bpy.app.handlers.scene_update_post.append(print_updated_objects)
$\endgroup$
0
$\begingroup$

Exactly as @CoDEmanX says, you can do this, anywhere in your script:

@persistent
def your_scene_update_handler( dummy ):    # <--- yes, really type dummy here
    your_function()

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

More on this topic: http://www.blender.org/documentation/blender_python_api_2_69_10/bpy.app.handlers.html

$\endgroup$
  • $\begingroup$ Those handlers will call the function on very precise events (like after render or after frame change). Which is interesting. It does not, though provide a way to call a function when a precise property of an object is changed. $\endgroup$ – user471 Mar 12 '14 at 3:00
  • $\begingroup$ Someone could notice that using the frame change handler we could avoid the "Driver" hack in my solution. Which would make it way better. $\endgroup$ – user471 Mar 12 '14 at 3:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy