Outliner Tweaks - Blender Addon
I think many Blender users would agree that organizing objects into layers is not a strong side of Blender. The concept of 20 unnamed layers is very unfortunate and easily tends to chaos in your files. Even when opening my own file, I always have to go through all the unnamed layers to see what objects are on which layer.
Moreover, there is the Outliner panel, which rather brings more confusion than help with organizing your objects. Well, you can see there a list of all objects, but you are unable to organize them into folders to bring some order into your scene. Besides, when you select some objects in the outliner, you don't see them in the viewport, because it collides with the layer visibility.
Later, I discovered there is the Groups options to display grouped objects, but again, it's implemented in a very unintuitive way. When you switch to Groups, you see only the existing groups and cannot create new groups. To make it even more confusing, it doesn't show objects that has no group assigned yet, so you can't select them and assign them to the new group.
So, my conclusion always was that the Outliner is just a confusing part of Blender and I never used it. But recently, I got really annoyed with the chaos in my Blender files, and tried to script some tweaks that would make Outliner more useful.
How it works?
The idea behind the addon is to introduce two auxiliary groups and commands that work on them. It adds a little icon in the corner of the Outliner menu and introduces some commands with hotkeys.
- Auxiliary Groups
- #DEFAULT_GROUP - This group collects all objects that are not grouped yet. So, you can finally see all ungrouped objects in the Outliner, even when the Outliner is set to display groups only. To update the #DEFAULT_GROUP group press U when your mouse is in the Outliner.
- #NEW_GROUP - This group enables to add selected objects into a new group from Outliner. Press N and the #NEW_GROUP is created. Then, you can rename this group as you wish.
- Update Default Group (U)
- Group all unsorted objects (add them to the #DEFAULT_GROUP).
- Clean the #DEFAULT_GROUP from the objects that already belong to other groups.
- Add to New Group (N)
- Add selected objects to the #NEW_GROUP.
- Remove them from all other groups.
- Remove from Groups (R)
- Remove selected objects from all groups.
- Add them to the DEFAULT_GROUP.
- Expand/Collapse One Level (E)
- Toggle Expand/Collapse one level for all items.
- Isolate Selection (Q)
- Isolate (display) the selected objects in the 3D viewport and make sure the right layers are enabled.
- Enable visibility and render for the selected objects.
- Restrict visibility and render for all other objects.
Conclusion
I think this addon finally makes Outliner usable. You can manage all objects and groups directly from the Outliner without the need to switch between panels. Also, you can finally display all the selected objects in the 3D viewport and don't need to bother with the 20 unnamed layers anymore.
I know there are plans to improve the layer management system in the milestone Blender 2.8. But I didn't want to wait, because I guess it will take lots of time yet, before we have a stable and bug-free version of Blender 2.8.
ChangeLog
- Version 0.1 (02.01.2018)
- Initial release
Download
blender-addons/outliner-tweaks/0.1/OutlinerTweaks.py (Source)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 |
#------------------------------------------------------------------------------- # Outliner Tweaks - Addon for Blender # # Auxiliary Groups # - #DEFAULT_GROUP - to collect ungrouped objects # - #NEW_GROUP # # Commands: # - Update Default Group (U) # - Add to New Group (N) # - Remove from Groups (R) # - Expand/Collapse One Level (E) # - Isolate Selection (Q) # #------------------------------------------------------------------------------- # Version: 0.1 # Revised: 2.1.2018 # Author: Miki (meshlogic) #------------------------------------------------------------------------------- bl_info = { "name": "Outliner Tweaks", "author": "Miki (meshlogic)", "category": "3D View", "description": "Make organizing objects and groups in Outliner easier.", "location": "Outliner > Header", "version": (0, 1), "blender": (2, 79, 0) } import bpy from bpy.props import * from bpy.types import Menu, Operator, Panel, UIList #--- Set default groups names DEFAULT_GROUP = "#DEFAULT_GROUP" NEW_GROUP = "#NEW_GROUP" #--- Print debug msg into console DEBUG = False def dprint(*msg): if DEBUG: print(*msg) #------------------------------------------------------------------------------- # MISC FUNC #------------------------------------------------------------------------------- def activate_all_layers(): for i in range(20): bpy.context.scene.layers[i] = True return def cache_layers(): layer_cache = [False]*20 for i in range(20): layer_cache[i] = bpy.context.scene.layers[i] return layer_cache def restore_layers(layer_cache): for (i, state) in enumerate(layer_cache): bpy.context.scene.layers[i] = state return #------------------------------------------------------------------------------- # IsolateSelection_OT #------------------------------------------------------------------------------- class IsolateSelection_OT(Operator): """Isolate the selected objects in the 3D viewport - 1. Enable visibility and render for the selected objects - 2. Restrict visibility and render for all other objects - 3. Make visible only the layers of the selected objects""" bl_idname = "outliner.isolate_selection" bl_label = "Isolate Selection" #--- Available only in object mode @classmethod def poll(cls, context): return context.mode == 'OBJECT' def execute(self, context): dprint("\n*** outliner.isolate_selection ***") #--- All layers must be active (you cannot get selected_objects on inactive layers) activate_all_layers() layer_mask = [False]*20 #--- Loop all objects for obj in bpy.data.objects: # 1. Make selected objects visible if obj in bpy.context.selected_objects: # Add object's layer to the mask layer_mask = [a or b for a,b in zip(obj.layers, layer_mask)] obj.hide = False obj.hide_render = False # 2. Hide all other objects else: obj.hide = True obj.hide_render = True #--- 3. Make visible only the layers of the selected objects restore_layers(layer_mask) return{'FINISHED'} #------------------------------------------------------------------------------- # ExpandCollapse_OT #------------------------------------------------------------------------------- class ExpandCollapse_OT(Operator): """Toggle Expand/Collapse one level for all items""" bl_idname = "outliner.expand_collapse_one_level" bl_label = "Expand/Collapse One Level" toggle_expand = BoolProperty(default = False) #--- Available only in object mode @classmethod def poll(cls, context): return context.mode == 'OBJECT' def execute(self, context): # Collapse all items for i in range(5): bpy.ops.outliner.show_one_level(open=False) # Toggle expand if not self.toggle_expand: bpy.ops.outliner.show_one_level(open=True) self.toggle_expand = not self.toggle_expand return{'FINISHED'} #------------------------------------------------------------------------------- # UpdateDefaultGroup_OT #------------------------------------------------------------------------------- class UpdateDefaultGroup_OT(Operator): """Update Default Group - 1. Group all unsorted objects (add them to the DEFAULT_GROUP) - 2. Clean the DEFAULT_GROUP from the objects that already belong to other groups""" bl_idname = "outliner.update_default_group" bl_label = "Update Default Group" #--- Available only in object mode @classmethod def poll(cls, context): return context.mode == 'OBJECT' def execute(self, context): dprint("\n*** outliner.update_default_group ***") #--- Create DEFAULT_GROUP if it doesn't exist yet if DEFAULT_GROUP not in bpy.data.groups: bpy.ops.group.create(name = DEFAULT_GROUP) #--- Loop all objects for obj in bpy.data.objects: # Get names of all groups the obj belongs to obj_groups = [g.name for g in obj.users_group] # 1. Add ungrouped object to DEFAULT_GROUP if len(obj_groups) == 0: bpy.data.groups.get(DEFAULT_GROUP).objects.link(obj) dprint(" ", obj.name, "added to", DEFAULT_GROUP) # 2. Remove grouped obj from DEFAULT_GROUP elif len(obj.users_group) > 1 and DEFAULT_GROUP in obj_groups: bpy.data.groups.get(DEFAULT_GROUP).objects.unlink(obj) dprint(" ", obj.name, "removed from", DEFAULT_GROUP) return{'FINISHED'} #------------------------------------------------------------------------------- # AddToNewGroup_OT #------------------------------------------------------------------------------- class AddToNew_OT(Operator): """Add to New Group - 1. Add selected objects to the NEW_GROUP - 2. Remove them from all other groups""" bl_idname = "outliner.add_to_new_group" bl_label = "Add to New Group" #--- Available only in object mode @classmethod def poll(cls, context): return context.mode == 'OBJECT' def execute(self, context): dprint("\n*** outliner.add_to_new_group ***") #--- All layers must be active (you cannot get selected_objects on inactive layers) layer_cache = cache_layers() activate_all_layers() #--- Create NEW_GROUP, if it doesn't exist yet if NEW_GROUP not in bpy.data.groups: bpy.ops.group.create(name = NEW_GROUP) #--- Loop the selected objects for obj in bpy.context.selected_objects: # 2. Remove from all groups for group in obj.users_group: group.objects.unlink(obj) dprint(" ", obj.name, "removed from", group.name) # 1. Add obj to the NEW_GROUP bpy.data.groups.get(NEW_GROUP).objects.link(obj) dprint(" ", obj.name, "added to", NEW_GROUP) #--- Restore layers visibility restore_layers(layer_cache) return{'FINISHED'} #------------------------------------------------------------------------------- # RemoveFromGroups_OT #------------------------------------------------------------------------------- class RemoveFromGroups_OT(Operator): """Remove from Groups - 1. Remove selected objects from all groups - 2. Add them to the DEFAULT_GROUP""" bl_idname = "outliner.remove_from_groups" bl_label = "Remove from Groups" #--- Available only in object mode @classmethod def poll(cls, context): return context.mode == 'OBJECT' def execute(self, context): dprint("\n*** outliner.remove_from_groups ***") #--- All layers must be active (you cannot get selected_objects on inactive layers) layer_cache = cache_layers() activate_all_layers() #--- Loop the selected objects for obj in bpy.context.selected_objects: # 1. Remove obj from all groups for group in obj.users_group: group.objects.unlink(obj) dprint(" ", obj.name, "removed from", group.name) # 2. Add obj to DEFAULT_GROUP bpy.data.groups.get(DEFAULT_GROUP).objects.link(obj) dprint(" ", obj.name, "added to", DEFAULT_GROUP) #--- Restore layers visibility restore_layers(layer_cache) return{'FINISHED'} #------------------------------------------------------------------------------- # MENU ITEMS #------------------------------------------------------------------------------- def draw_menu(self, context): layout = self.layout layout.menu("OutlinerTweaksMenu", icon='COLLAPSEMENU', text="") class OutlinerTweaksMenu(Menu): bl_idname = "OutlinerTweaksMenu" bl_label = "" def draw(self, context): layout = self.layout layout.operator("outliner.isolate_selection", icon='RESTRICT_VIEW_OFF') layout.operator("outliner.expand_collapse_one_level", icon='OOPS') layout.separator() layout.operator("outliner.remove_from_groups", icon='X') layout.operator("outliner.add_to_new_group", icon='GROUP') layout.operator("outliner.update_default_group", icon='FILE_REFRESH') #------------------------------------------------------------------------------- # REGISTER/UNREGISTER KEYMAPS #------------------------------------------------------------------------------- addon_keymaps = [] def register_keymaps(): wm = bpy.context.window_manager km = wm.keyconfigs.addon.keymaps.new(name="Outliner", space_type="OUTLINER") kmi = km.keymap_items.new("outliner.isolate_selection", "Q", "PRESS", shift=False) addon_keymaps.append((km, kmi)) km = wm.keyconfigs.addon.keymaps.new(name="Outliner", space_type="OUTLINER") kmi = km.keymap_items.new("outliner.expand_collapse_one_level", "E", "PRESS", shift=False) addon_keymaps.append((km, kmi)) km = wm.keyconfigs.addon.keymaps.new(name="Outliner", space_type="OUTLINER") kmi = km.keymap_items.new("outliner.update_default_group", "U", "PRESS", shift=False) addon_keymaps.append((km, kmi)) km = wm.keyconfigs.addon.keymaps.new(name="Outliner", space_type="OUTLINER") kmi = km.keymap_items.new("outliner.add_to_new_group", "N", "PRESS", shift=False) addon_keymaps.append((km, kmi)) km = wm.keyconfigs.addon.keymaps.new(name="Outliner", space_type="OUTLINER") kmi = km.keymap_items.new("outliner.remove_from_groups", "R", "PRESS", shift=False) addon_keymaps.append((km, kmi)) def unregister_keymaps(): for km, kmi in addon_keymaps: km.keymap_items.remove(kmi) #------------------------------------------------------------------------------- # REGISTER/UNREGISTER CLASSES #------------------------------------------------------------------------------- def register(): bpy.utils.register_module(__name__) bpy.types.OUTLINER_HT_header.prepend(draw_menu) register_keymaps() def unregister(): unregister_keymaps() bpy.types.OUTLINER_HT_header.remove(draw_menu) bpy.utils.unregister_module(__name__) if __name__ == "__main__": register() |
Comments
Comments powered by Disqus