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, see <http://www.gnu.org/licenses/>
15 # and write to the Free Software Foundation, Inc., 51 Franklin Street,
16 # Fifth Floor, Boston, MA 02110-1301, USA..
18 # The Original Code is Copyright (C) 2012 Blender Foundation ###
19 # All rights reserved.
22 # The Original Code is: all of this file.
24 # Contributor(s): Dan Eicher.
26 # ***** END GPL LICENSE BLOCK *****
34 "name": "Selection Set",
35 "author": "Dan Eicher",
38 "location": "Properties -> Object Data -> Selection Sets",
39 "description": "Selection Sets to select groups of bones",
40 "warning": "Proxy armatures need to export sets and run generated script on re-opening file",
41 "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Animation/SelectionSets",
42 "tracker_url": "http://projects.blender.org/tracker/index.php?func=detail&aid=31492",
43 "category": "Animation"
47 script_template = '''# generated by ANIM_OT_selection_set_export -- abandon all hope, ye who hand edit!
51 def selection_set_import():
52 arm = bpy.data.armatures['${name}']
54 set_list = [${set_list}
57 for name, bones in set_list:
58 if arm.selection_sets.find(name) == -1:
59 sel_set = arm.selection_sets.add()
63 set_bone = sel_set.bones.add()
67 if __name__ == "__main__":
68 selection_set_import()
72 def generic_poll(context):
73 return (context.mode == 'POSE' and context.object and context.object.type == 'ARMATURE')
76 def active_selection_set_update_func(self, context):
77 idx = self.active_selection_set
78 if idx < -1 or idx >= len(self.selection_sets):
79 self.active_selection_set = -1
80 raise IndexError('Armature.active_selection_set: out of range')
83 class SelectionSetBone(bpy.types.PropertyGroup):
84 name = bpy.props.StringProperty()
87 class SelectionSet(bpy.types.PropertyGroup):
88 name = bpy.props.StringProperty(options={'LIBRARY_EDITABLE'})
89 bones = bpy.props.CollectionProperty(type=SelectionSetBone)
92 class ANIM_OT_selection_set_add(bpy.types.Operator):
93 """Add a new selection set"""
94 bl_idname = "anim.selection_set_add"
95 bl_label = "Selection Set Add"
98 def poll(cls, context):
99 return generic_poll(context)
101 def execute(self, context):
102 arm = context.active_object.data
104 tmp_name = name = 'Set'
106 while arm.selection_sets.find(name) != -1:
107 name = tmp_name + ' ' + str(name_sub)
110 sel_set = arm.selection_sets.add()
113 arm.active_selection_set = arm.selection_sets.find(name)
118 class ANIM_OT_selection_set_remove(bpy.types.Operator):
119 """Remove the active selection set"""
120 bl_idname = "anim.selection_set_remove"
121 bl_label = "Selection Set Remove"
124 def poll(cls, context):
125 arm = context.active_object.data
126 return (generic_poll(context) and arm.active_selection_set != -1)
128 def execute(self, context):
129 arm = context.active_object.data
130 active_index = arm.active_selection_set
132 arm.selection_sets.remove(active_index)
134 if active_index >= len(arm.selection_sets):
135 arm.active_selection_set = len(arm.selection_sets) - 1
140 class ANIM_OT_selection_set_assign(bpy.types.Operator):
141 """Add selected bones to the active selection set"""
142 bl_idname = "anim.selection_set_assign"
143 bl_label = "Selection Set Assign"
146 def poll(cls, context):
147 arm = context.active_object.data
148 return (generic_poll(context) and arm.active_selection_set != -1)
150 def execute(self, context):
151 arm = context.active_object.data
152 sel_set = arm.selection_sets[arm.active_selection_set]
153 bones = [bone for bone in arm.bones if bone.select]
156 if sel_set.bones.find(bone.name) == -1:
157 set_bone = sel_set.bones.add()
158 set_bone.name = bone.name
163 class ANIM_OT_selection_set_unassign(bpy.types.Operator):
164 """Remove selected bones from the active selection set"""
165 bl_idname = "anim.selection_set_unassign"
166 bl_label = "Selection Set Unassign"
169 def poll(cls, context):
170 arm = context.active_object.data
171 return (generic_poll(context) and arm.active_selection_set != -1)
173 def execute(self, context):
174 arm = context.active_object.data
175 sel_set = arm.selection_sets[arm.active_selection_set]
176 bones = [bone for bone in arm.bones if bone.select]
179 bone_index = sel_set.bones.find(bone.name)
181 sel_set.bones.remove(bone_index)
186 class ANIM_OT_selection_set_select(bpy.types.Operator):
187 """Select bones in selection set"""
188 bl_idname = "anim.selection_set_select"
189 bl_label = "Selection Set Select Bones"
192 def poll(cls, context):
193 arm = context.active_object.data
194 return (generic_poll(context) and arm.active_selection_set != -1)
196 def execute(self, context):
197 arm = context.active_object.data
198 sel_set = arm.selection_sets[arm.active_selection_set]
200 for bone in sel_set.bones:
202 arm.bones[bone.name].select = True
204 bone_index = sel_set.bones.find(bone.name)
205 sel_set.bones.remove(bone_index)
210 class ANIM_OT_selection_set_deselect(bpy.types.Operator):
211 """Deselect bones in selection set"""
212 bl_idname = "anim.selection_set_deselect"
213 bl_label = "Selection Set Deselect Bones"
216 def poll(cls, context):
217 arm = context.active_object.data
218 return (generic_poll(context) and arm.active_selection_set != -1)
220 def execute(self, context):
221 arm = context.active_object.data
222 sel_set = arm.selection_sets[arm.active_selection_set]
224 for bone in sel_set.bones:
226 arm.bones[bone.name].select = False
228 bone_index = sel_set.bones.find(bone.name)
229 sel_set.bones.remove(bone_index)
234 class ANIM_OT_selection_set_export(bpy.types.Operator):
235 """Export selection set data to a python script"""
236 bl_idname = "anim.selection_set_export"
237 bl_label = "Selection Set Export"
240 def poll(cls, context):
241 return generic_poll(context)
243 def execute(self, context):
244 arm = context.active_object.data
245 set_script = string.Template(script_template)
248 for sel_set in arm.selection_sets:
250 for bone in sel_set.bones:
251 set_bones += "'" + bone.name + "',"
252 set_list += "\n ('{name}', [{bones}]),".format(name=sel_set.name, bones=set_bones)
255 script_file = bpy.data.texts['{arm.name}SelectionSetImport.py'.format(arm=arm)]
257 script_file = bpy.data.texts.new('{arm.name}SelectionSetImport.py'.format(arm=arm))
260 script_file.write(set_script.substitute(name=arm.name, set_list=set_list))
265 class DATA_PT_bone_sets(bpy.types.Panel):
266 bl_space_type = 'PROPERTIES'
267 bl_region_type = 'WINDOW'
269 bl_label = "Selection Sets"
272 def poll(cls, context):
273 return (context.object and context.object.type == 'ARMATURE' and context.object.pose)
275 def draw(self, context):
281 if arm.active_selection_set != -1:
283 sel_set = arm.selection_sets[arm.active_selection_set]
289 row.template_list(arm, "selection_sets", arm, "active_selection_set",
290 rows=(5 if len(arm.selection_sets) else 2))
292 col = row.column(align=True)
293 col.operator("anim.selection_set_add", icon='ZOOMIN', text="")
294 col.operator("anim.selection_set_remove", icon='ZOOMOUT', text="")
297 col = layout.column()
298 col.prop(sel_set, "name", text="Name")
302 sub = row.row(align=True)
303 sub.operator("anim.selection_set_assign", text="Assign")
304 sub.operator("anim.selection_set_unassign", text="Remove")
306 sub = row.row(align=True)
307 sub.operator("anim.selection_set_select", text="Select")
308 sub.operator("anim.selection_set_deselect", text="Deselect")
311 row.operator("anim.selection_set_export", text="Export Selection Sets")
315 bpy.utils.register_class(SelectionSetBone)
316 bpy.utils.register_class(SelectionSet)
317 bpy.utils.register_class(ANIM_OT_selection_set_add)
318 bpy.utils.register_class(ANIM_OT_selection_set_remove)
319 bpy.utils.register_class(ANIM_OT_selection_set_assign)
320 bpy.utils.register_class(ANIM_OT_selection_set_unassign)
321 bpy.utils.register_class(ANIM_OT_selection_set_select)
322 bpy.utils.register_class(ANIM_OT_selection_set_deselect)
323 bpy.utils.register_class(ANIM_OT_selection_set_export)
324 bpy.utils.register_class(DATA_PT_bone_sets)
326 bpy.types.Armature.selection_sets = bpy.props.CollectionProperty(type=SelectionSet,
327 name="Selection Sets",
328 description="Collection of bones to be selected")
330 bpy.types.Armature.active_selection_set = bpy.props.IntProperty(name="Active Selection Set",
332 options={'LIBRARY_EDITABLE'},
333 update=active_selection_set_update_func)
337 del bpy.types.Armature.selection_sets
338 del bpy.types.Armature.active_selection_set
340 bpy.utils.unregister_class(SelectionSet)
341 bpy.utils.unregister_class(SelectionSetBone)
342 bpy.utils.unregister_class(ANIM_OT_selection_set_add)
343 bpy.utils.unregister_class(ANIM_OT_selection_set_remove)
344 bpy.utils.unregister_class(ANIM_OT_selection_set_assign)
345 bpy.utils.unregister_class(ANIM_OT_selection_set_unassign)
346 bpy.utils.unregister_class(ANIM_OT_selection_set_select)
347 bpy.utils.unregister_class(ANIM_OT_selection_set_deselect)
348 bpy.utils.unregister_class(ANIM_OT_selection_set_export)
349 bpy.utils.unregister_class(DATA_PT_bone_sets)
351 if __name__ == "__main__":