Can we add a way to access the outliner eyeball for collections?

can we add a way to access the outliner eyeball for collections?

Here’s a link to a better explanation and an unnecessarily complex workaround.

You can link to the same collection several places in the outliner and still have separate restriction states for each item. This is done through layer collections.

context.layer_collection.children['Collection'].hide_viewport

The top level:

context.view_layer.layer_collection.children

Thanks. I’m just trying to automatically disable collections in the render if the eyeball is off:


for col in bpy.data.collections:
    print(col.hide_viewport)
    if col.hide_viewport:
        col.hide_render = True
    else:
        col.hide_render = False


I’m only using one view layer by the way, so I’m not too bothered about it being global. Probably a better solution though would be to exclude from the view layer if the eyeball is off, but I can’t find how to check if the eyeball is off for each collection.

Thanks, you put me on the right track. I’m totally new to the Blender API, can you give me a tip on how I should have figured this out from the API documentation? Right clicking the icon in the viewport just takes me to the below page, but I feel like I’m missing some fundamental knowledge on how to use the API, because I can’t see how to make the connection between the code you pointed me in the direction of, and what the below page shows:

https://docs.blender.org/api/master/bpy.types.Collection.html#bpy.types.Collection.hide_render

Here’s the code if anyone would find it useful. It basically makes the render match the viewport.

import bpy


for ob in bpy.data.objects:
    if ob.hide_get():
        ob.hide_render = True
    else:
        ob.hide_render = False
        
        
collections = bpy.context.layer_collection.children.values()
print(collections)


for col in collections:
    print(col.name,col.hide_viewport,col.exclude)


        
for col in collections:
   
    if col.hide_viewport:
        col.exclude = True
    else:
        col.exclude = False

I’m a bit confused, you initially started questioning about collections, but your last code sample now works on objects, is that on purpose?

It’s easy to overlook this, but as kaio already pointed out, you’ll need to deal with the LayerCollections, not Collections. If you iterate over bpy.data.collections, this will basically return only Collections to you. However, to read the state of the eye icon, you need to query the LayerCollection instead. A LayerCollection is what you can see visually in the outliner. It has a Python property that then references back to a Collection in bpy.data.

bpy.context.view_layer.active_layer_collection    # will retrieve the currently active collection in the outliner
bpy.context.view_layer.active_layer_collection.collection    # retrieves the Collection that is referenced by the LayerCollection from bpy.data

to verify the above, use

bpy.context.view_layer.active_layer_collection.type

and

bpy.context.view_layer.active_layer_collection.collection.type

Why is there two? The LayerCollection holds the eye icon, but also the holdout and indirect_only properties, which are relevant for rendering only. The Collection however holds hide_render and hide_viewport, which are also considered if you create a Collection Instance.

Try selecting a Layer Collection in the outliner first, and then run bpy.context.view_layer.active_layer_collection.hide_viewport = False for instance. This should turn the eye icon off. Now, to get to the instance visibility and render visibility states, you need to retrieve the Collection a LayerCollection is holding like mentioned above:

bpy.context.view_layer.active_layer_collection.collection.hide_viewport    # gets the instance visibility state, i.e. the screen icon
bpy.context.view_layer.active_layer_collection.collection.hide_render    # gets the render visibility state, i.e. the render icon

to synchronize the eye and the screen and render icons, you need to set them like this:

bpy.context.view_layer.active_layer_collection.collection.hide_viewport = bpy.context.view_layer.active_layer_collection.hide_viewport
bpy.context.view_layer.active_layer_collection.collection.hide_render = bpy.context.view_layer.active_layer_collection.hide_viewport

The master collection called Scene Collection can by the way be retrieved using

bpy.context.view_layer.layer_collection

Confused? So was I when I started. Especially because there is afaik still no simple way to grab all LayerCollections from the Outliner in one shot, you need to iterate recursively over them. Just as a hint, bpy.context.view_layer.active_layer_collection.children holds the direct child collections of the selected LayerCollection. Brecht admitted in another thread here on DevTalk that there should really be a better / more convenient API for accessing this.

1 Like

Thanks Rainer, yes that was intentional, if you read a bit further down the code, I am now excluding the container from the view layer based on whether or not the hide_viewport is True. The bit about the objects at the top is to hide objects from the renderer that are hidden at object level but are in a container that isn’t hidden.

So all working now thanks.

I wonder if you could help with my other question though. I feel like i’m missing some key understanding of the logic behind the api documentation. For example I want to find if the collection is hidden in the viewport, so I right click on the eyeball icon and then choose ‘Online python reference’, how would I use the information on the page:

https://docs.blender.org/api/master/bpy.types.LayerCollection.html#bpy.types.LayerCollection.hide_viewport

which just shows this:

hide_viewport
Temporarily hide in viewport

Type:
boolean, default False

to arrive at the actual code I needed to use:

collections = bpy.context.layer_collection.children.values()
for col in collections:
   
    if col.hide_viewport:
        # do something

Well the API docs state that any LayerCollection Python object has an attribute hide_viewport, which is a boolean type and by default has a value of False. The description to Temporarily hide in viewport is let’s say very brief but correct, it’s exactly what the eye icon does.

In your code the use of .values() is actually unneccessary. This yields a list of all children of the Scene Collection, which means in the background a full iteration over those elements has already happened, and memory has been assigned. In the for loop that follows, you already iterate over the same elements a second time without knowing it.

If you only use collections = bpy.context.layer_collection.children the returned element is an iterator - a function in simple terms. so far not much has happened then, the iteration only happens in your for loop for the first time. It’s a Python performance and efficiency thing. By the way, you can considerably shorten the code you produced:

import bpy

for ob in bpy.data.objects:
    ob.hide_render = ob.hide_get()
        
collections = bpy.context.layer_collection.children
print(collections)

for col in collections:
    print(col.name,col.hide_viewport,col.exclude)

for col in collections:
    col.exclude = col.hide_viewport

there is no need to check if col.hide_viewport is False and then assign False to something else, just pipe in the results directly and spare a bunch of if clauses.

Thanks for the tips to achieve more concise code and avoid iterating twice.

I’m still struggling a little to make the link in my mind between the information on the api docs page and the code I ended up at though. I think I’m missing a key understanding of how the documentation works.

Maybe you describe how you understand the docs in this example at the moment, not sure where you struggle right now.

I understood that I had to use something.hide_viewport, but it’s the finding what that something is that I’m failing to understand from the api page the link in the right click menu takes me to.

I’m think i’m missing a key bit of knowledge that would make it obvious what .hide_viewport needed to be appended to. Something on that documentation page itself.

That relevant info is at the top of the page: class bpy.types.LayerCollection(bpy_struct) describes what type of Python object you need to have in hand to find the properties you are looking for.

all the properties you can find on said object are listed below, in this case children, collection, hide_viewport and so on. So as soon as you have a LayerCollection in hand, like bpy.context.layer_collection, you will find those properties on it.

how are you arriving at:

bpy.context.layer_collection

from:

bpy.types.LayerCollection( bpy_struct )

or from:

bpy.types.ObjectBase ( bpy_struct )

to:

bpy.data.objects

The layer_collection attribute of bpy.context.layer_collection simply is of the type bpy.types.LayerCollection. You can see this if you run print(type(bpy.context.layer_collection))

To be more clear: Anything in bpy.types is similar to a class description, which means the custom Blender types are defined there. Those classes then can be used anywhere in the API by instantiating them there. LayerCollection is one of many examples.

What then happens is that somewhere (doesn’t really matter where, this can be in Blender source code or an Add-on all the same) it is defined that for example bpy.context.layer_collection is an instance of the class bpy.types.LayerCollection. Same with bpy.context.view_layer.active_layer_collection, also an instance of the very same class bpy.types.LayerCollection. This is just how Python works really. So to figure out what attributes you can find on each instance, you look at the documentation of the base class defining it. Even if you do something simple like

my_string = 'Python is flexible'

you technically are creating an instance of Python’s built in string class str, and assign a value to that instance. If you now check out the Python docs of the string class here you can see further down the docs that it implements a method upper. Where can you find this now? On each instance of str, also the one you just created, which is my_string:

my_string.upper()
# 'PYTHON IS FLEXIBLE'
1 Like

Thanks Rainer. So basically the page the link in blender takes me to in the documentation is actually the name of the class which defines the instance, so to find the instance name, I need to find the most suitable one in the references section at the bottom.

It was finding the name of the instances that I was struggling with, but I think something just clicked :slight_smile: