How to make a property pointing to an object

Hi!

I am creating a script where an object has a custom property which needs to point to another object. Right now, I provide it the name of the object for example:


my_object['my_property'] = bpy.data.objects['another_object'].name

This allows me to know from the first object what other object it is associated with. But the problem is that if another_object change its name, the property will not reflect the new name. Is there a way for a property of an object to reference another object in a way that it would reflect its name even if it changes. This relation should be saved in the .blend file.

An example of what I want is, when creating a mirror modifier, it is possible to use a Mirror Object. This property will reflect the name of the linked object even if it changes and will be saved with the .blend file.

Would I need a CollectionProperty, an EnumProperty or a PointerProperty? I am not sure to understand well these ones.

Thanks for the help!

1 Like

There is a good reason why this does not work as you expect. Because here you reference a string. When python stores a variable it stores it into a specific adress and reference a variable to it, if you assign a different string to your variable it does not edit the reference data, rather it creates a new reference. The reason why python is doing is obviously to avoid creating new data so for example

a= 1
b = 1
c = 1

may seem like here we got 3 data with the value of 1, but the data is only 1 just that all three variable reference to the same data.

A way to solve your problem will be

my_object[‘my_property’] = bpy.data.objects[‘another_object’]

then to find the name you will do

my_object[‘my_property’].name

oh oh… property has also a name attribute … damn which means the property’s name attribute somehow overide the object’s name attribute.

you could access the name of the object by number instead of using a reference like

bpy.data.objects[2].name

Surely there is a way to do this properly.

Here are a couple of workarounds. Set up a zero influence constraint of some sort, well one that requires a target obj on the object. Use the target prop of the constraint as your pointer.

Another way is to have a prop with some kind of key. Look for other objects in the scene with the same key.

Hi Kilon!
Thank you for your help.
I already tried what you told me. In fact, it does not work (or I don’t understand how it work). If I place the id_data in the property, this one seems to be empty. So doing :

my_object['my_property'] = bpy.data.objects[1]
print(my_object['my_property'])

it returns : <bpy id property from “OBmy_object”> This seems to be a property which has no value. Can I access my object from there? If yes, I don’t know how.

In fact, only floats, ints and dicts are allowed in ID property. So, is there a number or an address which does not change for a specific object, to be able to find it back even if its name has changed?

Here is a way of doing it using code from http://wiki.blender.org/index.php/Doc:2.5/Manual/Extensions/Python/Properties#Properties.2C_ID-Properties_and_their_differences
for custom dynamic properties.

Here is some code i came up with… used a simple global to keep the object in. Some kind of dictionary with the objects name would be better.


import bpy #needed in a script-text-window!


def fetch(collection,name,create=False,**newargs):
# returns item from collection , None if not there, creates if create is True passes newargs to new method.
    
    if name in collection:
        return collection[name]
    
# maybe put in code for replace too.
    
    if create:
        if len(newargs):
            try:
                
                newitem = collection.new(**newargs)
                newitem.name = name
            except:
                newitem = collection.new(name=name,**newargs)
        else:
            try:
                newitem = collection.new(name=name) #for things like scene objects
            except:
                newItem = None

            
        
        return newitem
    
    return None

pointerobj = None

def fget(self):                                 # custom get function
    global pointerobj
    po = fetch(bpy.data.objects,self["pointerobj"])
    if pointerobj == None:
        pointerobj = po
    if po != pointerobj:
        self["pointerobj"] = pointerobj.name 
    return pointerobj                             # return value
 
def fset(self, obj):    
    
    global pointerobj
    self["pointerobj"] = obj.name 
    pointerobj = obj                      # custom set function
                # if object is at origin
       
bpy.types.Object.pointer = property(fget, fset)# assign function to property
 
ob = bpy.context.active_object
ob["pointerobj"] = ""                  # get the active object
print(ob.pointer)                              # print distance to the console
ob.pointer = bpy.context.object                                 # set the distance
 
class myPanel(bpy.types.Panel):                 # panel to display new property
    bl_space_type = "VIEW_3D"                   # show up in: 3d-window
    bl_region_type = "UI"                       # show up in: properties panel
    bl_label = "My Panel"                       # name of the new panel
 
    def draw(self, context):
        # display "distance" of the active object
        self.layout.prop(context.object.pointer,"name")
        self.layout.label(text=str(bpy.context.active_object.pointer.name))
 
bpy.utils.register_class(myPanel)               # register panel

The name of the pointer object will be saved in the file and changes when you change the name. You also end up with a pointer property on all objects in the scene, as opposed to the ID prop [“pointerobj”] which is only on the objects it is defined on. This will throw a bug… etc etc… First time I’ve played around with dynamic props.

sorry for confusing you there , yeah it work but it references the entire object not just the name. My problem is that when you add a property to the object that property has a name attribute that overrides the name attribute of the object you have referenced.

Anyway sorry for the confusing post, no sorry I don’t know a way to solve your problem.

Hi batFINGER!
Thank you for your contribution.

Your solutions would seem to work. At first glimpse, I thought that using a constraint to store the relation was not very elegant. But after thinking about it, using the “Child of” constraint has some meaning to me. But I would really prefer if there was a kind of Dummy constraint, but this does not exist.

As you suggest, using a coded unique ID I create would be more elegant. But this would means to traverse all the objects to identify the one I need.

I will think about these options which would both work.

But does this mean that there is actually no way to directly create a property that points to an object? I think that this is a sad limitation.

I don’t think you understand what i said in my previous post let me clarify it

lets say we

a = “I am a string”
b = a

b-> “I am a string”

ok so far now if you

a =“I am not a string”

b ->“I am a string”

on the other hand if you

a = [“I am a string”]
b = a
b-> [“I am a string”]
a = [“I am not a string”]
b-> [“I am not a string”]

string variables reference by value , while lists, dictionaries and all other python object, reference by object. Thus you get different results

in this case

object[].name is a string type thus it will only reference by value and will not be able to reference by object as you want

for example if i do obname = object[].name what is reference is NOT name but actually the value of the name which in this case could be “Cube”

You can of course store object into properties in a scene like this

bpy.types.Scene.object_ref = bpy.data.objects[“Cube”]

even if we change the name of the mesh object “Cube” object_name will still point at the object

you can access the property like this

bpy.context.scene.object_ref

and its name

bpy.context.scene.object_ref.name

Properties stored in a scene are saved also with your blend file.

hope that helps

Oh crap… New it would be simple.


ob["pointer"] = ob2.id_data

Thank both of you. I think I’ll have to compute all of this information in my head for a moment. But your help is very welcome!

Actually, your approach with the dynamic pointer property() seems to be interesting. At least, it seems be a good clue. I’ll look into that, but this brings me to the next problem.

This would mean that every scenes would have this object_ref property. I need a property only on objects; and even more, on only certain objects. Here is the next problem.

If I cannot solve my problem with an ID property, then I will have to create a new property attached to bpy.types.Object. And then, all objects will have these property, which is not what I want.

So, is it possible to dynamically create a property() on a specific object instead than on the type. Or better, is it possible to create a type of object derived from bpy.types.Object which would have the properties I need, but then be recognised as so when reloading a .blend file? (evidently, I need my script to be loaded to recognise the new type)

Ok sorry. I don’t get it. It is what Kilon talked about.

but if ob is named ‘ob’
When I try to access ob[“pointer”] it returns :
<bpy id property from “OBob”>
this is not an id_data like bpy.data.objects[“ob2”]

and it is from “OBob” not “OBob2”.
So I don’t understand if it is really the way to do it. How do I access bpy.data.objects[‘ob2’] from ob[‘pointer’]?

Ignore my previous post … that doesn’t work either.

hmphr! I was so excited for a moment; and…

Then I come back to my last problem:
Is it possible to dynamically create a property() on a specific object instead than on the type. Or better, is it possible to create a type of object derived from bpy.types.Object which would have the properties I need, but then be recognised as so when reloading a .blend file? (evidently, I need my script to be loaded to recognise the new type)

Let me make this as simple as possible. What you trying to do is not possible in python. As I far as I know that is.

Because each time you try to reference the object name , because it is a string you will reference only its values. That meant that python will point to where you string is located in memory . This seem logical, BUT each time the name is changed a NEW STRING IS CREATED and thus THE SAME PREVIOUS STRING IS NOT EDITED.

Lets say you create a variable a and reference to the object name

a = ob.name

So far so good. What happened here , simply python made a variable and pointed to the same memory address that ob.name is located. Lets say for example that ob.name value is “Cube” and is located in memory address 0001 , a will also point to the address 0001 where a string “Cube” is stored. Thus each time you try to access the value of variable a it will point you to memory address 0001 where a string “Cube” is stored.

Ok so far so good, but here come the tricky part.

If you change your ob.name lest say you did ob.name = “Cube2” you would assume that all python is doing is going to memory address 0001 and change the string that is located there to “Cube2” , because the a variable already points to the same address then you equally assume that a has also the value “Cube2”. This a very logical assumption BUT THIS IS NOT HOW PYTHON WORKS!

What python REALLY does when you change ob.name is to create a NEW STRING and assign it a NEW MEMORY ADRESS , lets say for example memory address 0002. Because ob.name now points to address 0002 it has broken the reference with variable a which still points to address 0001.

WHY PYTHON DOES THIS ?

Python tries to save as much memory as it can so it make sure ANY variable that has the value “Cube” will point to exact same address , instead of each one having a different address. Thus if you got 1 million different variable but all of them were assigned the string “Cube” the would only consume a single slot from the memory and thus save you a million times more memory :wink:

I don’t think there is a workaround for this, but I may be wrong. The best way for you is to check periodically via a loop tif there is a change om the object name

@Kilon
Hi!
Thank you for all these precisions. It’s interesting to know what really happens when creating a value in python. But just to be clear, I never assumed that my_property value would update when ob.name changes in:

my_object['my_property'] = ob.name

In fact, this is precisely the problem I was exposing.

This is a temporary solution and I know that this will be a bug in my addon if it stays like that.

What I would need, is a reference to the object itself and not its name. This would work. Unfortunatly, there is no ID property that can store a reference to an object in the API, as I understand it. At least, this is a part of my question.

And if you can help me on that, it would be very appreciated. (I see that the mirror modifier can store a reference to an object in the mirror_object property)

Since the ID properties can only store basic values such as int, float, and string; the object itself cannot be referenced directly. But there could be an indirect way if there was a unique ID for each objects. Then, this unique ID could be saved as a string in the ID property and accessed back by using this unique ID.

In the API, each objects have an id_data. For example, the id_data of my_object could be <bpy_struct, Object(“Monkey”)>. This could be stored in the property as a string. But the only significant information it contains, is that it is an “object” which is named “my_object”. The problem with the id_data is that it is only unique at a precise moment. The id_data changes as the object name changes. And another object could take its name, and take the place of the first id_data. So id_data are not unique.

For example: Looking for a unique ID, I found that it is possible to get the address of an object by doing :

address =  my_object.as_pointer()

The address will be represented as an int which can easily be stored in an ID property. The object can be retrieved by traversing bpy.data.objects. (Maybe there is a more direct way but I don’t know it) The problem with the address, is that the object will most certainly not occupy the same memory addres when reloading the .blend file.

So here are my questions :

  • Is there a way to create with the python API a property like MirrorModifier.mirror_object which points to a specific object?
  • Is there a truly Unique ID for each objects which remains the same even if the name of the object has changed?

Any help is very appreciated.

Seems you can save a dynamic ref in a scene via kilon’s


bpy.types.Scene.myref = bpy.data.objects["foo"]

This however i’ve found buggy… well crashy. Had a squiz at a few addons and they often save in a similar fashion, (to defined props) to the WindowManager object. Here is a snippet from rigify’s init



    IDStore = bpy.types.WindowManager
    IDStore.rigify_collection = bpy.props.EnumProperty(items=col_enum_list, default="All", name="Rigify Active Collection", description="The selected rig collection")
    IDStore.rigify_types = bpy.props.CollectionProperty(type=RigifyName)
    IDStore.rigify_active_type = bpy.props.IntProperty(name="Rigify Active Type", description="The selected rig type")

Until that unique ID pops out I think the dynamic prop and using an IDstore to save could be the go.

yes I knew you did not assumed it was working I only tried to explain to you , why it does not and why as far as I know (being a python noob) there can be no workaround.

Nope this —> <bpy_struct, Object(“Monkey”)> IS NOT your id name. This —> “Monkey” is you id name. Ok what we observe here ?

  1. Its a , surprise surprise, string
  2. id name and object name are the same
  3. There more info printed —> bpy_struct, Object()

actually this the object definition. What is says to you is "Hi I am an object instance, my class is Object , my father class is bpy_struct and my id name is “Monkey” "

This is clear cut case of object inheritance . Class “Object” inherits from the super class “bpy_struct” which is the main class of all other classes inside bpy. Bpy_struct is actually a dictionary , a python dictionary. The id_name and the property name (object name) both are pointing to the dictionary key. Dictionaries keys are ALWAYS UNIQUE. So there is no way you can have different id name and different object name , or that you may have 2 id_names that are the same. Actually if you change the name of your object you change also the name of the id of the object.

BUT as i said you can access your objects by number. for example

bpy.data.objects[“Monkey”] = bpy.data.objects[3]

both parts point in the same object. Bare in mind that I am not doing an assignment here , I just try to show you that both are the same. There is no other way to change the number of your object by assignment you can only change it if you delete the object and of course the object next in the que will take its number. And this why i said to you to access your object by number , you can change the id name all you want , the number will forever stay the same even if you add new object to your scene.

Actually blender already shows you the number of each object in the Outliner window , not the actual number but the order of the objects.

The as_pointer() is only useful if you want to know the actually memory address as I said if you exclude strings and numbers everything else in python is assigned by reference , so knowing the address does not offer any substantial advantage.

so you could do

adress =bpy.data.objects[“Monkey”]
or
adress = bpy.data.objects[3] # assuming its 3rd in order
or even
adress = bpy.context.selected_objects[0] #assuming its selected or is selected with other object but is the first in the selection

the assignment always gives a reference to an python object in this case a blender object

I and BatFinger already explained you how to store a object reference in a property.

I think I answered all your questions I have nothing more to add ,good luck with your addon.

@batFINGER
Yes, I think that using the WindowManager to store the information would be another working solution. But it seems a little complex to cover all my needs. For example: when duplicating both objects, the new relation with the new objects should also be reflected.

Finally, I thought it was possible to create a reference to an object FROM an object. Since it doesn’t seem to be possible, and that all these solutions are workarounds, I think that the best, the simplest, and the most complete one was the first suggestion you made. Using a kind of dummy constraint with no influence would do the trick. It is not the most elegant solution but it has the advantages of being very simple, brings the possibility to reference as many objects as I need without puting some useless property on the class itself, will be saved in the .blend file and will update when duplicating the objects. This solution answers to almost all my needs.

I hope there will be a way to add this kind of reference property on the objects itself in the future.

If you’re interested, the addon I am writing will probably be named “Object Manipulator”. It will be a kind of shortcut to some more or less complex modifier setup. It will considerably accelerate certain workflow. Anyway, I will open a new thread when it is ready for testing.

Thank you for your help!
See you!

Hi Axon, im facing the same problem, trying to find a clean way to reference an existing object.

Ive been diggin into an object’s parent property which is a pointer property which references another object in your scene or None.
What im gonna try is something like:


bpy.types.Object.myReferenceObject = bpy.props.PointerProperty("something that i dont know yet").

what im missing is that we have to declare a propertygroup and im not sure what im supposed to provide there yet, but ill play around and see where it goes.

Thansk kilon and batfinger to provide tips to achieve this.

PointerProperty can’t be used to create an ID reference, we would need an IdProperty type, but there is none. The only way is to use a StringProperty, and that won’t update if idblock name is changed. With an app handler, it would be possible however to keep track of changes and update string props, but i worry it would add quite a lot overhead (more than its worth)