Merge branch 'blender-v4.0-release'
[blender-addons.git] / rigify / ui.py
blob7293555745d0d12d6d41f7eb1e5f9e715bcc4026
1 # SPDX-FileCopyrightText: 2010-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 import bpy
6 from bpy.types import UIList, UILayout, Armature
7 from bpy.props import (
8 BoolProperty,
9 IntProperty,
10 EnumProperty,
11 StringProperty
14 from collections import defaultdict
15 from typing import TYPE_CHECKING, Callable, Any
16 from mathutils import Color
18 from .utils.errors import MetarigError
19 from .utils.layers import ROOT_COLLECTION, SPECIAL_COLLECTIONS, validate_collection_references
20 from .utils.rig import write_metarig, get_rigify_type, get_rigify_target_rig, \
21 get_rigify_colors, get_rigify_params
22 from .utils.widgets import write_widget
23 from .utils.naming import unique_name
24 from .utils.rig import upgrade_metarig_types, outdated_types, upgrade_metarig_layers, \
25 is_valid_metarig, metarig_needs_upgrade
26 from .utils.misc import verify_armature_obj, ArmatureObject, IdPropSequence
28 from .rigs.utils import get_limb_generated_names
30 from .utils.animation import get_keyed_frames_in_range, bones_in_frame, overwrite_prop_animation
31 from .utils.animation import RIGIFY_OT_get_frame_range
33 from .utils.animation import register as animation_register
34 from .utils.animation import unregister as animation_unregister
36 from . import base_rig
37 from . import rig_lists
38 from . import generate
39 from . import rot_mode
40 from . import feature_set_list
42 if TYPE_CHECKING:
43 from . import RigifyName, RigifySelectionColors
46 def get_rigify_types(id_store: bpy.types.WindowManager) -> IdPropSequence['RigifyName']:
47 return id_store.rigify_types # noqa
50 def get_transfer_only_selected(id_store: bpy.types.WindowManager) -> bool:
51 return id_store.rigify_transfer_only_selected # noqa
54 def get_selection_colors(armature: bpy.types.Armature) -> 'RigifySelectionColors':
55 return armature.rigify_selection_colors # noqa
58 def get_colors_lock(armature: bpy.types.Armature) -> bool:
59 return armature.rigify_colors_lock # noqa
62 def get_colors_index(armature: bpy.types.Armature) -> int:
63 return armature.rigify_colors_index # noqa
66 def get_theme_to_add(armature: bpy.types.Armature) -> str:
67 return armature.rigify_theme_to_add # noqa
70 def build_type_list(context, rigify_types: IdPropSequence['RigifyName']):
71 rigify_types.clear()
73 for r in sorted(rig_lists.rigs):
74 if (context.object.data.active_feature_set in ('all', rig_lists.rigs[r]['feature_set'])
75 or len(feature_set_list.get_enabled_modules_names()) == 0):
76 a = rigify_types.add()
77 a.name = r
80 # noinspection PyPep8Naming
81 class DATA_PT_rigify(bpy.types.Panel):
82 bl_label = "Rigify"
83 bl_space_type = 'PROPERTIES'
84 bl_region_type = 'WINDOW'
85 bl_context = "data"
87 @classmethod
88 def poll(cls, context):
89 return is_valid_metarig(context, allow_needs_upgrade=True)
91 def draw(self, context):
92 C = context
93 layout = self.layout
94 obj = verify_armature_obj(C.object)
96 if metarig_needs_upgrade(obj):
97 layout.label(text="This metarig requires upgrading to Bone Collections", icon='ERROR')
98 layout.operator("armature.rigify_upgrade_layers", text="Upgrade Metarig")
99 return
101 WARNING = "Warning: Some features may change after generation"
102 show_warning = False
103 show_update_metarig = False
104 show_not_updatable = False
105 show_upgrade_face = False
107 check_props = ['IK_follow', 'root/parent', 'FK_limb_follow', 'IK_Stretch']
109 for pose_bone in obj.pose.bones:
110 bone = pose_bone.bone
111 if not bone:
112 # If we are in edit mode and the bone was just created,
113 # a pose bone won't exist yet.
114 continue
115 if list(set(pose_bone.keys()) & set(check_props)): # bone.layers[30] and
116 show_warning = True
117 break
119 old_rig = ''
120 old_bone = ''
122 for b in obj.pose.bones:
123 old_rig = get_rigify_type(b)
124 if old_rig in outdated_types:
125 old_bone = b.name
126 if outdated_types[old_rig]:
127 show_update_metarig = True
128 else:
129 show_update_metarig = False
130 show_not_updatable = True
131 break
132 elif old_rig == 'faces.super_face':
133 show_upgrade_face = True
135 if show_warning:
136 layout.label(text=WARNING, icon='ERROR')
138 enable_generate = not (show_not_updatable or show_update_metarig)
140 if show_not_updatable:
141 layout.label(text="WARNING: This metarig contains deprecated rigify rig-types and "
142 "cannot be upgraded automatically.", icon='ERROR')
143 layout.label(text="(" + old_rig + " on bone " + old_bone + ")")
144 elif show_update_metarig:
145 layout.label(text="This metarig contains old rig-types that can be automatically "
146 "upgraded to benefit from new rigify features.", icon='ERROR')
147 layout.label(text="(" + old_rig + " on bone " + old_bone + ")")
148 layout.operator("pose.rigify_upgrade_types", text="Upgrade Metarig")
149 elif show_upgrade_face:
150 layout.label(text="This metarig uses the old face rig.", icon='INFO')
151 layout.operator("pose.rigify_upgrade_face")
153 # Rig type field
155 col = layout.column(align=True)
156 col.active = ('rig_id' not in C.object.data)
158 col.separator()
159 row = col.row()
160 text = "Re-Generate Rig" if get_rigify_target_rig(obj.data) else "Generate Rig"
161 row.operator("pose.rigify_generate", text=text, icon='POSE_HLT')
162 row.enabled = enable_generate
165 # noinspection PyPep8Naming
166 class DATA_PT_rigify_advanced(bpy.types.Panel):
167 bl_space_type = 'PROPERTIES'
168 bl_region_type = 'WINDOW'
169 bl_context = "data"
170 bl_label = "Advanced"
171 bl_parent_id = 'DATA_PT_rigify'
172 bl_options = {'DEFAULT_CLOSED'}
174 @classmethod
175 def poll(cls, context):
176 return is_valid_metarig(context)
178 def draw(self, context):
179 layout = self.layout
180 layout.use_property_split = True
181 layout.use_property_decorate = False
183 armature_id_store = verify_armature_obj(context.object).data
185 col = layout.column()
187 row = col.row()
188 row.active = not get_rigify_target_rig(armature_id_store)
189 row.prop(armature_id_store, "rigify_rig_basename", text="Rig Name")
191 col.separator()
193 col2 = col.box().column()
194 col2.label(text="Overwrite Existing:")
195 col2.row().prop(armature_id_store, "rigify_target_rig", text="Target Rig")
196 col2.row().prop(armature_id_store, "rigify_rig_ui", text="Rig UI Script")
197 col2.row().prop(armature_id_store, "rigify_widgets_collection")
199 col.separator()
200 col.row().prop(armature_id_store, "rigify_force_widget_update")
201 col.row().prop(armature_id_store, "rigify_mirror_widgets")
202 col.separator()
203 col.row().prop(armature_id_store, "rigify_finalize_script", text="Run Script")
206 # noinspection PyPep8Naming
207 class DATA_PT_rigify_samples(bpy.types.Panel):
208 bl_label = "Samples"
209 bl_space_type = 'PROPERTIES'
210 bl_region_type = 'WINDOW'
211 bl_context = "data"
212 bl_parent_id = "DATA_PT_rigify"
213 bl_options = {'DEFAULT_CLOSED'}
215 @classmethod
216 def poll(cls, context):
217 return is_valid_metarig(context) and context.object.mode == 'EDIT'
219 def draw(self, context):
220 layout = self.layout
221 layout.use_property_split = True
222 layout.use_property_decorate = False
223 id_store = context.window_manager
225 # Build types list
226 rigify_types = get_rigify_types(id_store)
227 build_type_list(context, rigify_types)
229 if id_store.rigify_active_type > len(rigify_types):
230 id_store.rigify_active_type = 0
232 # Rig type list
233 if len(feature_set_list.get_enabled_modules_names()) > 0:
234 row = layout.row()
235 row.prop(context.object.data, "active_feature_set")
236 row = layout.row()
237 row.template_list("UI_UL_list", "rigify_types", id_store, "rigify_types", id_store, 'rigify_active_type')
239 props = layout.operator("armature.metarig_sample_add", text="Add sample")
240 props.metarig_type = rigify_types[id_store.rigify_active_type].name
243 # noinspection SpellCheckingInspection
244 # noinspection PyPep8Naming
245 class DATA_UL_rigify_bone_collections(UIList):
246 def draw_item(self, _context, layout, armature, bcoll, _icon, _active_data,
247 _active_prop_name, _index=0, _flt_flag=0):
248 active_bone = armature.edit_bones.active or armature.bones.active
249 has_active_bone = active_bone and bcoll.name in active_bone.collections
251 split = layout.split(factor=0.7)
253 split.prop(bcoll, "name", text="", emboss=False,
254 icon='DOT' if has_active_bone else 'BLANK1')
256 if cset := bcoll.rigify_color_set_name:
257 split.label(text=cset, icon="COLOR", translate=False)
259 icons = layout.row(align=True)
261 icons.prop(bcoll, "rigify_sel_set", text="", toggle=True, emboss=False,
262 icon='RADIOBUT_ON' if bcoll.rigify_sel_set else 'RADIOBUT_OFF')
263 icons.label(text="", icon='RESTRICT_SELECT_OFF' if bcoll.rigify_ui_row > 0 else 'RESTRICT_SELECT_ON')
264 icons.prop(bcoll, "is_visible", text="", emboss=False,
265 icon='HIDE_OFF' if bcoll.is_visible else 'HIDE_ON')
268 # noinspection PyPep8Naming
269 class DATA_PT_rigify_collection_list(bpy.types.Panel):
270 bl_label = "Bone Collection UI"
271 bl_space_type = 'PROPERTIES'
272 bl_region_type = 'WINDOW'
273 bl_context = "data"
274 bl_options = {'DEFAULT_CLOSED'}
275 bl_parent_id = "DATA_PT_rigify"
277 @classmethod
278 def poll(cls, context):
279 return is_valid_metarig(context)
281 def draw(self, context):
282 layout = self.layout
283 obj = verify_armature_obj(context.object)
284 arm = obj.data
286 # Copy the bone collection list
287 active_coll = arm.collections.active
289 row = layout.row()
291 row.template_list(
292 "DATA_UL_rigify_bone_collections",
293 "collections",
294 arm,
295 "collections",
296 arm.collections,
297 "active_index",
298 rows=(4 if active_coll else 1),
301 col = row.column(align=True)
302 col.operator("armature.collection_add", icon='ADD', text="")
303 col.operator("armature.collection_remove", icon='REMOVE', text="")
304 if active_coll:
305 col.separator()
306 col.operator("armature.collection_move", icon='TRIA_UP', text="").direction = 'UP'
307 col.operator("armature.collection_move", icon='TRIA_DOWN', text="").direction = 'DOWN'
309 layout.operator(operator='armature.rigify_validate_layers')
311 if active_coll:
312 col = layout.column()
313 col.use_property_split = True
314 col.use_property_decorate = False
316 col.prop(active_coll, "rigify_color_set_name", icon="COLOR")
317 col.prop(active_coll, "rigify_sel_set")
318 col.separator()
320 col.prop(active_coll, "rigify_ui_row", )
321 row = col.row()
322 row.active = active_coll.rigify_ui_row > 0 # noqa
323 row.prop(active_coll, "rigify_ui_title")
325 if ROOT_COLLECTION not in arm.collections:
326 layout.label(text=f"The '{ROOT_COLLECTION}' collection will be added upon generation", icon='INFO')
329 # noinspection PyPep8Naming
330 class DATA_PT_rigify_collection_ui(bpy.types.Panel):
331 bl_label = "UI Layout"
332 bl_space_type = 'PROPERTIES'
333 bl_region_type = 'WINDOW'
334 bl_context = "data"
335 bl_options = set()
336 bl_parent_id = "DATA_PT_rigify_collection_list"
338 @classmethod
339 def poll(cls, context):
340 return is_valid_metarig(context) and len(verify_armature_obj(context.object).data.collections)
342 @staticmethod
343 def draw_btn_block(arm: Armature, parent: UILayout, bcoll_id: int, loose=False):
344 bcoll = arm.collections[bcoll_id]
345 block = parent.row(align=True)
347 if bcoll == arm.collections.active:
348 block.prop(bcoll, "rigify_ui_title_name", text="", emboss=True)
350 if not loose:
351 props = block.operator(text="", icon="X", operator="armature.rigify_collection_set_ui_row")
352 props.index = bcoll_id
353 props.row = 0
354 else:
355 props = block.operator(text=bcoll.rigify_ui_title_name, operator="armature.rigify_collection_select")
356 props.index = bcoll_id
358 def draw(self, context):
359 layout = self.layout
360 obj = verify_armature_obj(context.object)
361 arm = obj.data
363 # Sort into button rows
364 row_table = defaultdict(list)
365 has_buttons = False
367 for i, bcoll in enumerate(arm.collections):
368 row_table[bcoll.rigify_ui_row].append(i)
370 if bcoll.rigify_ui_row > 0:
371 has_buttons = True
373 active_bcoll_idx = arm.collections.active_index
375 if active_bcoll_idx < 0:
376 layout.label(text="Click a button to select a collection:", icon="INFO")
378 box = layout.box()
379 last_row = max(row_table.keys())
381 for row_id in range(1, last_row + 2):
382 row = box.row()
383 row_items = row_table[row_id]
385 if row_id == 1 and not has_buttons:
386 row.label(text="Click to assign the button here:", icon="INFO")
388 grid = row.grid_flow(row_major=True, columns=len(row_items), even_columns=True)
389 for bcoll_id in row_items:
390 self.draw_btn_block(arm, grid, bcoll_id)
392 btn_row = row.row(align=True)
394 if active_bcoll_idx >= 0:
395 props = btn_row.operator(text="", icon="TRIA_LEFT", operator="armature.rigify_collection_set_ui_row")
396 props.index = active_bcoll_idx
397 props.row = row_id
399 if row_id < last_row + 1:
400 props = btn_row.operator(text="", icon="ADD", operator="armature.rigify_collection_add_ui_row")
401 props.row = row_id
402 props.add = True
403 else:
404 btn_row.label(text="", icon="BLANK1")
406 if row_id < last_row:
407 props = btn_row.operator(text="", icon="REMOVE", operator="armature.rigify_collection_add_ui_row")
408 props.row = row_id + 1
409 props.add = False
410 else:
411 btn_row.label(text="", icon="BLANK1")
413 if 0 in row_table:
414 box = layout.box()
415 box.label(text="Permanently hidden collections:")
417 grid = box.grid_flow(row_major=True, columns=2, even_columns=True)
419 for i, bcoll_id in enumerate(row_table[0]):
420 self.draw_btn_block(arm, grid, bcoll_id, loose=True)
423 # noinspection PyPep8Naming
424 class DATA_OT_rigify_collection_select(bpy.types.Operator):
425 bl_idname = "armature.rigify_collection_select"
426 bl_label = "Make Collection Active"
427 bl_description = "Make this collection active"
428 bl_options = {'UNDO_GROUPED'}
430 index: IntProperty(name="Index")
432 @staticmethod
433 def button(layout, *, index, **kwargs):
434 props = layout.operator(**kwargs)
435 props.index = index
437 @classmethod
438 def poll(cls, context):
439 return context.object and context.object.type == 'ARMATURE'
441 def execute(self, context):
442 obj = verify_armature_obj(context.object)
443 obj.data.collections.active_index = self.index
444 return {'FINISHED'}
447 # noinspection PyPep8Naming
448 class DATA_OT_rigify_collection_set_ui_row(bpy.types.Operator):
449 bl_idname = "armature.rigify_collection_set_ui_row"
450 bl_label = "Move Between UI Rows"
451 bl_options = {'UNDO'}
453 index: IntProperty(name="Index")
454 row: IntProperty(name="Row")
455 select: BoolProperty(name="Select")
457 @classmethod
458 def poll(cls, context):
459 return context.object and context.object.type == 'ARMATURE'
461 @classmethod
462 def description(cls, context, properties: Any):
463 if properties.row == 0:
464 return "Remove this button from the UI panel"
465 else:
466 return "Move the active button to this UI panel row"
468 def execute(self, context):
469 obj = verify_armature_obj(context.object)
470 if self.select:
471 obj.data.collections.active_index = self.index
472 obj.data.collections[self.index].rigify_ui_row = self.row
473 return {'FINISHED'}
476 # noinspection PyPep8Naming
477 class DATA_OT_rigify_collection_add_ui_row(bpy.types.Operator):
478 bl_idname = "armature.rigify_collection_add_ui_row"
479 bl_label = "Add/Remove UI Rows"
480 bl_options = {'UNDO'}
482 row: IntProperty(name="Row")
483 add: BoolProperty(name="Add")
485 @classmethod
486 def poll(cls, context):
487 return context.object and context.object.type == 'ARMATURE'
489 @classmethod
490 def description(cls, context, properties: Any):
491 if properties.add:
492 return "Insert a new row before this one, shifting buttons down"
493 else:
494 return "Remove this row, shifting buttons up"
496 def execute(self, context):
497 obj = verify_armature_obj(context.object)
498 for coll in obj.data.collections:
499 if coll.rigify_ui_row >= self.row:
500 coll.rigify_ui_row += (1 if self.add else -1)
501 return {'FINISHED'}
504 # noinspection PyPep8Naming
505 class DATA_OT_rigify_add_color_sets(bpy.types.Operator):
506 bl_idname = "armature.rigify_add_color_sets"
507 bl_label = "Rigify Add Standard Color Sets"
508 bl_options = {'UNDO'}
510 @classmethod
511 def poll(cls, context):
512 return context.object and context.object.type == 'ARMATURE'
514 def execute(self, context):
515 obj = verify_armature_obj(context.object)
516 armature = obj.data
518 if not hasattr(armature, 'rigify_colors'):
519 return {'FINISHED'}
521 rigify_colors = get_rigify_colors(armature)
522 groups = ['Root', 'IK', 'Special', 'Tweak', 'FK', 'Extra']
524 for g in groups:
525 if g in rigify_colors:
526 continue
528 color = rigify_colors.add()
529 color.name = g
531 color.select = Color((0.3140000104904175, 0.7839999794960022, 1.0))
532 color.active = Color((0.5490000247955322, 1.0, 1.0))
533 color.standard_colors_lock = True
535 if g == "Root":
536 color.normal = Color((0.43529415130615234, 0.18431372940540314, 0.41568630933761597))
537 if g == "IK":
538 color.normal = Color((0.6039215922355652, 0.0, 0.0))
539 if g == "Special":
540 color.normal = Color((0.9568628072738647, 0.7882353663444519, 0.0470588281750679))
541 if g == "Tweak":
542 color.normal = Color((0.03921568766236305, 0.21176472306251526, 0.5803921818733215))
543 if g == "FK":
544 color.normal = Color((0.11764706671237946, 0.5686274766921997, 0.03529411926865578))
545 if g == "Extra":
546 color.normal = Color((0.9686275124549866, 0.250980406999588, 0.0941176563501358))
548 return {'FINISHED'}
551 # noinspection PyPep8Naming
552 class DATA_OT_rigify_use_standard_colors(bpy.types.Operator):
553 bl_idname = "armature.rigify_use_standard_colors"
554 bl_label = "Rigify Get active/select colors from current theme"
555 bl_options = {'UNDO'}
557 @classmethod
558 def poll(cls, context):
559 return context.object and context.object.type == 'ARMATURE'
561 def execute(self, context):
562 obj = verify_armature_obj(context.object)
563 armature = obj.data
564 if not hasattr(armature, 'rigify_colors'):
565 return {'FINISHED'}
567 current_theme = bpy.context.preferences.themes.items()[0][0]
568 theme = bpy.context.preferences.themes[current_theme]
570 selection_colors = get_selection_colors(armature)
571 selection_colors.select = theme.view_3d.bone_pose
572 selection_colors.active = theme.view_3d.bone_pose_active
574 # for col in armature.rigify_colors:
575 # col.select = theme.view_3d.bone_pose
576 # col.active = theme.view_3d.bone_pose_active
578 return {'FINISHED'}
581 # noinspection PyPep8Naming
582 class DATA_OT_rigify_apply_selection_colors(bpy.types.Operator):
583 bl_idname = "armature.rigify_apply_selection_colors"
584 bl_label = "Rigify Apply user defined active/select colors"
585 bl_options = {'UNDO'}
587 @classmethod
588 def poll(cls, context):
589 return context.object and context.object.type == 'ARMATURE'
591 def execute(self, context):
592 obj = verify_armature_obj(context.object)
593 armature = obj.data
595 if not hasattr(armature, 'rigify_colors'):
596 return {'FINISHED'}
598 # current_theme = bpy.context.preferences.themes.items()[0][0]
599 # theme = bpy.context.preferences.themes[current_theme]
601 rigify_colors = get_rigify_colors(armature)
602 selection_colors = get_selection_colors(armature)
604 for col in rigify_colors:
605 col.select = selection_colors.select
606 col.active = selection_colors.active
608 return {'FINISHED'}
611 # noinspection PyPep8Naming
612 class DATA_OT_rigify_color_set_add(bpy.types.Operator):
613 bl_idname = "armature.rigify_color_set_add"
614 bl_label = "Rigify Add Color Set"
615 bl_options = {'UNDO'}
617 @classmethod
618 def poll(cls, context):
619 return context.object and context.object.type == 'ARMATURE'
621 def execute(self, context):
622 obj = context.object
623 armature = obj.data
625 if hasattr(armature, 'rigify_colors'):
626 armature.rigify_colors.add()
627 armature.rigify_colors[-1].name = unique_name(armature.rigify_colors, 'Group')
629 current_theme = bpy.context.preferences.themes.items()[0][0]
630 theme = bpy.context.preferences.themes[current_theme]
632 armature.rigify_colors[-1].normal = theme.view_3d.wire
633 armature.rigify_colors[-1].normal.hsv = theme.view_3d.wire.hsv
634 armature.rigify_colors[-1].select = theme.view_3d.bone_pose
635 armature.rigify_colors[-1].select.hsv = theme.view_3d.bone_pose.hsv
636 armature.rigify_colors[-1].active = theme.view_3d.bone_pose_active
637 armature.rigify_colors[-1].active.hsv = theme.view_3d.bone_pose_active.hsv
639 return {'FINISHED'}
642 # noinspection PyPep8Naming
643 class DATA_OT_rigify_color_set_add_theme(bpy.types.Operator):
644 bl_idname = "armature.rigify_color_set_add_theme"
645 bl_label = "Rigify Add Color Set from Theme"
646 bl_options = {"REGISTER", "UNDO"}
648 theme: EnumProperty(items=(
649 ('THEME01', 'THEME01', ''),
650 ('THEME02', 'THEME02', ''),
651 ('THEME03', 'THEME03', ''),
652 ('THEME04', 'THEME04', ''),
653 ('THEME05', 'THEME05', ''),
654 ('THEME06', 'THEME06', ''),
655 ('THEME07', 'THEME07', ''),
656 ('THEME08', 'THEME08', ''),
657 ('THEME09', 'THEME09', ''),
658 ('THEME10', 'THEME10', ''),
659 ('THEME11', 'THEME11', ''),
660 ('THEME12', 'THEME12', ''),
661 ('THEME13', 'THEME13', ''),
662 ('THEME14', 'THEME14', ''),
663 ('THEME15', 'THEME15', ''),
664 ('THEME16', 'THEME16', ''),
665 ('THEME17', 'THEME17', ''),
666 ('THEME18', 'THEME18', ''),
667 ('THEME19', 'THEME19', ''),
668 ('THEME20', 'THEME20', '')
670 name='Theme')
672 @classmethod
673 def poll(cls, context):
674 return context.object and context.object.type == 'ARMATURE'
676 def execute(self, context):
677 obj = verify_armature_obj(context.object)
678 armature = obj.data
680 if hasattr(armature, 'rigify_colors'):
681 rigify_colors = get_rigify_colors(armature)
683 if self.theme in rigify_colors.keys():
684 return {'FINISHED'}
686 rigify_colors.add()
687 rigify_colors[-1].name = self.theme
689 color_id = int(self.theme[-2:]) - 1
691 theme_color_set = bpy.context.preferences.themes[0].bone_color_sets[color_id]
693 rigify_colors[-1].normal = theme_color_set.normal
694 rigify_colors[-1].select = theme_color_set.select
695 rigify_colors[-1].active = theme_color_set.active
697 return {'FINISHED'}
700 # noinspection PyPep8Naming
701 class DATA_OT_rigify_color_set_remove(bpy.types.Operator):
702 bl_idname = "armature.rigify_color_set_remove"
703 bl_label = "Rigify Remove Color Set"
704 bl_options = {'UNDO'}
706 idx: IntProperty()
708 @classmethod
709 def poll(cls, context):
710 return context.object and context.object.type == 'ARMATURE'
712 def execute(self, context):
713 obj = verify_armature_obj(context.object)
715 rigify_colors = get_rigify_colors(obj.data)
716 rigify_colors.remove(self.idx)
718 # set layers references to 0
719 for coll in obj.data.collections:
720 idx = coll.rigify_color_set_id
722 if idx == self.idx + 1:
723 coll.rigify_color_set_id = 0
724 elif idx > self.idx + 1:
725 coll.rigify_color_set_id = idx - 1
727 return {'FINISHED'}
730 # noinspection PyPep8Naming
731 class DATA_OT_rigify_color_set_remove_all(bpy.types.Operator):
732 bl_idname = "armature.rigify_color_set_remove_all"
733 bl_label = "Rigify Remove All Color Sets"
734 bl_options = {'UNDO'}
736 @classmethod
737 def poll(cls, context):
738 return context.object and context.object.type == 'ARMATURE'
740 def execute(self, context):
741 obj = verify_armature_obj(context.object)
743 rigify_colors = get_rigify_colors(obj.data)
744 while len(rigify_colors) > 0:
745 rigify_colors.remove(0)
747 # set layers references to 0
748 for coll in obj.data.collections:
749 coll.rigify_color_set_id = 0
751 return {'FINISHED'}
754 # noinspection PyPep8Naming
755 class DATA_UL_rigify_color_sets(bpy.types.UIList):
756 def draw_item(self, context, layout, data, item, icon, active_data, active_prop_name, index=0, flt_flag=0):
757 row = layout.row(align=True)
758 row = row.split(factor=0.1)
759 row.label(text=str(index+1))
760 row = row.split(factor=0.7)
761 row.prop(item, "name", text='', emboss=False)
762 row = row.row(align=True)
763 # icon = 'LOCKED' if item.standard_colors_lock else 'UNLOCKED'
764 # row.prop(item, "standard_colors_lock", text='', icon=icon)
765 row.prop(item, "normal", text='')
766 row2 = row.row(align=True)
767 row2.prop(item, "select", text='')
768 row2.prop(item, "active", text='')
769 # row2.enabled = not item.standard_colors_lock
770 arm = verify_armature_obj(context.object).data
771 row2.enabled = not get_colors_lock(arm)
774 # noinspection PyPep8Naming
775 class DATA_MT_rigify_color_sets_context_menu(bpy.types.Menu):
776 bl_label = 'Rigify Color Sets Specials'
778 def draw(self, context):
779 layout = self.layout
781 layout.operator('armature.rigify_color_set_remove_all')
784 # noinspection SpellCheckingInspection
785 # noinspection PyPep8Naming
786 class DATA_PT_rigify_color_sets(bpy.types.Panel):
787 bl_label = "Color Sets"
788 bl_space_type = 'PROPERTIES'
789 bl_region_type = 'WINDOW'
790 bl_context = "data"
791 bl_options = {'DEFAULT_CLOSED'}
792 bl_parent_id = "DATA_PT_rigify"
794 @classmethod
795 def poll(cls, context):
796 return is_valid_metarig(context)
798 def draw(self, context):
799 obj = verify_armature_obj(context.object)
800 armature = obj.data
801 idx = get_colors_index(armature)
802 selection_colors = get_selection_colors(armature)
803 is_locked = get_colors_lock(armature)
804 theme = get_theme_to_add(armature)
806 layout = self.layout
807 row = layout.row()
808 row.operator("armature.rigify_use_standard_colors", icon='FILE_REFRESH', text='')
809 row = row.row(align=True)
810 row.prop(selection_colors, 'select', text='')
811 row.prop(selection_colors, 'active', text='')
812 row = layout.row(align=True)
813 icon = 'LOCKED' if is_locked else 'UNLOCKED'
814 row.prop(armature, 'rigify_colors_lock', text='Unified select/active colors', icon=icon)
815 row.operator("armature.rigify_apply_selection_colors", icon='FILE_REFRESH', text='Apply')
816 row = layout.row()
817 row.template_list("DATA_UL_rigify_color_sets", "", obj.data, "rigify_colors", obj.data, "rigify_colors_index")
819 col = row.column(align=True)
820 col.operator("armature.rigify_color_set_add", icon='ADD', text="")
821 col.operator("armature.rigify_color_set_remove", icon='REMOVE', text="").idx = idx
822 col.menu("DATA_MT_rigify_color_sets_context_menu", icon='DOWNARROW_HLT', text="")
823 row = layout.row()
824 row.prop(armature, 'rigify_theme_to_add', text='Theme')
825 op = row.operator("armature.rigify_color_set_add_theme", text="Add From Theme")
826 op.theme = theme
827 row = layout.row()
828 row.operator("armature.rigify_add_color_sets", text="Add Standard")
831 # noinspection PyPep8Naming
832 class BONE_PT_rigify_buttons(bpy.types.Panel):
833 bl_label = "Rigify Type"
834 bl_space_type = 'PROPERTIES'
835 bl_region_type = 'WINDOW'
836 bl_context = "bone"
837 # bl_options = {'DEFAULT_OPEN'}
839 @classmethod
840 def poll(cls, context):
841 return is_valid_metarig(context) and context.active_pose_bone
843 def draw(self, context):
844 C = context
845 id_store = C.window_manager
846 bone = context.active_pose_bone
847 rig_name = get_rigify_type(bone)
849 layout = self.layout
850 rig_types = get_rigify_types(id_store)
852 # Build types list
853 build_type_list(context, rig_types)
855 # Rig type field
856 if len(feature_set_list.get_enabled_modules_names()) > 0:
857 row = layout.row()
858 row.prop(context.object.data, "active_feature_set")
859 row = layout.row()
860 row.prop_search(bone, "rigify_type", id_store, "rigify_types", text="Rig type")
862 # Rig type parameters / Rig type non-exist alert
863 if rig_name != "":
864 try:
865 rig = rig_lists.rigs[rig_name]['module']
866 except (ImportError, AttributeError, KeyError):
867 row = layout.row()
868 box = row.box()
869 box.label(text="ERROR: type \"%s\" does not exist!" % rig_name, icon='ERROR')
870 else:
871 if hasattr(rig.Rig, 'parameters_ui'):
872 rig = rig.Rig
874 try:
875 param_cb = rig.parameters_ui
877 # Ignore the known empty base method
878 if getattr(param_cb, '__func__', None) == \
879 getattr(base_rig.BaseRig.parameters_ui, '__func__'):
880 param_cb = None
881 except AttributeError:
882 param_cb = None
884 if param_cb is None:
885 col = layout.column()
886 col.label(text="No options")
887 else:
888 col = layout.column()
889 col.label(text="Options:")
890 box = layout.box()
891 param_cb(box, get_rigify_params(bone))
894 # noinspection PyPep8Naming
895 class VIEW3D_PT_tools_rigify_dev(bpy.types.Panel):
896 bl_label = "Rigify Dev Tools"
897 bl_space_type = 'VIEW_3D'
898 bl_region_type = 'UI'
899 bl_category = "Rigify"
901 @classmethod
902 def poll(cls, context):
903 return context.mode in ['EDIT_ARMATURE', 'EDIT_MESH']
905 def draw(self, context):
906 obj = context.active_object
907 if obj is not None:
908 if context.mode == 'EDIT_ARMATURE':
909 r = self.layout.row()
910 r.operator("armature.rigify_encode_metarig", text="Encode Metarig to Python")
911 r = self.layout.row()
912 r.operator("armature.rigify_encode_metarig_sample", text="Encode Sample to Python")
914 if context.mode == 'EDIT_MESH':
915 r = self.layout.row()
916 r.operator("mesh.rigify_encode_mesh_widget", text="Encode Mesh Widget to Python")
919 # noinspection PyPep8Naming
920 class VIEW3D_PT_rigify_animation_tools(bpy.types.Panel):
921 bl_label = "Rigify Animation Tools"
922 bl_context = "posemode" # noqa
923 bl_space_type = 'VIEW_3D'
924 bl_region_type = 'UI'
925 bl_category = "Rigify"
927 @classmethod
928 def poll(cls, context):
929 obj = context.active_object
930 if obj and obj.type == 'ARMATURE':
931 rig_id = obj.data.get("rig_id")
932 if rig_id is not None:
933 has_arm = hasattr(bpy.types, 'POSE_OT_rigify_arm_ik2fk_' + rig_id)
934 has_leg = hasattr(bpy.types, 'POSE_OT_rigify_leg_ik2fk_' + rig_id)
935 return has_arm or has_leg
937 return False
939 def draw(self, context):
940 obj = context.active_object
941 id_store = context.window_manager
942 if obj is not None:
943 row = self.layout.row()
945 only_selected = get_transfer_only_selected(id_store)
947 if only_selected:
948 icon = 'OUTLINER_DATA_ARMATURE'
949 else:
950 icon = 'ARMATURE_DATA'
952 row.prop(id_store, 'rigify_transfer_only_selected', toggle=True, icon=icon)
954 row = self.layout.row(align=True)
955 row.operator("rigify.ik2fk", text='IK2FK Pose', icon='SNAP_ON')
956 row.operator("rigify.fk2ik", text='FK2IK Pose', icon='SNAP_ON')
958 row = self.layout.row(align=True)
959 row.operator("rigify.transfer_fk_to_ik", text='IK2FK Action', icon='ACTION_TWEAK')
960 row.operator("rigify.transfer_ik_to_fk", text='FK2IK Action', icon='ACTION_TWEAK')
962 row = self.layout.row(align=True)
963 row.operator("rigify.clear_animation", text="Clear IK Action", icon='CANCEL').anim_type = "IK"
964 row.operator("rigify.clear_animation", text="Clear FK Action", icon='CANCEL').anim_type = "FK"
966 row = self.layout.row(align=True)
967 op = row.operator("rigify.rotation_pole", icon='FORCE_HARMONIC', text='Switch to pole')
968 op.value = True
969 op.toggle = False
970 op.bake = True
971 op = row.operator("rigify.rotation_pole", icon='FORCE_MAGNETIC', text='Switch to rotation')
972 op.value = False
973 op.toggle = False
974 op.bake = True
975 RIGIFY_OT_get_frame_range.draw_range_ui(context, self.layout)
978 def rigify_report_exception(operator, exception):
979 import traceback
980 import sys
981 import os
982 # find the non-utils module name where the error happened
983 # hint, this is the metarig type!
984 _exception_type, _exception_value, exception_traceback = sys.exc_info()
985 fns = [item.filename for item in traceback.extract_tb(exception_traceback)]
986 fns_rig = [fn for fn in fns if os.path.basename(os.path.dirname(fn)) != 'utils']
987 fn = fns_rig[-1]
988 fn = os.path.basename(fn)
989 fn = os.path.splitext(fn)[0]
990 message = []
991 if fn.startswith("__"):
992 message.append("Incorrect armature...")
993 else:
994 message.append("Incorrect armature for type '%s'" % fn)
995 message.append(exception.message)
997 message.reverse() # XXX - stupid! menu's are upside down!
999 operator.report({'ERROR'}, '\n'.join(message))
1002 def is_metarig(obj):
1003 if not (obj and obj.data and obj.type == 'ARMATURE'):
1004 return False
1005 if 'rig_id' in obj.data:
1006 return False
1007 for b in obj.pose.bones:
1008 if b.rigify_type != "":
1009 return True
1010 return False
1013 class Generate(bpy.types.Operator):
1014 """Generates a rig from the active metarig armature"""
1016 bl_idname = "pose.rigify_generate"
1017 bl_label = "Rigify Generate Rig"
1018 bl_options = {'UNDO'}
1019 bl_description = 'Generates a rig from the active metarig armature'
1021 @classmethod
1022 def poll(cls, context):
1023 return is_metarig(context.object)
1025 def execute(self, context):
1026 metarig = verify_armature_obj(context.object)
1028 for bcoll in metarig.data.collections:
1029 if bcoll.rigify_ui_row > 0 and bcoll.name not in SPECIAL_COLLECTIONS:
1030 break
1031 else:
1032 self.report(
1033 {'ERROR'},
1034 'No bone collections have UI buttons assigned - please check the Bone Collections UI sub-panel.'
1036 return {'CANCELLED'}
1038 try:
1039 generate.generate_rig(context, metarig)
1040 except MetarigError as rig_exception:
1041 import traceback
1042 traceback.print_exc()
1044 rigify_report_exception(self, rig_exception)
1045 except Exception as rig_exception:
1046 import traceback
1047 traceback.print_exc()
1049 self.report({'ERROR'}, 'Generation has thrown an exception: ' + str(rig_exception))
1050 else:
1051 target_rig = get_rigify_target_rig(metarig.data)
1052 self.report({'INFO'}, f'Successfully generated: "{target_rig.name}"')
1053 finally:
1054 bpy.ops.object.mode_set(mode='OBJECT')
1056 return {'FINISHED'}
1059 class UpgradeMetarigTypes(bpy.types.Operator):
1060 """Upgrades metarig bones rigify_types"""
1062 bl_idname = "pose.rigify_upgrade_types"
1063 bl_label = "Rigify Upgrade Metarig Types"
1064 bl_description = 'Upgrades the rigify types on the active metarig armature'
1065 bl_options = {'UNDO'}
1067 def execute(self, context):
1068 upgrade_metarig_types(verify_armature_obj(context.active_object))
1069 return {'FINISHED'}
1072 class UpgradeMetarigLayers(bpy.types.Operator):
1073 """Upgrades the metarig from bone layers to bone collections"""
1075 bl_idname = "armature.rigify_upgrade_layers"
1076 bl_label = "Rigify Upgrade Metarig Layers"
1077 bl_description = 'Upgrades the metarig from bone layers to bone collections'
1078 bl_options = {'UNDO'}
1080 def execute(self, context):
1081 upgrade_metarig_layers(verify_armature_obj(context.active_object))
1082 return {'FINISHED'}
1085 class ValidateMetarigLayers(bpy.types.Operator):
1086 """Validates references from rig component settings to bone collections"""
1088 bl_idname = "armature.rigify_validate_layers"
1089 bl_label = "Validate Collection References"
1090 bl_description = 'Validate references from rig component settings to bone collections. Always run this both '\
1091 'before and after joining two metarig armature objects into one to avoid glitches'
1092 bl_options = {'UNDO'}
1094 @classmethod
1095 def poll(cls, context):
1096 return is_valid_metarig(context) and context.object.mode != 'EDIT'
1098 def execute(self, context):
1099 obj = verify_armature_obj(context.object)
1100 messages = validate_collection_references(obj)
1101 for msg in messages:
1102 self.report({'WARNING'}, msg)
1103 if not messages:
1104 self.report({'INFO'}, "No issues detected.")
1105 return {'FINISHED'}
1108 class Sample(bpy.types.Operator):
1109 """Create a sample metarig to be modified before generating the final rig"""
1111 bl_idname = "armature.metarig_sample_add"
1112 bl_label = "Add Metarig Sample"
1113 bl_options = {'UNDO'}
1115 metarig_type: StringProperty(
1116 name="Type",
1117 description="Name of the rig type to generate a sample of",
1118 maxlen=128,
1119 options={'SKIP_SAVE'}
1122 @classmethod
1123 def poll(cls, context):
1124 return context.mode == 'EDIT_ARMATURE'
1126 def draw(self, context):
1127 layout = self.layout
1128 layout.use_property_split = True
1129 layout.use_property_decorate = False
1130 col = layout.column()
1131 build_type_list(context, get_rigify_types(context.window_manager))
1132 col.prop(context.object.data, "active_feature_set")
1133 col.prop_search(self, "metarig_type", context.window_manager, "rigify_types")
1135 def invoke(self, context, event):
1136 if self.metarig_type == "":
1137 return context.window_manager.invoke_props_dialog(self)
1138 return self.execute(context)
1140 def execute(self, context):
1141 if self.metarig_type == "":
1142 self.report({'ERROR'}, "You must select a rig type to create a sample of.")
1143 return {'CANCELLED'}
1144 try:
1145 rig = rig_lists.rigs[self.metarig_type]["module"]
1146 create_sample = rig.create_sample
1147 except (ImportError, AttributeError, KeyError):
1148 raise Exception("rig type '" + self.metarig_type + "' has no sample.")
1149 else:
1150 create_sample(context.active_object)
1151 finally:
1152 bpy.ops.object.mode_set(mode='EDIT')
1154 return {'FINISHED'}
1157 class EncodeMetarig(bpy.types.Operator):
1158 """Creates Python code that will generate the selected metarig"""
1159 bl_idname = "armature.rigify_encode_metarig"
1160 bl_label = "Rigify Encode Metarig"
1161 bl_options = {'UNDO'}
1163 @classmethod
1164 def poll(cls, context):
1165 return context.mode == 'EDIT_ARMATURE' and is_metarig(context.object)
1167 def execute(self, context):
1168 name = "metarig.py"
1170 if name in bpy.data.texts:
1171 text_block = bpy.data.texts[name]
1172 text_block.clear()
1173 else:
1174 text_block = bpy.data.texts.new(name)
1176 obj = verify_armature_obj(context.active_object)
1177 text = write_metarig(obj, layers=True, func_name="create", groups=True, widgets=True)
1178 text_block.write(text)
1179 bpy.ops.object.mode_set(mode='EDIT')
1180 self.report({'INFO'}, f"Metarig written to text datablock: {text_block.name}")
1181 return {'FINISHED'}
1184 class EncodeMetarigSample(bpy.types.Operator):
1185 """Creates Python code that will generate the selected metarig as a sample"""
1186 bl_idname = "armature.rigify_encode_metarig_sample"
1187 bl_label = "Rigify Encode Metarig Sample"
1188 bl_options = {'UNDO'}
1190 @classmethod
1191 def poll(cls, context):
1192 return context.mode == 'EDIT_ARMATURE' and is_metarig(context.object)
1194 def execute(self, context):
1195 name = "metarig_sample.py"
1197 if name in bpy.data.texts:
1198 text_block = bpy.data.texts[name]
1199 text_block.clear()
1200 else:
1201 text_block = bpy.data.texts.new(name)
1203 obj = verify_armature_obj(context.active_object)
1204 text = write_metarig(obj, layers=False, func_name="create_sample")
1205 text_block.write(text)
1206 bpy.ops.object.mode_set(mode='EDIT')
1208 self.report({'INFO'}, f"Metarig Sample written to text datablock: {text_block.name}")
1209 return {'FINISHED'}
1212 # noinspection PyPep8Naming
1213 class VIEW3D_MT_rigify(bpy.types.Menu):
1214 bl_label = "Rigify"
1215 bl_idname = "VIEW3D_MT_rigify"
1217 append: Callable
1218 remove: Callable
1220 def draw(self, context):
1221 layout = self.layout
1222 obj = verify_armature_obj(context.object)
1223 target_rig = get_rigify_target_rig(obj.data)
1225 text = "Re-Generate Rig" if target_rig else "Generate Rig"
1226 layout.operator(Generate.bl_idname, text=text)
1228 if context.mode == 'EDIT_ARMATURE':
1229 layout.separator()
1230 layout.operator(Sample.bl_idname)
1231 layout.separator()
1232 layout.operator(EncodeMetarig.bl_idname, text="Encode Metarig")
1233 layout.operator(EncodeMetarigSample.bl_idname, text="Encode Metarig Sample")
1236 def draw_rigify_menu(self, context):
1237 if is_metarig(context.object):
1238 self.layout.menu(VIEW3D_MT_rigify.bl_idname)
1241 class EncodeWidget(bpy.types.Operator):
1242 """ Creates Python code that will generate the selected metarig.
1244 bl_idname = "mesh.rigify_encode_mesh_widget"
1245 bl_label = "Rigify Encode Widget"
1246 bl_options = {'UNDO'}
1248 @classmethod
1249 def poll(cls, context):
1250 return context.mode == 'EDIT_MESH'
1252 def execute(self, context):
1253 name = "widget.py"
1255 if name in bpy.data.texts:
1256 text_block = bpy.data.texts[name]
1257 text_block.clear()
1258 else:
1259 text_block = bpy.data.texts.new(name)
1261 text = write_widget(context.active_object)
1262 text_block.write(text)
1263 bpy.ops.object.mode_set(mode='EDIT')
1265 return {'FINISHED'}
1268 def draw_mesh_edit_menu(self, _context: bpy.types.Context):
1269 self.layout.operator(EncodeWidget.bl_idname)
1270 self.layout.separator()
1273 def fk_to_ik(rig: ArmatureObject, window='ALL'):
1274 scn = bpy.context.scene
1275 id_store = bpy.context.window_manager
1277 rig_id = rig.data['rig_id']
1278 leg_ik2fk = eval('bpy.ops.pose.rigify_leg_ik2fk_' + rig_id)
1279 arm_ik2fk = eval('bpy.ops.pose.rigify_arm_ik2fk_' + rig_id)
1280 limb_generated_names = get_limb_generated_names(rig)
1282 if window == 'ALL':
1283 frames = get_keyed_frames_in_range(bpy.context, rig)
1284 elif window == 'CURRENT':
1285 frames = [scn.frame_current]
1286 else:
1287 frames = [scn.frame_current]
1289 only_selected = get_transfer_only_selected(id_store)
1291 if not only_selected:
1292 pose_bones = rig.pose.bones
1293 bpy.ops.pose.select_all(action='DESELECT')
1294 else:
1295 pose_bones = bpy.context.selected_pose_bones
1296 bpy.ops.pose.select_all(action='DESELECT')
1298 for b in pose_bones:
1299 for group in limb_generated_names:
1300 if b.name in limb_generated_names[group].values() or b.name in limb_generated_names[group]['controls']\
1301 or b.name in limb_generated_names[group]['ik_ctrl']:
1302 names = limb_generated_names[group]
1303 if names['limb_type'] == 'arm':
1304 func = arm_ik2fk
1305 controls = names['controls']
1306 ik_ctrl = names['ik_ctrl']
1307 # fk_ctrl = names['fk_ctrl']
1308 parent = names['parent']
1309 pole = names['pole']
1310 rig.pose.bones[controls[0]].bone.select = True
1311 rig.pose.bones[controls[4]].bone.select = True
1312 rig.pose.bones[pole].bone.select = True
1313 rig.pose.bones[parent].bone.select = True
1314 kwargs = {'uarm_fk': controls[1], 'farm_fk': controls[2], 'hand_fk': controls[3],
1315 'uarm_ik': controls[0], 'farm_ik': ik_ctrl[1], 'hand_ik': controls[4],
1316 'pole': pole, 'main_parent': parent}
1317 args = (controls[0], controls[1], controls[2], controls[3],
1318 controls[4], pole, parent)
1319 else:
1320 func = leg_ik2fk
1321 controls = names['controls']
1322 ik_ctrl = names['ik_ctrl']
1323 # fk_ctrl = names['fk_ctrl']
1324 parent = names['parent']
1325 pole = names['pole']
1326 rig.pose.bones[controls[0]].bone.select = True
1327 rig.pose.bones[controls[6]].bone.select = True
1328 rig.pose.bones[controls[5]].bone.select = True
1329 rig.pose.bones[pole].bone.select = True
1330 rig.pose.bones[parent].bone.select = True
1331 # noinspection SpellCheckingInspection
1332 kwargs = {'thigh_fk': controls[1], 'shin_fk': controls[2], 'foot_fk': controls[3],
1333 'mfoot_fk': controls[7], 'thigh_ik': controls[0], 'shin_ik': ik_ctrl[1],
1334 'foot_ik': controls[6], 'pole': pole, 'footroll': controls[5], 'mfoot_ik': ik_ctrl[2],
1335 'main_parent': parent}
1336 args = (controls[0], controls[1], controls[2], controls[3],
1337 controls[6], controls[5], pole, parent)
1339 for f in frames:
1340 if not bones_in_frame(f, rig, *args):
1341 continue
1342 scn.frame_set(f)
1343 func(**kwargs)
1344 bpy.ops.anim.keyframe_insert_menu(type='BUILTIN_KSI_VisualLocRot')
1345 bpy.ops.anim.keyframe_insert_menu(type='Scaling')
1347 bpy.ops.pose.select_all(action='DESELECT')
1348 limb_generated_names.pop(group)
1349 break
1352 def ik_to_fk(rig: ArmatureObject, window='ALL'):
1353 scn = bpy.context.scene
1354 id_store = bpy.context.window_manager
1356 rig_id = rig.data['rig_id']
1357 leg_fk2ik = eval('bpy.ops.pose.rigify_leg_fk2ik_' + rig_id)
1358 arm_fk2ik = eval('bpy.ops.pose.rigify_arm_fk2ik_' + rig_id)
1359 limb_generated_names = get_limb_generated_names(rig)
1361 if window == 'ALL':
1362 frames = get_keyed_frames_in_range(bpy.context, rig)
1363 elif window == 'CURRENT':
1364 frames = [scn.frame_current]
1365 else:
1366 frames = [scn.frame_current]
1368 only_selected = get_transfer_only_selected(id_store)
1370 if not only_selected:
1371 bpy.ops.pose.select_all(action='DESELECT')
1372 pose_bones = rig.pose.bones
1373 else:
1374 pose_bones = bpy.context.selected_pose_bones
1375 bpy.ops.pose.select_all(action='DESELECT')
1377 for b in pose_bones:
1378 for group in limb_generated_names:
1379 if b.name in limb_generated_names[group].values() or b.name in limb_generated_names[group]['controls']\
1380 or b.name in limb_generated_names[group]['ik_ctrl']:
1381 names = limb_generated_names[group]
1382 if names['limb_type'] == 'arm':
1383 func = arm_fk2ik
1384 controls = names['controls']
1385 ik_ctrl = names['ik_ctrl']
1386 # fk_ctrl = names['fk_ctrl']
1387 parent = names['parent']
1388 pole = names['pole']
1389 rig.pose.bones[controls[1]].bone.select = True
1390 rig.pose.bones[controls[2]].bone.select = True
1391 rig.pose.bones[controls[3]].bone.select = True
1392 kwargs = {'uarm_fk': controls[1], 'farm_fk': controls[2], 'hand_fk': controls[3],
1393 'uarm_ik': controls[0], 'farm_ik': ik_ctrl[1],
1394 'hand_ik': controls[4]}
1395 args = (controls[0], controls[1], controls[2], controls[3],
1396 controls[4], pole, parent)
1397 else:
1398 func = leg_fk2ik
1399 controls = names['controls']
1400 ik_ctrl = names['ik_ctrl']
1401 # fk_ctrl = names['fk_ctrl']
1402 parent = names['parent']
1403 pole = names['pole']
1404 rig.pose.bones[controls[1]].bone.select = True
1405 rig.pose.bones[controls[2]].bone.select = True
1406 rig.pose.bones[controls[3]].bone.select = True
1407 # noinspection SpellCheckingInspection
1408 kwargs = {'thigh_fk': controls[1], 'shin_fk': controls[2], 'foot_fk': controls[3],
1409 'mfoot_fk': controls[7], 'thigh_ik': controls[0], 'shin_ik': ik_ctrl[1],
1410 'foot_ik': ik_ctrl[2], 'mfoot_ik': ik_ctrl[2]}
1411 args = (controls[0], controls[1], controls[2], controls[3],
1412 controls[6], controls[5], pole, parent)
1414 for f in frames:
1415 if not bones_in_frame(f, rig, *args):
1416 continue
1417 scn.frame_set(f)
1418 func(**kwargs)
1419 bpy.ops.anim.keyframe_insert_menu(type='BUILTIN_KSI_VisualLocRot')
1420 bpy.ops.anim.keyframe_insert_menu(type='Scaling')
1422 bpy.ops.pose.select_all(action='DESELECT')
1423 limb_generated_names.pop(group)
1424 break
1427 def clear_animation(act, anim_type, names):
1428 bones = []
1429 for group in names:
1430 if names[group]['limb_type'] == 'arm':
1431 if anim_type == 'IK':
1432 bones.extend([names[group]['controls'][0], names[group]['controls'][4]])
1433 elif anim_type == 'FK':
1434 bones.extend([names[group]['controls'][1], names[group]['controls'][2], names[group]['controls'][3]])
1435 else:
1436 if anim_type == 'IK':
1437 bones.extend([names[group]['controls'][0], names[group]['controls'][6], names[group]['controls'][5],
1438 names[group]['controls'][4]])
1439 elif anim_type == 'FK':
1440 bones.extend([names[group]['controls'][1], names[group]['controls'][2], names[group]['controls'][3],
1441 names[group]['controls'][4]])
1442 f_curves = []
1443 for fcu in act.fcurves:
1444 words = fcu.data_path.split('"')
1445 if words[0] == "pose.bones[" and words[1] in bones:
1446 f_curves.append(fcu)
1448 if not f_curves:
1449 return
1451 for fcu in f_curves:
1452 act.fcurves.remove(fcu)
1454 # Put cleared bones back to rest pose
1455 bpy.ops.pose.loc_clear()
1456 bpy.ops.pose.rot_clear()
1457 bpy.ops.pose.scale_clear()
1459 # updateView3D()
1462 def rot_pole_toggle(rig: ArmatureObject, window='ALL', value=False, toggle=False, bake=False):
1463 scn = bpy.context.scene
1464 id_store = bpy.context.window_manager
1466 rig_id = rig.data['rig_id']
1467 leg_fk2ik = eval('bpy.ops.pose.rigify_leg_fk2ik_' + rig_id)
1468 arm_fk2ik = eval('bpy.ops.pose.rigify_arm_fk2ik_' + rig_id)
1469 leg_ik2fk = eval('bpy.ops.pose.rigify_leg_ik2fk_' + rig_id)
1470 arm_ik2fk = eval('bpy.ops.pose.rigify_arm_ik2fk_' + rig_id)
1471 limb_generated_names = get_limb_generated_names(rig)
1473 if window == 'ALL':
1474 frames = get_keyed_frames_in_range(bpy.context, rig)
1475 elif window == 'CURRENT':
1476 frames = [scn.frame_current]
1477 else:
1478 frames = [scn.frame_current]
1480 only_selected = get_transfer_only_selected(id_store)
1482 if not only_selected:
1483 bpy.ops.pose.select_all(action='DESELECT')
1484 pose_bones = rig.pose.bones
1485 else:
1486 pose_bones = bpy.context.selected_pose_bones
1487 bpy.ops.pose.select_all(action='DESELECT')
1489 for b in pose_bones:
1490 for group in limb_generated_names:
1491 names = limb_generated_names[group]
1493 if toggle:
1494 new_pole_vector_value = not rig.pose.bones[names['parent']]['pole_vector']
1495 else:
1496 new_pole_vector_value = value
1498 if b.name in names.values() or b.name in names['controls'] or b.name in names['ik_ctrl']:
1499 if names['limb_type'] == 'arm':
1500 func1 = arm_fk2ik
1501 func2 = arm_ik2fk
1502 controls = names['controls']
1503 ik_ctrl = names['ik_ctrl']
1504 # fk_ctrl = names['fk_ctrl']
1505 parent = names['parent']
1506 pole = names['pole']
1507 rig.pose.bones[controls[0]].bone.select = not new_pole_vector_value
1508 rig.pose.bones[controls[4]].bone.select = not new_pole_vector_value
1509 rig.pose.bones[parent].bone.select = not new_pole_vector_value
1510 rig.pose.bones[pole].bone.select = new_pole_vector_value
1512 kwargs1 = {'uarm_fk': controls[1], 'farm_fk': controls[2], 'hand_fk': controls[3],
1513 'uarm_ik': controls[0], 'farm_ik': ik_ctrl[1],
1514 'hand_ik': controls[4]}
1515 kwargs2 = {'uarm_fk': controls[1], 'farm_fk': controls[2], 'hand_fk': controls[3],
1516 'uarm_ik': controls[0], 'farm_ik': ik_ctrl[1], 'hand_ik': controls[4],
1517 'pole': pole, 'main_parent': parent}
1518 args = (controls[0], controls[4], pole, parent)
1519 else:
1520 func1 = leg_fk2ik
1521 func2 = leg_ik2fk
1522 controls = names['controls']
1523 ik_ctrl = names['ik_ctrl']
1524 # fk_ctrl = names['fk_ctrl']
1525 parent = names['parent']
1526 pole = names['pole']
1527 rig.pose.bones[controls[0]].bone.select = not new_pole_vector_value
1528 rig.pose.bones[controls[6]].bone.select = not new_pole_vector_value
1529 rig.pose.bones[controls[5]].bone.select = not new_pole_vector_value
1530 rig.pose.bones[parent].bone.select = not new_pole_vector_value
1531 rig.pose.bones[pole].bone.select = new_pole_vector_value
1533 # noinspection SpellCheckingInspection
1534 kwargs1 = {'thigh_fk': controls[1], 'shin_fk': controls[2], 'foot_fk': controls[3],
1535 'mfoot_fk': controls[7], 'thigh_ik': controls[0], 'shin_ik': ik_ctrl[1],
1536 'foot_ik': ik_ctrl[2], 'mfoot_ik': ik_ctrl[2]}
1537 # noinspection SpellCheckingInspection
1538 kwargs2 = {'thigh_fk': controls[1], 'shin_fk': controls[2], 'foot_fk': controls[3],
1539 'mfoot_fk': controls[7], 'thigh_ik': controls[0], 'shin_ik': ik_ctrl[1],
1540 'foot_ik': controls[6], 'pole': pole, 'footroll': controls[5], 'mfoot_ik': ik_ctrl[2],
1541 'main_parent': parent}
1542 args = (controls[0], controls[6], controls[5], pole, parent)
1544 for f in frames:
1545 if bake and not bones_in_frame(f, rig, *args):
1546 continue
1547 scn.frame_set(f)
1548 func1(**kwargs1)
1549 rig.pose.bones[names['parent']]['pole_vector'] = new_pole_vector_value
1550 func2(**kwargs2)
1551 if bake:
1552 bpy.ops.anim.keyframe_insert_menu(type='BUILTIN_KSI_VisualLocRot')
1553 bpy.ops.anim.keyframe_insert_menu(type='Scaling')
1554 overwrite_prop_animation(rig, rig.pose.bones[parent], 'pole_vector', new_pole_vector_value, [f])
1556 bpy.ops.pose.select_all(action='DESELECT')
1557 limb_generated_names.pop(group)
1558 break
1559 scn.frame_set(0)
1562 # noinspection PyPep8Naming
1563 class OBJECT_OT_IK2FK(bpy.types.Operator):
1564 """ Snaps IK limb on FK limb at current frame"""
1565 bl_idname = "rigify.ik2fk"
1566 bl_label = "IK2FK"
1567 bl_description = "Snaps IK limb on FK"
1568 bl_options = {'INTERNAL'}
1570 def execute(self, context):
1571 rig = verify_armature_obj(context.object)
1573 fk_to_ik(rig, window='CURRENT')
1575 return {'FINISHED'}
1578 # noinspection PyPep8Naming
1579 class OBJECT_OT_FK2IK(bpy.types.Operator):
1580 """ Snaps FK limb on IK limb at current frame"""
1581 bl_idname = "rigify.fk2ik"
1582 bl_label = "FK2IK"
1583 bl_description = "Snaps FK limb on IK"
1584 bl_options = {'INTERNAL'}
1586 def execute(self, context):
1587 rig = verify_armature_obj(context.object)
1589 ik_to_fk(rig, window='CURRENT')
1591 return {'FINISHED'}
1594 # noinspection PyPep8Naming
1595 class OBJECT_OT_TransferFKtoIK(bpy.types.Operator):
1596 """Transfers FK animation to IK"""
1597 bl_idname = "rigify.transfer_fk_to_ik"
1598 bl_label = "Transfer FK anim to IK"
1599 bl_description = "Transfer FK animation to IK bones"
1600 bl_options = {'INTERNAL'}
1602 def execute(self, context):
1603 rig = verify_armature_obj(context.object)
1605 fk_to_ik(rig)
1607 return {'FINISHED'}
1610 # noinspection PyPep8Naming
1611 class OBJECT_OT_TransferIKtoFK(bpy.types.Operator):
1612 """Transfers FK animation to IK"""
1613 bl_idname = "rigify.transfer_ik_to_fk"
1614 bl_label = "Transfer IK anim to FK"
1615 bl_description = "Transfer IK animation to FK bones"
1616 bl_options = {'INTERNAL'}
1618 def execute(self, context):
1619 rig = verify_armature_obj(context.object)
1621 ik_to_fk(rig)
1623 return {'FINISHED'}
1626 # noinspection PyPep8Naming
1627 class OBJECT_OT_ClearAnimation(bpy.types.Operator):
1628 bl_idname = "rigify.clear_animation"
1629 bl_label = "Clear Animation"
1630 bl_description = "Clear Animation For FK or IK Bones"
1631 bl_options = {'INTERNAL'}
1633 anim_type: StringProperty()
1635 def execute(self, context):
1636 rig = verify_armature_obj(context.object)
1638 if not rig.animation_data:
1639 return {'FINISHED'}
1641 act = rig.animation_data.action
1642 if not act:
1643 return {'FINISHED'}
1645 clear_animation(act, self.anim_type, names=get_limb_generated_names(rig))
1646 return {'FINISHED'}
1649 # noinspection PyPep8Naming
1650 class OBJECT_OT_Rot2Pole(bpy.types.Operator):
1651 bl_idname = "rigify.rotation_pole"
1652 bl_label = "Rotation - Pole toggle"
1653 bl_description = "Toggles IK chain between rotation and pole target"
1654 bl_options = {'INTERNAL'}
1656 bone_name: StringProperty(default='')
1657 window: StringProperty(default='ALL')
1658 toggle: BoolProperty(default=True)
1659 value: BoolProperty(default=True)
1660 bake: BoolProperty(default=True)
1662 def execute(self, context):
1663 rig = verify_armature_obj(context.object)
1665 if self.bone_name:
1666 bpy.ops.pose.select_all(action='DESELECT')
1667 rig.pose.bones[self.bone_name].bone.select = True
1669 rot_pole_toggle(rig, window=self.window, toggle=self.toggle, value=self.value, bake=self.bake)
1670 return {'FINISHED'}
1673 # noinspection PyPep8Naming
1674 class POSE_OT_rigify_collection_ref_add(bpy.types.Operator):
1675 bl_idname = "pose.rigify_collection_ref_add"
1676 bl_label = "Add Bone Collection Reference"
1677 bl_description = "Add a new row to the bone collection reference list"
1678 bl_options = {'UNDO'}
1680 prop_name: StringProperty(name="Property Name")
1682 @classmethod
1683 def poll(cls, context):
1684 return is_valid_metarig(context) and context.active_pose_bone
1686 def execute(self, context):
1687 params = get_rigify_params(context.active_pose_bone)
1688 getattr(params, self.prop_name).add()
1689 return {'FINISHED'}
1692 # noinspection PyPep8Naming
1693 class POSE_OT_rigify_collection_ref_remove(bpy.types.Operator):
1694 bl_idname = "pose.rigify_collection_ref_remove"
1695 bl_label = "Remove Bone Collection Reference"
1696 bl_description = "Remove this row from the bone collection reference list"
1697 bl_options = {'UNDO'}
1699 prop_name: StringProperty(name="Property Name")
1700 index: IntProperty(name="Entry Index")
1702 @classmethod
1703 def poll(cls, context):
1704 return is_valid_metarig(context) and context.active_pose_bone
1706 def execute(self, context):
1707 params = get_rigify_params(context.active_pose_bone)
1708 getattr(params, self.prop_name).remove(self.index)
1709 return {'FINISHED'}
1712 ###############
1713 # Registering
1715 classes = (
1716 DATA_OT_rigify_add_color_sets,
1717 DATA_OT_rigify_use_standard_colors,
1718 DATA_OT_rigify_apply_selection_colors,
1719 DATA_OT_rigify_color_set_add,
1720 DATA_OT_rigify_color_set_add_theme,
1721 DATA_OT_rigify_color_set_remove,
1722 DATA_OT_rigify_color_set_remove_all,
1723 DATA_UL_rigify_color_sets,
1724 DATA_MT_rigify_color_sets_context_menu,
1725 DATA_PT_rigify,
1726 DATA_PT_rigify_advanced,
1727 DATA_PT_rigify_color_sets,
1728 DATA_UL_rigify_bone_collections,
1729 DATA_PT_rigify_collection_list,
1730 DATA_PT_rigify_collection_ui,
1731 DATA_OT_rigify_collection_select,
1732 DATA_OT_rigify_collection_set_ui_row,
1733 DATA_OT_rigify_collection_add_ui_row,
1734 DATA_PT_rigify_samples,
1735 BONE_PT_rigify_buttons,
1736 VIEW3D_PT_rigify_animation_tools,
1737 VIEW3D_PT_tools_rigify_dev,
1738 Generate,
1739 UpgradeMetarigTypes,
1740 UpgradeMetarigLayers,
1741 ValidateMetarigLayers,
1742 Sample,
1743 VIEW3D_MT_rigify,
1744 EncodeMetarig,
1745 EncodeMetarigSample,
1746 EncodeWidget,
1747 OBJECT_OT_FK2IK,
1748 OBJECT_OT_IK2FK,
1749 OBJECT_OT_TransferFKtoIK,
1750 OBJECT_OT_TransferIKtoFK,
1751 OBJECT_OT_ClearAnimation,
1752 OBJECT_OT_Rot2Pole,
1753 POSE_OT_rigify_collection_ref_add,
1754 POSE_OT_rigify_collection_ref_remove,
1758 def register():
1759 from bpy.utils import register_class
1761 animation_register()
1763 # Classes.
1764 for cls in classes:
1765 register_class(cls)
1767 bpy.types.VIEW3D_MT_editor_menus.append(draw_rigify_menu)
1768 bpy.types.VIEW3D_MT_edit_mesh.prepend(draw_mesh_edit_menu)
1770 # Sub-modules.
1771 rot_mode.register()
1774 def unregister():
1775 from bpy.utils import unregister_class
1777 # Sub-modules.
1778 rot_mode.unregister()
1780 # Classes.
1781 for cls in classes:
1782 unregister_class(cls)
1784 bpy.types.VIEW3D_MT_editor_menus.remove(draw_rigify_menu)
1785 bpy.types.VIEW3D_MT_edit_mesh.remove(draw_mesh_edit_menu)
1787 animation_unregister()