diff options
author | Ines Almeida <britalmeida@gmail.com> | 2016-02-26 22:50:29 +0300 |
---|---|---|
committer | Ines Almeida <britalmeida@gmail.com> | 2016-03-03 04:08:21 +0300 |
commit | d7cd2f6dfe8c424a11bdd6f0a9060ad985c851c7 (patch) | |
tree | 5240d3927b8cec175d85eac39b3414ca77a914a6 /bone_selection_sets.py | |
parent | d33b3e15ca6672490062774d18965af3d4968cf0 (diff) |
Refactored Selection Sets to have an interface similar to Bone Groups.
'Toggle' operator was converted into Select/Deselect, which adds and deletes from the selection, respectively.
Added Assign/Unassign to be able to edit a set.
Tweaked bl-info and properties descriptions and labels
Motionpath functions from the Gooseberry branch were removed
Diffstat (limited to 'bone_selection_sets.py')
-rw-r--r-- | bone_selection_sets.py | 315 |
1 files changed, 315 insertions, 0 deletions
diff --git a/bone_selection_sets.py b/bone_selection_sets.py new file mode 100644 index 00000000..8bd4841e --- /dev/null +++ b/bone_selection_sets.py @@ -0,0 +1,315 @@ +# ***** 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, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ***** END GPL LICENCE BLOCK ***** + +bl_info = { + "name": "Bone Selection Sets", + "author": "Antony Riakiotakis, InĂªs Almeida", + "version": (2, 0, 0), + "blender": (2, 75, 0), + "location": "Properties > Object Data (Armature) > Selection Sets", + "description": "List of Bone sets for easy selection while animating", + "warning": "", + "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/" + "Scripts/Animation/SelectionSets", + "category": "Animation", +} + +import bpy +from bpy.types import ( + Operator, + Menu, + Panel, + UIList, + PropertyGroup, +) + +from bpy.props import ( + StringProperty, + BoolProperty, + IntProperty, + FloatProperty, + EnumProperty, + CollectionProperty, + BoolVectorProperty, + FloatVectorProperty, +) + +# Data Structure ############################################################## + +# Note: bones are stored by name, this means that if the bone is renamed, +# there can be problems. However, bone renaming is unlikely during animation +class SelectionEntry(PropertyGroup): + name = StringProperty(name="Bone Name") + + +class SelectionSet(PropertyGroup): + name = StringProperty(name="Set Name") + bone_ids = CollectionProperty(type=SelectionEntry) + + +# UI Panel w/ UIList ########################################################## + +class POSE_MT_selection_sets_specials(Menu): + bl_label = "Selection Sets Specials" + + def draw(self, context): + layout = self.layout + + # TODO + #layout.operator("pose.selection_sets_sort", icon='SORTALPHA', text="Sort by Name").sort_type = 'NAME' + + +class POSE_PT_selection_sets(Panel): + bl_label = "Selection Sets" + bl_space_type = 'PROPERTIES' + bl_region_type = 'WINDOW' + bl_context = "data" + + @classmethod + def poll(cls, context): + return (context.armature + and context.object + and context.object.type == 'ARMATURE' + and context.object.pose + ) + + def draw(self, context): + layout = self.layout + + ob = context.object + arm = context.object + + layout.enabled = (ob.proxy is None) + + row = layout.row() + + # UI list + rows = 4 #TODO if is being used, else 1 + row.template_list( + "POSE_UL_selection_set", "", + arm, "selection_sets", + arm, "active_selection_set", + rows=rows + ) + + # add/remove/specials UI list Menu + col = row.column(align=True) + col.operator("pose.selection_set_add", icon='ZOOMIN', text="") + col.operator("pose.selection_set_remove", icon='ZOOMOUT', text="") + # TODO specials like sorting + #col.menu("POSE_MT_selection_sets_specials", icon='DOWNARROW_HLT', text="") + + # TODO move up/down arrows + + # buttons + row = layout.row() + + sub = row.row(align=True) + sub.operator("pose.selection_set_assign", text="Assign") + sub.operator("pose.selection_set_unassign", text="Remove") + + sub = row.row(align=True) + sub.operator("pose.selection_set_select", text="Select") + sub.operator("pose.selection_set_deselect", text="Deselect") + + +class POSE_UL_selection_set(UIList): + def draw_item(self, context, layout, data, set, icon, active_data, active_propname, index): + layout.prop(set, "name", text="", emboss=False) + + +# Operators ################################################################### + +class PluginOperator(Operator): + @classmethod + def poll(self, context): + return (context.object and + context.object.type == 'ARMATURE' and + context.mode == 'POSE') + +class NeedSelSetPluginOperator(PluginOperator): + @classmethod + def poll(self, context): + if super().poll(context): + arm = context.object + return (arm.active_selection_set < len(arm.selection_sets)) + return False + + +class POSE_OT_selection_set_add(PluginOperator): + bl_idname = "pose.selection_set_add" + bl_label = "Create Selection Set" + bl_description = "Creates a new empty Selection Set" + bl_options = {'UNDO', 'REGISTER'} + + created_counter = 0 + + def execute(self, context): + arm = context.object + + selection_set = arm.selection_sets.add() + + selection_set.name = "SelectionSet" + if POSE_OT_selection_set_add.created_counter > 0: + selection_set.name += ".{:03d}".format(POSE_OT_selection_set_add.created_counter) + POSE_OT_selection_set_add.created_counter += 1 + + arm.active_selection_set = len(arm.selection_sets) - 1 + + return {'FINISHED'} + + +class POSE_OT_selection_set_remove(NeedSelSetPluginOperator): + bl_idname = "pose.selection_set_remove" + bl_label = "Delete Selection Set" + bl_description = "Delete a Selection Set" + bl_options = {'UNDO', 'REGISTER'} + + def execute(self, context): + arm = context.object + + arm.selection_sets.remove(arm.active_selection_set) + numsets = len(arm.selection_sets) + if (arm.active_selection_set > (numsets - 1) and numsets > 0): + arm.active_selection_set = len(arm.selection_sets) - 1 + return {'FINISHED'} + + +class POSE_OT_selection_set_assign(NeedSelSetPluginOperator): + bl_idname = "pose.selection_set_assign" + bl_label = "Add Bones to Selection Set" + bl_description = "Add selected bones to Selection Set" + bl_options = {'UNDO', 'REGISTER'} + + def execute(self, context): + arm = context.object + pose = arm.pose + + #if arm.active_selection_set <= 0: + # arm.selection_sets.add() + #TODO naming convention + # return {'FINISHED'} + + selection_set = arm.selection_sets[arm.active_selection_set] + for bone in pose.bones: + if bone.bone.select: + bone_id = selection_set.bone_ids.add() + bone_id.name = bone.name + + return {'FINISHED'} + + +class POSE_OT_selection_set_unassign(NeedSelSetPluginOperator): + bl_idname = "pose.selection_set_unassign" + bl_label = "Remove Bones from Selection Set" + bl_description = "Remove selected bones from Selection Set" + bl_options = {'UNDO', 'REGISTER'} + + def execute(self, context): + arm = context.object + pose = arm.pose + + selection_set = arm.selection_sets[arm.active_selection_set] + for bone in pose.bones: + if bone.bone.select and bone.name in selection_set.bone_ids: + selection_set.bone_ids[bone.name].remove() + + return {'FINISHED'} + + +class POSE_OT_selection_set_select(NeedSelSetPluginOperator): + bl_idname = "pose.selection_set_select" + bl_label = "Select Selection Set" + bl_description = "Add Selection Set bones to current selection" + bl_options = {'UNDO', 'REGISTER'} + + def execute(self, context): + arm = context.object + pose = arm.pose + + selection_set = arm.selection_sets[arm.active_selection_set] + for bone in pose.bones: + if bone.name in selection_set.bone_ids: + bone.bone.select = True + + return {'FINISHED'} + + +class POSE_OT_selection_set_deselect(NeedSelSetPluginOperator): + bl_idname = "pose.selection_set_deselect" + bl_label = "Deselect Selection Set" + bl_description = "Remove Selection Set bones from current selection" + bl_options = {'UNDO', 'REGISTER'} + + def execute(self, context): + arm = context.object + pose = arm.pose + + selection_set = arm.selection_sets[arm.active_selection_set] + for bone in pose.bones: + if bone.name in selection_set.bone_ids: + bone.bone.select = False + + return {'FINISHED'} + + +# Registry #################################################################### + +classes = ( + POSE_MT_selection_sets_specials, + POSE_PT_selection_sets, + POSE_UL_selection_set, + SelectionEntry, + SelectionSet, + POSE_OT_selection_set_add, + POSE_OT_selection_set_remove, + POSE_OT_selection_set_assign, + POSE_OT_selection_set_unassign, + POSE_OT_selection_set_select, + POSE_OT_selection_set_deselect, +) + +def register(): + for cls in classes: + bpy.utils.register_class(cls) + + bpy.types.Object.selection_sets = CollectionProperty( + type=SelectionSet, + name="Selection Sets", + description="List of groups of bones for easy selection" + ) + bpy.types.Object.active_selection_set = IntProperty( + name="Active Selection Set", + description="Index of the currently active selection set", + default=0 + ) + + bpy.types.DATA_PT_motion_paths.append(copy_frames_armature) + bpy.types.OBJECT_PT_motion_paths.append(copy_frames_object) + + +def unregister(): + for cls in classes: + bpy.utils.unregister_class(cls) + + del bpy.types.Object.selection_sets + del bpy.types.Object.active_selection_set + + +if __name__ == "__main__": + register() |