anim_selection_sets.py 11.49 KiB
# ***** 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, see <http://www.gnu.org/licenses/>
# and write to the Free Software Foundation, Inc., 51 Franklin Street,
# Fifth Floor, Boston, MA 02110-1301, USA..
#
# The Original Code is Copyright (C) 2012 Blender Foundation ###
# All rights reserved.
#
#
# The Original Code is: all of this file.
#
# Contributor(s): Dan Eicher.
#
# ***** END GPL LICENSE BLOCK *****
# <pep8 compliant>
import string
import bpy
bl_info = {
"name": "Selection Set",
"author": "Dan Eicher",
"version": (0, 1, 1),
"blender": (2, 65, 4),
"location": "Properties > Object data (Armature) > Selection Sets",
"description": "Selection Sets to select groups of posebones",
"warning": "Proxy armatures need to export sets and "
"run generated script on re-opening file",
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
"Scripts/Animation/SelectionSets",
"tracker_url": "https://developer.blender.org/T31492",
"category": "Animation"
}
script_template = '''# generated by ANIM_OT_selection_set_export -- abandon all hope, ye who hand edit!
import bpy
def selection_set_import():
arm = bpy.data.armatures['${name}']
set_list = [${set_list}
]
for name, bones in set_list:
if arm.selection_sets.find(name) == -1:
sel_set = arm.selection_sets.add()
sel_set.name = name
for bone in bones:
set_bone = sel_set.bones.add()
set_bone.name = bone
if __name__ == "__main__":
selection_set_import()
'''
def generic_poll(context):
return (context.mode == 'POSE' and context.object and context.object.type == 'ARMATURE')
def active_selection_set_update_func(self, context):
idx = self.active_selection_set
if idx < -1 or idx >= len(self.selection_sets):
self.active_selection_set = -1
raise IndexError('Armature.active_selection_set: out of range')
class SelectionSetBone(bpy.types.PropertyGroup):
name = bpy.props.StringProperty()
class SelectionSet(bpy.types.PropertyGroup):
name = bpy.props.StringProperty(options={'LIBRARY_EDITABLE'})
bones = bpy.props.CollectionProperty(type=SelectionSetBone)
class ANIM_OT_selection_set_add(bpy.types.Operator):
"""Add a new selection set"""
bl_idname = "anim.selection_set_add"
bl_label = "Selection Set Add"
@classmethod
def poll(cls, context):
return generic_poll(context)
def execute(self, context):
arm = context.active_object.data
tmp_name = name = 'Set'
name_sub = 1
while arm.selection_sets.find(name) != -1:
name = tmp_name + ' ' + str(name_sub)
name_sub += 1
sel_set = arm.selection_sets.add()
sel_set.name = name
arm.active_selection_set = arm.selection_sets.find(name)
return {'FINISHED'}
class ANIM_OT_selection_set_remove(bpy.types.Operator):
"""Remove the active selection set"""
bl_idname = "anim.selection_set_remove"
bl_label = "Selection Set Remove"
@classmethod
def poll(cls, context):
arm = context.active_object.data
return (generic_poll(context) and arm.active_selection_set != -1)
def execute(self, context):
arm = context.active_object.data
active_index = arm.active_selection_set
arm.selection_sets.remove(active_index)
if active_index >= len(arm.selection_sets):
arm.active_selection_set = len(arm.selection_sets) - 1
return {'FINISHED'}
class ANIM_OT_selection_set_assign(bpy.types.Operator):
"""Add selected bones to the active selection set"""
bl_idname = "anim.selection_set_assign"
bl_label = "Selection Set Assign"
@classmethod
def poll(cls, context):
arm = context.active_object.data
return (generic_poll(context) and arm.active_selection_set != -1)
def execute(self, context):
arm = context.active_object.data
sel_set = arm.selection_sets[arm.active_selection_set]
bones = [bone for bone in arm.bones if bone.select]
for bone in bones:
if sel_set.bones.find(bone.name) == -1:
set_bone = sel_set.bones.add()
set_bone.name = bone.name
return {'FINISHED'}
class ANIM_OT_selection_set_unassign(bpy.types.Operator):
"""Remove selected bones from the active selection set"""
bl_idname = "anim.selection_set_unassign"
bl_label = "Selection Set Unassign"
@classmethod
def poll(cls, context):
arm = context.active_object.data
return (generic_poll(context) and arm.active_selection_set != -1)
def execute(self, context):
arm = context.active_object.data
sel_set = arm.selection_sets[arm.active_selection_set]
bones = [bone for bone in arm.bones if bone.select]
for bone in bones:
bone_index = sel_set.bones.find(bone.name)
if bone_index != -1:
sel_set.bones.remove(bone_index)
return {'FINISHED'}
class ANIM_OT_selection_set_select(bpy.types.Operator):
"""Select bones in selection set"""
bl_idname = "anim.selection_set_select"
bl_label = "Selection Set Select Bones"
@classmethod
def poll(cls, context):
arm = context.active_object.data
return (generic_poll(context) and arm.active_selection_set != -1)
def execute(self, context):
arm = context.active_object.data
sel_set = arm.selection_sets[arm.active_selection_set]
for bone in sel_set.bones:
try:
arm.bones[bone.name].select = True
except:
bone_index = sel_set.bones.find(bone.name)
sel_set.bones.remove(bone_index)
return {'FINISHED'}
class ANIM_OT_selection_set_deselect(bpy.types.Operator):
"""Deselect bones in selection set"""
bl_idname = "anim.selection_set_deselect"
bl_label = "Selection Set Deselect Bones"
@classmethod
def poll(cls, context):
arm = context.active_object.data
return (generic_poll(context) and arm.active_selection_set != -1)
def execute(self, context):
arm = context.active_object.data
sel_set = arm.selection_sets[arm.active_selection_set]
for bone in sel_set.bones:
try:
arm.bones[bone.name].select = False
except:
bone_index = sel_set.bones.find(bone.name)
sel_set.bones.remove(bone_index)
return {'FINISHED'}
class ANIM_OT_selection_set_export(bpy.types.Operator):
"""Export selection set data to a python script"""
bl_idname = "anim.selection_set_export"
bl_label = "Selection Set Export"
@classmethod
def poll(cls, context):
return generic_poll(context)
def execute(self, context):
arm = context.active_object.data
set_script = string.Template(script_template)
set_list = ""
for sel_set in arm.selection_sets:
set_bones = ""
for bone in sel_set.bones:
set_bones += "'" + bone.name + "',"
set_list += "\n ('{name}', [{bones}]),".format(name=sel_set.name, bones=set_bones)
try:
script_file = bpy.data.texts['{arm.name}SelectionSetImport.py'.format(arm=arm)]
except:
script_file = bpy.data.texts.new('{arm.name}SelectionSetImport.py'.format(arm=arm))
script_file.clear()
script_file.write(set_script.substitute(name=arm.name, set_list=set_list))
return {'FINISHED'}
class DATA_PT_bone_sets(bpy.types.Panel):
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "data"
bl_label = "Selection Sets"
@classmethod
def poll(cls, context):
return (context.object and context.object.type == 'ARMATURE' and context.object.pose)
def draw(self, context):
layout = self.layout
ob = context.object
arm = ob.data
sel_set = None
if arm.active_selection_set != -1:
try:
sel_set = arm.selection_sets[arm.active_selection_set]
except:
pass
row = layout.row()
row.template_list("UI_UL_list", "armature_selection_sets", arm, "selection_sets", arm, "active_selection_set",
rows=(5 if len(arm.selection_sets) else 2))
col = row.column(align=True)
col.operator("anim.selection_set_add", icon='ZOOMIN', text="")
col.operator("anim.selection_set_remove", icon='ZOOMOUT', text="")
if sel_set:
col = layout.column()
col.prop(sel_set, "name", text="Name")
row = layout.row()
sub = row.row(align=True)
sub.operator("anim.selection_set_assign", text="Assign")
sub.operator("anim.selection_set_unassign", text="Remove")
sub = row.row(align=True)
sub.operator("anim.selection_set_select", text="Select")
sub.operator("anim.selection_set_deselect", text="Deselect")
row = layout.row()
row.operator("anim.selection_set_export", text="Export Selection Sets")
def register():
bpy.utils.register_class(SelectionSetBone)
bpy.utils.register_class(SelectionSet)
bpy.utils.register_class(ANIM_OT_selection_set_add)
bpy.utils.register_class(ANIM_OT_selection_set_remove)
bpy.utils.register_class(ANIM_OT_selection_set_assign)
bpy.utils.register_class(ANIM_OT_selection_set_unassign)
bpy.utils.register_class(ANIM_OT_selection_set_select)
bpy.utils.register_class(ANIM_OT_selection_set_deselect)
bpy.utils.register_class(ANIM_OT_selection_set_export)
bpy.utils.register_class(DATA_PT_bone_sets)
bpy.types.Armature.selection_sets = bpy.props.CollectionProperty(type=SelectionSet,
name="Selection Sets",
description="Collection of bones to be selected")
bpy.types.Armature.active_selection_set = bpy.props.IntProperty(name="Active Selection Set",
default=-1,
options={'LIBRARY_EDITABLE'},
update=active_selection_set_update_func)
def unregister():
del bpy.types.Armature.selection_sets
del bpy.types.Armature.active_selection_set
bpy.utils.unregister_class(SelectionSet)
bpy.utils.unregister_class(SelectionSetBone)
bpy.utils.unregister_class(ANIM_OT_selection_set_add)
bpy.utils.unregister_class(ANIM_OT_selection_set_remove)
bpy.utils.unregister_class(ANIM_OT_selection_set_assign)
bpy.utils.unregister_class(ANIM_OT_selection_set_unassign)
bpy.utils.unregister_class(ANIM_OT_selection_set_select)
bpy.utils.unregister_class(ANIM_OT_selection_set_deselect)
bpy.utils.unregister_class(ANIM_OT_selection_set_export)
bpy.utils.unregister_class(DATA_PT_bone_sets)
if __name__ == "__main__":
register()