1
$\begingroup$

Many similar question but none solved my problem. My selection and hierarchy looks like this: enter image description here

I want to loop through all selected collections, select the collection and it's children and export. How to select it's children and export I already know but I have troubling selecting the collection.

import bpy

# Set the area to the outliner
area = bpy.context.area
old_type = area.type 
area.type = 'OUTLINER'

for x in bpy.context.selected_ids:
    if x.bl_rna.identifier == 'Collection':
        bpy.ops.object.select_all(action = 'DESELECT')
        bpy.context.view_layer.active_layer_collection = bpy.context.view_layer.layer_collection.children[x.name]

# Reset the area 
area.type = old_type

But my code always throws the error:

KeyError: 'bpy_prop_collection[key]: key "Collection 5" not found'
$\endgroup$
6
  • $\begingroup$ It seems you're indistinctly talking about LayerCollection and Collection which are not the same thing. Could you elaborate on what you're trying to do ? $\endgroup$
    – Gorgious
    Jan 5, 2022 at 9:14
  • $\begingroup$ I updated my question a bit. What is the difference between LayerCollections and Collections? $\endgroup$
    – Lala_Ghost
    Jan 5, 2022 at 9:28
  • 1
    $\begingroup$ Thanks. What exactly do you want to export ? A Layer collection is an object that holds parameters that are specific to each view layer, wherehas a collection is an object that doesn't depend on the current view layer. They are linked in some way, but they are not the same thing. Working with layer collections is much, much more complicated than working with collections. $\endgroup$
    – Gorgious
    Jan 5, 2022 at 9:46
  • $\begingroup$ Than I guess I want to work with collections, since I want to select all objects that are inside a collection, mostly meshes and empties and export them, then repeat with the next collection $\endgroup$
    – Lala_Ghost
    Jan 5, 2022 at 10:09
  • $\begingroup$ Including objects that are in children collections or only the ones that are directly in the collection ? $\endgroup$
    – Gorgious
    Jan 5, 2022 at 10:11

2 Answers 2

3
$\begingroup$

In your script, as shown above, the type of the current area is switched to Outliner. That´s the area you´ve run the script (Texteditor). Now you have two areas with type outliner and as Cod3R wrote in his answer, there´s no selection in the new one. Not so important, because only operators can get a list of selected data-blocks via bpy.context.selected_ids - see here

Regarding your last comment, the panel is grayed out because that´s exactly what a poll function does. Wrong context sets operator inactive. In summary we need an context override for the operator which wants to get data from Outliner. In following example there´s a helper function for this. The queried data are stored in properties and an example with panel-operator is included to refresh data.

import bpy

# needs Blender 2.92 +

# outliner OP
class Outliner_OT_Items_Selected(bpy.types.Operator):    # only operators get selected Outliner items 
    bl_idname = 'outliner.items_selected'    
    bl_label = "Selected items in Outliner" 
    def execute(self,context):
        scene = context.scene
        scene['outl_items'] = context.selected_ids                                                  # all
        scene['outl_objs']  = [o for o in context.selected_ids if o.name in bpy.data.objects]       # only objs
        scene['outl_coll']  = [c for c in context.selected_ids if c.name in bpy.data.collections]   # only collections

        return {'FINISHED'}

# register outliner OP
bpy.utils.register_class(Outliner_OT_Items_Selected)

# storage
scene = bpy.data.scenes[bpy.context.scene.name]
scene['outl_items'] = []
scene['outl_objs'] = []
scene['outl_coll'] = []

# helper function
def call_op_with_override():
    ctxt = bpy.context.copy()                      # copy actual context
    for area in bpy.context.screen.areas:          # looping all areas in this workspace
        if area.type == 'OUTLINER':                # searching for area.type = Outliner to get right area      
            ctxt["area"] = area                    # change 'area' in ctxt (not area.type, this would change this area to type.Outliner)
            bpy.ops.outliner.items_selected(ctxt)  # now let OP do something in another context (override)
            break

# testcall script
call_op_with_override()   # if run script, we are in the Area with TextEditor, but we need Area with Outliner
print("\nselected Items:", [i.name for i in scene['outl_items']])
print("selected Objects:", [o.name for o in scene['outl_objs']])
print("selected Collections:", [c.name for c in scene['outl_coll']]) 

# Panel
class TESTPANEL_PT_Main(bpy.types.Panel):
    bl_idname = 'TEST_PANEL_PT_main'
    bl_label = "Test Panel"                      
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = "TEST"
    def draw(self, context):
        layout = self.layout

        layout.label(text = "All Selected Items:")
        box = layout.box()          
        if scene['outl_items']:
            for i in scene['outl_items']:
                box.label(text = "- %s" % i.name if i is not None else "-deleted-")
        else:
            box.label(text = "no item selected", icon='ERROR')
            
        layout.label(text = "Selected Objects:")
        box = layout.box()          
        if scene['outl_objs']:
            for o in scene['outl_objs']:
                box.label(text = "- %s" % o.name if o is not None else "-deleted-")            
        else:
            box.label(text = "no object selected", icon='ERROR')
                        
        layout.label(text = "Selected Collections:")
        box = layout.box()          
        if scene['outl_coll']:
            for c in scene['outl_coll']:
                box.label(text = "- %s" % c.name if c is not None else "-deleted-")                            
        else:
            box.label(text = "no collection selected", icon='ERROR')
                    
        layout.operator('outliner.items_refresh', text="refresh")

        layout.separator()
        layout.label(text = "Active Items:")
        box = layout.box()  
        box.label(text = "Active Object: %s" % context.active_object.name if context.active_object is not None else "no active obj")
        box.label(text = "Active Collection: %s" % context.view_layer.active_layer_collection.name)

# OP refresh for panel
class Outliner_OT_Items_Refresh(bpy.types.Operator): 
    bl_idname = 'outliner.items_refresh' 
    bl_label = "refresh selection"  
    def execute(self,context):
        call_op_with_override()  # if run OP, we are in the Area with 3DView, but we need Area with Outliner

        # ... do what you want here
        [o.hide_set(False) for o in scene['outl_objs']] # unhide selected objs
            
        return {'FINISHED'}                           
       
# register Panel + OP refresh
bpy.utils.register_class(TESTPANEL_PT_Main)
bpy.utils.register_class(Outliner_OT_Items_Refresh)
$\endgroup$
6
  • $\begingroup$ A bit more complicated than I hoped for, but I should be able to work through this. But when I run the script in the text editor, the panel does not recognize that I have a collection selected, even when I hit refresh $\endgroup$
    – Lala_Ghost
    Jan 14, 2022 at 7:27
  • $\begingroup$ Hmm...strange. Just tested with 2.93.7 and 3.0.0 and it works. Did you use 2.92 or higher? Are multiple outliners open? And we are talking about selected items, not active ones? Script edited. $\endgroup$
    – relaxed
    Jan 14, 2022 at 8:32
  • 1
    $\begingroup$ It is really awkward to have to use an operator to access selected items... I really hope they'll expand this feature to be more usable in the future. $\endgroup$
    – Gorgious
    Jan 14, 2022 at 8:38
  • $\begingroup$ I am using 3.0.0, only one outliner is open, displaying the active collection works, but displaying the selected ones does not $\endgroup$
    – Lala_Ghost
    Jan 14, 2022 at 8:45
  • $\begingroup$ Script changed again to catch errors when items are deleted. @ Lala_Ghost I can´t find a way to make it not work. Are there multiple scenes in your file? $\endgroup$
    – relaxed
    Jan 14, 2022 at 9:22
1
$\begingroup$

Did you notice that your selection is different when you switch text editor to outliner? I think you have to code an operator and run it in the current outliner (f3) or add a button.

import bpy

class SimpleOperator(bpy.types.Operator):
    """Tooltip"""
    bl_idname = "outliner.simple_operator"
    bl_label = "Simple Outliner Operator"

    @classmethod
    def poll(cls, context):
        return context.area.type == 'OUTLINER'

    def execute(self, context):
        sel = []
        for i in context.selected_ids:
            if i.bl_rna.identifier == 'Collection':
                sel.append(i)
        
        for i in sel:
            bpy.ops.object.select_all(action = 'DESELECT')
            for o in i.objects:
                o.select_set(True)
                 
            # Export ...
        
        return {'FINISHED'}

def register():
    bpy.utils.register_class(SimpleOperator)
def unregister():
    bpy.utils.unregister_class(SimpleOperator)
if __name__ == "__main__":
    register()
$\endgroup$
2
  • $\begingroup$ I don't understand why do I need to use the poll function with context.area.type == 'OUTLINER' here. Couldn't I just do it without it? My class if of type Operator & ExportHelper and gets loaded into a button inside a panel in the end. $\endgroup$
    – Lala_Ghost
    Jan 7, 2022 at 9:46
  • $\begingroup$ When I leave the poll method in and load it as an operator into a panel it is greyed out and not clickable Is there maybe a workaround? $\endgroup$
    – Lala_Ghost
    Jan 7, 2022 at 10:04

You must log in to answer this question.

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