Properties aren't there ... until you look at them?

Properties are very strange indeed …

Here’s a simple addon:

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()

It just creates a property group (“context.scene.app”) with one string property (“s”).

If you enable the addon, you can check to see if “app” is in the scene via the Python Console:

  >>> 'app' in C.scene
  False

So it seems that it’s not there. But if you “look” at the scene (using auto completion, for example), it shows up:

  >>> C.scene.  (Control-Space)
              active_clip
              active_layer
              animation_data
              animation_data_clear(
              animation_data_create(
              app
              as_pointer(
              audio_distance_model
              audio_doppler_factor
              audio_doppler_speed
              audio_volume
              background_set
              users
              values(
              view_settings
              world

Then when you check again … it’s magically there!!

  >>> 'app' in C.scene
        True

I’m trying to determine when a property exists or not, and it’s not clear what’s the most reliable method.

Any thoughts?

If you use

hasattr

to check for its existence it is there, you can also use

getattr

to access it, if you set it using

container.stuff = "Hello"

testing with

stuff in container

will turn out true.
I guess auto completing triggers the creation in some way.

If you get a pointer property like

app = C.scene.app

and it does not exist yet, you would have to return a “default” / proxy object delegating method / attribute calls.

As soon as you set an attribute of app you would have to replace the proxy object app is binded to with an real instance of the PropertyGroup behind. Since you can nest PropertyGroups you might get in trouble soon.

So for me it seems like creating PropertyGroups directly if you get a PointerProperty is easier.

Maybe blender does not save empty dictionaries to the blend.

“app” in C.scene

checks for the key “app”, so basically:

C.scene.get(“app”) is not None

whereas C.scene.app will try to access the attribute “app”.

>>> C.scene["app"]
Traceback (most recent call last):
  File "<blender_console>", line 1, in <module>
KeyError: 'bpy_struct[key]: key "app" not found'

>>> C.scene.app
bpy.data.scenes['Scene'].app

>>> C.scene["app"]
<bpy id prop: owner="SCScene", name="app", address=0x00000056C9F5A848>

I assume the property registration will make C.scene.app available for Python, but does not create an ID property internally yet (there are only ID props in .blend, global properties in Python are essentially sugar for scripters). As the attribute is accessed, an ID prop is apparently created in the background, which makes C.scene[“app”] available alongside from C.scene.app

Thanks for the great suggestions, but I’m not sure what to do with them.

Maybe it will help if I give a little explanation …

We are developing a Blender addon that acts as a front end for a fairly complex simulation. Our end users will use the Blender interface to design their model, run it, and display the results. This all works very well!!

But now we need to start making changes to the properties that we store in the .blend files for a variety of reasons. Some of those changes will “break” compatibility with previously stored .blend files saved by our end users. So we want to establish a system for identifying which version of the addon wrote a particular .blend file so we can “upgrade” the properties when we open it via a load_post handler.

One impediment is that Blender properties (like s = StringProperty(name=“s”,default=“A”)) don’t appear to be stored in the .blend file as ID properties unless their value is changed. Since a version number won’t be changed within the application, it will always return the current default (in other words, when a .blend file saved with default=“1.1” is opened by an updated addon with default=“1.2”, the value read from the .blend file will be “1.2” even though it had been “stored” with the addon of version 1.1).

It’s been a little maddening (and eye-opening!!) trying to figure this out. If anyone has any suggestions, they would be welcome!!

Set the version of the property instead of storing as default


VERSION = (1,2)

class Something:
    version = StringProperty()

# in some init function or pre_save_hander perhaps
my_something.version = ".".join(VERSION)

Thanks again for the great suggestions.

I think the hasattr approach might work:

>>> 'app' in C.scene
False

>>> hasattr(C.scene,'app')
True

>>> 'app' in C.scene
True