Switch hide/unhide between selected objects

Hello,

need help with this task.
Example: scene with many objects some of them is hidden and I need to switch between two selected (one hidden, one not).

Code
    class OUTLINER_OT_toggle_hide(Operator):
        """Toggle hide/unhide for selected objects"""
        bl_idname = "outliner.toggle_hide"
        bl_label = "Toggle Hide"
        bl_options = {'REGISTER', 'UNDO'}

        @classmethod
        def poll(self, context):
            ar = context.screen.areas
            __class__.area = next(
                (a for a in ar if a.type == 'OUTLINER'), None)
            return __class__.area

        def execute(self, context):
            objs = context.view_layer.objects
            sel_objs = {o for o in objs if o.select_get()}
            hid_objs = {o for o in objs if o.hide_get()}

            # Hide selected
            for o in sel_objs:
                o.hide_set(True)

            # Unhide hidden
            for o in hid_objs:
                o.hide_set(False)
                o.select_set(state=True)

            # Select objects marked in outliner
            bpy.ops.outliner.object_operation(
                {'area': __class__.area}, type='SELECT')

            # Re-hide others
            for o in hid_objs:
                if not o.select_get():
                    o.hide_set(True)
                    
            objs.active = o
            return {'FINISHED'}

The problem is that it only works if scene have no hidden objects except one I need to toggle with.
It seems that it not keep selection in outliner.

As far as I’m aware it’s not possible to select the highlighted objects in the Outliner with Python, when they are not visible in the viewport, which is a big limitation. I think this is the main reason your script fails.

Here is a work around you could use for your case.
The two objects need to be visible initially so you can select them in the viewport.
After executing the operator it will store the names of the two selected objects in the scene. And it will hide the first selected.
Second time you execute the operator if nothing is selected it will get the stored names of the objects and toggle their visibility.

class APEC_OT_toggle_hide(Operator):
    """APEC toggle hide"""
    
    bl_idname = "apec.toggle_hide"
    bl_label = "Toggle the viewport visibility between two initially visible and selected objects in the viewport."
    
    
    @classmethod
    def poll(cls, context):
        
        if (bpy.context.area.type == "VIEW_3D" or bpy.context.area.type == "OUTLINER"):
            return True
    
    def execute(self, context):
        # Get the current scene so we can store our selected objects in it.
        scene = bpy.context.scene
        selected_objects = context.selected_objects
        
        # This is our custom property where we will store the object names in.
        # Returns None if the property doesn't exists. To avoid try/except.
        prop = scene.get("APEC_hide_toggle_objs", None)
        
        if prop is not None and len(prop)>0 and len(selected_objects)==0:
            # Get the stored objects
            toggle_objs_list = scene["APEC_hide_toggle_objs"]
            
            # Reverse their visibility
            toggle_objs_list[0].hide_viewport = not toggle_objs_list[0].hide_viewport
            toggle_objs_list[1].hide_viewport = not toggle_objs_list[0].hide_viewport
            
        elif len(selected_objects)>0:
            # If we selected new objects, then store them in the scene
            bpy.context.scene["APEC_hide_toggle_objs"] = selected_objects
            # Hide the first selected and deselect the second
            selected_objects[0].hide_viewport = True
            selected_objects[1].select_set(False)
        
        return {'FINISHED'}

There are many things about selection and visibility in Blender that I find confusing and unpracticle.
Some example threads:
Disable selection in viewport problem - papercut, design issue or a bug?
How can I retrieve the currently selected or active item in the Outliner?
Confusing selection states in Outliner and Viewport
Decoupling x-ray and limit selection to visible

1 Like

Is it possible now with 2.92 api bpy.context.selected_ids toggle between selected objects?
I have this mini addon
TH

Summary
import bpy
from bpy.types import Panel, Operator

# Add-on info
bl_info = {
    "name": "Toggle Hide Selected",
    "author": "USERNAME",
    "version": (0, 0, 1),
    "blender": (2, 92, 0),
    "location": "View3D > Properties > Toggle Hide",
    "description": "", 
    "doc_url": "",
    "tracker_url": "",      
    "category": "3D View"
}

class OUTLINER_OT_toggle_hide(Operator):
    """Toggle hide/unhide for selected objects"""
    bl_idname = "outliner.toggle_hide"
    bl_label = "Toggle Hide"
    bl_options = {'REGISTER', 'UNDO'}

#    @classmethod
#    def poll(self, context):
#        ar = context.screen.areas
#        __class__.area = next(
#            (a for a in ar if a.type == 'OUTLINER'), None)
#        return __class__.area

    def execute(self, context):
        #ids = bpy.context.selected_ids
         
        #selection_names = [obj.name for obj in ids]
        #print(selection_names)

        objs = context.view_layer.objects

        sel_objs = {o for o in objs if o.select_get()}
        hid_objs = {o for o in objs if o.hide_get()}

        # Hide selected
        for o in sel_objs:
            o.hide_set(True)
            o.hide_viewport = o.hide_render = True
            
        # Unhide hidden
        for o in hid_objs:
            o.hide_set(False)
            o.hide_viewport = o.hide_render = False
            o.select_set(True)

        return {'FINISHED'}
    
class OUTLINER_PT_toggle_hide(Panel):          
    bl_label = "Toggle Hide"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = 'Toggle Hide'
    
    def draw (self, context):
        layout = self.layout
        
        col = layout.column()
        col.operator("outliner.toggle_hide")
        
classes = (
            OUTLINER_OT_toggle_hide,
            OUTLINER_PT_toggle_hide,
)

def register():
    from bpy.utils import register_class
    for cls in classes:
        register_class(cls)
    
def unregister():
    from bpy.utils import unregister_class
    for cls in reversed(classes):
        unregister_class(cls)
        
if __name__ == "__main__":
    register()

and I’m trying to achieve affecting only selected in Outliner and/or in 3d_view

Apec - thank you very much for this, but unfortunately it’s not there yet :frowning:
I was investigating what is wrong, but with my poor conding skills I wasn’t able to fix it… basically
it’s affecting also non selected objects (neither in 3dview nor outliner). And that makes it really not usable, because it will unhide everything on your scene, even you just wanna toggle something completely different… take a look on teh video - do you think it’s possible to make it work only on selected objs (3dview and outliner)?

That’s why I’m asking for

My second attempt with IDs also fails (due to my low skills). It already binded to Y key and work only if hover mouse in outliner

Summary
import bpy
from bpy.types import Operator

# Add-on info
bl_info = {
    "name": "Toggle Hide Selected",
    "author": "USERNAME",
    "version": (0, 0, 1),
    "blender": (2, 92, 0),
    "location": "Outliner > 'Y' key",
    "description": "", 
    "doc_url": "",
    "tracker_url": "",      
    "category": "Outliner"
}

class OUTLINER_OT_toggle_hide(Operator):
    """Toggle hide/unhide for selected objects"""
    bl_idname = "outliner.toggle_hide"
    bl_label = "Toggle Hide"
    bl_options = {'REGISTER', 'UNDO'}

#    @classmethod
#    def poll(self, context):
#        ar = context.screen.areas
#        __class__.area = next(
#            (a for a in ar if a.type == 'OUTLINER'), None)
#        return __class__.area

    def execute(self, context):
    
        ids = bpy.context.selected_ids

        #objs = context.view_layer.objects

        sel_objs = {o for o in ids if o.select_get()}
        hid_objs = {o for o in ids if o.hide_get()}

        selection_names = [obj.name for obj in ids]

        # Hide selected
        for o in sel_objs:
            o.hide_set(True)
            o.hide_viewport = o.hide_render = True

        # Unhide hidden
        for o in hid_objs:  
            o.hide_set(False)
            o.hide_viewport = o.hide_render = False
            o.select_set(True)
         
        print(ids)
                
        return {'FINISHED'}
    
###########################################################################################
##################################### Register ############################################
########################################################################################### 	

addon_keymaps = []


def register():
    bpy.utils.register_class(OUTLINER_OT_toggle_hide)
    wm = bpy.context.window_manager
    kc = wm.keyconfigs.addon.keymaps
    km = kc.get("Outliner")
    if not km:
        km = kc.new("Outliner", space_type="OUTLINER")
    kmi = km.keymap_items.new("outliner.toggle_hide", "Y", "PRESS")
    addon_keymaps.append((km, kmi))


def unregister():
    bpy.utils.unregister_class(OUTLINER_OT_toggle_hide)

    for km, kmi in addon_keymaps:
        km.keymap_items.remove(kmi)
    addon_keymaps.clear()
    
if __name__ == "__main__":
    register()

it hide visible and unhide hidden selected in outliner, but then toggle only objects that was hidden.

Oh sorry - I haven’t noticed it was a question actually - hopefully somebody help…

@Kubanis Try this one. For now only for Outliner with Y key, because I don’t know how to execute this in 3d View yet…

Summary
import bpy
from bpy.types import Operator

# Add-on info
bl_info = {
    "name": "Toggle Hide Selected",
    "author": "APEC",
    "version": (0, 0, 1),
    "blender": (2, 92, 0),
    "location": "Outliner > 'Y' key",
    "description": "", 
    "doc_url": "",
    "tracker_url": "",      
    "category": "Outliner"
}

class OUTLINER_OT_toggle_hide(Operator):
    """Toggle hide/unhide for selected objects"""
    bl_idname = "outliner.toggle_hide"
    bl_label = "Toggle Hide"
    bl_options = {'REGISTER', 'UNDO'}

#    @classmethod
#    def poll(self, context):
#        ar = context.screen.areas
#        __class__.area = next(
#            (a for a in ar if a.type == 'OUTLINER'), None)
#        return __class__.area

    def execute(self, context):
    
        context = bpy.context

        without = []
        with_eye = []
        with_monitor = []
        with_eye_and_monitor = []
        
        ids = bpy.context.selected_ids
        
        for o in ids:

            if o.visible_get():
                without.append(o)
                
            
            if not o.hide_viewport and not o.visible_get():
                with_eye.append(o)
                
            
            if o.hide_viewport:
                o.hide_viewport = False # toggle to test visibility
                
                if o.visible_get():
                    with_monitor.append(o)
                    
                else:
                    with_eye_and_monitor.append(o)
                    
                o.hide_viewport = True # toggle back
            
        if without:
            print(
                "Without:\n",
                [o.name for o in without])
                
        if with_eye:
            print(
                "With eye only:\n",
                [o.name for o in with_eye])
            
        if with_monitor:
            print(
                "With monitor only:\n",
                [o.name for o in with_monitor])
                
        if with_eye_and_monitor:
            print(
                "With eye and monitor:\n",
                [o.name for o in with_eye_and_monitor])
                
        for o in without:
            o.hide_set(True)
        for o in with_eye:
            o.hide_set(False)
            
        #print(ids)
                
        return {'FINISHED'}
    
###########################################################################################
##################################### Register ############################################
########################################################################################### 	

addon_keymaps = []


def register():
    bpy.utils.register_class(OUTLINER_OT_toggle_hide)
    wm = bpy.context.window_manager
    kc = wm.keyconfigs.addon.keymaps
    km = kc.get("Outliner")
    if not km:
        km = kc.new("Outliner", space_type="OUTLINER")
    kmi = km.keymap_items.new("outliner.toggle_hide", "Y", "PRESS")
    addon_keymaps.append((km, kmi))


def unregister():
    bpy.utils.unregister_class(OUTLINER_OT_toggle_hide)

    for km, kmi in addon_keymaps:
        km.keymap_items.remove(kmi)
    addon_keymaps.clear()
    
if __name__ == "__main__":
    register()

This is the same but compact version, but also hide from view and render

Summary
import bpy
from bpy.types import Operator

# Add-on info
bl_info = {
    "name": "Toggle Hide Selected",
    "author": "APEC",
    "version": (0, 0, 1),
    "blender": (2, 92, 0),
    "location": "Outliner > 'Y' key",
    "description": "", 
    "doc_url": "",
    "tracker_url": "",      
    "category": "Outliner"
}

class OUTLINER_OT_toggle_hide(Operator):
    """Toggle hide/unhide for selected objects"""
    bl_idname = "outliner.toggle_hide"
    bl_label = "Toggle Hide"
    bl_options = {'REGISTER', 'UNDO'}

#    @classmethod
#    def poll(self, context):
#        ar = context.screen.areas
#        __class__.area = next(
#            (a for a in ar if a.type == 'OUTLINER'), None)
#        return __class__.area

    def execute(self, context):
    
        context = bpy.context

        without = []
        with_any = []
        
        ids = bpy.context.selected_ids
        
        for o in ids:

            if o.visible_get():
                without.append(o)
                
            if not o.visible_get():
                with_any.append(o)
            
        if without:
            print(
                "Without:\n",
                [o.name for o in without])
                
        if with_any:
            print(
                "With any:\n",
                [o.name for o in with_any])
                
        for o in without:
            o.hide_set(True)
            o.hide_viewport = o.hide_render = True
        for o in with_any:
            o.hide_set(False)
            o.hide_viewport = o.hide_render = False
            
        #print(ids)
                
        return {'FINISHED'}
    
###########################################################################################
##################################### Register ############################################
########################################################################################### 	

addon_keymaps = []


def register():
    bpy.utils.register_class(OUTLINER_OT_toggle_hide)
    wm = bpy.context.window_manager
    kc = wm.keyconfigs.addon.keymaps
    km = kc.get("Object Mode")
    if not km:
        km = kc.new("Object Mode")
    kmi = km.keymap_items.new("outliner.toggle_hide", "Y", "PRESS")
    addon_keymaps.append((km, kmi))

    km = kc.get("Outliner")
    if not km:
        km = kc.new("Outliner", space_type="OUTLINER")
    kmi = km.keymap_items.new("outliner.toggle_hide", "Y", "PRESS")
    addon_keymaps.append((km, kmi))


def unregister():
    bpy.utils.unregister_class(OUTLINER_OT_toggle_hide)

    for km, kmi in addon_keymaps:
        km.keymap_items.remove(kmi)
    addon_keymaps.clear()
    
if __name__ == "__main__":
    register()

Added:
unable to run script in areas other than Outliner. See description Blender 2.92: Python API

In the Outliner, operators can get a list of the selected data-blocks, via bpy.context.selected_ids (rBaf008f55). This may become available in more editors in future.

Thank you Apec - it’s moving forward! But if this id function is not available anywhere else than Outliner, that’s pretty limited - hope they’ll change it soon!
I don’t know if it’s allowed to changing and reuploading your code in this forum, but I was so frustrated lacking this simple feature in Blender (I simply can not live/work without it) I decided to push it as far as my poor skills allow me. So I’ve spend all day trying and failing and finally it’s even closer for real use - at least from my workflow point of view. But please don’t laugh at me, it’s probably ultra messy - but it works - kinda. the goal was to have this toggle functionality in outliner and 3dview(or else) as well. So now it’s branching based on the hovering area - outliner uses Ids other uses your older setup. Of course limitation is, one has to stay and perform the script in the Outliner or 3dview, but not crossing actions in both places. That wouldn’t work, but I guess with this simple rule I can use the toogle without to much harm anyway - I’ll try and let you know! It also creates scene property where it’s storing the hidden objects - probably there is a waaay more elegant solution… One think I wasn’t able to fix - in outliner ID method, unhidden object lost their selection state (in 3dview), which is bad - better would be to retain selection. But when I’ve tried to implement it in the loop, it started to behave weirdly - maybe you would know how to do it.

I’ve forgotten - I’ve also changed the hotkey, because Y is mapped in many places… And even in Blender maual they are iforming they have F-keys mostly reserved for custom hotkeys. F4 works the best for me, I am trying to use it in every app for this toggle/hide task, if it’s possible.

import bpy
from bpy.types import Operator

# Add-on info
bl_info = {
    "name": "Toggle Hide Selected",
    "author": "APEC - Kuban Edit_02",
    "version": (0, 0, 1),
    "blender": (2, 92, 0),
    "location": "Outliner > 'F4' key",
    "description": "", 
    "doc_url": "",
    "tracker_url": "",      
    "category": "Outliner"
}

class OUTLINER_OT_toggle_hide(Operator):
    """Toggle hide/unhide for selected objects"""
    bl_idname = "outliner.toggle_hide"
    bl_label = "Toggle Hide"
    bl_options = {'REGISTER', 'UNDO'}

#    @classmethod
#    def poll(self, context):
#        ar = context.screen.areas
#        __class__.area = next(
#            (a for a in ar if a.type == 'OUTLINER'), None)
#        return __class__.area


    def execute(self, context):


        #check the scene toogle property and make it if it's not there
        try:
            print("checking scene property")
            bpy.context.scene["hide_toggle"]
            print("hide_toggle scene property - present")
        except:
            print ("NOPENOPE")
            
            bpy.context.scene['_RNA_UI'] = bpy.context.scene.get('_RNA_UI', {})

            bpy.context.scene["hide_toggle"] = "nothing"
            bpy.context.scene['_RNA_UI']["MyProp"] = {"name": "Hide toggle",
                                          "description": "ToolTip",
                                        }
            print("hide_toggle scene property - created")




        #start toggleing based on the area
        area_type = bpy.context.area.type

        if area_type == 'OUTLINER':

            #=========== toggle for OUTLINER ==========

            context = bpy.context

            visible = []
            notvisible = []
            
            ids = bpy.context.selected_ids
            
            for o in ids:

                if o.visible_get():
                    visible.append(o)
                    
                if not o.visible_get():
                    notvisible.append(o)
                
            if visible:
                print(
                    "Visible:\n",
                    [o.name for o in visible])
                    
            if notvisible:
                print(
                    "Not visible:\n",
                    [o.name for o in notvisible])
                    
            for o in visible:
                o.hide_set(True)
                o.hide_viewport = o.hide_render = True
            for o in notvisible:
                o.hide_set(False)
                o.hide_viewport = o.hide_render = False
                #o.select_set(True)

        else:

            #=========== toggle for 3d view and ===============

            objs = bpy.context.view_layer.objects

            sel_objs = {o for o in objs if o.select_get()}

            #-------------

            if bpy.context.scene["hide_toggle"] == 'nothing' and len(sel_objs) <= 0:
                print("nothing stored or selected to unhide")
                
            else:
                if bpy.context.scene["hide_toggle"] != 'nothing' and len(sel_objs) <= 0:
                
                    #retrive data from scene property - hidden objects    
                    hid_objs = eval(bpy.context.scene["hide_toggle"])
                    #clear the scene property
                    bpy.context.scene["hide_toggle"] = 'nothing'

                    #unhide stored objects    
                    for o in hid_objs:
                        print(o)
                        o.hide_set(False)
                        o.hide_viewport = o.hide_render = False
                        o.select_set(True)
                    
                    
                else:
                    #hide selected objects
                    for o in sel_objs:
                        o.hide_set(True)
                        o.hide_viewport = o.hide_render = True
                        
                    bpy.context.scene["hide_toggle"] = str(sel_objs)
                    
            #==============================================


                
        return {'FINISHED'}
    
###########################################################################################
##################################### Register ############################################
########################################################################################### 	

addon_keymaps = []


def register():
    bpy.utils.register_class(OUTLINER_OT_toggle_hide)
    wm = bpy.context.window_manager
    kc = wm.keyconfigs.addon.keymaps
    km = kc.get("Object Mode")
    if not km:
        km = kc.new("Object Mode")
    kmi = km.keymap_items.new("outliner.toggle_hide", "F4", "PRESS")
    addon_keymaps.append((km, kmi))

    km = kc.get("Outliner")
    if not km:
        km = kc.new("Outliner", space_type="OUTLINER")
    kmi = km.keymap_items.new("outliner.toggle_hide", "F4", "PRESS")
    addon_keymaps.append((km, kmi))


def unregister():
    bpy.utils.unregister_class(OUTLINER_OT_toggle_hide)

    for km, kmi in addon_keymaps:
        km.keymap_items.remove(kmi)
    addon_keymaps.clear()
    
if __name__ == "__main__":
    register()

As I know there is nothing to do with it. Because hidden objects can’t be selected via python,
you can try to hide camera for example and then use bpy.data.objects["Camera"].select_set(True) in console - nothing happen…

Old example (where you need to switch to outliner from text editor, select something, and back to the text editor for running script) with multiple selection in outliner where some objects are hidden

import bpy

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

# some operations
ids = bpy.context.selected_ids
names = [o.name for o in ids]
print (names)

for o in names:
    obj = bpy.context.scene.objects.get(o)
    bpy.context.view_layer.objects.active = obj
    obj.select_set(True)

# Reset the area 
area.type = old_type  

after execution it selects only visible, but in the list would be all names with hidden objects also.

So you can make objects selected in 3dView after hide/unhide toggle, but after that you loose hidden objects highlights in outliner. I can see workaround only by making separate class with selecting objects in 3dView after hide/unhide and bind it on a key.