diff --git a/ui_layer_manager.py b/ui_layer_manager.py new file mode 100644 index 0000000000000000000000000000000000000000..5a94c2cfdf9f8316184e555b4bb48d41ec4bb669 --- /dev/null +++ b/ui_layer_manager.py @@ -0,0 +1,557 @@ +# ##### 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, 70, 0), + "location": "View3D > Properties panel > Layer Management", + "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_category = "Layer" + 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()