1
$\begingroup$

I had a complex code, which failed. To test things out I simplified it to this:

from bpy.types import Scene, PropertyGroup
from bpy.utils import register_class, unregister_class
from bpy.props import StringProperty, PointerProperty

class PropGroup_1(PropertyGroup):
    my_str_1: StringProperty()

class PropGroup_2(PropertyGroup):
    my_str_2: StringProperty()

my_prop_groups = [
    ('my_props_1', PropGroup_1),
    ('my_props_2', PropGroup_2)
]


def set_prop_groups(enable):
    if enable:
        for group_name,group in my_prop_groups:
            register_class(group)
            setattr(Scene, group_name, PointerProperty(type=group))
    else:
        for group_name,group in reversed(my_prop_groups):
            delattr(Scene, group_name)
            unregister_class(group)


set_prop_groups(True)  #True/False switched

Describing the problem :

  1. When I run the code with set_prop_groups(True) — it works fine.

  2. Few seconds later when I run it with set_prop_groups(False) — an error emerges,

unregister_class(group) causes the exception:

RuntimeError: unregister_class(...):, missing bl_rna attribute from 'RNAMetaPropGroup' instance (may not be registered)
  1. Out of curiosity, I tried to test the automated switching, hence I replaced the last line with this:
from time import sleep

set_prop_groups(True)
sleep(2)
set_prop_groups(False)

(!) Oddly enough, with automated delay the code works fine.

* Disregarding if the delay is set to few seconds or one millisecond – all delays do the job.


The question:

Why unregister_class(group) fails in case when I switch True/False manually?

$\endgroup$
0

1 Answer 1

2
$\begingroup$

The reason is because every time you run a script in the text editor new instances of the classes are defined and the links to the previous ones are discarded. You can try print(id(group)) in your function and see every time your run the script the property groups point to a different address in memory. The sleep method works, because the classes are the same (point to the same memory location) in both function calls.

This is why addons need to implement a register and unregister function, the pointers are stored until the addon is unregistered.

Unfortunately the python scripting editor doesn't support unregister, so you'll have to build and run your script as an addon if you want to use this feature.

Here's an extremely hacky solution that will parse subclasses of PropertyGroup and try unregistering the classes at runtime if name matches. Note every other time you run the script 2 new orphaned instances of your property groups will be added to the namespace. They won't be used because the script will unregister the ones from the previous iteration. It will reset when you quit and restart Blender, but still, small memory leak warning.

from bpy.types import Scene, PropertyGroup
from bpy.utils import register_class, unregister_class
from bpy.props import StringProperty, PointerProperty
import bpy

class PropGroup_1(PropertyGroup):
    my_str_1: StringProperty()

class PropGroup_2(PropertyGroup):
    my_str_2: StringProperty()

my_prop_groups = [
    ('my_props_1', PropGroup_1),
    ('my_props_2', PropGroup_2)
]


def set_prop_groups(enable):
    if enable:
        for group_name, group in my_prop_groups:
            register_class(group)
            setattr(Scene, group_name, PointerProperty(type=group))
    else:
        for group_name,group in reversed(my_prop_groups):
            delattr(Scene, group_name)
            for child in PropertyGroup.__subclasses__():
                if child.__name__ == group.__name__:
                    try:
                        unregister_class(child)
                    except RuntimeError:  # This one is not registered
                        pass


set_prop_groups(not hasattr(Scene, "my_props_1"))  # Toggle True / False
$\endgroup$

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.