Properties in Addons and Blend Files

I’ve observed some unexpected behavior of properties in .blend files, and I’m looking for some help understanding it.

It appears that PropertyGroup properties are NOT stored in .blend files if they haven’t been changed from their default values. So if an addon changes the default value of a property, that value will be changed “retroactively” in all .blend files that have used that addon and not modified the property.

To be more specific, assume that an addon sets the default for a StringProperty (named ‘s’) to the value of “A”. When the .blend file is saved, we might expect the StringProperty ‘s’ to have a value of “A” in that .blend file. It doesn’t. If you open the .blend file with the same addon enabled, you will find that the value of ‘s’ is still “A”. But if you change the default value in the addon to something else (like “B”), then when you open your saved .blend file (saved when ‘s’ was “A”) you’ll find that the value of ‘s’ is now … “B”!! However, if you change the value of ‘s’ from its default to anything else (like “C”) within Blender (via the console, for example) and save the .blend file, then that value WILL be stored in the .blend file, and it will be read as “C” regardless of the default value in the addon.

You can get more insight into this by examining the “ID” properties that are generated by the addon. It appears that if the default value (“A” in this case) isn’t changed, then no “ID” property is generated or stored in the .blend file. But as soon as the value for ‘s’ changes, then the “ID” property is created to maintain this changed value in the .blend file.

Is this a bug or can anyone offer an explanation that would make sense of this behavior? Thanks in advance.

P.S. Here’s a simple addon that I used (along with the console) to come to these conclusions:

bl_info = {
   "version": "0.1",
   "name": "Simple Property",
   'author': 'Bob',
   "location": "Properties > Scene",
   "category": "Blender Experiments"
   }
 
 import bpy
 from bpy.props import *
 
 
 class AppPropertyGroup(bpy.types.PropertyGroup):
     s = StringProperty(name="s",default="A")
 
 def register():
     print ("Registering ", __name__)
     bpy.utils.register_module(__name__)
     bpy.types.Scene.app = bpy.props.PointerProperty(type=AppPropertyGroup)
 
 def unregister():
     print ("Unregistering ", __name__)
     del bpy.types.Scene.app
     bpy.utils.unregister_module(__name__)
 
 if __name__ == "__main__":
     register()
 

There are two kinds of properties in python scripting, ID properties (local to an object / instance) and bpy.props properties (global to a type).

However, internally there are only ID properties.

bpy.props properties are stored as ID properties in datablocks, but only if the value is different than the default - you don’t need to store a bunch of ID properties if Blender knows the default value in case that property is abscent from a certain datablock.

Thanks for confirming what I had suspected (translation: feared).

you don’t need to store a bunch of ID properties if Blender knows the default value in case that property is absent from a certain datablock

That’s true if addons never change their defaults.

Let’s say that an addon uses a default for a particular boolean of “False”. That boolean is attached to hundreds of objects in a scene - “True” in some cases, and “False” in others … as chosen by the artist. As the addon evolves, it is decided that “True” would be a better default, and that change is made. Now when a previously saved .blend file is opened all of the previously defaulted “False” values suddenly become “True”. I think the artist would be surprised (at a minimum!!).

Is it true that If we use ID properties for everything, then we won’t have this problem?

Also, is it possible to do everything (including user interface stuff) with just ID properties? All the examples I’ve seen use bpy.props properties for user interface code, and I was wondering if that can be avoided. Any thoughts?

Thanks for replying.

If you explicitly set them to their default values they are also stored.

thanks for the hint, pink vertex!

@BlenderHawkBob: you can use ID properties in UI too


import bpy


class HelloWorldPanel(bpy.types.Panel):
    """Creates a Panel in the Object properties window"""
    bl_label = "Hello World Panel"
    bl_idname = "OBJECT_PT_hello"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "object"

    def draw(self, context):
        layout = self.layout

        obj = context.object

        row = layout.row()
        row.prop(obj, '["foo"]')
        
        # wrong: bad quote marks
        #row.prop(obj, "['foo']")


def register():
    bpy.utils.register_class(HelloWorldPanel)


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


if __name__ == "__main__":
    bpy.context.object["foo"] = "moo"
    register()
    


But you don’t get the same amout of freedom, such as step size property, subtypes, arrays…

Well, you could put the version number of the addon used with the blend file to the blend file and check for it with an load_post handler (and adjust the version number here if lower).

You could also store the default values somewhere in the blend and use a modified getter to read it from the blend. I.e.


def get_prop(self):
    value = self.get('prop_name')
    if value:
                 return value
    else:
                 return bpy.data.scenes['Scene']['default_values']['prop_name']

So if the version number is not present in the blend file you could create the legacy default values for the blend file.