How to create a Propetry Group that stores a reference to an Object?

Hey Blender Community,

I have created a propertyGroup that should store objects along with other data. I then create a list with those property group items. It looks like this:

class BakeObjectList(bpy.types.PropertyGroup):
    '''name = bpy.props.PointerProperty()''' #name of the object
    index = bpy.props.IntProperty()
    cage_object = bpy.props.StringProperty()
    extrusion = bpy.props.FloatProperty(name="Extrusion", default = 0)
    active = bpy.props.BoolProperty()
    uv_map = bpy.props.StringProperty()

def register():
    bpy.utils.register_module(__name__)
    bpy.types.Scene.bake_objects = CollectionProperty(type=BakeObjectList)
    bpy.types.Scene.bake_object_index = IntProperty()



def unregister():
    bpy.utils.unregister_module(__name__)
    del bpy.types.Scene.bake_objects
    del bpy.types.Scene.bake_object_index

The Proplem is that if the user changes the name of an object, the list loses the connection to that object. I want to have the name to be a pointer to the object name so that the connection cant break. Or I would like to store a reference to the entire object.

How do i do that?
Do you have a better solution to my problem?

I hope you understand my explaination:D

You have a BakeObjectList for each object? Wouldn’t it be easier to link those properties to the Object type and show them in the Object panel instead?

If you still want to store it in the Scene type, you can store a CollectionProperty of IntProperties, that int being the index of the object in the bpy.data.objects list. Then, use the name of the object the list points to instead.

To actually answer your question…

How do i do that?

You can’t do that.

This is one of the bigger inadequacies of the Python API. You cannot store safely store references to objects at all. If you store a Python reference to an object and then later reuse it, Blender might crash, because those references become invalid (through an UNDO, for instance).

Storing the name string is safe, but renames won’t be honored (as you found out) and there’s no way to be notified for renames, either.

batFINGER’s proposed “solution” of adding an identifying property breaks down when a user copies the object (something you also get no notification of), which copies the (now wrong) value along.

The copy situation is easily handled by using something like


obj["is_the_one"] = obj.name

when setting. The copy will fail obj[“is_theone”] == obj.name

This test also fails when the object is renamed, which was the original problem to begin with.

Oh dear,

… it was intended as an additional test to property set method of my previous post.

besides, If there is only one object with Id property is_theone and obj[“is_theone”] != obj.name then it has most likely been renamed.

There are other workaround solutions to this as well, one of which is setting up a dummy driver & make the pointed to object a variable target.

Your example also does not show how to solve the problem at all. The problem is not how to have a “singleton” object reference, it’s to store references to other objects in a property list, with a 1:N relationship.

I consider your example more misleading that helpful, to be honest. It’s also obviously quite inefficient to iterate through all objects, just to retrieve a reference. It may still be acceptable in your example, for other use-cases it isn’t.

If there is only one object with Id property is_theone and obj[“is_theone”] != obj.name then it has most likely been renamed.

An “is_the_one” property makes no sense in this example. You want to store an identifier, if anything. Yes, you can detect renames/copies that way, but when are you given the chance to do that? Technically, anything could happen between your getter/setter calls.

There are other workaround solutions to this as well, one of which is setting up a dummy driver & make the pointed to object a variable target.

I’m sure there are lots of terrible workarounds to this (such as installing a scene update callback and detecting renames there).

A robust solution is to build a tracker for tagged objects. It’s a little bit of a pain to setup, but it works.

This file untitled.blend (467 KB) saves the object and scene reference into the struct, and lazily caches the object when later retrieved. I deliberately don’t cache other objects during the search in case it starts to become costly. You might decide to change that, it’s only a simple demo.

Your solution has the same problem with copying.

Even scene handler callbacks can’t solve the issue IMO, because undo operations pretty much break everything (i.e. lead to crashes sooner or later or messing up the references somehow).

I was thinking about tracking renames/copies that way, not storing references. I’m pretty sure I saw you giving an example for that somewhere, which is why I brought it up.

To the doubters, oracles, know alls, righteous, disingenuous & those that like to find a solution to something that they say can’t be done

test this out.

It uses the suggestion (aka terrible workaround) of setting up a driver and using driver targets as a way to keep object references.
The bake object list has a field to keep the driver variable name that references the cage object, & another to flag to see if it is a valid object.

AFAICT it passes the rename, copy and ‘UNDO’ tests.

It will fail ofcourse if the driver is deleted, (or drivers are not enabled) or the power is off or whatever.

I also have code that does the same with id props, but I find this way preferable… less tests and more efficient, as there is the need to iterate thru the objects in some cases.


import bpy
from bpy.props import (CollectionProperty,
                       IntProperty,
                       BoolProperty,
                       FloatProperty,
                       StringProperty)

def set_cage(self, objname):
    self.name = objname
    bake_object = self
    scene = self.id_data
    self.save_cage_object = objname
    obj = scene.objects.get(objname)
    if obj is not None:
        self.valid_cage_object = True
    dref = [d for d in scene.animation_data.drivers 
            if d.data_path.startswith('["cage_object"]')]      
    if len(dref):
        d = dref[0]
        var = d.driver.variables.get(self.driver_var_name)
        if var is None:
            var = d.driver.variables.new()            
        self.driver_var_name = var.name
        var.targets[0].id = obj
    
    return None
   

def get_cage(self):
    # if there is an object return it

    scene = self.id_data
    
    obj = scene.objects.get(self.save_cage_object)
    if obj is not None:
        self.valid_cage_object = True
        return self.save_cage_object
    
    # ok it's been removed or renamed
    dref = [d for d in scene.animation_data.drivers if d.data_path.startswith('["cage_object"]')]
    if len(dref):

        d = dref[0].driver
        var = d.variables.get(self.driver_var_name)
        if var is not None:
            test = var.targets[0].id
            if test is not None:
                print("setting from name")
                self.save_cage_object = test.name
            else:
                self.valid_cage_object = False

    return self.save_cage_object
            

class BakeObjectList(bpy.types.PropertyGroup):
    '''name = bpy.props.PointerProperty()''' #name of the object
    index = IntProperty()
    driver_var_name = StringProperty() # var name matching object
    save_cage_object = StringProperty() # save the object name for quick ref.
    cage_object = StringProperty(set=set_cage, get=get_cage)
    extrusion = FloatProperty(name="Extrusion", default = 0)
    active = BoolProperty()
    valid_cage_object = BoolProperty(default=False)
    uv_map = StringProperty()

def register():

   
    bpy.utils.register_module(__name__)
    bpy.types.Scene.bake_objects = CollectionProperty(type=BakeObjectList)
    bpy.types.Scene.bake_object_index = IntProperty()



def unregister():
    bpy.utils.unregister_module(__name__)
    del bpy.types.Scene.bake_objects
    del bpy.types.Scene.bake_object_index
    
if __name__ == '__main__':
    register()
    scene = bpy.context.scene
    bpy.context.scene['cage_object'] = 0
    dref = [d for d in scene.animation_data.drivers 
            if d.data_path.startswith('["cage_object"]')]
    if not len(dref):
        scene.driver_add('["cage_object"]')
    else:
        scene["cage_object"] = len(dref[0].driver.variables)
    #add the Cube to bake_objects
    o = bpy.context.scene.bake_objects.add()
    o.cage_object = "Cube"

And a quick panel code to test


import bpy


class LayoutDemoPanel(bpy.types.Panel):
    """Creates a Panel in the scene context of the properties editor"""
    bl_label = "Layout Demo"
    bl_idname = "SCENE_PT_layout"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "scene"

    def draw(self, context):
        layout = self.layout
        scene = context.scene
        bos = context.scene.bake_objects
        for bo in bos:
            row = layout.row()
            row.alert = not bo.valid_cage_object
            row.prop(bo, "cage_object")
            row.prop_search(bo, "cage_object", scene, "objects", text="")
            
def register():
    bpy.utils.register_class(LayoutDemoPanel)


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


if __name__ == "__main__":
    register()

I’ve never doubted that there are ugly workarounds to this problem. The OP asked how to store pointers to objects (like you can do perfectly well in C) from Python. That can’t be done.

I laud you for posting some actual code for your workaround. Unfortunately your attempt at optimization made the code less correct. I must reiterate, in the time between your getter/setter calls, anything can happen. If you have a reference to an object by the name “A” and your user renames that object to “B” and some other object to “A”, your references will be incorrect if the name has been “saved” before.

It would be simpler and more reliable to just store a driver variable whose identifier you “control” and not use the object name at all.

If you follow CoDEmanX’s example (assuming that it is working correctly), you could track renames/deletes and fix up references that way. This would have the benefit of having no performance penalty during “normal” operation and not spamming the UI with boilerplate drivers.

Thank you for all of your replies. They have been very useful, I read all of them. Apparently I couldn’t participate the discussion, as I wanted to.