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 LICENCE BLOCK #####
20 "name": "Bone Selection Sets",
21 "author": "InĂªs Almeida, Antony Riakiotakis, Dan Eicher",
23 "blender": (2, 75, 0),
24 "location": "Properties > Object Data (Armature) > Selection Sets",
25 "description": "List of Bone sets for easy selection while animating",
27 "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/"
28 "Scripts/Animation/SelectionSets",
29 "category": "Animation",
33 from bpy
.types
import (
40 from bpy
.props
import (
48 # Data Structure ##############################################################
50 # Note: bones are stored by name, this means that if the bone is renamed,
51 # there can be problems. However, bone renaming is unlikely during animation
52 class SelectionEntry(PropertyGroup
):
53 name
= StringProperty(name
="Bone Name")
56 class SelectionSet(PropertyGroup
):
57 name
= StringProperty(name
="Set Name")
58 bone_ids
= CollectionProperty(type=SelectionEntry
)
61 # UI Panel w/ UIList ##########################################################
63 class POSE_MT_selection_sets_specials(Menu
):
64 bl_label
= "Selection Sets Specials"
66 def draw(self
, context
):
69 layout
.operator("pose.selection_set_delete_all", icon
='X')
70 layout
.operator("pose.selection_set_remove_bones", icon
='X')
73 class POSE_PT_selection_sets(Panel
):
74 bl_label
= "Selection Sets"
75 bl_space_type
= 'PROPERTIES'
76 bl_region_type
= 'WINDOW'
80 def poll(cls
, context
):
81 return (context
.object and
82 context
.object.type == 'ARMATURE' and
85 def draw(self
, context
):
91 row
.enabled
= (context
.mode
== 'POSE')
94 rows
= 4 if len(arm
.selection_sets
) > 0 else 1
96 "POSE_UL_selection_set", "", # type and unique id
97 arm
, "selection_sets", # pointer to the CollectionProperty
98 arm
, "active_selection_set", # pointer to the active identifier
102 # add/remove/specials UI list Menu
103 col
= row
.column(align
=True)
104 col
.operator("pose.selection_set_add", icon
='ZOOMIN', text
="")
105 col
.operator("pose.selection_set_remove", icon
='ZOOMOUT', text
="")
106 col
.menu("POSE_MT_selection_sets_specials", icon
='DOWNARROW_HLT', text
="")
108 # move up/down arrows
109 if len(arm
.selection_sets
) > 0:
111 col
.operator("pose.selection_set_move", icon
='TRIA_UP', text
="").direction
= 'UP'
112 col
.operator("pose.selection_set_move", icon
='TRIA_DOWN', text
="").direction
= 'DOWN'
117 sub
= row
.row(align
=True)
118 sub
.operator("pose.selection_set_assign", text
="Assign")
119 sub
.operator("pose.selection_set_unassign", text
="Remove")
121 sub
= row
.row(align
=True)
122 sub
.operator("pose.selection_set_select", text
="Select")
123 sub
.operator("pose.selection_set_deselect", text
="Deselect")
126 class POSE_UL_selection_set(UIList
):
127 def draw_item(self
, context
, layout
, data
, set, icon
, active_data
, active_propname
, index
):
128 layout
.prop(set, "name", text
="", icon
='GROUP_BONE', emboss
=False)
131 class POSE_MT_create_new_selection_set(Menu
):
132 bl_idname
= "POSE_MT_selection_set_create"
133 bl_label
= "Choose Selection Set"
135 def draw(self
, context
):
137 layout
.operator("pose.selection_set_add_and_assign",
138 text
="New Selection Set")
141 # Operators ###################################################################
143 class PluginOperator(Operator
):
145 def poll(self
, context
):
146 return (context
.object and
147 context
.object.type == 'ARMATURE' and
148 context
.mode
== 'POSE')
151 class NeedSelSetPluginOperator(PluginOperator
):
153 def poll(self
, context
):
154 if super().poll(context
):
156 return (arm
.active_selection_set
< len(arm
.selection_sets
) and
157 arm
.active_selection_set
>= 0)
161 class POSE_OT_selection_set_delete_all(PluginOperator
):
162 bl_idname
= "pose.selection_set_delete_all"
163 bl_label
= "Delete All Sets"
164 bl_description
= "Deletes All Selection Sets"
165 bl_options
= {'UNDO', 'REGISTER'}
167 def execute(self
, context
):
169 arm
.selection_sets
.clear()
173 class POSE_OT_selection_set_remove_bones(PluginOperator
):
174 bl_idname
= "pose.selection_set_remove_bones"
175 bl_label
= "Remove Bones from Sets"
176 bl_description
= "Removes the Active Bones from All Sets"
177 bl_options
= {'UNDO', 'REGISTER'}
179 def execute(self
, context
):
182 # iterate only the selected bones in current pose that are not hidden
183 for bone
in context
.selected_pose_bones
:
184 for selset
in arm
.selection_sets
:
185 if bone
.name
in selset
.bone_ids
:
186 idx
= selset
.bone_ids
.find(bone
.name
)
187 selset
.bone_ids
.remove(idx
)
192 class POSE_OT_selection_set_move(NeedSelSetPluginOperator
):
193 bl_idname
= "pose.selection_set_move"
194 bl_label
= "Move Selection Set in List"
195 bl_description
= "Move the active Selection Set up/down the list of sets"
196 bl_options
= {'UNDO', 'REGISTER'}
198 direction
= EnumProperty(
199 name
="Move Direction",
200 description
="Direction to move the active Selection Set: UP (default) or DOWN",
202 ('UP', "Up", "", -1),
203 ('DOWN', "Down", "", 1),
209 def poll(self
, context
):
210 if super().poll(context
):
212 return len(arm
.selection_sets
) > 1
215 def execute(self
, context
):
218 active_idx
= arm
.active_selection_set
219 new_idx
= active_idx
+ (-1 if self
.direction
== 'UP' else 1)
221 if new_idx
< 0 or new_idx
>= len(arm
.selection_sets
):
224 arm
.selection_sets
.move(active_idx
, new_idx
)
225 arm
.active_selection_set
= new_idx
230 class POSE_OT_selection_set_add(PluginOperator
):
231 bl_idname
= "pose.selection_set_add"
232 bl_label
= "Create Selection Set"
233 bl_description
= "Creates a new empty Selection Set"
234 bl_options
= {'UNDO', 'REGISTER'}
236 def execute(self
, context
):
239 new_sel_set
= arm
.selection_sets
.add()
242 if "SelectionSet" not in arm
.selection_sets
:
243 new_sel_set
.name
= "SelectionSet"
246 for selset
in arm
.selection_sets
:
247 if selset
.name
.startswith("SelectionSet."):
248 index
= selset
.name
[13:]
250 sorted_sets
.append(index
)
251 sorted_sets
= sorted(sorted_sets
)
253 for num
in sorted_sets
:
258 new_sel_set
.name
= "SelectionSet.{:03d}".format(min_index
)
260 # select newly created set
261 arm
.active_selection_set
= len(arm
.selection_sets
) - 1
266 class POSE_OT_selection_set_remove(NeedSelSetPluginOperator
):
267 bl_idname
= "pose.selection_set_remove"
268 bl_label
= "Delete Selection Set"
269 bl_description
= "Delete a Selection Set"
270 bl_options
= {'UNDO', 'REGISTER'}
272 def execute(self
, context
):
275 arm
.selection_sets
.remove(arm
.active_selection_set
)
277 # change currently active selection set
278 numsets
= len(arm
.selection_sets
)
279 if (arm
.active_selection_set
> (numsets
- 1) and numsets
> 0):
280 arm
.active_selection_set
= len(arm
.selection_sets
) - 1
285 class POSE_OT_selection_set_assign(PluginOperator
):
286 bl_idname
= "pose.selection_set_assign"
287 bl_label
= "Add Bones to Selection Set"
288 bl_description
= "Add selected bones to Selection Set"
289 bl_options
= {'UNDO', 'REGISTER'}
291 def invoke(self
, context
, event
):
294 if not (arm
.active_selection_set
< len(arm
.selection_sets
)):
295 bpy
.ops
.wm
.call_menu("INVOKE_DEFAULT",
296 name
="POSE_MT_selection_set_create")
298 bpy
.ops
.pose
.selection_set_assign('EXEC_DEFAULT')
302 def execute(self
, context
):
304 act_sel_set
= arm
.selection_sets
[arm
.active_selection_set
]
306 # iterate only the selected bones in current pose that are not hidden
307 for bone
in context
.selected_pose_bones
:
308 if bone
.name
not in act_sel_set
.bone_ids
:
309 bone_id
= act_sel_set
.bone_ids
.add()
310 bone_id
.name
= bone
.name
315 class POSE_OT_selection_set_unassign(NeedSelSetPluginOperator
):
316 bl_idname
= "pose.selection_set_unassign"
317 bl_label
= "Remove Bones from Selection Set"
318 bl_description
= "Remove selected bones from Selection Set"
319 bl_options
= {'UNDO', 'REGISTER'}
321 def execute(self
, context
):
323 act_sel_set
= arm
.selection_sets
[arm
.active_selection_set
]
325 # iterate only the selected bones in current pose that are not hidden
326 for bone
in context
.selected_pose_bones
:
327 if bone
.name
in act_sel_set
.bone_ids
:
328 idx
= act_sel_set
.bone_ids
.find(bone
.name
)
329 act_sel_set
.bone_ids
.remove(idx
)
334 class POSE_OT_selection_set_select(NeedSelSetPluginOperator
):
335 bl_idname
= "pose.selection_set_select"
336 bl_label
= "Select Selection Set"
337 bl_description
= "Add Selection Set bones to current selection"
338 bl_options
= {'UNDO', 'REGISTER'}
340 def execute(self
, context
):
342 act_sel_set
= arm
.selection_sets
[arm
.active_selection_set
]
344 for bone
in context
.visible_pose_bones
:
345 if bone
.name
in act_sel_set
.bone_ids
:
346 bone
.bone
.select
= True
351 class POSE_OT_selection_set_deselect(NeedSelSetPluginOperator
):
352 bl_idname
= "pose.selection_set_deselect"
353 bl_label
= "Deselect Selection Set"
354 bl_description
= "Remove Selection Set bones from current selection"
355 bl_options
= {'UNDO', 'REGISTER'}
357 def execute(self
, context
):
359 act_sel_set
= arm
.selection_sets
[arm
.active_selection_set
]
361 for bone
in context
.selected_pose_bones
:
362 if bone
.name
in act_sel_set
.bone_ids
:
363 bone
.bone
.select
= False
368 class POSE_OT_selection_set_add_and_assign(PluginOperator
):
369 bl_idname
= "pose.selection_set_add_and_assign"
370 bl_label
= "Create and Add Bones to Selection Set"
371 bl_description
= "Creates a new Selection Set with the currently selected bones"
372 bl_options
= {'UNDO', 'REGISTER'}
374 def execute(self
, context
):
375 bpy
.ops
.pose
.selection_set_add('EXEC_DEFAULT')
376 bpy
.ops
.pose
.selection_set_assign('EXEC_DEFAULT')
380 # Registry ####################################################################
383 POSE_MT_create_new_selection_set
,
384 POSE_MT_selection_sets_specials
,
385 POSE_PT_selection_sets
,
386 POSE_UL_selection_set
,
389 POSE_OT_selection_set_delete_all
,
390 POSE_OT_selection_set_remove_bones
,
391 POSE_OT_selection_set_move
,
392 POSE_OT_selection_set_add
,
393 POSE_OT_selection_set_remove
,
394 POSE_OT_selection_set_assign
,
395 POSE_OT_selection_set_unassign
,
396 POSE_OT_selection_set_select
,
397 POSE_OT_selection_set_deselect
,
398 POSE_OT_selection_set_add_and_assign
,
404 bpy
.utils
.register_class(cls
)
406 bpy
.types
.Object
.selection_sets
= CollectionProperty(
408 name
="Selection Sets",
409 description
="List of groups of bones for easy selection"
411 bpy
.types
.Object
.active_selection_set
= IntProperty(
412 name
="Active Selection Set",
413 description
="Index of the currently active selection set",
420 bpy
.utils
.unregister_class(cls
)
422 del bpy
.types
.Object
.selection_sets
423 del bpy
.types
.Object
.active_selection_set
426 if __name__
== "__main__":