mirror of
https://github.com/blender/blender-addons.git
synced 2025-08-20 13:22:58 +00:00

The overall goal of this patch is to improve the UI/UX of the panel previously known as "Rigify Buttons" which presumably takes its name from the old "Buttons Panel" which is now known as the Properties Editor. Before: {F10511640} After: {F10511624} - Make Rigify less reliant on name matching when it comes to maintaining the link between the metarig, the UI script, the generated rig, and the widgets collection. (Use pointers only, names shouldn't matter!) - Change the "Advanced" toggle button into a real sub-panel. - Split up the "Rigify Buttons" panels into "Rigify Generation" and "Rigify Samples" panels in non-edit and edit mode respectively, to better describe what the user will find there. Changes in the Rigify Buttons panel: - Removed the "overwrite/new" enum. - If there is a target rig object, it will be overwritten. If not, it will be created. - If a rig object with the desired name already existed, but wasn't selected as the target rig, the "overwrite" option still overwrote that rig. I don't agree with that because this meant messing with data without indicating that that data is going to be messed with. Unaware users could lose data/work. With these changes, the worst thing that can happen is that your rig ends up with a .001 suffix. - Removed the "rig name" text input field. Before this patch, this would always rename your rig object and your rig script text datablock, which I think is more frustrating than useful. Now you can simply rename them after generation yourself, and the names will be kept in subsequent generations. - Single-column layout - Changed the "Advanced Options" into a sub-panel instead. On request: - Added an info message to show the name of the successfully generated rig: {F10159079} Feedback welcome. Reviewed By: angavrilov Differential Revision: https://developer.blender.org/D11356
1445 lines
52 KiB
Python
1445 lines
52 KiB
Python
#====================== 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 LICENSE BLOCK ========================
|
|
|
|
# <pep8 compliant>
|
|
|
|
import bpy
|
|
from bpy.props import (
|
|
BoolProperty,
|
|
IntProperty,
|
|
EnumProperty,
|
|
StringProperty
|
|
)
|
|
|
|
from mathutils import Color
|
|
|
|
from .utils.errors import MetarigError
|
|
from .utils.rig import write_metarig
|
|
from .utils.widgets import write_widget
|
|
from .utils.naming import unique_name
|
|
from .utils.rig import upgradeMetarigTypes, outdated_types
|
|
|
|
from .rigs.utils import get_limb_generated_names
|
|
|
|
from .utils.animation import get_keyed_frames_in_range, bones_in_frame, overwrite_prop_animation
|
|
from .utils.animation import RIGIFY_OT_get_frame_range
|
|
|
|
from .utils.animation import register as animation_register
|
|
from .utils.animation import unregister as animation_unregister
|
|
|
|
from . import base_rig
|
|
from . import rig_lists
|
|
from . import generate
|
|
from . import rot_mode
|
|
from . import feature_set_list
|
|
|
|
|
|
def build_type_list(context, rigify_types):
|
|
rigify_types.clear()
|
|
|
|
for r in sorted(rig_lists.rigs):
|
|
if (context.object.data.active_feature_set in ('all', rig_lists.rigs[r]['feature_set'])
|
|
or len(feature_set_list.get_installed_list()) == 0
|
|
):
|
|
a = rigify_types.add()
|
|
a.name = r
|
|
|
|
|
|
class DATA_PT_rigify_generate(bpy.types.Panel):
|
|
bl_label = "Rigify Generation"
|
|
bl_space_type = 'PROPERTIES'
|
|
bl_region_type = 'WINDOW'
|
|
bl_context = "data"
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
obj = context.object
|
|
if not context.object:
|
|
return False
|
|
return obj.type == 'ARMATURE' \
|
|
and obj.data.get("rig_id") is None \
|
|
and obj.mode in {'POSE', 'OBJECT'}
|
|
|
|
def draw(self, context):
|
|
C = context
|
|
layout = self.layout
|
|
obj = C.object
|
|
|
|
if obj.mode in {'POSE', 'OBJECT'}:
|
|
WARNING = "Warning: Some features may change after generation"
|
|
show_warning = False
|
|
show_update_metarig = False
|
|
show_not_updatable = False
|
|
show_upgrade_face = False
|
|
|
|
check_props = ['IK_follow', 'root/parent', 'FK_limb_follow', 'IK_Stretch']
|
|
|
|
for bone in obj.pose.bones:
|
|
if bone.bone.layers[30] and (list(set(bone.keys()) & set(check_props))):
|
|
show_warning = True
|
|
break
|
|
|
|
for b in obj.pose.bones:
|
|
if b.rigify_type in outdated_types.keys():
|
|
old_bone = b.name
|
|
old_rig = b.rigify_type
|
|
if outdated_types[b.rigify_type]:
|
|
show_update_metarig = True
|
|
else:
|
|
show_update_metarig = False
|
|
show_not_updatable = True
|
|
break
|
|
elif b.rigify_type == 'faces.super_face':
|
|
show_upgrade_face = True
|
|
|
|
if show_warning:
|
|
layout.label(text=WARNING, icon='ERROR')
|
|
|
|
enable_generate = not (show_not_updatable or show_update_metarig)
|
|
|
|
if show_not_updatable:
|
|
layout.label(text="WARNING: This metarig contains deprecated rigify rig-types and cannot be upgraded automatically.", icon='ERROR')
|
|
layout.label(text="("+old_rig+" on bone "+old_bone+")")
|
|
elif show_update_metarig:
|
|
layout.label(text="This metarig contains old rig-types that can be automatically upgraded to benefit of rigify's new features.", icon='ERROR')
|
|
layout.label(text="("+old_rig+" on bone "+old_bone+")")
|
|
layout.operator("pose.rigify_upgrade_types", text="Upgrade Metarig")
|
|
elif show_upgrade_face:
|
|
layout.label(text="This metarig uses the old face rig.", icon='INFO')
|
|
layout.operator("pose.rigify_upgrade_face")
|
|
|
|
row = layout.row()
|
|
# Rig type field
|
|
|
|
col = layout.column(align=True)
|
|
col.active = (not 'rig_id' in C.object.data)
|
|
|
|
col.separator()
|
|
row = col.row()
|
|
text = "Re-Generate Rig" if obj.data.rigify_target_rig else "Generate Rig"
|
|
row.operator("pose.rigify_generate", text=text, icon='POSE_HLT')
|
|
row.enabled = enable_generate
|
|
|
|
|
|
class DATA_PT_rigify_generate_advanced(bpy.types.Panel):
|
|
bl_space_type = 'PROPERTIES'
|
|
bl_region_type = 'WINDOW'
|
|
bl_context = "data"
|
|
bl_label = "Advanced"
|
|
bl_parent_id = 'DATA_PT_rigify_generate'
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
layout.use_property_decorate = False
|
|
|
|
armature_id_store = context.object.data
|
|
|
|
col = layout.column()
|
|
col.row().prop(armature_id_store, "rigify_target_rig", text="Target Rig")
|
|
col.row().prop(armature_id_store, "rigify_rig_ui", text="Rig UI Script")
|
|
col.separator()
|
|
col.row().prop(armature_id_store, "rigify_widgets_collection")
|
|
col.row().prop(armature_id_store, "rigify_force_widget_update")
|
|
col.row().prop(armature_id_store, "rigify_mirror_widgets")
|
|
col.separator()
|
|
col.row().prop(armature_id_store, "rigify_finalize_script", text="Run Script")
|
|
|
|
|
|
class DATA_PT_rigify_samples(bpy.types.Panel):
|
|
bl_label = "Rigify Samples"
|
|
bl_space_type = 'PROPERTIES'
|
|
bl_region_type = 'WINDOW'
|
|
bl_context = "data"
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
obj = context.object
|
|
if not obj:
|
|
return False
|
|
return obj.type == 'ARMATURE' \
|
|
and obj.data.get("rig_id") is None \
|
|
and obj.mode == 'EDIT'
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
layout.use_property_decorate = False
|
|
obj = context.object
|
|
id_store = context.window_manager
|
|
|
|
# Build types list
|
|
build_type_list(context, id_store.rigify_types)
|
|
|
|
if id_store.rigify_active_type > len(id_store.rigify_types):
|
|
id_store.rigify_active_type = 0
|
|
|
|
# Rig type list
|
|
if len(feature_set_list.get_installed_list()) > 0:
|
|
row = layout.row()
|
|
row.prop(context.object.data, "active_feature_set")
|
|
row = layout.row()
|
|
row.template_list("UI_UL_list", "rigify_types", id_store, "rigify_types", id_store, 'rigify_active_type')
|
|
|
|
props = layout.operator("armature.metarig_sample_add", text="Add sample")
|
|
props.metarig_type = id_store.rigify_types[id_store.rigify_active_type].name
|
|
|
|
|
|
class DATA_PT_rigify_layer_names(bpy.types.Panel):
|
|
bl_label = "Rigify Layer Names"
|
|
bl_space_type = 'PROPERTIES'
|
|
bl_region_type = 'WINDOW'
|
|
bl_context = "data"
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
if not context.object:
|
|
return False
|
|
return context.object.type == 'ARMATURE' and context.active_object.data.get("rig_id") is None
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
obj = context.object
|
|
arm = obj.data
|
|
|
|
# Ensure that the layers exist
|
|
if 0:
|
|
for i in range(1 + len(arm.rigify_layers), 29):
|
|
arm.rigify_layers.add()
|
|
else:
|
|
# Can't add while drawing, just use button
|
|
if len(arm.rigify_layers) < 29:
|
|
layout.operator("pose.rigify_layer_init")
|
|
return
|
|
|
|
# UI
|
|
main_row = layout.row(align=True).split(factor=0.05)
|
|
col1 = main_row.column()
|
|
col2 = main_row.column()
|
|
col1.label()
|
|
for i in range(32):
|
|
if i == 16 or i == 29:
|
|
col1.label()
|
|
col1.label(text=str(i+1) + '.')
|
|
|
|
for i, rigify_layer in enumerate(arm.rigify_layers):
|
|
# note: rigify_layer == arm.rigify_layers[i]
|
|
if (i % 16) == 0:
|
|
col = col2.column()
|
|
if i == 0:
|
|
col.label(text="Top Row:")
|
|
else:
|
|
col.label(text="Bottom Row:")
|
|
if (i % 8) == 0:
|
|
col = col2.column()
|
|
if i != 28:
|
|
row = col.row(align=True)
|
|
icon = 'RESTRICT_VIEW_OFF' if arm.layers[i] else 'RESTRICT_VIEW_ON'
|
|
row.prop(arm, "layers", index=i, text="", toggle=True, icon=icon)
|
|
#row.prop(arm, "layers", index=i, text="Layer %d" % (i + 1), toggle=True, icon=icon)
|
|
row.prop(rigify_layer, "name", text="")
|
|
row.prop(rigify_layer, "row", text="UI Row")
|
|
icon = 'RADIOBUT_ON' if rigify_layer.selset else 'RADIOBUT_OFF'
|
|
row.prop(rigify_layer, "selset", text="", toggle=True, icon=icon)
|
|
row.prop(rigify_layer, "group", text="Bone Group")
|
|
else:
|
|
row = col.row(align=True)
|
|
|
|
icon = 'RESTRICT_VIEW_OFF' if arm.layers[i] else 'RESTRICT_VIEW_ON'
|
|
row.prop(arm, "layers", index=i, text="", toggle=True, icon=icon)
|
|
# row.prop(arm, "layers", index=i, text="Layer %d" % (i + 1), toggle=True, icon=icon)
|
|
row1 = row.split(align=True).row(align=True)
|
|
row1.prop(rigify_layer, "name", text="")
|
|
row1.prop(rigify_layer, "row", text="UI Row")
|
|
row1.enabled = False
|
|
icon = 'RADIOBUT_ON' if rigify_layer.selset else 'RADIOBUT_OFF'
|
|
row.prop(rigify_layer, "selset", text="", toggle=True, icon=icon)
|
|
row.prop(rigify_layer, "group", text="Bone Group")
|
|
if rigify_layer.group == 0:
|
|
row.label(text='None')
|
|
else:
|
|
row.label(text=arm.rigify_colors[rigify_layer.group-1].name)
|
|
|
|
col = col2.column()
|
|
col.label(text="Reserved:")
|
|
# reserved_names = {28: 'Root', 29: 'DEF', 30: 'MCH', 31: 'ORG'}
|
|
reserved_names = {29: 'DEF', 30: 'MCH', 31: 'ORG'}
|
|
# for i in range(28, 32):
|
|
for i in range(29, 32):
|
|
row = col.row(align=True)
|
|
icon = 'RESTRICT_VIEW_OFF' if arm.layers[i] else 'RESTRICT_VIEW_ON'
|
|
row.prop(arm, "layers", index=i, text="", toggle=True, icon=icon)
|
|
row.label(text=reserved_names[i])
|
|
|
|
|
|
class DATA_OT_rigify_add_bone_groups(bpy.types.Operator):
|
|
bl_idname = "armature.rigify_add_bone_groups"
|
|
bl_label = "Rigify Add Standard Bone Groups"
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.object and context.object.type == 'ARMATURE'
|
|
|
|
def execute(self, context):
|
|
obj = context.object
|
|
armature = obj.data
|
|
if not hasattr(armature, 'rigify_colors'):
|
|
return {'FINISHED'}
|
|
|
|
groups = ['Root', 'IK', 'Special', 'Tweak', 'FK', 'Extra']
|
|
|
|
for g in groups:
|
|
if g in armature.rigify_colors.keys():
|
|
continue
|
|
|
|
armature.rigify_colors.add()
|
|
armature.rigify_colors[-1].name = g
|
|
|
|
armature.rigify_colors[g].select = Color((0.3140000104904175, 0.7839999794960022, 1.0))
|
|
armature.rigify_colors[g].active = Color((0.5490000247955322, 1.0, 1.0))
|
|
armature.rigify_colors[g].standard_colors_lock = True
|
|
|
|
if g == "Root":
|
|
armature.rigify_colors[g].normal = Color((0.43529415130615234, 0.18431372940540314, 0.41568630933761597))
|
|
if g == "IK":
|
|
armature.rigify_colors[g].normal = Color((0.6039215922355652, 0.0, 0.0))
|
|
if g== "Special":
|
|
armature.rigify_colors[g].normal = Color((0.9568628072738647, 0.7882353663444519, 0.0470588281750679))
|
|
if g== "Tweak":
|
|
armature.rigify_colors[g].normal = Color((0.03921568766236305, 0.21176472306251526, 0.5803921818733215))
|
|
if g== "FK":
|
|
armature.rigify_colors[g].normal = Color((0.11764706671237946, 0.5686274766921997, 0.03529411926865578))
|
|
if g== "Extra":
|
|
armature.rigify_colors[g].normal = Color((0.9686275124549866, 0.250980406999588, 0.0941176563501358))
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class DATA_OT_rigify_use_standard_colors(bpy.types.Operator):
|
|
bl_idname = "armature.rigify_use_standard_colors"
|
|
bl_label = "Rigify Get active/select colors from current theme"
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.object and context.object.type == 'ARMATURE'
|
|
|
|
def execute(self, context):
|
|
obj = context.object
|
|
armature = obj.data
|
|
if not hasattr(armature, 'rigify_colors'):
|
|
return {'FINISHED'}
|
|
|
|
current_theme = bpy.context.preferences.themes.items()[0][0]
|
|
theme = bpy.context.preferences.themes[current_theme]
|
|
|
|
armature.rigify_selection_colors.select = theme.view_3d.bone_pose
|
|
armature.rigify_selection_colors.active = theme.view_3d.bone_pose_active
|
|
|
|
# for col in armature.rigify_colors:
|
|
# col.select = theme.view_3d.bone_pose
|
|
# col.active = theme.view_3d.bone_pose_active
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class DATA_OT_rigify_apply_selection_colors(bpy.types.Operator):
|
|
bl_idname = "armature.rigify_apply_selection_colors"
|
|
bl_label = "Rigify Apply user defined active/select colors"
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.object and context.object.type == 'ARMATURE'
|
|
|
|
def execute(self, context):
|
|
obj = context.object
|
|
armature = obj.data
|
|
if not hasattr(armature, 'rigify_colors'):
|
|
return {'FINISHED'}
|
|
|
|
#current_theme = bpy.context.preferences.themes.items()[0][0]
|
|
#theme = bpy.context.preferences.themes[current_theme]
|
|
|
|
for col in armature.rigify_colors:
|
|
col.select = armature.rigify_selection_colors.select
|
|
col.active = armature.rigify_selection_colors.active
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class DATA_OT_rigify_bone_group_add(bpy.types.Operator):
|
|
bl_idname = "armature.rigify_bone_group_add"
|
|
bl_label = "Rigify Add Bone Group color set"
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.object and context.object.type == 'ARMATURE'
|
|
|
|
def execute(self, context):
|
|
obj = context.object
|
|
armature = obj.data
|
|
|
|
if hasattr(armature, 'rigify_colors'):
|
|
armature.rigify_colors.add()
|
|
armature.rigify_colors[-1].name = unique_name(armature.rigify_colors, 'Group')
|
|
|
|
current_theme = bpy.context.preferences.themes.items()[0][0]
|
|
theme = bpy.context.preferences.themes[current_theme]
|
|
|
|
armature.rigify_colors[-1].normal = theme.view_3d.wire
|
|
armature.rigify_colors[-1].normal.hsv = theme.view_3d.wire.hsv
|
|
armature.rigify_colors[-1].select = theme.view_3d.bone_pose
|
|
armature.rigify_colors[-1].select.hsv = theme.view_3d.bone_pose.hsv
|
|
armature.rigify_colors[-1].active = theme.view_3d.bone_pose_active
|
|
armature.rigify_colors[-1].active.hsv = theme.view_3d.bone_pose_active.hsv
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class DATA_OT_rigify_bone_group_add_theme(bpy.types.Operator):
|
|
bl_idname = "armature.rigify_bone_group_add_theme"
|
|
bl_label = "Rigify Add Bone Group color set from Theme"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
theme: EnumProperty(items=(
|
|
('THEME01', 'THEME01', ''),
|
|
('THEME02', 'THEME02', ''),
|
|
('THEME03', 'THEME03', ''),
|
|
('THEME04', 'THEME04', ''),
|
|
('THEME05', 'THEME05', ''),
|
|
('THEME06', 'THEME06', ''),
|
|
('THEME07', 'THEME07', ''),
|
|
('THEME08', 'THEME08', ''),
|
|
('THEME09', 'THEME09', ''),
|
|
('THEME10', 'THEME10', ''),
|
|
('THEME11', 'THEME11', ''),
|
|
('THEME12', 'THEME12', ''),
|
|
('THEME13', 'THEME13', ''),
|
|
('THEME14', 'THEME14', ''),
|
|
('THEME15', 'THEME15', ''),
|
|
('THEME16', 'THEME16', ''),
|
|
('THEME17', 'THEME17', ''),
|
|
('THEME18', 'THEME18', ''),
|
|
('THEME19', 'THEME19', ''),
|
|
('THEME20', 'THEME20', '')
|
|
),
|
|
name='Theme')
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.object and context.object.type == 'ARMATURE'
|
|
|
|
def execute(self, context):
|
|
obj = context.object
|
|
armature = obj.data
|
|
|
|
if hasattr(armature, 'rigify_colors'):
|
|
|
|
if self.theme in armature.rigify_colors.keys():
|
|
return {'FINISHED'}
|
|
armature.rigify_colors.add()
|
|
armature.rigify_colors[-1].name = self.theme
|
|
|
|
id = int(self.theme[-2:]) - 1
|
|
|
|
theme_color_set = bpy.context.preferences.themes[0].bone_color_sets[id]
|
|
|
|
armature.rigify_colors[-1].normal = theme_color_set.normal
|
|
armature.rigify_colors[-1].select = theme_color_set.select
|
|
armature.rigify_colors[-1].active = theme_color_set.active
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class DATA_OT_rigify_bone_group_remove(bpy.types.Operator):
|
|
bl_idname = "armature.rigify_bone_group_remove"
|
|
bl_label = "Rigify Remove Bone Group color set"
|
|
|
|
idx: IntProperty()
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.object and context.object.type == 'ARMATURE'
|
|
|
|
def execute(self, context):
|
|
obj = context.object
|
|
obj.data.rigify_colors.remove(self.idx)
|
|
|
|
# set layers references to 0
|
|
for l in obj.data.rigify_layers:
|
|
if l.group == self.idx + 1:
|
|
l.group = 0
|
|
elif l.group > self.idx + 1:
|
|
l.group -= 1
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class DATA_OT_rigify_bone_group_remove_all(bpy.types.Operator):
|
|
bl_idname = "armature.rigify_bone_group_remove_all"
|
|
bl_label = "Rigify Remove All Bone Groups"
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.object and context.object.type == 'ARMATURE'
|
|
|
|
def execute(self, context):
|
|
obj = context.object
|
|
|
|
for i, col in enumerate(obj.data.rigify_colors):
|
|
obj.data.rigify_colors.remove(0)
|
|
# set layers references to 0
|
|
for l in obj.data.rigify_layers:
|
|
if l.group == i + 1:
|
|
l.group = 0
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class DATA_UL_rigify_bone_groups(bpy.types.UIList):
|
|
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
|
|
row = layout.row(align=True)
|
|
row = row.split(factor=0.1)
|
|
row.label(text=str(index+1))
|
|
row = row.split(factor=0.7)
|
|
row.prop(item, "name", text='', emboss=False)
|
|
row = row.row(align=True)
|
|
icon = 'LOCKED' if item.standard_colors_lock else 'UNLOCKED'
|
|
#row.prop(item, "standard_colors_lock", text='', icon=icon)
|
|
row.prop(item, "normal", text='')
|
|
row2 = row.row(align=True)
|
|
row2.prop(item, "select", text='')
|
|
row2.prop(item, "active", text='')
|
|
#row2.enabled = not item.standard_colors_lock
|
|
row2.enabled = not bpy.context.object.data.rigify_colors_lock
|
|
|
|
|
|
class DATA_MT_rigify_bone_groups_context_menu(bpy.types.Menu):
|
|
bl_label = 'Rigify Bone Groups Specials'
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
|
|
layout.operator('armature.rigify_bone_group_remove_all')
|
|
|
|
|
|
class DATA_PT_rigify_bone_groups(bpy.types.Panel):
|
|
bl_label = "Rigify Bone Groups"
|
|
bl_space_type = 'PROPERTIES'
|
|
bl_region_type = 'WINDOW'
|
|
bl_context = "data"
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
if not context.object:
|
|
return False
|
|
return context.object.type == 'ARMATURE' and context.active_object.data.get("rig_id") is None
|
|
|
|
def draw(self, context):
|
|
obj = context.object
|
|
armature = obj.data
|
|
color_sets = obj.data.rigify_colors
|
|
idx = obj.data.rigify_colors_index
|
|
|
|
layout = self.layout
|
|
row = layout.row()
|
|
row.operator("armature.rigify_use_standard_colors", icon='FILE_REFRESH', text='')
|
|
row = row.row(align=True)
|
|
row.prop(armature.rigify_selection_colors, 'select', text='')
|
|
row.prop(armature.rigify_selection_colors, 'active', text='')
|
|
row = layout.row(align=True)
|
|
icon = 'LOCKED' if armature.rigify_colors_lock else 'UNLOCKED'
|
|
row.prop(armature, 'rigify_colors_lock', text = 'Unified select/active colors', icon=icon)
|
|
row.operator("armature.rigify_apply_selection_colors", icon='FILE_REFRESH', text='Apply')
|
|
row = layout.row()
|
|
row.template_list("DATA_UL_rigify_bone_groups", "", obj.data, "rigify_colors", obj.data, "rigify_colors_index")
|
|
|
|
col = row.column(align=True)
|
|
col.operator("armature.rigify_bone_group_add", icon='ZOOM_IN', text="")
|
|
col.operator("armature.rigify_bone_group_remove", icon='ZOOM_OUT', text="").idx = obj.data.rigify_colors_index
|
|
col.menu("DATA_MT_rigify_bone_groups_context_menu", icon='DOWNARROW_HLT', text="")
|
|
row = layout.row()
|
|
row.prop(armature, 'rigify_theme_to_add', text = 'Theme')
|
|
op = row.operator("armature.rigify_bone_group_add_theme", text="Add From Theme")
|
|
op.theme = armature.rigify_theme_to_add
|
|
row = layout.row()
|
|
row.operator("armature.rigify_add_bone_groups", text="Add Standard")
|
|
|
|
|
|
class BONE_PT_rigify_buttons(bpy.types.Panel):
|
|
bl_label = "Rigify Type"
|
|
bl_space_type = 'PROPERTIES'
|
|
bl_region_type = 'WINDOW'
|
|
bl_context = "bone"
|
|
#bl_options = {'DEFAULT_OPEN'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
if not context.object:
|
|
return False
|
|
return context.object.type == 'ARMATURE' and context.active_pose_bone\
|
|
and context.active_object.data.get("rig_id") is None
|
|
|
|
def draw(self, context):
|
|
C = context
|
|
id_store = C.window_manager
|
|
bone = context.active_pose_bone
|
|
rig_name = str(context.active_pose_bone.rigify_type).replace(" ", "")
|
|
|
|
layout = self.layout
|
|
|
|
# Build types list
|
|
build_type_list(context, id_store.rigify_types)
|
|
|
|
# Rig type field
|
|
if len(feature_set_list.get_installed_list()) > 0:
|
|
row = layout.row()
|
|
row.prop(context.object.data, "active_feature_set")
|
|
row = layout.row()
|
|
row.prop_search(bone, "rigify_type", id_store, "rigify_types", text="Rig type")
|
|
|
|
# Rig type parameters / Rig type non-exist alert
|
|
if rig_name != "":
|
|
try:
|
|
rig = rig_lists.rigs[rig_name]['module']
|
|
except (ImportError, AttributeError, KeyError):
|
|
row = layout.row()
|
|
box = row.box()
|
|
box.label(text="ERROR: type \"%s\" does not exist!" % rig_name, icon='ERROR')
|
|
else:
|
|
if hasattr(rig.Rig, 'parameters_ui'):
|
|
rig = rig.Rig
|
|
|
|
try:
|
|
param_cb = rig.parameters_ui
|
|
|
|
# Ignore the known empty base method
|
|
if getattr(param_cb, '__func__', None) == base_rig.BaseRig.parameters_ui.__func__:
|
|
param_cb = None
|
|
except AttributeError:
|
|
param_cb = None
|
|
|
|
if param_cb is None:
|
|
col = layout.column()
|
|
col.label(text="No options")
|
|
else:
|
|
col = layout.column()
|
|
col.label(text="Options:")
|
|
box = layout.box()
|
|
param_cb(box, bone.rigify_parameters)
|
|
|
|
|
|
class VIEW3D_PT_tools_rigify_dev(bpy.types.Panel):
|
|
bl_label = "Rigify Dev Tools"
|
|
bl_space_type = 'VIEW_3D'
|
|
bl_region_type = 'UI'
|
|
bl_category = "Rigify"
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.mode in ['EDIT_ARMATURE', 'EDIT_MESH']
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.mode in ['EDIT_ARMATURE', 'EDIT_MESH']
|
|
|
|
def draw(self, context):
|
|
obj = context.active_object
|
|
if obj is not None:
|
|
if context.mode == 'EDIT_ARMATURE':
|
|
r = self.layout.row()
|
|
r.operator("armature.rigify_encode_metarig", text="Encode Metarig to Python")
|
|
r = self.layout.row()
|
|
r.operator("armature.rigify_encode_metarig_sample", text="Encode Sample to Python")
|
|
|
|
if context.mode == 'EDIT_MESH':
|
|
r = self.layout.row()
|
|
r.operator("mesh.rigify_encode_mesh_widget", text="Encode Mesh Widget to Python")
|
|
|
|
|
|
class VIEW3D_PT_rigify_animation_tools(bpy.types.Panel):
|
|
bl_label = "Rigify Animation Tools"
|
|
bl_context = "posemode"
|
|
bl_space_type = 'VIEW_3D'
|
|
bl_region_type = 'UI'
|
|
bl_category = "Rigify"
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
obj = context.active_object
|
|
if obj and obj.type == 'ARMATURE':
|
|
rig_id = obj.data.get("rig_id")
|
|
if rig_id is not None:
|
|
has_arm = hasattr(bpy.types, 'POSE_OT_rigify_arm_ik2fk_' + rig_id)
|
|
has_leg = hasattr(bpy.types, 'POSE_OT_rigify_leg_ik2fk_' + rig_id)
|
|
return has_arm or has_leg
|
|
|
|
return False
|
|
|
|
def draw(self, context):
|
|
obj = context.active_object
|
|
id_store = context.window_manager
|
|
if obj is not None:
|
|
row = self.layout.row()
|
|
|
|
if id_store.rigify_transfer_only_selected:
|
|
icon = 'OUTLINER_DATA_ARMATURE'
|
|
else:
|
|
icon = 'ARMATURE_DATA'
|
|
|
|
row.prop(id_store, 'rigify_transfer_only_selected', toggle=True, icon=icon)
|
|
|
|
row = self.layout.row(align=True)
|
|
row.operator("rigify.ik2fk", text='IK2FK Pose', icon='SNAP_ON')
|
|
row.operator("rigify.fk2ik", text='FK2IK Pose', icon='SNAP_ON')
|
|
|
|
row = self.layout.row(align=True)
|
|
row.operator("rigify.transfer_fk_to_ik", text='IK2FK Action', icon='ACTION_TWEAK')
|
|
row.operator("rigify.transfer_ik_to_fk", text='FK2IK Action', icon='ACTION_TWEAK')
|
|
|
|
row = self.layout.row(align=True)
|
|
row.operator("rigify.clear_animation", text="Clear IK Action", icon='CANCEL').anim_type = "IK"
|
|
row.operator("rigify.clear_animation", text="Clear FK Action", icon='CANCEL').anim_type = "FK"
|
|
|
|
row = self.layout.row(align=True)
|
|
op = row.operator("rigify.rotation_pole", icon='FORCE_HARMONIC', text='Switch to pole')
|
|
op.value = True
|
|
op.toggle = False
|
|
op.bake = True
|
|
op = row.operator("rigify.rotation_pole", icon='FORCE_MAGNETIC', text='Switch to rotation')
|
|
op.value = False
|
|
op.toggle = False
|
|
op.bake = True
|
|
RIGIFY_OT_get_frame_range.draw_range_ui(context, self.layout)
|
|
|
|
|
|
def rigify_report_exception(operator, exception):
|
|
import traceback
|
|
import sys
|
|
import os
|
|
# find the non-utils module name where the error happened
|
|
# hint, this is the metarig type!
|
|
exceptionType, exceptionValue, exceptionTraceback = sys.exc_info()
|
|
fns = [ item.filename for item in traceback.extract_tb(exceptionTraceback) ]
|
|
fns_rig = [ fn for fn in fns if os.path.basename(os.path.dirname(fn)) != 'utils' ]
|
|
fn = fns_rig[-1]
|
|
fn = os.path.basename(fn)
|
|
fn = os.path.splitext(fn)[0]
|
|
message = []
|
|
if fn.startswith("__"):
|
|
message.append("Incorrect armature...")
|
|
else:
|
|
message.append("Incorrect armature for type '%s'" % fn)
|
|
message.append(exception.message)
|
|
|
|
message.reverse() # XXX - stupid! menu's are upside down!
|
|
|
|
operator.report({'ERROR'}, '\n'.join(message))
|
|
|
|
|
|
class LayerInit(bpy.types.Operator):
|
|
"""Initialize armature rigify layers"""
|
|
|
|
bl_idname = "pose.rigify_layer_init"
|
|
bl_label = "Add Rigify Layers"
|
|
bl_options = {'UNDO', 'INTERNAL'}
|
|
|
|
def execute(self, context):
|
|
obj = context.object
|
|
arm = obj.data
|
|
for i in range(1 + len(arm.rigify_layers), 30):
|
|
arm.rigify_layers.add()
|
|
arm.rigify_layers[28].name = 'Root'
|
|
arm.rigify_layers[28].row = 14
|
|
return {'FINISHED'}
|
|
|
|
|
|
def is_metarig(obj):
|
|
if not (obj and obj.data and obj.type == 'ARMATURE'):
|
|
return False
|
|
if 'rig_id' in obj.data:
|
|
return False
|
|
for b in obj.pose.bones:
|
|
if b.rigify_type != "":
|
|
return True
|
|
return False
|
|
|
|
class Generate(bpy.types.Operator):
|
|
"""Generates a rig from the active metarig armature"""
|
|
|
|
bl_idname = "pose.rigify_generate"
|
|
bl_label = "Rigify Generate Rig"
|
|
bl_options = {'UNDO'}
|
|
bl_description = 'Generates a rig from the active metarig armature'
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return is_metarig(context.object)
|
|
|
|
def execute(self, context):
|
|
metarig = context.object
|
|
try:
|
|
generate.generate_rig(context, metarig)
|
|
except MetarigError as rig_exception:
|
|
import traceback
|
|
traceback.print_exc()
|
|
|
|
rigify_report_exception(self, rig_exception)
|
|
except Exception as rig_exception:
|
|
import traceback
|
|
traceback.print_exc()
|
|
|
|
self.report({'ERROR'}, 'Generation has thrown an exception: ' + str(rig_exception))
|
|
else:
|
|
self.report({'INFO'}, 'Successfully generated: "' + metarig.data.rigify_target_rig.name + '"')
|
|
finally:
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class UpgradeMetarigTypes(bpy.types.Operator):
|
|
"""Upgrades metarig bones rigify_types"""
|
|
|
|
bl_idname = "pose.rigify_upgrade_types"
|
|
bl_label = "Rigify Upgrade Metarig Types"
|
|
bl_description = 'Upgrades the rigify types on the active metarig armature'
|
|
bl_options = {'UNDO'}
|
|
|
|
def execute(self, context):
|
|
for obj in bpy.data.objects:
|
|
if type(obj.data) == bpy.types.Armature:
|
|
upgradeMetarigTypes(obj)
|
|
return {'FINISHED'}
|
|
class Sample(bpy.types.Operator):
|
|
"""Create a sample metarig to be modified before generating the final rig"""
|
|
|
|
bl_idname = "armature.metarig_sample_add"
|
|
bl_label = "Add Metarig Sample"
|
|
bl_options = {'UNDO'}
|
|
|
|
metarig_type: StringProperty(
|
|
name="Type",
|
|
description="Name of the rig type to generate a sample of",
|
|
maxlen=128,
|
|
options={'SKIP_SAVE'}
|
|
)
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.mode == 'EDIT_ARMATURE'
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
layout.use_property_decorate = False
|
|
col = layout.column()
|
|
build_type_list(context, context.window_manager.rigify_types)
|
|
col.prop(context.object.data, "active_feature_set")
|
|
col.prop_search(self, "metarig_type", context.window_manager, "rigify_types")
|
|
|
|
def invoke(self, context, event):
|
|
if self.metarig_type == "":
|
|
return context.window_manager.invoke_props_dialog(self)
|
|
return self.execute(context)
|
|
|
|
def execute(self, context):
|
|
if self.metarig_type == "":
|
|
self.report({'ERROR'}, "You must select a rig type to create a sample of.")
|
|
return {'CANCELLED'}
|
|
try:
|
|
rig = rig_lists.rigs[self.metarig_type]["module"]
|
|
create_sample = rig.create_sample
|
|
except (ImportError, AttributeError, KeyError):
|
|
raise Exception("rig type '" + self.metarig_type + "' has no sample.")
|
|
else:
|
|
create_sample(context.active_object)
|
|
finally:
|
|
bpy.ops.object.mode_set(mode='EDIT')
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class EncodeMetarig(bpy.types.Operator):
|
|
"""Creates Python code that will generate the selected metarig"""
|
|
bl_idname = "armature.rigify_encode_metarig"
|
|
bl_label = "Rigify Encode Metarig"
|
|
bl_options = {'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(self, context):
|
|
return context.mode == 'EDIT_ARMATURE' and is_metarig(context.object)
|
|
|
|
def execute(self, context):
|
|
name = "metarig.py"
|
|
|
|
if name in bpy.data.texts:
|
|
text_block = bpy.data.texts[name]
|
|
text_block.clear()
|
|
else:
|
|
text_block = bpy.data.texts.new(name)
|
|
|
|
text = write_metarig(context.active_object, layers=True, func_name="create", groups=True, widgets=True)
|
|
text_block.write(text)
|
|
bpy.ops.object.mode_set(mode='EDIT')
|
|
self.report({'INFO'}, f"Metarig written to text datablock: {text_block.name}")
|
|
return {'FINISHED'}
|
|
|
|
|
|
class EncodeMetarigSample(bpy.types.Operator):
|
|
"""Creates Python code that will generate the selected metarig as a sample"""
|
|
bl_idname = "armature.rigify_encode_metarig_sample"
|
|
bl_label = "Rigify Encode Metarig Sample"
|
|
bl_options = {'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(self, context):
|
|
return context.mode == 'EDIT_ARMATURE' and is_metarig(context.object)
|
|
|
|
def execute(self, context):
|
|
name = "metarig_sample.py"
|
|
|
|
if name in bpy.data.texts:
|
|
text_block = bpy.data.texts[name]
|
|
text_block.clear()
|
|
else:
|
|
text_block = bpy.data.texts.new(name)
|
|
|
|
text = write_metarig(context.active_object, layers=False, func_name="create_sample")
|
|
text_block.write(text)
|
|
bpy.ops.object.mode_set(mode='EDIT')
|
|
|
|
self.report({'INFO'}, f"Metarig Sample written to text datablock: {text_block.name}")
|
|
return {'FINISHED'}
|
|
|
|
|
|
class VIEW3D_MT_rigify(bpy.types.Menu):
|
|
bl_label = "Rigify"
|
|
bl_idname = "VIEW3D_MT_rigify"
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
obj = context.object
|
|
|
|
text = "Re-Generate Rig" if obj.data.rigify_target_rig else "Generate Rig"
|
|
layout.operator(Generate.bl_idname, text=text)
|
|
|
|
if context.mode == 'EDIT_ARMATURE':
|
|
layout.separator()
|
|
layout.operator(Sample.bl_idname)
|
|
layout.separator()
|
|
layout.operator(EncodeMetarig.bl_idname, text="Encode Metarig")
|
|
layout.operator(EncodeMetarigSample.bl_idname, text="Encode Metarig Sample")
|
|
|
|
|
|
def draw_rigify_menu(self, context):
|
|
if is_metarig(context.object):
|
|
self.layout.menu(VIEW3D_MT_rigify.bl_idname)
|
|
|
|
class EncodeWidget(bpy.types.Operator):
|
|
""" Creates Python code that will generate the selected metarig.
|
|
"""
|
|
bl_idname = "mesh.rigify_encode_mesh_widget"
|
|
bl_label = "Rigify Encode Widget"
|
|
bl_options = {'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(self, context):
|
|
return context.mode == 'EDIT_MESH'
|
|
|
|
def execute(self, context):
|
|
name = "widget.py"
|
|
|
|
if name in bpy.data.texts:
|
|
text_block = bpy.data.texts[name]
|
|
text_block.clear()
|
|
else:
|
|
text_block = bpy.data.texts.new(name)
|
|
|
|
text = write_widget(context.active_object)
|
|
text_block.write(text)
|
|
bpy.ops.object.mode_set(mode='EDIT')
|
|
|
|
return {'FINISHED'}
|
|
|
|
def draw_mesh_edit_menu(self, context):
|
|
self.layout.operator(EncodeWidget.bl_idname)
|
|
self.layout.separator()
|
|
|
|
|
|
def FktoIk(rig, window='ALL'):
|
|
|
|
scn = bpy.context.scene
|
|
id_store = bpy.context.window_manager
|
|
|
|
rig_id = rig.data['rig_id']
|
|
leg_ik2fk = eval('bpy.ops.pose.rigify_leg_ik2fk_' + rig_id)
|
|
arm_ik2fk = eval('bpy.ops.pose.rigify_arm_ik2fk_' + rig_id)
|
|
limb_generated_names = get_limb_generated_names(rig)
|
|
|
|
if window == 'ALL':
|
|
frames = get_keyed_frames_in_range(bpy.context, rig)
|
|
elif window == 'CURRENT':
|
|
frames = [scn.frame_current]
|
|
else:
|
|
frames = [scn.frame_current]
|
|
|
|
if not id_store.rigify_transfer_only_selected:
|
|
pbones = rig.pose.bones
|
|
bpy.ops.pose.select_all(action='DESELECT')
|
|
else:
|
|
pbones = bpy.context.selected_pose_bones
|
|
bpy.ops.pose.select_all(action='DESELECT')
|
|
|
|
for b in pbones:
|
|
for group in limb_generated_names:
|
|
if b.name in limb_generated_names[group].values() or b.name in limb_generated_names[group]['controls']\
|
|
or b.name in limb_generated_names[group]['ik_ctrl']:
|
|
names = limb_generated_names[group]
|
|
if names['limb_type'] == 'arm':
|
|
func = arm_ik2fk
|
|
controls = names['controls']
|
|
ik_ctrl = names['ik_ctrl']
|
|
fk_ctrl = names['fk_ctrl']
|
|
parent = names['parent']
|
|
pole = names['pole']
|
|
rig.pose.bones[controls[0]].bone.select = True
|
|
rig.pose.bones[controls[4]].bone.select = True
|
|
rig.pose.bones[pole].bone.select = True
|
|
rig.pose.bones[parent].bone.select = True
|
|
kwargs = {'uarm_fk': controls[1], 'farm_fk': controls[2], 'hand_fk': controls[3],
|
|
'uarm_ik': controls[0], 'farm_ik': ik_ctrl[1], 'hand_ik': controls[4],
|
|
'pole': pole, 'main_parent': parent}
|
|
args = (controls[0], controls[1], controls[2], controls[3],
|
|
controls[4], pole, parent)
|
|
else:
|
|
func = leg_ik2fk
|
|
controls = names['controls']
|
|
ik_ctrl = names['ik_ctrl']
|
|
fk_ctrl = names['fk_ctrl']
|
|
parent = names['parent']
|
|
pole = names['pole']
|
|
rig.pose.bones[controls[0]].bone.select = True
|
|
rig.pose.bones[controls[6]].bone.select = True
|
|
rig.pose.bones[controls[5]].bone.select = True
|
|
rig.pose.bones[pole].bone.select = True
|
|
rig.pose.bones[parent].bone.select = True
|
|
kwargs = {'thigh_fk': controls[1], 'shin_fk': controls[2], 'foot_fk': controls[3],
|
|
'mfoot_fk': controls[7], 'thigh_ik': controls[0], 'shin_ik': ik_ctrl[1],
|
|
'foot_ik': controls[6], 'pole': pole, 'footroll': controls[5], 'mfoot_ik': ik_ctrl[2],
|
|
'main_parent': parent}
|
|
args = (controls[0], controls[1], controls[2], controls[3],
|
|
controls[6], controls[5], pole, parent)
|
|
|
|
for f in frames:
|
|
if not bones_in_frame(f, rig, *args):
|
|
continue
|
|
scn.frame_set(f)
|
|
func(**kwargs)
|
|
bpy.ops.anim.keyframe_insert_menu(type='BUILTIN_KSI_VisualLocRot')
|
|
bpy.ops.anim.keyframe_insert_menu(type='Scaling')
|
|
|
|
bpy.ops.pose.select_all(action='DESELECT')
|
|
limb_generated_names.pop(group)
|
|
break
|
|
|
|
|
|
def IktoFk(rig, window='ALL'):
|
|
|
|
scn = bpy.context.scene
|
|
id_store = bpy.context.window_manager
|
|
|
|
rig_id = rig.data['rig_id']
|
|
leg_fk2ik = eval('bpy.ops.pose.rigify_leg_fk2ik_' + rig_id)
|
|
arm_fk2ik = eval('bpy.ops.pose.rigify_arm_fk2ik_' + rig_id)
|
|
limb_generated_names = get_limb_generated_names(rig)
|
|
|
|
if window == 'ALL':
|
|
frames = get_keyed_frames_in_range(bpy.context, rig)
|
|
elif window == 'CURRENT':
|
|
frames = [scn.frame_current]
|
|
else:
|
|
frames = [scn.frame_current]
|
|
|
|
if not id_store.rigify_transfer_only_selected:
|
|
bpy.ops.pose.select_all(action='DESELECT')
|
|
pbones = rig.pose.bones
|
|
else:
|
|
pbones = bpy.context.selected_pose_bones
|
|
bpy.ops.pose.select_all(action='DESELECT')
|
|
|
|
for b in pbones:
|
|
for group in limb_generated_names:
|
|
if b.name in limb_generated_names[group].values() or b.name in limb_generated_names[group]['controls']\
|
|
or b.name in limb_generated_names[group]['ik_ctrl']:
|
|
names = limb_generated_names[group]
|
|
if names['limb_type'] == 'arm':
|
|
func = arm_fk2ik
|
|
controls = names['controls']
|
|
ik_ctrl = names['ik_ctrl']
|
|
fk_ctrl = names['fk_ctrl']
|
|
parent = names['parent']
|
|
pole = names['pole']
|
|
rig.pose.bones[controls[1]].bone.select = True
|
|
rig.pose.bones[controls[2]].bone.select = True
|
|
rig.pose.bones[controls[3]].bone.select = True
|
|
kwargs = {'uarm_fk': controls[1], 'farm_fk': controls[2], 'hand_fk': controls[3],
|
|
'uarm_ik': controls[0], 'farm_ik': ik_ctrl[1],
|
|
'hand_ik': controls[4]}
|
|
args = (controls[0], controls[1], controls[2], controls[3],
|
|
controls[4], pole, parent)
|
|
else:
|
|
func = leg_fk2ik
|
|
controls = names['controls']
|
|
ik_ctrl = names['ik_ctrl']
|
|
fk_ctrl = names['fk_ctrl']
|
|
parent = names['parent']
|
|
pole = names['pole']
|
|
rig.pose.bones[controls[1]].bone.select = True
|
|
rig.pose.bones[controls[2]].bone.select = True
|
|
rig.pose.bones[controls[3]].bone.select = True
|
|
kwargs = {'thigh_fk': controls[1], 'shin_fk': controls[2], 'foot_fk': controls[3],
|
|
'mfoot_fk': controls[7], 'thigh_ik': controls[0], 'shin_ik': ik_ctrl[1],
|
|
'foot_ik': ik_ctrl[2], 'mfoot_ik': ik_ctrl[2]}
|
|
args = (controls[0], controls[1], controls[2], controls[3],
|
|
controls[6], controls[5], pole, parent)
|
|
|
|
for f in frames:
|
|
if not bones_in_frame(f, rig, *args):
|
|
continue
|
|
scn.frame_set(f)
|
|
func(**kwargs)
|
|
bpy.ops.anim.keyframe_insert_menu(type='BUILTIN_KSI_VisualLocRot')
|
|
bpy.ops.anim.keyframe_insert_menu(type='Scaling')
|
|
|
|
bpy.ops.pose.select_all(action='DESELECT')
|
|
limb_generated_names.pop(group)
|
|
break
|
|
|
|
|
|
def clearAnimation(act, anim_type, names):
|
|
|
|
bones = []
|
|
for group in names:
|
|
if names[group]['limb_type'] == 'arm':
|
|
if anim_type == 'IK':
|
|
bones.extend([names[group]['controls'][0], names[group]['controls'][4]])
|
|
elif anim_type == 'FK':
|
|
bones.extend([names[group]['controls'][1], names[group]['controls'][2], names[group]['controls'][3]])
|
|
else:
|
|
if anim_type == 'IK':
|
|
bones.extend([names[group]['controls'][0], names[group]['controls'][6], names[group]['controls'][5],
|
|
names[group]['controls'][4]])
|
|
elif anim_type == 'FK':
|
|
bones.extend([names[group]['controls'][1], names[group]['controls'][2], names[group]['controls'][3],
|
|
names[group]['controls'][4]])
|
|
FCurves = []
|
|
for fcu in act.fcurves:
|
|
words = fcu.data_path.split('"')
|
|
if (words[0] == "pose.bones[" and
|
|
words[1] in bones):
|
|
FCurves.append(fcu)
|
|
|
|
if FCurves == []:
|
|
return
|
|
|
|
for fcu in FCurves:
|
|
act.fcurves.remove(fcu)
|
|
|
|
# Put cleared bones back to rest pose
|
|
bpy.ops.pose.loc_clear()
|
|
bpy.ops.pose.rot_clear()
|
|
bpy.ops.pose.scale_clear()
|
|
|
|
# updateView3D()
|
|
|
|
|
|
def rotPoleToggle(rig, window='ALL', value=False, toggle=False, bake=False):
|
|
|
|
scn = bpy.context.scene
|
|
id_store = bpy.context.window_manager
|
|
|
|
rig_id = rig.data['rig_id']
|
|
leg_fk2ik = eval('bpy.ops.pose.rigify_leg_fk2ik_' + rig_id)
|
|
arm_fk2ik = eval('bpy.ops.pose.rigify_arm_fk2ik_' + rig_id)
|
|
leg_ik2fk = eval('bpy.ops.pose.rigify_leg_ik2fk_' + rig_id)
|
|
arm_ik2fk = eval('bpy.ops.pose.rigify_arm_ik2fk_' + rig_id)
|
|
limb_generated_names = get_limb_generated_names(rig)
|
|
|
|
if window == 'ALL':
|
|
frames = get_keyed_frames_in_range(bpy.context, rig)
|
|
elif window == 'CURRENT':
|
|
frames = [scn.frame_current]
|
|
else:
|
|
frames = [scn.frame_current]
|
|
|
|
if not id_store.rigify_transfer_only_selected:
|
|
bpy.ops.pose.select_all(action='DESELECT')
|
|
pbones = rig.pose.bones
|
|
else:
|
|
pbones = bpy.context.selected_pose_bones
|
|
bpy.ops.pose.select_all(action='DESELECT')
|
|
|
|
for b in pbones:
|
|
for group in limb_generated_names:
|
|
names = limb_generated_names[group]
|
|
|
|
if toggle:
|
|
new_pole_vector_value = not rig.pose.bones[names['parent']]['pole_vector']
|
|
else:
|
|
new_pole_vector_value = value
|
|
|
|
if b.name in names.values() or b.name in names['controls'] or b.name in names['ik_ctrl']:
|
|
if names['limb_type'] == 'arm':
|
|
func1 = arm_fk2ik
|
|
func2 = arm_ik2fk
|
|
controls = names['controls']
|
|
ik_ctrl = names['ik_ctrl']
|
|
fk_ctrl = names['fk_ctrl']
|
|
parent = names['parent']
|
|
pole = names['pole']
|
|
rig.pose.bones[controls[0]].bone.select = not new_pole_vector_value
|
|
rig.pose.bones[controls[4]].bone.select = not new_pole_vector_value
|
|
rig.pose.bones[parent].bone.select = not new_pole_vector_value
|
|
rig.pose.bones[pole].bone.select = new_pole_vector_value
|
|
|
|
kwargs1 = {'uarm_fk': controls[1], 'farm_fk': controls[2], 'hand_fk': controls[3],
|
|
'uarm_ik': controls[0], 'farm_ik': ik_ctrl[1],
|
|
'hand_ik': controls[4]}
|
|
kwargs2 = {'uarm_fk': controls[1], 'farm_fk': controls[2], 'hand_fk': controls[3],
|
|
'uarm_ik': controls[0], 'farm_ik': ik_ctrl[1], 'hand_ik': controls[4],
|
|
'pole': pole, 'main_parent': parent}
|
|
args = (controls[0], controls[4], pole, parent)
|
|
else:
|
|
func1 = leg_fk2ik
|
|
func2 = leg_ik2fk
|
|
controls = names['controls']
|
|
ik_ctrl = names['ik_ctrl']
|
|
fk_ctrl = names['fk_ctrl']
|
|
parent = names['parent']
|
|
pole = names['pole']
|
|
rig.pose.bones[controls[0]].bone.select = not new_pole_vector_value
|
|
rig.pose.bones[controls[6]].bone.select = not new_pole_vector_value
|
|
rig.pose.bones[controls[5]].bone.select = not new_pole_vector_value
|
|
rig.pose.bones[parent].bone.select = not new_pole_vector_value
|
|
rig.pose.bones[pole].bone.select = new_pole_vector_value
|
|
|
|
kwargs1 = {'thigh_fk': controls[1], 'shin_fk': controls[2], 'foot_fk': controls[3],
|
|
'mfoot_fk': controls[7], 'thigh_ik': controls[0], 'shin_ik': ik_ctrl[1],
|
|
'foot_ik': ik_ctrl[2], 'mfoot_ik': ik_ctrl[2]}
|
|
kwargs2 = {'thigh_fk': controls[1], 'shin_fk': controls[2], 'foot_fk': controls[3],
|
|
'mfoot_fk': controls[7], 'thigh_ik': controls[0], 'shin_ik': ik_ctrl[1],
|
|
'foot_ik': controls[6], 'pole': pole, 'footroll': controls[5], 'mfoot_ik': ik_ctrl[2],
|
|
'main_parent': parent}
|
|
args = (controls[0], controls[6], controls[5], pole, parent)
|
|
|
|
for f in frames:
|
|
if bake and not bones_in_frame(f, rig, *args):
|
|
continue
|
|
scn.frame_set(f)
|
|
func1(**kwargs1)
|
|
rig.pose.bones[names['parent']]['pole_vector'] = new_pole_vector_value
|
|
func2(**kwargs2)
|
|
if bake:
|
|
bpy.ops.anim.keyframe_insert_menu(type='BUILTIN_KSI_VisualLocRot')
|
|
bpy.ops.anim.keyframe_insert_menu(type='Scaling')
|
|
overwrite_prop_animation(rig, rig.pose.bones[parent], 'pole_vector', new_pole_vector_value, [f])
|
|
|
|
bpy.ops.pose.select_all(action='DESELECT')
|
|
limb_generated_names.pop(group)
|
|
break
|
|
scn.frame_set(0)
|
|
|
|
|
|
class OBJECT_OT_IK2FK(bpy.types.Operator):
|
|
""" Snaps IK limb on FK limb at current frame"""
|
|
bl_idname = "rigify.ik2fk"
|
|
bl_label = "IK2FK"
|
|
bl_description = "Snaps IK limb on FK"
|
|
bl_options = {'INTERNAL'}
|
|
|
|
def execute(self,context):
|
|
rig = context.object
|
|
id_store = context.window_manager
|
|
|
|
FktoIk(rig, window='CURRENT')
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class OBJECT_OT_FK2IK(bpy.types.Operator):
|
|
""" Snaps FK limb on IK limb at current frame"""
|
|
bl_idname = "rigify.fk2ik"
|
|
bl_label = "FK2IK"
|
|
bl_description = "Snaps FK limb on IK"
|
|
bl_options = {'INTERNAL'}
|
|
|
|
def execute(self,context):
|
|
rig = context.object
|
|
|
|
IktoFk(rig, window='CURRENT')
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class OBJECT_OT_TransferFKtoIK(bpy.types.Operator):
|
|
"""Transfers FK animation to IK"""
|
|
bl_idname = "rigify.transfer_fk_to_ik"
|
|
bl_label = "Transfer FK anim to IK"
|
|
bl_description = "Transfer FK animation to IK bones"
|
|
bl_options = {'INTERNAL'}
|
|
|
|
def execute(self, context):
|
|
rig = context.object
|
|
id_store = context.window_manager
|
|
|
|
FktoIk(rig)
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class OBJECT_OT_TransferIKtoFK(bpy.types.Operator):
|
|
"""Transfers FK animation to IK"""
|
|
bl_idname = "rigify.transfer_ik_to_fk"
|
|
bl_label = "Transfer IK anim to FK"
|
|
bl_description = "Transfer IK animation to FK bones"
|
|
bl_options = {'INTERNAL'}
|
|
|
|
def execute(self, context):
|
|
rig = context.object
|
|
|
|
IktoFk(rig)
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class OBJECT_OT_ClearAnimation(bpy.types.Operator):
|
|
bl_idname = "rigify.clear_animation"
|
|
bl_label = "Clear Animation"
|
|
bl_description = "Clear Animation For FK or IK Bones"
|
|
bl_options = {'INTERNAL'}
|
|
|
|
anim_type: StringProperty()
|
|
|
|
def execute(self, context):
|
|
rig = context.object
|
|
scn = context.scene
|
|
if not rig.animation_data:
|
|
return {'FINISHED'}
|
|
act = rig.animation_data.action
|
|
if not act:
|
|
return {'FINISHED'}
|
|
|
|
clearAnimation(act, self.anim_type, names=get_limb_generated_names(rig))
|
|
return {'FINISHED'}
|
|
|
|
|
|
class OBJECT_OT_Rot2Pole(bpy.types.Operator):
|
|
bl_idname = "rigify.rotation_pole"
|
|
bl_label = "Rotation - Pole toggle"
|
|
bl_description = "Toggles IK chain between rotation and pole target"
|
|
bl_options = {'INTERNAL'}
|
|
|
|
bone_name: StringProperty(default='')
|
|
window: StringProperty(default='ALL')
|
|
toggle: BoolProperty(default=True)
|
|
value: BoolProperty(default=True)
|
|
bake: BoolProperty(default=True)
|
|
|
|
def execute(self, context):
|
|
rig = context.object
|
|
|
|
if self.bone_name:
|
|
bpy.ops.pose.select_all(action='DESELECT')
|
|
rig.pose.bones[self.bone_name].bone.select = True
|
|
|
|
rotPoleToggle(rig, window=self.window, toggle=self.toggle, value=self.value, bake=self.bake)
|
|
return {'FINISHED'}
|
|
|
|
|
|
### Registering ###
|
|
|
|
|
|
classes = (
|
|
DATA_OT_rigify_add_bone_groups,
|
|
DATA_OT_rigify_use_standard_colors,
|
|
DATA_OT_rigify_apply_selection_colors,
|
|
DATA_OT_rigify_bone_group_add,
|
|
DATA_OT_rigify_bone_group_add_theme,
|
|
DATA_OT_rigify_bone_group_remove,
|
|
DATA_OT_rigify_bone_group_remove_all,
|
|
DATA_UL_rigify_bone_groups,
|
|
DATA_MT_rigify_bone_groups_context_menu,
|
|
DATA_PT_rigify_bone_groups,
|
|
DATA_PT_rigify_layer_names,
|
|
DATA_PT_rigify_generate,
|
|
DATA_PT_rigify_generate_advanced,
|
|
DATA_PT_rigify_samples,
|
|
BONE_PT_rigify_buttons,
|
|
VIEW3D_PT_rigify_animation_tools,
|
|
VIEW3D_PT_tools_rigify_dev,
|
|
LayerInit,
|
|
Generate,
|
|
UpgradeMetarigTypes,
|
|
Sample,
|
|
VIEW3D_MT_rigify,
|
|
EncodeMetarig,
|
|
EncodeMetarigSample,
|
|
EncodeWidget,
|
|
OBJECT_OT_FK2IK,
|
|
OBJECT_OT_IK2FK,
|
|
OBJECT_OT_TransferFKtoIK,
|
|
OBJECT_OT_TransferIKtoFK,
|
|
OBJECT_OT_ClearAnimation,
|
|
OBJECT_OT_Rot2Pole,
|
|
)
|
|
|
|
|
|
def register():
|
|
from bpy.utils import register_class
|
|
|
|
animation_register()
|
|
|
|
# Classes.
|
|
for cls in classes:
|
|
register_class(cls)
|
|
|
|
bpy.types.VIEW3D_MT_editor_menus.append(draw_rigify_menu)
|
|
bpy.types.VIEW3D_MT_edit_mesh.prepend(draw_mesh_edit_menu)
|
|
|
|
# Sub-modules.
|
|
rot_mode.register()
|
|
|
|
|
|
def unregister():
|
|
from bpy.utils import unregister_class
|
|
|
|
# Sub-modules.
|
|
rot_mode.unregister()
|
|
|
|
# Classes.
|
|
for cls in classes:
|
|
unregister_class(cls)
|
|
|
|
bpy.types.VIEW3D_MT_editor_menus.remove(draw_rigify_menu)
|
|
bpy.types.VIEW3D_MT_edit_mesh.remove(draw_mesh_edit_menu)
|
|
|
|
animation_unregister()
|