Skip to content
Snippets Groups Projects
ui_layer_manager.py 20.1 KiB
Newer Older
  • Learn to ignore specific revisions
  • # ##### BEGIN GPL LICENSE BLOCK #####
    #
    #  This program is free software; you can redistribute it and/or
    #  modify it under the terms of the GNU General Public License
    #  as published by the Free Software Foundation; either version 2
    #  of the License, or (at your option) any later version.
    #
    #  This program is distributed in the hope that it will be useful,
    #  but WITHOUT ANY WARRANTY; without even the implied warranty of
    #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    #  GNU General Public License for more details.
    #
    #  You should have received a copy of the GNU General Public License
    #  along with this program; if not, write to the Free Software Foundation,
    #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
    #
    # ##### END GPL LICENSE BLOCK #####
    
    # <pep8 compliant>
    #
    bl_info = {
        "name": "Layer Management",
        "author": "Alfonso Annarumma",
        "version": (1, 5, 1),
    
        "blender": (2, 71, 0),
        "location": "Toolshelf > Layers Tab",
    
    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 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512
        "warning": "",
        "description": "Display and Edit Layer Name",
        "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/3D_interaction/layer_manager",
        "category": "3D View",
    }
    
    import bpy
    from bpy.types import Menu, Panel, UIList, PropertyGroup
    from bpy.props import StringProperty, BoolProperty, IntProperty, CollectionProperty, BoolVectorProperty, PointerProperty
    from bpy.app.handlers import persistent
    
    EDIT_MODES = {'EDIT_MESH', 'EDIT_CURVE', 'EDIT_SURFACE', 'EDIT_METABALL', 'EDIT_TEXT', 'EDIT_ARMATURE'}
    
    
    class NamedLayer(PropertyGroup):
        name = StringProperty(name="Layer Name")
        use_lock = BoolProperty(name="Lock Layer", default=False)
        use_object_select = BoolProperty(name="Object Select", default=True)
        use_wire = BoolProperty(name="Wire Layer", default=False)
    
    
    class NamedLayers(PropertyGroup):
        layers = CollectionProperty(type=NamedLayer)
        use_hide_empty_layers = BoolProperty(name="Hide Empty Layer", default=False)
        use_extra_options = BoolProperty(name="Show Extra Options", default=True)
        use_layer_indices = BoolProperty(name="Show Layer Indices", default=False)
        use_classic = BoolProperty(name="Classic", default=False, description="Use a classic layer selection visibility")
    
        use_init = BoolProperty(default=True, options={'HIDDEN'})
    
    
    # Stupid, but only solution currently is to use a handler to init that layers collection...
    @persistent
    def check_init_data(scene):
        namedlayers = scene.namedlayers
        if namedlayers.use_init:
            while namedlayers.layers:
                namedlayers.layers.remove(0)
            for i in range(20):
                layer = namedlayers.layers.add()
                layer.name = "Layer%.2d" % i
            namedlayers.use_init = False
    
    
    class LayerGroup(PropertyGroup):
        use_toggle = BoolProperty(name="", default=False)
        use_wire = BoolProperty(name="", default=False)
        use_lock = BoolProperty(name="", default=False)
    
        layers = BoolVectorProperty(name="Layers", default=([False] * 20), size=20, subtype='LAYER')
    
    
    class SCENE_OT_namedlayer_group_add(bpy.types.Operator):
        """Add and select a new layer group"""
        bl_idname = "scene.namedlayer_group_add"
        bl_label = "Add Layer Group"
    
        layers = BoolVectorProperty(name="Layers", default=([False] * 20), size=20)
    
        @classmethod
        def poll(cls, context):
            return bool(context.scene)
    
        def execute(self, context):
            scene = context.scene
            layergroups = scene.layergroups
            layers = self.layers
    
            group_idx = len(layergroups)
            layer_group = layergroups.add()
            layer_group.name = "LayerGroup.%.3d" % group_idx
            layer_group.layers = layers
            scene.layergroups_index = group_idx
    
            return {'FINISHED'}
    
    
    class SCENE_OT_namedlayer_group_remove(bpy.types.Operator):
        """Remove selected layer group"""
        bl_idname = "scene.namedlayer_group_remove"
        bl_label = "Remove Layer Group"
    
        group_idx = bpy.props.IntProperty()
    
        @classmethod
        def poll(cls, context):
            return bool(context.scene)
    
        def execute(self, context):
            scene = context.scene
            group_idx = self.group_idx
    
            scene.layergroups.remove(group_idx)
            if scene.layergroups_index > len(scene.layergroups) - 1:
                scene.layergroups_index = len(scene.layergroups) - 1
    
            return {'FINISHED'}
    
    
    class SCENE_OT_namedlayer_toggle_visibility(bpy.types.Operator):
        """Show or hide given layer (shift to extend)"""
        bl_idname = "scene.namedlayer_toggle_visibility"
        bl_label = "Show/Hide Layer"
    
        layer_idx = IntProperty()
        group_idx = IntProperty()
        use_spacecheck = BoolProperty()
        extend = BoolProperty(options={'SKIP_SAVE'})
    
        @classmethod
        def poll(cls, context):
            return context.scene and (context.area.spaces.active.type == 'VIEW_3D')
    
        def execute(self, context):
            scene = context.scene
            layer_cont = context.area.spaces.active if self.use_spacecheck else context.scene
            layer_idx = self.layer_idx
    
            if layer_idx == -1:
                group_idx = self.group_idx
                layergroups = scene.layergroups[group_idx]
                group_layers = layergroups.layers
                layers = layer_cont.layers
    
                if layergroups.use_toggle:
                    layer_cont.layers = [not group_layer and layer for group_layer, layer in zip(group_layers, layers)]
                    layergroups.use_toggle = False
                else:
                    layer_cont.layers = [group_layer or layer for group_layer, layer in zip(group_layers, layers)]
                    layergroups.use_toggle = True
            else:
                if self.extend:
                    layer_cont.layers[layer_idx] = not layer_cont.layers[layer_idx]
                else:
                    layers = [False] * 20
                    layers[layer_idx] = True
                    layer_cont.layers = layers
            return {'FINISHED'}
    
        def invoke(self, context, event):
            self.extend = event.shift
            return self.execute(context)
    
    
    class SCENE_OT_namedlayer_move_to_layer(bpy.types.Operator):
        """Move selected objects to this Layer (shift to extend)"""
        bl_idname = "scene.namedlayer_move_to_layer"
        bl_label = "Move Objects To Layer"
    
        layer_idx = IntProperty()
        extend = BoolProperty(options={'SKIP_SAVE'})
    
        @classmethod
        def poll(cls, context):
            return context.scene
    
        def execute(self, context):
            layer_idx = self.layer_idx
            scene = context.scene
    
            # Cycle all objects in the layer
            for obj in scene.objects:
                if obj.select:
                    # If object is in at least one of the scene's visible layers...
                    if True in {ob_layer and sce_layer for ob_layer, sce_layer in zip(obj.layers, scene.layers)}:
                        if self.extend:
                            obj.layers[layer_idx] = not obj.layers[layer_idx]
                        else:
                            layer = [False] * 20
                            layer[layer_idx] = True
                            obj.layers = layer
            return {'FINISHED'}
    
        def invoke(self, context, event):
            self.extend = event.shift
            return self.execute(context)
    
    
    class SCENE_OT_namedlayer_toggle_wire(bpy.types.Operator):
        """Toggle all objects on this layer draw as wire"""
        bl_idname = "scene.namedlayer_toggle_wire"
        bl_label = "Toggle Objects Draw Wire"
    
        layer_idx = IntProperty()
        use_wire = BoolProperty()
        group_idx = IntProperty()
    
        @classmethod
        def poll(cls, context):
            return context.scene and (context.area.spaces.active.type == 'VIEW_3D')
    
        def execute(self, context):
            scene = context.scene
            layer_idx = self.layer_idx
            use_wire = self.use_wire
    
            view_3d = context.area.spaces.active
    
            # Check if layer have some thing
            if view_3d.layers_used[layer_idx] or layer_idx == -1:
                display = 'WIRE' if use_wire else 'TEXTURED'
                # Cycle all objects in the layer.
                for obj in context.scene.objects:
                    if layer_idx == -1:
                        group_idx = self.group_idx
                        group_layers = scene.layergroups[group_idx].layers
                        layers = obj.layers
                        if True in {layer and group_layer for layer, group_layer in zip(layers, group_layers)}:
                            obj.draw_type = display
                            scene.layergroups[group_idx].use_wire = use_wire
                    else:
                        if obj.layers[layer_idx]:
                            obj.draw_type = display
                            scene.namedlayers.layers[layer_idx].use_wire = use_wire
    
            return {'FINISHED'}
    
    
    class SCENE_OT_namedlayer_lock_selected(bpy.types.Operator):
        """Lock all selected objects on this layer"""
        bl_idname = "scene.namedlayer_lock_selected"
        bl_label = "Lock Objects"
    
        layer_idx = IntProperty()
        use_lock = BoolProperty()
        group_idx = IntProperty()
    
        @classmethod
        def poll(cls, context):
            return context.scene and (context.area.spaces.active.type == 'VIEW_3D')
    
        def execute(self, context):
            scene = context.scene
            view_3d = context.area.spaces.active
            layer_idx = self.layer_idx
            group_idx = self.group_idx
            group_layers = scene.layergroups[group_idx].layers
            use_lock = self.use_lock
    
            # check if layer have some thing
            if layer_idx == -1 or view_3d.layers_used[layer_idx]:
                # Cycle all objects in the layer.
                for obj in context.scene.objects:
                    if layer_idx == -1:
                        layers = obj.layers
                        if True in {layer and group_layer for layer, group_layer in zip(layers, group_layers)}:
                            obj.hide_select = not use_lock
                            obj.select = False
                            scene.layergroups[group_idx].use_lock = not use_lock
                    else:
                        if obj.layers[layer_idx]:
                            obj.hide_select = not use_lock
                            obj.select = False
                            scene.namedlayers.layers[layer_idx].use_lock = not use_lock
    
            return {'FINISHED'}
    
    
    class SCENE_OT_namedlayer_select_objects_by_layer(bpy.types.Operator):
        """Select all the objects on this Layer (shift for multi selection, ctrl to make active the last selected object)"""
        bl_idname = "scene.namedlayer_select_objects_by_layer"
        bl_label = "Select Objects In Layer"
    
        select_obj = BoolProperty()
        layer_idx = IntProperty()
    
        extend = BoolProperty(options={'SKIP_SAVE'})
        active = BoolProperty(options={'SKIP_SAVE'})
    
        @classmethod
        def poll(cls, context):
            return context.scene and (context.area.spaces.active.type == 'VIEW_3D')
    
        def execute(self, context):
            scene = context.scene
            view_3d = context.area.spaces.active
            select_obj = self.select_obj
            layer_idx = self.layer_idx
    
            not_all_selected = 0
            # check if layer have some thing
            if view_3d.layers_used[layer_idx]:
                objects = []
                for obj in context.scene.objects:
                    if obj.layers[layer_idx]:
                        objects.append(obj)
                        not_all_selected -= 1
                        if self.active:
                            context.scene.objects.active = obj
                        if obj.select:
                            not_all_selected += 1
                if not not_all_selected:
                    for obj in objects:
                        obj.select = False
                else:
                    bpy.ops.object.select_by_layer(extend=self.extend, layers=layer_idx + 1)
    
            return {'FINISHED'}
    
        def invoke(self, context, event):
            self.extend = event.shift
            self.active = event.ctrl
            return self.execute(context)
    
    
    class SCENE_OT_namedlayer_show_all(bpy.types.Operator):
        """Show or hide all layers in the scene"""
        bl_idname = "scene.namedlayer_show_all"
        bl_label = "Select All Layers"
    
        show = BoolProperty()
    
        @classmethod
        def poll(cls, context):
            return context.scene and (context.area.spaces.active.type == 'VIEW_3D')
    
        def execute(self, context):
            scene = context.scene
            view_3d = context.area.spaces.active
            show = self.show
            active_layer = scene.active_layer
    
            # check for lock camera and layer is active
            layer_cont = scene if view_3d.lock_camera_and_layers else view_3d
    
            if show:
                layer_cont.layers[:] = [True] * 20
                # Restore active layer (stupid, but Scene.active_layer is readonly).
                layer_cont.layers[active_layer] = False
                layer_cont.layers[active_layer] = True
            else:
                layers = [False] * 20
                # Keep selection of active layer
                layers[active_layer] = True
                layer_cont.layers[:] = layers
    
            return {'FINISHED'}
    
    
    class SCENE_PT_namedlayer_layers(bpy.types.Panel):
        bl_space_type = 'VIEW_3D'
        bl_region_type = 'TOOLS'
        bl_label = "Layer Management"
        bl_options = {'DEFAULT_CLOSED'}
        bl_category = "Layer"
    
        @classmethod
        def poll(self, context):
            return ((getattr(context, "mode", 'EDIT_MESH') not in EDIT_MODES) and
                    (context.area.spaces.active.type == 'VIEW_3D'))
    
        def draw(self, context):
            scene = context.scene
            view_3d = context.area.spaces.active
            actob = context.object
            namedlayers = scene.namedlayers
            use_extra = namedlayers.use_extra_options
            use_hide = namedlayers.use_hide_empty_layers
            use_indices = namedlayers.use_layer_indices
            use_classic = namedlayers.use_classic
    
            # Check for lock camera and layer is active
            if view_3d.lock_camera_and_layers:
                layer_cont = scene
                use_spacecheck = False
            else:
                layer_cont = view_3d
                use_spacecheck = True
    
            layout = self.layout
            row = layout.row()
            col = row.column()
            col.prop(view_3d, "lock_camera_and_layers", text="")
            # Check if there is a layer off
            show = (False in {layer for layer in layer_cont.layers})
            icon = 'RESTRICT_VIEW_ON' if show else 'RESTRICT_VIEW_OFF'
            col.operator("scene.namedlayer_show_all", emboss=False, icon=icon, text="").show = show
    
            col = row.column()
            col.prop(namedlayers, "use_classic")
            col.prop(namedlayers, "use_extra_options", text="Options")
    
            col = row.column()
            col.prop(namedlayers, "use_layer_indices", text="Indices")
            col.prop(namedlayers, "use_hide_empty_layers", text="Hide Empty")
    
            col = layout.column()
            for layer_idx in range(20):
                namedlayer = namedlayers.layers[layer_idx]
                is_layer_used = view_3d.layers_used[layer_idx]
    
                if (use_hide and not is_layer_used):
                    # Hide unused layers and this one is unused, skip.
                    continue
    
                row = col.row(align=True)
    
                # layer index
                if use_indices:
                    row.label(text="%.2d." % (layer_idx + 1))
    
                # visualization
                icon = 'RESTRICT_VIEW_OFF' if layer_cont.layers[layer_idx] else 'RESTRICT_VIEW_ON'
                if use_classic:
                    op = row.operator("scene.namedlayer_toggle_visibility", text="", icon=icon, emboss=True)
                    op.layer_idx = layer_idx
                    op.use_spacecheck = use_spacecheck
                else:
                    row.prop(layer_cont, "layers", index=layer_idx, emboss=True, icon=icon, toggle=True, text="")
    
                # Name (use special icon for active layer).
                icon = 'FILE_TICK' if (getattr(layer_cont, "active_layer", -1) == layer_idx) else 'NONE'
                row.prop(namedlayer, "name", text="", icon=icon)
    
                if use_extra:
                    use_lock = namedlayer.use_lock
    
                    # Select by type operator
                    sub = row.column(align=True)
                    sub.enabled = not use_lock
                    sub.operator("scene.namedlayer_select_objects_by_layer", icon='RESTRICT_SELECT_OFF',
                                 text="", emboss=True).layer_idx = layer_idx
    
                    # Lock operator
                    icon = 'LOCKED' if use_lock else 'UNLOCKED'
                    op = row.operator("scene.namedlayer_lock_selected", text="", emboss=True, icon=icon)
                    op.layer_idx = layer_idx
                    op.use_lock = use_lock
    
                    # Merge layer
                    # check if layer has something
                    has_active = (actob and actob.layers[layer_idx])
                    icon = ('LAYER_ACTIVE' if has_active else 'LAYER_USED') if is_layer_used else 'RADIOBUT_OFF'
                    row.operator("scene.namedlayer_move_to_layer", text="", emboss=True, icon=icon).layer_idx = layer_idx
    
                    # Wire view
                    use_wire = namedlayer.use_wire
                    icon = 'WIRE' if use_wire else 'POTATO'
                    op = row.operator("scene.namedlayer_toggle_wire", text="", emboss=True, icon=icon)
                    op.layer_idx = layer_idx
                    op.use_wire = not use_wire
    
            if len(scene.objects) == 0:
                layout.label(text="No objects in scene")
    
    
    class SCENE_UL_namedlayer_groups(UIList):
        def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
            layer_group = item
    
            # check for lock camera and layer is active
            view_3d = context.area.spaces.active  # Ensured it is a 'VIEW_3D' in panel's poll(), weak... :/
            use_spacecheck = False if view_3d.lock_camera_and_layers else True
    
            if self.layout_type in {'DEFAULT', 'COMPACT'}:
                layout.prop(layer_group, "name", text="", emboss=False)
                # lock operator
                use_lock = layer_group.use_lock
                icon = 'LOCKED' if use_lock else 'UNLOCKED'
                op = layout.operator("scene.namedlayer_lock_selected", text="", emboss=False, icon=icon)
                op.use_lock = use_lock
                op.group_idx = index
                op.layer_idx = -1
    
                # view operator
                icon = 'RESTRICT_VIEW_OFF' if layer_group.use_toggle else 'RESTRICT_VIEW_ON'
                op = layout.operator("scene.namedlayer_toggle_visibility", text="", emboss=False, icon=icon)
                op.use_spacecheck = use_spacecheck
                op.group_idx = index
                op.layer_idx = -1
    
                # wire operator
                use_wire = layer_group.use_wire
                icon = 'WIRE' if use_wire else 'POTATO'
                op = layout.operator("scene.namedlayer_toggle_wire", text="", emboss=False, icon=icon)
                op.use_wire = not use_wire
                op.group_idx = index
                op.layer_idx = -1
    
            elif self.layout_type in {'GRID'}:
                layout.alignment = 'CENTER'
    
    
    class SCENE_PT_namedlayer_groups(bpy.types.Panel):
        bl_space_type = 'VIEW_3D'
        bl_region_type = 'TOOLS'
    
        bl_label = "Layer Groups"
        bl_options = {'DEFAULT_CLOSED'}
    
        @classmethod
        def poll(self, context):
            return ((getattr(context, "mode", 'EDIT_MESH') not in EDIT_MODES) and
                    (context.area.spaces.active.type == 'VIEW_3D'))
    
        def draw(self, context):
            scene = context.scene
            group_idx = scene.layergroups_index
    
            layout = self.layout
            row = layout.row()
            row.template_list("SCENE_UL_namedlayer_groups", "", scene, "layergroups", scene, "layergroups_index")
    
            col = row.column(align=True)
            col.operator("scene.namedlayer_group_add", icon='ZOOMIN', text="").layers = scene.layers
            col.operator("scene.namedlayer_group_remove", icon='ZOOMOUT', text="").group_idx = group_idx
    
            if bool(scene.layergroups):
                layout.prop(scene.layergroups[group_idx], "layers", text="", toggle=True)
                layout.prop(scene.layergroups[group_idx], "name", text="Name:")
    
    
    def register():
        bpy.utils.register_module(__name__)
        bpy.types.Scene.layergroups = CollectionProperty(type=LayerGroup)
        # Unused, but this is needed for the TemplateList to work...
        bpy.types.Scene.layergroups_index = IntProperty(default=-1)
        bpy.types.Scene.namedlayers = PointerProperty(type=NamedLayers)
        bpy.app.handlers.scene_update_post.append(check_init_data)
    
    
    def unregister():
        bpy.app.handlers.scene_update_post.remove(check_init_data)
        del bpy.types.Scene.layergroups
        del bpy.types.Scene.layergroups_index
        del bpy.types.Scene.namedlayers
        bpy.utils.unregister_module(__name__)
    
    
    if __name__ == "__main__":
        register()