2
$\begingroup$

Consider the following situation:
Suppose you store the Cube in a global variable and prints the object's position every 2 seconds. Assume you never delete the object.

import bpy

ob = [bpy.data.objects["Cube"]]

def in_2_seconds():
    print(ob[0].location)
    return 2.0

bpy.app.timers.register(in_2_seconds)

Scripts will not work when you perform undo/redo operations in the UI.

Traceback (most recent call last):
  File "\Text", line 6, in in_2_seconds
ReferenceError: StructRNA of type Object has been removed

The simple fix should be:

import bpy

ob = [bpy.data.objects["Cube"]]
name = [ob[0].name]

def in_2_seconds():
    try: ob[0].name
    except:
        print("---- Object is Dead ----")
        ob[0] = bpy.data.objects[name[0]]
        name[0] = ob[0].name

    print(ob[0].location)
    return 2.0

bpy.app.timers.register(in_2_seconds)

But it's not always correct, like when you rename the Cube then undo once and redo once.
Then what is the correct way to fix that?

$\endgroup$
1
  • 1
    $\begingroup$ I don't have 4.1 installed so I can't test atm but it includes bpy.types.ID.session_uid which should do what you're looking for. docs.blender.org/api/current/… it should remain the same so long as you don't reload the file $\endgroup$
    – Gorgious
    Commented Apr 5 at 10:55

1 Answer 1

1
$\begingroup$

Use bpy.types.PointerProperty assigned to bpy.types.Scene.

bpy.props.PointerProperty(type = bpy.types.ID)

Can use any ID, not affected by renaming and it is safe for Undo/Redo.

For many objects you can use bpy.props.CollectionProperty that as type uses a sub-class of bpy.types.PropertyGroup that contains the pointer property.

import bpy


if not hasattr(bpy.types.Scene, 'my_objects'):
    
    class Object_Pointer(bpy.types.PropertyGroup):
        target: bpy.props.PointerProperty(type = bpy.types.ID)
    
    bpy.utils.register_class(Object_Pointer)

    bpy.types.Scene.my_objects = bpy.props.CollectionProperty(type=Object_Pointer)


if not hasattr(bpy.types.WindowManager, 'my_data'):
    bpy.types.WindowManager.my_data = {}


bpy.context.scene.my_objects.clear()

for object in bpy.data.objects:
    item = bpy.context.scene.my_objects.add()
    item.target = object


def in_2_seconds():

    print("Count:", len(bpy.context.scene.my_objects))

    for index, item in enumerate(bpy.context.scene.my_objects):
        object = item.target
        if object:
            print(index, object.name_full, object.location)
        else:
            print(index, "Looks like the object has been deleted.")

    return 2.0


wm = bpy.data.window_managers[0]

prev_func = wm.my_data.get('prev_func')
if prev_func and bpy.app.timers.is_registered(prev_func):
    bpy.app.timers.unregister(prev_func)
    
bpy.app.timers.register(in_2_seconds, persistent = True)

wm.my_data['prev_func'] = in_2_seconds
$\endgroup$
5
  • $\begingroup$ Nice solution. In my case, the number of objects I want to store is uncertain, please tell me if there is a way to modify the quantity dynamically. $\endgroup$
    – X Y
    Commented Apr 6 at 23:31
  • $\begingroup$ @XY I has updated the answer with info on bpy.props.CollectionProperty. Not sure if the answer updating notifies the OP. Does it? $\endgroup$
    – unwave
    Commented Apr 6 at 23:45
  • $\begingroup$ Thanks, this should solve my problem. $\endgroup$
    – X Y
    Commented Apr 6 at 23:48
  • $\begingroup$ Not yet understanding what a bpy.props.PointerProperty is and how to use it, I'm not seeing the connection between this answer and the question. Perhaps you could show the explicit solution you're thinking of, i.e. the original 6 line script rewritten using bpy.types.PointerProperty to avoid the error? $\endgroup$
    – Don Hatch
    Commented Oct 31 at 22:18
  • $\begingroup$ @DonHatch I have added an example. $\endgroup$
    – unwave
    Commented Nov 1 at 22:09

You must log in to answer this question.

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