1
$\begingroup$

So I'm trying to convert the following script in 2.8

I have replaced scene with depsgraph.

But I'm getting an erroneous on last line: Scene object has no attribute 'cursor.location'


import bpy
from mathutils import Vector

#A class that takes into account a context and one of its attributes value
#If the value changes a callback is fired
class EventWatcher:

    #Set of watchers
    eventWatchers = set()

    @staticmethod
    def AddWatcher( watcher ):
        EventWatcher.eventWatchers.add( watcher )    

    @staticmethod
    def RemoveWatcher( watcher ):
        EventWatcher.eventWatchers.remove( watcher )

    @staticmethod
    def RemoveAllWatchers():
        EventWatcher.eventWatchers.clear()

    #From 'context', 'path' needs to exist
    #'comparer' is to compare the previous value of context.path to its new value
    #'callback' is the cb called if the value if changed
    #'copyValue' indicates if the value needs to be copied (that can be needed as if not old and new value may point onto the same object)
    def __init__( self, context, path, comparer, callback, copyValue ):
        self.context = context
        self.path = path
        self.comparer = comparer
        self.callback = callback
        self.copyValue = copyValue
        self.currentValue = self.GetValue()

    def GetValue( self ):
        value = getattr( self.context, self.path )
        if self.copyValue:
            value = value.copy()
        return value

    def Fire( self ):
        newValue = self.GetValue()
        if self.comparer( self.currentValue, newValue ) == False:
            self.callback( self, newValue )
            self.currentValue = newValue

#Global loop on the watchers. This callback responds to scene_update_post global handler
def cb_scene_update(context):
    for ew in EventWatcher.eventWatchers:
        ew.Fire()

#To stop the calls at the scene_update_post event level
class StopCallback(bpy.types.Operator):
    bl_idname = "scene.stop_callback"
    bl_label = "Stop Callback"

    @classmethod
    def poll(cls, context):
        return cb_scene_update in bpy.app.handlers.depsgraph_update_post

    def execute(self, context):
        bpy.app.handlers.scene_depsgraph_post.remove(cb_scene_update)
        return {'FINISHED'}

#To start the calls at the scene_update_post event level
class StartCallback(bpy.types.Operator):
    bl_idname = "scene.start_callback"
    bl_label = "Start Callback"

    @classmethod
    def poll(cls, context):
        return cb_scene_update not in bpy.app.handlers.depsgraph_update_post

    def execute(self, context):
        bpy.app.handlers.depsgraph_update_post.append(cb_scene_update)
        return {'FINISHED'}

#The panel is located in the scene properties
class SceneEventsPanel(bpy.types.Panel):
    bl_label = "Scene events"
    bl_idname = "OBJECT_PT_scene_events"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "scene"

    def draw(self, context):
        layout = self.layout
        row = layout.row()
        row.operator("scene.start_callback")
        row = layout.row()
        row.operator("scene.stop_callback")


def register():
    bpy.utils.register_class(SceneEventsPanel)
    bpy.utils.register_class(StartCallback)
    bpy.utils.register_class(StopCallback)

def unregister():
    bpy.utils.unregister_class(SceneEventsPanel)
    bpy.utils.unregister_class(StartCallback)
    bpy.utils.unregister_class(StopCallback)


if __name__ == '__main__':
    register()


print( '--------' )

#Example:

#The comparaison (for cursor location, it is a vector comparison)
def CompareLocation( l1, l2 ):
    return l1 == l2

#The callback to execute when the cursor's location changes    
def CompareLocationCallback( watcher, newValue ):
    print( 'New value', newValue )

#Install the watcher which will run the callback
EventWatcher.AddWatcher( EventWatcher( bpy.data.scenes[0], "cursor.location", CompareLocation, CompareLocationCallback, True ) )

$\endgroup$

1 Answer 1

1
$\begingroup$

Actually it is not the last line that is causing the error but the GetValue method.

In order to access the location attribute of the cursor attribute of the scene, you must execute scene.cursor.location. Meaning you must first get access to the cursor, then to its location. What you can do is replace your GetValuemethod with :

def GetValue( self ):
    split_path = self.path.split(".")
    attr_path = split_path[0]
    if not hasattr(self.context, attr_path):
        print(f"Error : {self.context} does not have any attribute named {attr_path}")
        return None
    value = getattr(self.context, split_path[0])  # Access 1st member of the property path
    for depth in range(len(split_path[1::])):  # Iteratively access the other members
        attr_path = split_path[depth + 1]
        if not hasattr(value, attr_path):                
            print(f"Error : {value} does not have any attribute named {attr_path}")
            break
        value = getattr(value, attr_path)
    if self.copyValue and hasattr(value, "copy"):
        value = value.copy()
    return value

Meaning, if you want to access the X coordinate of the cursor location, you can change the last line to :

EventWatcher.AddWatcher(EventWatcher(bpy.data.scenes[0], "cursor.location.x", CompareLocation, CompareLocationCallback, True))

Result :

enter image description here

By the way, I suggest looking at the guidelines for python naming and such in Blender.

$\endgroup$
2
  • $\begingroup$ thank you so much. How can I add a cube in those locations, pe each click? $\endgroup$
    – Andy Andy
    Commented Jun 30, 2020 at 14:03
  • $\begingroup$ Glad it helped. :) I can answer your other question but it is outside the scope of this one, would you mind adding it as another question and then linking it in the comments ? $\endgroup$
    – Gorgious
    Commented Jun 30, 2020 at 15:39

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .