I know bones can be arranged by layers in the armature properties. Is it possible to somehow "lock" a layer so that all bones in that layer cannot be selected?
1 Answer
As far as I know, this function does not exist. Generally all collections, objects and bones can be set to non-selectable with the outliner filter Selectable
which looks like an arrowhead. Manual
The filter acts on both modes at once - pose and editmode. Unfortunately you have to navigate through the armature hierarchy to find all the bones of one a layer and set the selectability individually.
You may want to give the following script a chance. It adds a menu to the skeleton panel on top of the layer field that allows to lock and unlock all or just the bones of the active layer(s).
import bpy
from bpy.types import Menu, Operator
# special menu
class DATA_MT_skeleton_specials(Menu):
bl_idname = "DATA_MT_skeleton_specials"
bl_label = "Armature Layer Specials"
def draw(self, _context):
layout = self.layout
layout.label(text="Selectable Bones")
op = layout.operator('data.skeleton_specials_op', text="Lock All", icon='RESTRICT_SELECT_ON')
op.do = "lock_all"
op.descript = "Make all Bones non-selectable"
op = layout.operator('data.skeleton_specials_op', text="Lock Layer", icon='RESTRICT_SELECT_ON')
op.do = "lock_layer"
op.descript = "Make Bones in active layer(s) non-selectable"
op = layout.operator('data.skeleton_specials_op', text="Unlock All", icon='RESTRICT_SELECT_OFF')
op.do = "unlock_all"
op.descript = "Make all Bones selectable"
op = layout.operator('data.skeleton_specials_op', text="Unlock Layer", icon='RESTRICT_SELECT_OFF')
op.do = "unlock_layer"
op.descript = "Make Bones in active layer(s) selectable"
# special op
class DATA_OT_skeleton_specials(Operator):
bl_idname = 'data.skeleton_specials_op'
bl_label = ""
do : bpy.props.StringProperty(default="")
descript : bpy.props.StringProperty()
def execute(self, context):
if context.mode == 'EDIT_ARMATURE':
bone_data = context.armature.edit_bones
else:
bone_data = context.armature.bones
bones = []
layers = []
set = False
if self.do.startswith('lock'):
set = True
if self.do.endswith('all'):
bones = bone_data
else:
for idx, layer in enumerate(context.armature.layers):
if layer:
layers.append(idx)
for bone in bone_data:
for layer in layers:
if bone.layers[layer]:
bones.append(bone)
for bone in bones:
bone_data[bone.name].hide_select = set
return {'FINISHED'}
@classmethod
def description(cls, context, properties):
return properties.descript
### REGISTRY
def register():
skeleton_draw_old = bpy.types.DATA_PT_skeleton.draw # store original
bpy.utils.register_class(DATA_MT_skeleton_specials)
bpy.utils.register_class(DATA_OT_skeleton_specials)
def skeleton_draw_new(self, context):
layout = self.layout
arm = context.armature
layout.row().prop(arm, "pose_position", expand=True)
col = layout.column()
row = col.row()
row.label(text="Layers:")
row.menu('DATA_MT_skeleton_specials', text="", icon='DOWNARROW_HLT')
col.prop(arm, "layers", text="")
col.label(text="Protected Layers:")
col.prop(arm, "layers_protected", text="")
bpy.types.DATA_PT_skeleton.draw = skeleton_draw_new # replace draw
def unregister():
bpy.utils.unregister_class(DATA_MT_skeleton_specials)
bpy.utils.unregister_class(DATA_OT_skeleton_specials)
bpy.types.DATA_PT_skeleton.draw = skeleton_draw_old # restore draw
if __name__ == "__main__":
register()
Open a new text block in Scripting workspace, copy the whole script into it and press Run. The script is stored with the blenderfile but needs to be run again.
(tested in 2.93, 3.32, 3.51)