1
$\begingroup$

actually I want to show a EnumProperty with all the current collections.

all works fine if I run the addon from text editor

enter image description here

but if I try to install it I have '_RestrictData' or '_RestrictContext' atributeErrors.

enter image description here

I test some solutions like "persistent" AttributeError: '_RestrictData' object has no attribute 'filepath'

or "prepend"

AttributeError: '_RestrictContext' object has no attribute 'space_data'

and others, but nothing works

What could I do to achieve my goal?

bl_info = {
    "name" : "test",
    "author" : "Diego Quevedo",
    "description" : "",
    "blender" : (2, 80, 0),
    "version" : (0, 1, 0),
    "location" : "",
    "warning" : "",
    "category" : "Generic"
}



import bpy
from bpy.props import *
from bpy.types import (
    AddonPreferences,
    Operator,
    Panel,
    PropertyGroup
)
from bpy.app.handlers import persistent


@persistent 
def returnCollections():

    if len(bpy.context.scene.collection.children) <= 0:
        bpy.data.collections.new("TEST")
        
    collections = [c.name for c in bpy.context.scene.collection.children]
   
    items =[]
    
    for c in collections:
        items.append((c,c,c))
    

     
    r= (items,collections[len(collections)-1])
    
    return r    
       
bpy.app.handlers.save_post.append(returnCollections)

         
    
    
class Test_OT_Test(bpy.types.Operator):
    bl_idname = "view3d.test"
    bl_label = "Test"
    bl_category = "Test"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_options = {'REGISTER', 'UNDO'}
    
    
    CollectionName:  EnumProperty(
        default= returnCollections()[1], 
        items =returnCollections()[0],     
        name="Collections"
    )



    def Test(self, context, CollectionName):
        
        print("test")
        
        
    @classmethod
    def poll(self, context):
       return context

    def invoke(self, context, event):
        return context.window_manager.invoke_props_dialog(self)
    def execute(self, context):
        D = bpy.data
        C = bpy.context

        self.Test(C, self.CollectionName)
 

        return {'FINISHED'}

####################
class Test_PT_Test(bpy.types.Panel):
    bl_idname = "panel test"
    bl_label = "Test"
    bl_category = "Test"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"

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

        row = layout.row()

        row.operator("view3d.test", text="test", icon = "DOT")


            
def register():
    bpy.types.VIEW3D_HT_header.prepend(returnCollections)
    
    bpy.utils.register_class(Test_OT_Test)
    bpy.utils.register_class(Test_PT_Test)
  

def unregister():
    bpy.utils.unregister_class(Test_OT_Test)
    bpy.utils.unregister_class(Test_PT_Test)
  

if __name__ == "__main__":
    register()
$\endgroup$
1
  • $\begingroup$ IMO Don't mix context and handlers. There is restricted context / data When the addon is registered where it is imported and the register method called. Handler functions usually return None. Wouldn't use a handler method as some quasi item loader for an enum property. Possibly better to use a dynamic enum property. eg blender.stackexchange.com/questions/122589/… $\endgroup$
    – batFINGER
    Commented Jul 29, 2020 at 18:22

1 Answer 1

4
$\begingroup$

Use a dynamic enum or a string property.

When an addon is enabled, it is imported and the register method called. At this stage both the data and context are restricted. The window manager is one of the few objects outside the restriction.

See no reason to have the persistent handler, have taken it out. Using it as an items loader from context when the addon is registered is no good either.

Cleaned up some other stuff, to prevent warnings in the console, eg the panel class name in form UPPERCASE_PT_TitleCase.

Try and avoid import *.

Test script: Have made the enum property dynamic and populated it with the immediate children of the context scene. For example sake have added a string property and used a prop search to give options from same.

Have shown and example of an operator enum menu in panel layout.

bl_info = {
    "name" : "test",
    "author" : "Diego Quevedo",
    "description" : "",
    "blender" : (2, 80, 0),
    "version" : (0, 1, 0),
    "location" : "",
    "warning" : "",
    "category" : "Generic"
}



import bpy
from bpy.props import EnumProperty, StringProperty
from bpy.types import (
    AddonPreferences,
    Operator,
    Panel,
    PropertyGroup
)
    
    
class TEST_OT_Test(bpy.types.Operator):
    bl_idname = "view3d.test"
    bl_label = "Test"
    bl_category = "Test"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_options = {'REGISTER', 'UNDO'}
    
    def enum_items(self, context):
        scene = context.scene
        return [(c.name,) * 3 for c in scene.collection.children]
    
    coll_enum:  EnumProperty( 
        items=enum_items,     
        name="Collections"
    )
    
    coll_name: StringProperty()

    def test(self, context, collection):        
        print("test", collection)
        
        
    @classmethod
    def poll(self, context):
       return context.scene.collection.children

    def invoke(self, context, event):
        return context.window_manager.invoke_props_dialog(self)

    def execute(self, context):
        self.test(context, self.coll_enum)
        return {'FINISHED'}
    
    def draw(self, context):
        layout = self.layout
        scene = context.scene

        col = layout.column()
        col.prop(self, "coll_enum")
        col.prop_search(self, "coll_name", scene.collection, "children")

####################
class TEST_PT_Test(bpy.types.Panel):
    bl_idname = "TEST_PT_Test"
    bl_label = "Test"
    bl_category = "Test"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"

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

        col = layout.column()

        col.operator("view3d.test", text="test", icon = "DOT")
        col.operator_menu_enum("view3d.test", "coll_enum")


            
def register():    
    bpy.utils.register_class(TEST_OT_Test)
    bpy.utils.register_class(TEST_PT_Test)
  

def unregister():
    bpy.utils.unregister_class(TEST_OT_Test)
    bpy.utils.unregister_class(TEST_PT_Test)
  

if __name__ == "__main__":
    register()

Edit:

For the case of adding a collection to a scene with no child collections can:

    def enum_items(self, context):
        scene = context.scene
        if not scene.collection.children:
            foo = bpy.data.collections.new("Foo")
            scene.collection.children.link(foo)
        return [(c.name,) * 3 for c in scene.collection.children]

which IMO works best in conjunction with the operator menu enum layout.

$\endgroup$

You must log in to answer this question.

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