1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
22 "name": "Layer Management",
23 "author": "Alfonso Annarumma, Bastien Montagne",
25 "blender": (2, 76, 0),
26 "location": "Toolshelf > Layers Tab",
28 "description": "Display and Edit Layer Name",
29 "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/"
30 "Scripts/3D_interaction/layer_manager",
31 "category": "3D View",
35 from bpy
.types
import (
42 from bpy
.props
import (
50 from bpy
.app
.handlers
import persistent
52 EDIT_MODES
= {'EDIT_MESH', 'EDIT_CURVE', 'EDIT_SURFACE', 'EDIT_METABALL', 'EDIT_TEXT', 'EDIT_ARMATURE'}
56 FAKE_LAYER_GROUP
= [True] * NUM_LAYERS
59 class NamedLayer(PropertyGroup
):
60 name
= StringProperty(
63 use_lock
= BoolProperty(
67 use_object_select
= BoolProperty(
71 use_wire
= BoolProperty(
77 class NamedLayers(PropertyGroup
):
78 layers
= CollectionProperty(type=NamedLayer
)
80 use_hide_empty_layers
= BoolProperty(
81 name
="Hide Empty Layer",
84 use_extra_options
= BoolProperty(
85 name
="Show Extra Options",
88 use_layer_indices
= BoolProperty(
89 name
="Show Layer Indices",
92 use_classic
= BoolProperty(
95 description
="Use a classic layer selection visibility"
97 use_init
= BoolProperty(
103 # Stupid, but only solution currently is to use a handler to init that layers collection...
105 def check_init_data(scene
):
106 namedlayers
= scene
.namedlayers
107 if namedlayers
.use_init
:
108 while namedlayers
.layers
:
109 namedlayers
.layers
.remove(0)
110 for i
in range(NUM_LAYERS
):
111 layer
= namedlayers
.layers
.add()
112 layer
.name
= "Layer%.2d" % (i
+ 1) # Blender use layer nums starting from 1, not 0.
113 namedlayers
.use_init
= False
116 class LayerGroup(PropertyGroup
):
117 use_toggle
= BoolProperty(name
="", default
=False)
118 use_wire
= BoolProperty(name
="", default
=False)
119 use_lock
= BoolProperty(name
="", default
=False)
121 layers
= BoolVectorProperty(name
="Layers", default
=([False] * NUM_LAYERS
), size
=NUM_LAYERS
, subtype
='LAYER')
124 class SCENE_OT_namedlayer_group_add(Operator
):
125 """Add and select a new layer group"""
126 bl_idname
= "scene.namedlayer_group_add"
127 bl_label
= "Add Layer Group"
129 layers
= BoolVectorProperty(name
="Layers", default
=([False] * NUM_LAYERS
), size
=NUM_LAYERS
)
132 def poll(cls
, context
):
133 return bool(context
.scene
)
135 def execute(self
, context
):
136 scene
= context
.scene
137 layergroups
= scene
.layergroups
140 group_idx
= len(layergroups
)
141 layer_group
= layergroups
.add()
142 layer_group
.name
= "LayerGroup.%.3d" % group_idx
143 layer_group
.layers
= layers
144 scene
.layergroups_index
= group_idx
149 class SCENE_OT_namedlayer_group_remove(Operator
):
150 """Remove selected layer group"""
151 bl_idname
= "scene.namedlayer_group_remove"
152 bl_label
= "Remove Layer Group"
154 group_idx
= bpy
.props
.IntProperty()
157 def poll(cls
, context
):
158 return bool(context
.scene
)
160 def execute(self
, context
):
161 scene
= context
.scene
162 group_idx
= self
.group_idx
164 scene
.layergroups
.remove(group_idx
)
165 if scene
.layergroups_index
> len(scene
.layergroups
) - 1:
166 scene
.layergroups_index
= len(scene
.layergroups
) - 1
171 class SCENE_OT_namedlayer_toggle_visibility(Operator
):
172 """Show or hide given layer (shift to extend)"""
173 bl_idname
= "scene.namedlayer_toggle_visibility"
174 bl_label
= "Show/Hide Layer"
176 layer_idx
= IntProperty()
177 group_idx
= IntProperty()
178 use_spacecheck
= BoolProperty()
179 extend
= BoolProperty(options
={'SKIP_SAVE'})
182 def poll(cls
, context
):
183 return context
.scene
and (context
.area
.spaces
.active
.type == 'VIEW_3D')
185 def execute(self
, context
):
186 scene
= context
.scene
187 layer_cont
= context
.area
.spaces
.active
if self
.use_spacecheck
else context
.scene
188 layer_idx
= self
.layer_idx
191 group_idx
= self
.group_idx
192 layergroups
= scene
.layergroups
[group_idx
]
193 group_layers
= layergroups
.layers
194 layers
= layer_cont
.layers
196 if layergroups
.use_toggle
:
197 layer_cont
.layers
= [not group_layer
and layer
for group_layer
, layer
in zip(group_layers
, layers
)]
198 layergroups
.use_toggle
= False
200 layer_cont
.layers
= [group_layer
or layer
for group_layer
, layer
in zip(group_layers
, layers
)]
201 layergroups
.use_toggle
= True
204 layer_cont
.layers
[layer_idx
] = not layer_cont
.layers
[layer_idx
]
206 layers
= [False] * NUM_LAYERS
207 layers
[layer_idx
] = True
208 layer_cont
.layers
= layers
211 def invoke(self
, context
, event
):
212 self
.extend
= event
.shift
213 return self
.execute(context
)
216 class SCENE_OT_namedlayer_move_to_layer(Operator
):
217 """Move selected objects to this Layer (shift to extend)"""
218 bl_idname
= "scene.namedlayer_move_to_layer"
219 bl_label
= "Move Objects To Layer"
221 layer_idx
= IntProperty()
222 extend
= BoolProperty(options
={'SKIP_SAVE'})
225 def poll(cls
, context
):
228 def execute(self
, context
):
229 layer_idx
= self
.layer_idx
230 scene
= context
.scene
232 # Cycle all objects in the layer
233 for obj
in scene
.objects
:
235 # If object is in at least one of the scene's visible layers...
236 if True in {ob_layer
and sce_layer
for ob_layer
, sce_layer
in zip(obj
.layers
, scene
.layers
)}:
238 obj
.layers
[layer_idx
] = not obj
.layers
[layer_idx
]
240 layer
= [False] * NUM_LAYERS
241 layer
[layer_idx
] = True
245 def invoke(self
, context
, event
):
246 self
.extend
= event
.shift
247 return self
.execute(context
)
250 class SCENE_OT_namedlayer_toggle_wire(Operator
):
251 """Toggle all objects on this layer draw as wire"""
252 bl_idname
= "scene.namedlayer_toggle_wire"
253 bl_label
= "Toggle Objects Draw Wire"
255 layer_idx
= IntProperty()
256 use_wire
= BoolProperty()
257 group_idx
= IntProperty()
260 def poll(cls
, context
):
261 return context
.scene
and (context
.area
.spaces
.active
.type == 'VIEW_3D')
263 def execute(self
, context
):
264 scene
= context
.scene
265 layer_idx
= self
.layer_idx
266 use_wire
= self
.use_wire
268 view_3d
= context
.area
.spaces
.active
270 # Check if layer have some thing
271 if view_3d
.layers_used
[layer_idx
] or layer_idx
== -1:
272 display
= 'WIRE' if use_wire
else 'TEXTURED'
273 # Cycle all objects in the layer.
274 for obj
in context
.scene
.objects
:
276 group_idx
= self
.group_idx
277 group_layers
= scene
.layergroups
[group_idx
].layers
279 if True in {layer
and group_layer
for layer
, group_layer
in zip(layers
, group_layers
)}:
280 obj
.draw_type
= display
281 scene
.layergroups
[group_idx
].use_wire
= use_wire
283 if obj
.layers
[layer_idx
]:
284 obj
.draw_type
= display
285 scene
.namedlayers
.layers
[layer_idx
].use_wire
= use_wire
290 class SCENE_OT_namedlayer_lock_all(Operator
):
291 """Lock all objects on this layer"""
292 bl_idname
= "scene.namedlayer_lock_all"
293 bl_label
= "Lock Objects"
295 layer_idx
= IntProperty()
296 use_lock
= BoolProperty()
297 group_idx
= IntProperty()
300 def poll(cls
, context
):
301 return context
.scene
and (context
.area
.spaces
.active
.type == 'VIEW_3D')
303 def execute(self
, context
):
304 scene
= context
.scene
305 view_3d
= context
.area
.spaces
.active
306 layer_idx
= self
.layer_idx
307 group_idx
= self
.group_idx
308 group_layers
= FAKE_LAYER_GROUP
if group_idx
< 0 else scene
.layergroups
[group_idx
].layers
309 use_lock
= self
.use_lock
311 # check if layer have some thing
312 if layer_idx
== -1 or view_3d
.layers_used
[layer_idx
]:
313 # Cycle all objects in the layer.
314 for obj
in context
.scene
.objects
:
317 if True in {layer
and group_layer
for layer
, group_layer
in zip(layers
, group_layers
)}:
318 obj
.hide_select
= not use_lock
320 scene
.layergroups
[group_idx
].use_lock
= not use_lock
322 if obj
.layers
[layer_idx
]:
323 obj
.hide_select
= not use_lock
325 scene
.namedlayers
.layers
[layer_idx
].use_lock
= not use_lock
330 class SCENE_OT_namedlayer_select_objects_by_layer(Operator
):
331 """Select all the objects on this Layer (shift for multi selection, ctrl to make active the last selected object)"""
332 bl_idname
= "scene.namedlayer_select_objects_by_layer"
333 bl_label
= "Select Objects In Layer"
335 select_obj
= BoolProperty()
336 layer_idx
= IntProperty()
338 extend
= BoolProperty(options
={'SKIP_SAVE'})
339 active
= BoolProperty(options
={'SKIP_SAVE'})
342 def poll(cls
, context
):
343 return context
.scene
and (context
.area
.spaces
.active
.type == 'VIEW_3D')
345 def execute(self
, context
):
346 scene
= context
.scene
347 view_3d
= context
.area
.spaces
.active
348 select_obj
= self
.select_obj
349 layer_idx
= self
.layer_idx
352 # check if layer have some thing
353 if view_3d
.layers_used
[layer_idx
]:
355 for obj
in context
.scene
.objects
:
356 if obj
.layers
[layer_idx
]:
358 not_all_selected
-= 1
360 context
.scene
.objects
.active
= obj
362 not_all_selected
+= 1
363 if not not_all_selected
:
367 bpy
.ops
.object.select_by_layer(match
='SHARED', extend
=self
.extend
, layers
=layer_idx
+ 1)
371 def invoke(self
, context
, event
):
372 self
.extend
= event
.shift
373 self
.active
= event
.ctrl
374 return self
.execute(context
)
377 class SCENE_OT_namedlayer_show_all(Operator
):
378 """Show or hide all layers in the scene"""
379 bl_idname
= "scene.namedlayer_show_all"
380 bl_label
= "Select All Layers"
382 show
= BoolProperty()
385 def poll(cls
, context
):
386 return context
.scene
and (context
.area
.spaces
.active
.type == 'VIEW_3D')
388 def execute(self
, context
):
389 scene
= context
.scene
390 view_3d
= context
.area
.spaces
.active
392 active_layer
= scene
.active_layer
394 # check for lock camera and layer is active
395 layer_cont
= scene
if view_3d
.lock_camera_and_layers
else view_3d
398 layer_cont
.layers
[:] = [True] * NUM_LAYERS
399 # Restore active layer (stupid, but Scene.active_layer is readonly).
400 layer_cont
.layers
[active_layer
] = False
401 layer_cont
.layers
[active_layer
] = True
403 layers
= [False] * NUM_LAYERS
404 # Keep selection of active layer
405 layers
[active_layer
] = True
406 layer_cont
.layers
[:] = layers
411 class SCENE_PT_namedlayer_layers(Panel
):
412 bl_space_type
= 'VIEW_3D'
413 bl_region_type
= 'TOOLS'
414 bl_label
= "Layer Management"
415 bl_category
= "Layers"
416 bl_context
= "objectmode"
419 def poll(self
, context
):
420 return ((getattr(context
, "mode", 'EDIT_MESH') not in EDIT_MODES
) and
421 (context
.area
.spaces
.active
.type == 'VIEW_3D'))
423 def draw(self
, context
):
424 scene
= context
.scene
425 view_3d
= context
.area
.spaces
.active
426 actob
= context
.object
427 namedlayers
= scene
.namedlayers
428 use_extra
= namedlayers
.use_extra_options
429 use_hide
= namedlayers
.use_hide_empty_layers
430 use_indices
= namedlayers
.use_layer_indices
431 use_classic
= namedlayers
.use_classic
433 # Check for lock camera and layer is active
434 if view_3d
.lock_camera_and_layers
:
436 use_spacecheck
= False
439 use_spacecheck
= True
444 col
.prop(view_3d
, "lock_camera_and_layers", text
="")
445 # Check if there is a layer off
446 show
= (False in {layer
for layer
in layer_cont
.layers
})
447 icon
= 'RESTRICT_VIEW_ON' if show
else 'RESTRICT_VIEW_OFF'
448 col
.operator("scene.namedlayer_show_all", emboss
=False, icon
=icon
, text
="").show
= show
451 col
.prop(namedlayers
, "use_classic")
452 col
.prop(namedlayers
, "use_extra_options", text
="Options")
455 col
.prop(namedlayers
, "use_layer_indices", text
="Indices")
456 col
.prop(namedlayers
, "use_hide_empty_layers", text
="Hide Empty")
458 col
= layout
.column()
459 for layer_idx
in range(NUM_LAYERS
):
460 namedlayer
= namedlayers
.layers
[layer_idx
]
461 is_layer_used
= view_3d
.layers_used
[layer_idx
]
463 if (use_hide
and not is_layer_used
):
464 # Hide unused layers and this one is unused, skip.
467 row
= col
.row(align
=True)
471 sub
= row
.row(align
=True)
472 sub
.alignment
= 'LEFT'
473 sub
.label(text
="%.2d." % (layer_idx
+ 1))
476 icon
= 'RESTRICT_VIEW_OFF' if layer_cont
.layers
[layer_idx
] else 'RESTRICT_VIEW_ON'
478 op
= row
.operator("scene.namedlayer_toggle_visibility", text
="", icon
=icon
, emboss
=True)
479 op
.layer_idx
= layer_idx
480 op
.use_spacecheck
= use_spacecheck
482 row
.prop(layer_cont
, "layers", index
=layer_idx
, emboss
=True, icon
=icon
, toggle
=True, text
="")
484 # Name (use special icon for active layer)
485 icon
= 'FILE_TICK' if (getattr(layer_cont
, "active_layer", -1) == layer_idx
) else 'NONE'
486 row
.prop(namedlayer
, "name", text
="", icon
=icon
)
489 use_lock
= namedlayer
.use_lock
491 # Select by type operator
492 sub
= row
.column(align
=True)
493 sub
.enabled
= not use_lock
494 sub
.operator("scene.namedlayer_select_objects_by_layer", icon
='RESTRICT_SELECT_OFF',
495 text
="", emboss
=True).layer_idx
= layer_idx
498 icon
= 'LOCKED' if use_lock
else 'UNLOCKED'
499 op
= row
.operator("scene.namedlayer_lock_all", text
="", emboss
=True, icon
=icon
)
500 op
.layer_idx
= layer_idx
502 op
.use_lock
= use_lock
505 # check if layer has something
506 has_active
= (actob
and actob
.layers
[layer_idx
])
507 icon
= ('LAYER_ACTIVE' if has_active
else 'LAYER_USED') if is_layer_used
else 'RADIOBUT_OFF'
508 row
.operator("scene.namedlayer_move_to_layer", text
="", emboss
=True, icon
=icon
).layer_idx
= layer_idx
511 use_wire
= namedlayer
.use_wire
512 icon
= 'WIRE' if use_wire
else 'POTATO'
513 op
= row
.operator("scene.namedlayer_toggle_wire", text
="", emboss
=True, icon
=icon
)
514 op
.layer_idx
= layer_idx
515 op
.use_wire
= not use_wire
517 if not (layer_idx
+ 1) % 5:
520 if len(scene
.objects
) == 0:
521 layout
.label(text
="No objects in scene")
524 class SCENE_UL_namedlayer_groups(UIList
):
525 def draw_item(self
, context
, layout
, data
, item
, icon
, active_data
, active_propname
, index
):
528 # check for lock camera and layer is active
529 view_3d
= context
.area
.spaces
.active
# Ensured it is a 'VIEW_3D' in panel's poll(), weak... :/
530 use_spacecheck
= False if view_3d
.lock_camera_and_layers
else True
532 if self
.layout_type
in {'DEFAULT', 'COMPACT'}:
533 layout
.prop(layer_group
, "name", text
="", emboss
=False)
535 use_lock
= layer_group
.use_lock
536 icon
= 'LOCKED' if use_lock
else 'UNLOCKED'
537 op
= layout
.operator("scene.namedlayer_lock_all", text
="", emboss
=False, icon
=icon
)
538 op
.use_lock
= use_lock
543 icon
= 'RESTRICT_VIEW_OFF' if layer_group
.use_toggle
else 'RESTRICT_VIEW_ON'
544 op
= layout
.operator("scene.namedlayer_toggle_visibility", text
="", emboss
=False, icon
=icon
)
545 op
.use_spacecheck
= use_spacecheck
550 use_wire
= layer_group
.use_wire
551 icon
= 'WIRE' if use_wire
else 'POTATO'
552 op
= layout
.operator("scene.namedlayer_toggle_wire", text
="", emboss
=False, icon
=icon
)
553 op
.use_wire
= not use_wire
557 elif self
.layout_type
in {'GRID'}:
558 layout
.alignment
= 'CENTER'
561 class SCENE_PT_namedlayer_groups(Panel
):
562 bl_space_type
= 'VIEW_3D'
563 bl_region_type
= 'TOOLS'
564 bl_context
= "objectmode"
565 bl_category
= "Layers"
566 bl_label
= "Layer Groups"
567 bl_options
= {'DEFAULT_CLOSED'}
570 def poll(self
, context
):
571 return ((getattr(context
, "mode", 'EDIT_MESH') not in EDIT_MODES
) and
572 (context
.area
.spaces
.active
.type == 'VIEW_3D'))
574 def draw(self
, context
):
575 scene
= context
.scene
576 group_idx
= scene
.layergroups_index
580 row
.template_list("SCENE_UL_namedlayer_groups", "", scene
, "layergroups", scene
, "layergroups_index")
582 col
= row
.column(align
=True)
583 col
.operator("scene.namedlayer_group_add", icon
='ZOOMIN', text
="").layers
= scene
.layers
584 col
.operator("scene.namedlayer_group_remove", icon
='ZOOMOUT', text
="").group_idx
= group_idx
586 if bool(scene
.layergroups
):
587 layout
.prop(scene
.layergroups
[group_idx
], "layers", text
="", toggle
=True)
588 layout
.prop(scene
.layergroups
[group_idx
], "name", text
="Name:")
591 # Add-ons Preferences Update Panel
593 # Define Panel classes for updating
595 SCENE_PT_namedlayer_layers
,
596 SCENE_PT_namedlayer_groups
,
600 def update_panel(self
, context
):
601 message
= "Layer Management: Updating Panel locations has failed"
604 if "bl_rna" in panel
.__dict
__:
605 bpy
.utils
.unregister_class(panel
)
608 panel
.bl_category
= context
.user_preferences
.addons
[__name__
].preferences
.category
609 bpy
.utils
.register_class(panel
)
611 except Exception as e
:
612 print("\n[{}]\n{}\n\nError:\n{}".format(__name__
, message
, e
))
616 class LayerMAddonPreferences(AddonPreferences
):
617 # this must match the addon name, use '__package__'
618 # when defining this in a submodule of a python package.
621 category
= StringProperty(
623 description
="Choose a name for the category of the panel",
628 def draw(self
, context
):
633 col
.label(text
="Tab Category:")
634 col
.prop(self
, "category", text
="")
638 bpy
.utils
.register_module(__name__
)
639 bpy
.types
.Scene
.layergroups
= CollectionProperty(type=LayerGroup
)
640 # Unused, but this is needed for the TemplateList to work...
641 bpy
.types
.Scene
.layergroups_index
= IntProperty(default
=-1)
642 bpy
.types
.Scene
.namedlayers
= PointerProperty(type=NamedLayers
)
643 bpy
.app
.handlers
.scene_update_post
.append(check_init_data
)
644 update_panel(None, bpy
.context
)
648 bpy
.app
.handlers
.scene_update_post
.remove(check_init_data
)
649 del bpy
.types
.Scene
.layergroups
650 del bpy
.types
.Scene
.layergroups_index
651 del bpy
.types
.Scene
.namedlayers
652 bpy
.utils
.unregister_module(__name__
)
655 if __name__
== "__main__":