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
This commit is contained in:
parent
d33b3e15ca
commit
d7cd2f6dfe
|
@ -1,244 +0,0 @@
|
|||
# ***** 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 Groups",
|
||||
"author": "Antony Riakiotakis",
|
||||
"version": (1, 0, 2),
|
||||
"blender": (2, 75, 0),
|
||||
"location": "Properties > Object Buttons",
|
||||
"description": "Operator and storage for restoration of bone selection state.",
|
||||
"category": "Animation",
|
||||
}
|
||||
|
||||
import bpy
|
||||
from bpy.types import (
|
||||
Operator,
|
||||
Panel,
|
||||
UIList,
|
||||
)
|
||||
|
||||
from bpy.props import (
|
||||
StringProperty,
|
||||
BoolProperty,
|
||||
IntProperty,
|
||||
FloatProperty,
|
||||
EnumProperty,
|
||||
CollectionProperty,
|
||||
BoolVectorProperty,
|
||||
FloatVectorProperty,
|
||||
)
|
||||
|
||||
|
||||
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.object.type == 'ARMATURE'
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
armature = context.object
|
||||
|
||||
# Rig type list
|
||||
row = layout.row()
|
||||
row.template_list(
|
||||
"POSE_UL_selection_set", "",
|
||||
armature, "selection_sets",
|
||||
armature, "active_selection_set")
|
||||
|
||||
col = row.column()
|
||||
colsub = col.column(align=True)
|
||||
colsub.operator("pose.selection_set_add", icon='ZOOMIN', text="")
|
||||
colsub.operator("pose.selection_set_remove", icon='ZOOMOUT', text="")
|
||||
|
||||
layout.operator("pose.selection_set_toggle")
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
class SelectionEntry(bpy.types.PropertyGroup):
|
||||
name = StringProperty(name="Bone Name")
|
||||
|
||||
|
||||
class SelectionSet(bpy.types.PropertyGroup):
|
||||
name = StringProperty(name="Set Name")
|
||||
bone_ids = CollectionProperty(type=SelectionEntry)
|
||||
|
||||
|
||||
class PluginOperator(Operator):
|
||||
@classmethod
|
||||
def poll(self, context):
|
||||
return (context.object and
|
||||
context.object.type == 'ARMATURE' and
|
||||
context.mode == 'POSE')
|
||||
|
||||
|
||||
class POSE_OT_selection_set_add(PluginOperator):
|
||||
bl_idname = "pose.selection_set_add"
|
||||
bl_label = "Add Selection Set"
|
||||
bl_options = {'UNDO', 'REGISTER'}
|
||||
|
||||
def execute(self, context):
|
||||
keep = False
|
||||
armature = context.object
|
||||
pose = armature.pose
|
||||
|
||||
selection_set = armature.selection_sets.add()
|
||||
selection_set.name = "SelectionSet.%d" % len(armature.selection_sets)
|
||||
armature.active_selection_set = len(armature.selection_sets) - 1
|
||||
for bone in pose.bones:
|
||||
if (bone.bone.select):
|
||||
bone_id = selection_set.bone_ids.add()
|
||||
bone_id.name = bone.name
|
||||
keep = True
|
||||
|
||||
if (not keep):
|
||||
armature.selection_sets.remove(armature.active_selection_set)
|
||||
numsets = len(armature.selection_sets)
|
||||
if (armature.active_selection_set > (numsets - 1) and numsets > 0):
|
||||
armature.active_selection_set = len(armature.selection_sets) - 1
|
||||
return {'CANCELLED'}
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class POSE_OT_selection_set_remove(PluginOperator):
|
||||
bl_idname = "pose.selection_set_remove"
|
||||
bl_label = "Delete Selection Set"
|
||||
bl_options = {'UNDO', 'REGISTER'}
|
||||
|
||||
def execute(self, context):
|
||||
armature = context.object
|
||||
|
||||
armature.selection_sets.remove(armature.active_selection_set)
|
||||
numsets = len(armature.selection_sets)
|
||||
if (armature.active_selection_set > (numsets - 1) and numsets > 0):
|
||||
armature.active_selection_set = len(armature.selection_sets) - 1
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class POSE_OT_selection_set_toggle(PluginOperator):
|
||||
bl_idname = "pose.selection_set_toggle"
|
||||
bl_label = "Toggle Selection Set"
|
||||
bl_options = {'UNDO', 'REGISTER'}
|
||||
|
||||
def execute(self, context):
|
||||
armature = context.object
|
||||
pose = armature.pose
|
||||
|
||||
selection_set = armature.selection_sets[armature.active_selection_set]
|
||||
for bone in pose.bones:
|
||||
bone.bone.select = False
|
||||
|
||||
for bone in selection_set.bone_ids:
|
||||
pose.bones[bone.name].bone.select = True
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MotionPathsCopyStartFrame(Operator):
|
||||
bl_idname = "anim.motionpaths_copy_scene_startframe"
|
||||
bl_label = "Copy Scene Start Frame"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
armature_paths = BoolProperty()
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return (context.object)
|
||||
|
||||
def execute(self, context):
|
||||
avs = None
|
||||
motionpath = None
|
||||
ob = context.object
|
||||
scene = context.scene
|
||||
|
||||
if (self.armature_paths):
|
||||
avs = ob.pose.animation_visualization
|
||||
else:
|
||||
avs = ob.animation_visualization
|
||||
|
||||
preview = scene.use_preview_range
|
||||
|
||||
if (avs):
|
||||
motionpath = avs.motion_path
|
||||
|
||||
if (motionpath):
|
||||
if (preview):
|
||||
motionpath.frame_start = scene.frame_preview_start
|
||||
motionpath.frame_end = scene.frame_preview_end
|
||||
else:
|
||||
motionpath.frame_start = scene.frame_start
|
||||
motionpath.frame_end = scene.frame_end
|
||||
else:
|
||||
return {'CANCELLED'}
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
classes = (
|
||||
POSE_PT_selection_sets,
|
||||
POSE_UL_selection_set,
|
||||
SelectionEntry,
|
||||
SelectionSet,
|
||||
POSE_OT_selection_set_add,
|
||||
POSE_OT_selection_set_remove,
|
||||
POSE_OT_selection_set_toggle,
|
||||
MotionPathsCopyStartFrame
|
||||
)
|
||||
|
||||
|
||||
def copy_frames_armature(self, context):
|
||||
layout = self.layout
|
||||
layout.operator("anim.motionpaths_copy_scene_startframe").armature_paths = True
|
||||
|
||||
|
||||
def copy_frames_object(self, context):
|
||||
layout = self.layout
|
||||
layout.operator("anim.motionpaths_copy_scene_startframe").armature_paths = False
|
||||
|
||||
|
||||
def register():
|
||||
for cls in classes:
|
||||
bpy.utils.register_class(cls)
|
||||
|
||||
bpy.types.Object.selection_sets = CollectionProperty(type=SelectionSet)
|
||||
bpy.types.Object.active_selection_set = IntProperty()
|
||||
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()
|
|
@ -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()
|
Loading…
Reference in New Issue