3
$\begingroup$

How to set a default value for bpy.props.CollectionProperty?

Let's use the example in the documentation for an answer.

I tried to set the default value in the register function, but neither bpy.data nor bpy.context is available at that moment.

$\endgroup$
4
  • $\begingroup$ There is no default for a collection. By default the collection is empty when first defined. $\endgroup$
    – batFINGER
    Commented Sep 26, 2018 at 16:07
  • $\begingroup$ Maybe there is a walk-around. I tried to set the default value in the register function, but neither bpy.data nor bpy.context is available at that moment. $\endgroup$
    – vvoovv
    Commented Sep 26, 2018 at 17:06
  • $\begingroup$ Can you add to question what you want the default to be? From comment sounds like you want to add a default item on creation. One trick is to add a handler during rego that runs until one "default" item is added, then removes itself (the handler). $\endgroup$
    – batFINGER
    Commented Sep 27, 2018 at 12:06
  • $\begingroup$ @batFINGER I've added a solution that worked for me as the answer $\endgroup$
    – vvoovv
    Commented Sep 27, 2018 at 23:45

2 Answers 2

5
$\begingroup$

The idea is to use application handlers to set the default value for bpy.props.CollectionProperty:

Here is the code:

import bpy
from bpy.app.handlers import persistent

class MyCollectionItem(bpy.types.PropertyGroup):
    name = bpy.props.StringProperty(name="Name", default="Unknown")
    value = bpy.props.StringProperty(name="Value", default="Unknown")

def setDefaultCollectionValue():
    myCollection = bpy.context.scene.myCollection
    # set default value if <myCollection> is empty
    if not myCollection:
        collectionItem = myCollection.add()
        collectionItem.name = "Name 1"
        collectionItem.value = "Value 1"
        collectionItem = myCollection.add()
        collectionItem.name = "Name 2"
        collectionItem.value = "Value 2"

# This handler is needed to set the default value for <bpy.context.scene.myCollection>
# right after the addon registration via register()
def onRegister(scene):
    setDefaultCollectionValue()
    # the handler isn't needed anymore, so remove it
    bpy.app.handlers.scene_update_post.remove(onRegister)

# This handler is needed to set the default value for <bpy.context.scene.myCollection>
# after any of the following events:
# - opening Blender
# - reloading the start-up file via the keys Ctrl N
# - opening any Blender file
@persistent
def onFileLoaded(scene):
    addDefaultLevels()

def register():
    bpy.utils.register_class(MyCollectionItem)
    bpy.types.Scene.myCollection = bpy.props.CollectionProperty(type=MyCollectionItem)
    # adding handlers defined above in the code
    bpy.app.handlers.scene_update_post.append(onRegister)
    bpy.app.handlers.load_post.append(onFileLoaded)

def unregister():
    bpy.utils.unregister_class(MyCollectionItem)
    del bpy.types.Scene.myCollection
    bpy.app.handlers.load_post.remove(onFileLoaded)

if __name__ == "__main__":
    register()
$\endgroup$
1
  • $\begingroup$ This isn't working as excpected :( It only fires, when I change something in the scene, after registering the addon. What is unfortunate, because I have a collectionProperty that needs a default value and gives me an error without. $\endgroup$
    – user151528
    Commented Feb 18, 2023 at 13:50
0
$\begingroup$

This is a hacky, not optimal solution, but I've given up trying alternatives.

Register a preference (better is to use an existing one, so your preferences aren't cluttered)

class Preferences(AddonPreferences):
    example: StringProperty(
        name="foo"
        update=callbackfunc
    )

In the callbackfunc, you can populate your CollectionProperty via .add.

Then just change this preference in order to trigger the callback:

bpy.context.preferences.addons[__name__].preferences.example = str(bpy.context.preferences.addons[__name__].preferences.example)

Just beware that draw() gets called an awful lot so have some sort of "do we need to update?" function so you don't hammer your CPU/disk.

skipUpdate = False

class MyPanel(Panel):

    def draw(self, context):
        global skipUpdate

        if not skipUpdate:
            bpy.context.preferences.addons[__name__].preferences.example = str(bpy.context.preferences.addons[__name__].preferences.example)
            skipUpdate = True

I'm sure with more development time you could find a different callback that doesn't require registering a preference.

(By the way, this seems to be the method Material Library VX uses, which is where I shamefully borrowed it from.)

$\endgroup$

You must log in to answer this question.

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