Show/hide collection Blender Beta >= 2.80

I’m working with Blender’s beta 2.80 and have faced couple of issues trying the hide and show collection from Python. There is a lack of a robust API for doing that IMHO.

Please see what I mean. Currently we have only these three options for hiding a collection:

bpy.data.collections[0].hide_render
bpy.data.collections[0].hide_select
bpy.data.collections[0].hide_viewport

But Outliner has also the forth option with an opened/closed eye icon (the leftmost button compared to those three mentioned above).

There is two issues from my point of view:

  1. Description says that the eye hides object in viewport but the monitor disables object in viewport.
    At the same time the real scripting variable name for the latter is .hide_viewport which is a little bit confusing.

But it’s not a big deal though.

  1. There is no simple way to interact with the eye button to show and hide a collection from Python. But I finally found the one and just want to share it.
  • may be someone will find it helpful
  • may be Blender’s developers will add two distinct options like collection.hide_viewport and collection.disable_viewport :slight_smile:

So here is the code:

set_collection_viewport_visibility('some_collection_name')
set_collection_viewport_visibility('some_collection_name', visibility=False)
def get_viewport_ordered_collections(context):
    def fn(c, out, addme):
        if addme:
            out.append(c)
        for c1 in c.children:
            out.append(c1)
        for c1 in c.children:
            fn(c1, out, False)
    collections = []
    fn(context.scene.collection, collections, True)
    return collections

def get_area_from_context(context, area_type):
    area = None
    for a in context.screen.areas:
        if a.type == area_type:
            area = a
            break
    return area

def set_collection_viewport_visibility(context, collection_name, visibility=True):
    collections = get_viewport_ordered_collections(context)

    collection = None
    index = 0
    for c in collections:
        if c.name == collection_name:
            collection = c
            break
        index += 1

    if collection is None:
        return

    first_object = None
    if len(collection.objects) > 0:
        first_object = collection.objects[0]

    try:
        bpy.ops.object.hide_collection(context, collection_index=index, toggle=True)

        if first_object.visible_get() != visibility:
            bpy.ops.object.hide_collection(context, collection_index=index, toggle=True)
    except:
        context_override = context.copy()
        context_override['area'] = get_area_from_context(context, 'VIEW_3D')

        bpy.ops.object.hide_collection(context_override, collection_index=index, toggle=True)

        if first_object.visible_get() != visibility:
            bpy.ops.object.hide_collection(context_override, collection_index=index, toggle=True)

    return collection
5 Likes

Nothing to add, just wanted to say thank you for this. I’m sure I’ll need it one day.

Hi, Dmitriy. I tried your script and it’s didn’t work.

I imported your code like this it blender text editor

import bpy

def get_viewport_ordered_collections(context):
    def fn(c, out, addme):
        if addme:
            out.append(c)
        for c1 in c.children:
            out.append(c1)
        for c1 in c.children:
            fn(c1, out, False)
    collections = []
    fn(context.scene.collection, collections, True)
    return collections

def get_area_from_context(context, area_type):
    area = None
    for a in context.screen.areas:
        if a.type == area_type:
            area = a
            break
    return area

def set_collection_viewport_visibility(context, collection_name, visibility=True):
    collections = get_viewport_ordered_collections(context)
    collection = None
    index = 0
    for c in collections:
        if c.name == collection_name:
            collection = c
            break
        index += 1
    if collection is None:
        return
    first_object = None
    if len(collection.objects) > 0:
        first_object = collection.objects[0]
    try:
        bpy.ops.object.hide_collection(context, collection_index=index, toggle=True)
        if first_object.visible_get() != visibility:
            bpy.ops.object.hide_collection(context, collection_index=index, toggle=True)
    except:
        context_override = context.copy()
        context_override['area'] = get_area_from_context(context, 'VIEW_3D')
        bpy.ops.object.hide_collection(context_override, collection_index=index, toggle=True)
        if first_object.visible_get() != visibility:
            bpy.ops.object.hide_collection(context_override, collection_index=index, toggle=True)
    return collection

set_collection_viewport_visibility('High_poly', visibility=False)

The error is:

Traceback (most recent call last):
  File "\Collection hide", line 49, in <module>
TypeError: set_collection_viewport_visibility() missing 1 required positional argument: 'collection_name'
Error: Python script failed, check the message in the system console

@FeralMan Put
context = bpy.context
just after import bpy
And the last line should read
set_collection_viewport_visibility(context, 'YOUR_COLLECTION_NAME', visibility=False) (or True depending on what you want :grinning:)

1 Like
cols = bpy.context.view_layer.layer_collection.children
for col in cols:
  col.hide_viewport = True

doesn’t do nested collections eg. children of children though.

Lol now this is confusing. So in the outliner they have the options “Disable in viewport” & “Hide in viewport”.

bpy objects has a method “hide_viewport”, which toggles/activates “disable in viewport” :neutral_face:

So I’ve also been wrestling with this one too. This is the code I’ve got working in 2.93.1.

The strange thing is when it comes to hiding the nested collections again this code works in the scripting panel but not in my addon.

import bpy
from bpy import context

scene = context.scene

hiddenList = []

scene_collection = bpy.context.view_layer.layer_collection
bpy.context.view_layer.active_layer_collection = scene_collection
currentLayer = bpy.context.view_layer

colList = scene_collection.children

for coll in colList:
    if currentLayer.layer_collection.children[coll.name].hide_viewport == True:
        currentLayer.layer_collection.children[coll.name].hide_viewport = False

    for subColl in coll.children: #nested collections
        if subColl.hide_viewport == True:
            subColl.hide_viewport = False
            hiddenList.append(subColl)
            
for coll in hiddenList: #rehide sub collections
    subColl.hide_viewport = True

it’s super annoying that there are so many things in the outliner that don’t have API access or parity. I just ran into this issue today where I needed to use msgbus to subscribe to a collection’s visibility. Out of the box the collection’s hide_viewport property isn’t even visible, so 10 out of 10 users are going to click the eyeball icon, which cannot be subscribed to since it doesn’t have a proper data path.

fantastic.

I’m glad we got so many outliner improvements, I’m not trying to be salty about it- but I wish when devs were adding features like this they kept it in the back of their mind that studios have pipelines to maintain and it makes it really hard to do that when they treat the api like an afterthought (if they think of it at all).

The screen icon toggles the collections in bpy.data.
The eye icon toggles the layer collection instances.

Try subscribing to bpy.types.LayerCollection for the “eye” toggles.

yeah that definitely works, part of my problem is that I needed to show specific collections in a UIList and there’s no easy way to get the CollectionLayer of a given collection, but once I wrote a recursive search it’s not really an issue anymore. Not the cleanest solution, mind you- but it works

recursive utility function for anyone who might need it:

def get_layer_collection(collection):
    '''Returns the view layer LayerCollection for a specificied Collection'''
    def scan_children(lc, result=None):
        for c in lc.children:
            if c.collection == collection:
                return c
            result = scan_children(c, result)
        return result

    return scan_children(bpy.context.view_layer.layer_collection)
2 Likes