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, 72, 0),
26 "location": "Toolshelf > Layers Tab",
28 "description": "Display and Edit Layer Name",
29 "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/3D_interaction/layer_manager",
30 "category": "3D View",
34 from bpy
.types
import Menu
, Panel
, UIList
, PropertyGroup
35 from bpy
.props
import StringProperty
, BoolProperty
, IntProperty
, CollectionProperty
, BoolVectorProperty
, PointerProperty
36 from bpy
.app
.handlers
import persistent
38 EDIT_MODES
= {'EDIT_MESH', 'EDIT_CURVE', 'EDIT_SURFACE', 'EDIT_METABALL', 'EDIT_TEXT', 'EDIT_ARMATURE'}
42 FAKE_LAYER_GROUP
= [True] * NUM_LAYERS
44 class NamedLayer(PropertyGroup
):
45 name
= StringProperty(name
="Layer Name")
46 use_lock
= BoolProperty(name
="Lock Layer", default
=False)
47 use_object_select
= BoolProperty(name
="Object Select", default
=True)
48 use_wire
= BoolProperty(name
="Wire Layer", default
=False)
51 class NamedLayers(PropertyGroup
):
52 layers
= CollectionProperty(type=NamedLayer
)
53 use_hide_empty_layers
= BoolProperty(name
="Hide Empty Layer", default
=False)
54 use_extra_options
= BoolProperty(name
="Show Extra Options", default
=True)
55 use_layer_indices
= BoolProperty(name
="Show Layer Indices", default
=False)
56 use_classic
= BoolProperty(name
="Classic", default
=False, description
="Use a classic layer selection visibility")
58 use_init
= BoolProperty(default
=True, options
={'HIDDEN'})
61 # Stupid, but only solution currently is to use a handler to init that layers collection...
63 def check_init_data(scene
):
64 namedlayers
= scene
.namedlayers
65 if namedlayers
.use_init
:
66 while namedlayers
.layers
:
67 namedlayers
.layers
.remove(0)
68 for i
in range(NUM_LAYERS
):
69 layer
= namedlayers
.layers
.add()
70 layer
.name
= "Layer%.2d" % (i
+ 1) # Blender use layer nums starting from 1, not 0.
71 namedlayers
.use_init
= False
74 class LayerGroup(PropertyGroup
):
75 use_toggle
= BoolProperty(name
="", default
=False)
76 use_wire
= BoolProperty(name
="", default
=False)
77 use_lock
= BoolProperty(name
="", default
=False)
79 layers
= BoolVectorProperty(name
="Layers", default
=([False] * NUM_LAYERS
), size
=NUM_LAYERS
, subtype
='LAYER')
82 class SCENE_OT_namedlayer_group_add(bpy
.types
.Operator
):
83 """Add and select a new layer group"""
84 bl_idname
= "scene.namedlayer_group_add"
85 bl_label
= "Add Layer Group"
87 layers
= BoolVectorProperty(name
="Layers", default
=([False] * NUM_LAYERS
), size
=NUM_LAYERS
)
90 def poll(cls
, context
):
91 return bool(context
.scene
)
93 def execute(self
, context
):
95 layergroups
= scene
.layergroups
98 group_idx
= len(layergroups
)
99 layer_group
= layergroups
.add()
100 layer_group
.name
= "LayerGroup.%.3d" % group_idx
101 layer_group
.layers
= layers
102 scene
.layergroups_index
= group_idx
107 class SCENE_OT_namedlayer_group_remove(bpy
.types
.Operator
):
108 """Remove selected layer group"""
109 bl_idname
= "scene.namedlayer_group_remove"
110 bl_label
= "Remove Layer Group"
112 group_idx
= bpy
.props
.IntProperty()
115 def poll(cls
, context
):
116 return bool(context
.scene
)
118 def execute(self
, context
):
119 scene
= context
.scene
120 group_idx
= self
.group_idx
122 scene
.layergroups
.remove(group_idx
)
123 if scene
.layergroups_index
> len(scene
.layergroups
) - 1:
124 scene
.layergroups_index
= len(scene
.layergroups
) - 1
129 class SCENE_OT_namedlayer_toggle_visibility(bpy
.types
.Operator
):
130 """Show or hide given layer (shift to extend)"""
131 bl_idname
= "scene.namedlayer_toggle_visibility"
132 bl_label
= "Show/Hide Layer"
134 layer_idx
= IntProperty()
135 group_idx
= IntProperty()
136 use_spacecheck
= BoolProperty()
137 extend
= BoolProperty(options
={'SKIP_SAVE'})
140 def poll(cls
, context
):
141 return context
.scene
and (context
.area
.spaces
.active
.type == 'VIEW_3D')
143 def execute(self
, context
):
144 scene
= context
.scene
145 layer_cont
= context
.area
.spaces
.active
if self
.use_spacecheck
else context
.scene
146 layer_idx
= self
.layer_idx
149 group_idx
= self
.group_idx
150 layergroups
= scene
.layergroups
[group_idx
]
151 group_layers
= layergroups
.layers
152 layers
= layer_cont
.layers
154 if layergroups
.use_toggle
:
155 layer_cont
.layers
= [not group_layer
and layer
for group_layer
, layer
in zip(group_layers
, layers
)]
156 layergroups
.use_toggle
= False
158 layer_cont
.layers
= [group_layer
or layer
for group_layer
, layer
in zip(group_layers
, layers
)]
159 layergroups
.use_toggle
= True
162 layer_cont
.layers
[layer_idx
] = not layer_cont
.layers
[layer_idx
]
164 layers
= [False] * NUM_LAYERS
165 layers
[layer_idx
] = True
166 layer_cont
.layers
= layers
169 def invoke(self
, context
, event
):
170 self
.extend
= event
.shift
171 return self
.execute(context
)
174 class SCENE_OT_namedlayer_move_to_layer(bpy
.types
.Operator
):
175 """Move selected objects to this Layer (shift to extend)"""
176 bl_idname
= "scene.namedlayer_move_to_layer"
177 bl_label
= "Move Objects To Layer"
179 layer_idx
= IntProperty()
180 extend
= BoolProperty(options
={'SKIP_SAVE'})
183 def poll(cls
, context
):
186 def execute(self
, context
):
187 layer_idx
= self
.layer_idx
188 scene
= context
.scene
190 # Cycle all objects in the layer
191 for obj
in scene
.objects
:
193 # If object is in at least one of the scene's visible layers...
194 if True in {ob_layer
and sce_layer
for ob_layer
, sce_layer
in zip(obj
.layers
, scene
.layers
)}:
196 obj
.layers
[layer_idx
] = not obj
.layers
[layer_idx
]
198 layer
= [False] * NUM_LAYERS
199 layer
[layer_idx
] = True
203 def invoke(self
, context
, event
):
204 self
.extend
= event
.shift
205 return self
.execute(context
)
208 class SCENE_OT_namedlayer_toggle_wire(bpy
.types
.Operator
):
209 """Toggle all objects on this layer draw as wire"""
210 bl_idname
= "scene.namedlayer_toggle_wire"
211 bl_label
= "Toggle Objects Draw Wire"
213 layer_idx
= IntProperty()
214 use_wire
= BoolProperty()
215 group_idx
= IntProperty()
218 def poll(cls
, context
):
219 return context
.scene
and (context
.area
.spaces
.active
.type == 'VIEW_3D')
221 def execute(self
, context
):
222 scene
= context
.scene
223 layer_idx
= self
.layer_idx
224 use_wire
= self
.use_wire
226 view_3d
= context
.area
.spaces
.active
228 # Check if layer have some thing
229 if view_3d
.layers_used
[layer_idx
] or layer_idx
== -1:
230 display
= 'WIRE' if use_wire
else 'TEXTURED'
231 # Cycle all objects in the layer.
232 for obj
in context
.scene
.objects
:
234 group_idx
= self
.group_idx
235 group_layers
= scene
.layergroups
[group_idx
].layers
237 if True in {layer
and group_layer
for layer
, group_layer
in zip(layers
, group_layers
)}:
238 obj
.draw_type
= display
239 scene
.layergroups
[group_idx
].use_wire
= use_wire
241 if obj
.layers
[layer_idx
]:
242 obj
.draw_type
= display
243 scene
.namedlayers
.layers
[layer_idx
].use_wire
= use_wire
248 class SCENE_OT_namedlayer_lock_all(bpy
.types
.Operator
):
249 """Lock all objects on this layer"""
250 bl_idname
= "scene.namedlayer_lock_all"
251 bl_label
= "Lock Objects"
253 layer_idx
= IntProperty()
254 use_lock
= BoolProperty()
255 group_idx
= IntProperty()
258 def poll(cls
, context
):
259 return context
.scene
and (context
.area
.spaces
.active
.type == 'VIEW_3D')
261 def execute(self
, context
):
262 scene
= context
.scene
263 view_3d
= context
.area
.spaces
.active
264 layer_idx
= self
.layer_idx
265 group_idx
= self
.group_idx
266 group_layers
= FAKE_LAYER_GROUP
if group_idx
< 0 else scene
.layergroups
[group_idx
].layers
267 use_lock
= self
.use_lock
269 # check if layer have some thing
270 if layer_idx
== -1 or view_3d
.layers_used
[layer_idx
]:
271 # Cycle all objects in the layer.
272 for obj
in context
.scene
.objects
:
275 if True in {layer
and group_layer
for layer
, group_layer
in zip(layers
, group_layers
)}:
276 obj
.hide_select
= not use_lock
278 scene
.layergroups
[group_idx
].use_lock
= not use_lock
280 if obj
.layers
[layer_idx
]:
281 obj
.hide_select
= not use_lock
283 scene
.namedlayers
.layers
[layer_idx
].use_lock
= not use_lock
288 class SCENE_OT_namedlayer_select_objects_by_layer(bpy
.types
.Operator
):
289 """Select all the objects on this Layer (shift for multi selection, ctrl to make active the last selected object)"""
290 bl_idname
= "scene.namedlayer_select_objects_by_layer"
291 bl_label
= "Select Objects In Layer"
293 select_obj
= BoolProperty()
294 layer_idx
= IntProperty()
296 extend
= BoolProperty(options
={'SKIP_SAVE'})
297 active
= BoolProperty(options
={'SKIP_SAVE'})
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 select_obj
= self
.select_obj
307 layer_idx
= self
.layer_idx
310 # check if layer have some thing
311 if view_3d
.layers_used
[layer_idx
]:
313 for obj
in context
.scene
.objects
:
314 if obj
.layers
[layer_idx
]:
316 not_all_selected
-= 1
318 context
.scene
.objects
.active
= obj
320 not_all_selected
+= 1
321 if not not_all_selected
:
325 bpy
.ops
.object.select_by_layer(extend
=self
.extend
, layers
=layer_idx
+ 1)
329 def invoke(self
, context
, event
):
330 self
.extend
= event
.shift
331 self
.active
= event
.ctrl
332 return self
.execute(context
)
335 class SCENE_OT_namedlayer_show_all(bpy
.types
.Operator
):
336 """Show or hide all layers in the scene"""
337 bl_idname
= "scene.namedlayer_show_all"
338 bl_label
= "Select All Layers"
340 show
= BoolProperty()
343 def poll(cls
, context
):
344 return context
.scene
and (context
.area
.spaces
.active
.type == 'VIEW_3D')
346 def execute(self
, context
):
347 scene
= context
.scene
348 view_3d
= context
.area
.spaces
.active
350 active_layer
= scene
.active_layer
352 # check for lock camera and layer is active
353 layer_cont
= scene
if view_3d
.lock_camera_and_layers
else view_3d
356 layer_cont
.layers
[:] = [True] * NUM_LAYERS
357 # Restore active layer (stupid, but Scene.active_layer is readonly).
358 layer_cont
.layers
[active_layer
] = False
359 layer_cont
.layers
[active_layer
] = True
361 layers
= [False] * NUM_LAYERS
362 # Keep selection of active layer
363 layers
[active_layer
] = True
364 layer_cont
.layers
[:] = layers
369 class SCENE_PT_namedlayer_layers(bpy
.types
.Panel
):
370 bl_space_type
= 'VIEW_3D'
371 bl_region_type
= 'TOOLS'
372 bl_label
= "Layer Management"
373 bl_options
= {'DEFAULT_CLOSED'}
374 bl_category
= "Layers"
377 def poll(self
, context
):
378 return ((getattr(context
, "mode", 'EDIT_MESH') not in EDIT_MODES
) and
379 (context
.area
.spaces
.active
.type == 'VIEW_3D'))
381 def draw(self
, context
):
382 scene
= context
.scene
383 view_3d
= context
.area
.spaces
.active
384 actob
= context
.object
385 namedlayers
= scene
.namedlayers
386 use_extra
= namedlayers
.use_extra_options
387 use_hide
= namedlayers
.use_hide_empty_layers
388 use_indices
= namedlayers
.use_layer_indices
389 use_classic
= namedlayers
.use_classic
391 # Check for lock camera and layer is active
392 if view_3d
.lock_camera_and_layers
:
394 use_spacecheck
= False
397 use_spacecheck
= True
402 col
.prop(view_3d
, "lock_camera_and_layers", text
="")
403 # Check if there is a layer off
404 show
= (False in {layer
for layer
in layer_cont
.layers
})
405 icon
= 'RESTRICT_VIEW_ON' if show
else 'RESTRICT_VIEW_OFF'
406 col
.operator("scene.namedlayer_show_all", emboss
=False, icon
=icon
, text
="").show
= show
409 col
.prop(namedlayers
, "use_classic")
410 col
.prop(namedlayers
, "use_extra_options", text
="Options")
413 col
.prop(namedlayers
, "use_layer_indices", text
="Indices")
414 col
.prop(namedlayers
, "use_hide_empty_layers", text
="Hide Empty")
416 col
= layout
.column()
417 for layer_idx
in range(NUM_LAYERS
):
418 namedlayer
= namedlayers
.layers
[layer_idx
]
419 is_layer_used
= view_3d
.layers_used
[layer_idx
]
421 if (use_hide
and not is_layer_used
):
422 # Hide unused layers and this one is unused, skip.
425 row
= col
.row(align
=True)
429 sub
= row
.row(align
=True)
430 sub
.alignment
= 'LEFT'
431 sub
.label(text
="%.2d." % (layer_idx
+ 1))
434 icon
= 'RESTRICT_VIEW_OFF' if layer_cont
.layers
[layer_idx
] else 'RESTRICT_VIEW_ON'
436 op
= row
.operator("scene.namedlayer_toggle_visibility", text
="", icon
=icon
, emboss
=True)
437 op
.layer_idx
= layer_idx
438 op
.use_spacecheck
= use_spacecheck
440 row
.prop(layer_cont
, "layers", index
=layer_idx
, emboss
=True, icon
=icon
, toggle
=True, text
="")
442 # Name (use special icon for active layer)
443 icon
= 'FILE_TICK' if (getattr(layer_cont
, "active_layer", -1) == layer_idx
) else 'NONE'
444 row
.prop(namedlayer
, "name", text
="", icon
=icon
)
447 use_lock
= namedlayer
.use_lock
449 # Select by type operator
450 sub
= row
.column(align
=True)
451 sub
.enabled
= not use_lock
452 sub
.operator("scene.namedlayer_select_objects_by_layer", icon
='RESTRICT_SELECT_OFF',
453 text
="", emboss
=True).layer_idx
= layer_idx
456 icon
= 'LOCKED' if use_lock
else 'UNLOCKED'
457 op
= row
.operator("scene.namedlayer_lock_all", text
="", emboss
=True, icon
=icon
)
458 op
.layer_idx
= layer_idx
460 op
.use_lock
= use_lock
463 # check if layer has something
464 has_active
= (actob
and actob
.layers
[layer_idx
])
465 icon
= ('LAYER_ACTIVE' if has_active
else 'LAYER_USED') if is_layer_used
else 'RADIOBUT_OFF'
466 row
.operator("scene.namedlayer_move_to_layer", text
="", emboss
=True, icon
=icon
).layer_idx
= layer_idx
469 use_wire
= namedlayer
.use_wire
470 icon
= 'WIRE' if use_wire
else 'POTATO'
471 op
= row
.operator("scene.namedlayer_toggle_wire", text
="", emboss
=True, icon
=icon
)
472 op
.layer_idx
= layer_idx
473 op
.use_wire
= not use_wire
475 if not (layer_idx
+ 1) % 5:
478 if len(scene
.objects
) == 0:
479 layout
.label(text
="No objects in scene")
482 class SCENE_UL_namedlayer_groups(UIList
):
483 def draw_item(self
, context
, layout
, data
, item
, icon
, active_data
, active_propname
, index
):
486 # check for lock camera and layer is active
487 view_3d
= context
.area
.spaces
.active
# Ensured it is a 'VIEW_3D' in panel's poll(), weak... :/
488 use_spacecheck
= False if view_3d
.lock_camera_and_layers
else True
490 if self
.layout_type
in {'DEFAULT', 'COMPACT'}:
491 layout
.prop(layer_group
, "name", text
="", emboss
=False)
493 use_lock
= layer_group
.use_lock
494 icon
= 'LOCKED' if use_lock
else 'UNLOCKED'
495 op
= layout
.operator("scene.namedlayer_lock_all", text
="", emboss
=False, icon
=icon
)
496 op
.use_lock
= use_lock
501 icon
= 'RESTRICT_VIEW_OFF' if layer_group
.use_toggle
else 'RESTRICT_VIEW_ON'
502 op
= layout
.operator("scene.namedlayer_toggle_visibility", text
="", emboss
=False, icon
=icon
)
503 op
.use_spacecheck
= use_spacecheck
508 use_wire
= layer_group
.use_wire
509 icon
= 'WIRE' if use_wire
else 'POTATO'
510 op
= layout
.operator("scene.namedlayer_toggle_wire", text
="", emboss
=False, icon
=icon
)
511 op
.use_wire
= not use_wire
515 elif self
.layout_type
in {'GRID'}:
516 layout
.alignment
= 'CENTER'
519 class SCENE_PT_namedlayer_groups(bpy
.types
.Panel
):
520 bl_space_type
= 'VIEW_3D'
521 bl_region_type
= 'TOOLS'
522 bl_category
= "Layers"
523 bl_label
= "Layer Groups"
524 bl_options
= {'DEFAULT_CLOSED'}
527 def poll(self
, context
):
528 return ((getattr(context
, "mode", 'EDIT_MESH') not in EDIT_MODES
) and
529 (context
.area
.spaces
.active
.type == 'VIEW_3D'))
531 def draw(self
, context
):
532 scene
= context
.scene
533 group_idx
= scene
.layergroups_index
537 row
.template_list("SCENE_UL_namedlayer_groups", "", scene
, "layergroups", scene
, "layergroups_index")
539 col
= row
.column(align
=True)
540 col
.operator("scene.namedlayer_group_add", icon
='ZOOMIN', text
="").layers
= scene
.layers
541 col
.operator("scene.namedlayer_group_remove", icon
='ZOOMOUT', text
="").group_idx
= group_idx
543 if bool(scene
.layergroups
):
544 layout
.prop(scene
.layergroups
[group_idx
], "layers", text
="", toggle
=True)
545 layout
.prop(scene
.layergroups
[group_idx
], "name", text
="Name:")
549 bpy
.utils
.register_module(__name__
)
550 bpy
.types
.Scene
.layergroups
= CollectionProperty(type=LayerGroup
)
551 # Unused, but this is needed for the TemplateList to work...
552 bpy
.types
.Scene
.layergroups_index
= IntProperty(default
=-1)
553 bpy
.types
.Scene
.namedlayers
= PointerProperty(type=NamedLayers
)
554 bpy
.app
.handlers
.scene_update_post
.append(check_init_data
)
558 bpy
.app
.handlers
.scene_update_post
.remove(check_init_data
)
559 del bpy
.types
.Scene
.layergroups
560 del bpy
.types
.Scene
.layergroups_index
561 del bpy
.types
.Scene
.namedlayers
562 bpy
.utils
.unregister_module(__name__
)
565 if __name__
== "__main__":