Export_3ds: Improved distance cue node search
[blender-addons.git] / rigify / ui.py
blob35e1fc78a70e3b76fb50d456ee34f2ce55158492
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, flatten_children
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 filter_items(self, _context, data, propname):
247 assert propname == 'collections_all'
248 collections = data.collections_all
249 flags = []
251 # Filtering by name
252 if self.filter_name:
253 print(self.filter_name, self.use_filter_invert)
254 flags = bpy.types.UI_UL_list.filter_items_by_name(
255 self.filter_name, self.bitflag_filter_item, collections, "name")
256 if not flags:
257 flags = [self.bitflag_filter_item] * len(collections)
259 # Reorder by name.
260 if self.use_filter_sort_alpha:
261 indices = bpy.types.UI_UL_list.sort_items_by_name(collections, "name")
262 # Sort by tree order
263 else:
264 index_map = {c.name: i for i, c in enumerate(flatten_children(data.collections))}
265 indices = [index_map[c.name] for c in collections]
267 return flags, indices
269 def draw_item(self, _context, layout, armature, bcoll, _icon, _active_data,
270 _active_prop_name, _index=0, _flt_flag=0):
271 active_bone = armature.edit_bones.active or armature.bones.active
272 has_active_bone = active_bone and bcoll.name in active_bone.collections
274 split = layout.split(factor=0.7)
276 split.prop(bcoll, "name", text="", emboss=False,
277 icon='DOT' if has_active_bone else 'BLANK1')
279 if cset := bcoll.rigify_color_set_name:
280 split.label(text=cset, icon="COLOR", translate=False)
282 icons = layout.row(align=True)
284 icons.prop(bcoll, "rigify_sel_set", text="", toggle=True, emboss=False,
285 icon='RADIOBUT_ON' if bcoll.rigify_sel_set else 'RADIOBUT_OFF')
286 icons.label(text="", icon='RESTRICT_SELECT_OFF' if bcoll.rigify_ui_row > 0 else 'RESTRICT_SELECT_ON')
287 icons.prop(bcoll, "is_visible", text="", emboss=False,
288 icon='HIDE_OFF' if bcoll.is_visible else 'HIDE_ON')
291 # noinspection PyPep8Naming
292 class DATA_PT_rigify_collection_list(bpy.types.Panel):
293 bl_label = "Bone Collection UI"
294 bl_space_type = 'PROPERTIES'
295 bl_region_type = 'WINDOW'
296 bl_context = "data"
297 bl_options = {'DEFAULT_CLOSED'}
298 bl_parent_id = "DATA_PT_rigify"
300 @classmethod
301 def poll(cls, context):
302 return is_valid_metarig(context)
304 def draw(self, context):
305 layout = self.layout
306 obj = verify_armature_obj(context.object)
307 arm = obj.data
309 # Copy the bone collection list
310 active_coll = arm.collections.active
312 row = layout.row()
314 row.template_list(
315 "DATA_UL_rigify_bone_collections",
317 arm,
318 "collections_all",
319 arm.collections,
320 "active_index",
321 rows=(4 if active_coll else 1),
324 col = row.column(align=True)
325 col.operator("armature.collection_add", icon='ADD', text="")
326 col.operator("armature.collection_remove", icon='REMOVE', text="")
327 if active_coll:
328 col.separator()
329 col.operator("armature.collection_move", icon='TRIA_UP', text="").direction = 'UP'
330 col.operator("armature.collection_move", icon='TRIA_DOWN', text="").direction = 'DOWN'
332 layout.operator(operator='armature.rigify_validate_layers')
334 if active_coll:
335 col = layout.column()
336 col.use_property_split = True
337 col.use_property_decorate = False
339 col.prop(active_coll, "rigify_color_set_name", icon="COLOR")
340 col.prop(active_coll, "rigify_sel_set")
341 col.separator()
343 col.prop(active_coll, "rigify_ui_row", )
344 row = col.row()
345 row.active = active_coll.rigify_ui_row > 0 # noqa
346 row.prop(active_coll, "rigify_ui_title")
348 if ROOT_COLLECTION not in arm.collections_all:
349 layout.label(text=f"The '{ROOT_COLLECTION}' collection will be added upon generation", icon='INFO')
352 # noinspection PyPep8Naming
353 class DATA_PT_rigify_collection_ui(bpy.types.Panel):
354 bl_label = "UI Layout"
355 bl_space_type = 'PROPERTIES'
356 bl_region_type = 'WINDOW'
357 bl_context = "data"
358 bl_options = set()
359 bl_parent_id = "DATA_PT_rigify_collection_list"
361 @classmethod
362 def poll(cls, context):
363 return is_valid_metarig(context) and len(verify_armature_obj(context.object).data.collections_all)
365 @staticmethod
366 def draw_btn_block(arm: Armature, parent: UILayout, bcoll_id: int, loose=False):
367 bcoll = arm.collections_all[bcoll_id]
368 block = parent.row(align=True)
370 if bcoll == arm.collections.active:
371 block.prop(bcoll, "rigify_ui_title_name", text="", emboss=True)
373 if not loose:
374 props = block.operator(text="", icon="X", operator="armature.rigify_collection_set_ui_row")
375 props.index = bcoll_id
376 props.row = 0
377 else:
378 props = block.operator(text=bcoll.rigify_ui_title_name, operator="armature.rigify_collection_select")
379 props.index = bcoll_id
381 def draw(self, context):
382 layout = self.layout
383 obj = verify_armature_obj(context.object)
384 arm = obj.data
386 # Sort into button rows
387 row_table = defaultdict(list)
388 has_buttons = False
390 index_map = {c.name: i for i, c in enumerate(arm.collections_all)}
392 for bcoll in flatten_children(arm.collections):
393 row_table[bcoll.rigify_ui_row].append(index_map[bcoll.name])
395 if bcoll.rigify_ui_row > 0:
396 has_buttons = True
398 active_bcoll_idx = arm.collections.active_index
400 if active_bcoll_idx < 0:
401 layout.label(text="Click a button to select a collection:", icon="INFO")
403 box = layout.box()
404 last_row = max(row_table.keys())
406 for row_id in range(1, last_row + 2):
407 row = box.row()
408 row_items = row_table[row_id]
410 if row_id == 1 and not has_buttons:
411 row.label(text="Click to assign the button here:", icon="INFO")
413 grid = row.grid_flow(row_major=True, columns=len(row_items), even_columns=True)
414 for bcoll_id in row_items:
415 self.draw_btn_block(arm, grid, bcoll_id)
417 btn_row = row.row(align=True)
419 if active_bcoll_idx >= 0:
420 props = btn_row.operator(text="", icon="TRIA_LEFT", operator="armature.rigify_collection_set_ui_row")
421 props.index = active_bcoll_idx
422 props.row = row_id
424 if row_id < last_row + 1:
425 props = btn_row.operator(text="", icon="ADD", operator="armature.rigify_collection_add_ui_row")
426 props.row = row_id
427 props.add = True
428 else:
429 btn_row.label(text="", icon="BLANK1")
431 if row_id < last_row:
432 props = btn_row.operator(text="", icon="REMOVE", operator="armature.rigify_collection_add_ui_row")
433 props.row = row_id + 1
434 props.add = False
435 else:
436 btn_row.label(text="", icon="BLANK1")
438 if 0 in row_table:
439 box = layout.box()
440 box.label(text="Permanently hidden collections:")
442 grid = box.grid_flow(row_major=True, columns=2, even_columns=True)
444 for i, bcoll_id in enumerate(row_table[0]):
445 self.draw_btn_block(arm, grid, bcoll_id, loose=True)
448 # noinspection PyPep8Naming
449 class DATA_OT_rigify_collection_select(bpy.types.Operator):
450 bl_idname = "armature.rigify_collection_select"
451 bl_label = "Make Collection Active"
452 bl_description = "Make this collection active"
453 bl_options = {'UNDO_GROUPED'}
455 index: IntProperty(name="Index")
457 @staticmethod
458 def button(layout, *, index, **kwargs):
459 props = layout.operator(**kwargs)
460 props.index = index
462 @classmethod
463 def poll(cls, context):
464 return context.object and context.object.type == 'ARMATURE'
466 def execute(self, context):
467 obj = verify_armature_obj(context.object)
468 obj.data.collections.active_index = self.index
469 return {'FINISHED'}
472 # noinspection PyPep8Naming
473 class DATA_OT_rigify_collection_set_ui_row(bpy.types.Operator):
474 bl_idname = "armature.rigify_collection_set_ui_row"
475 bl_label = "Move Between UI Rows"
476 bl_options = {'UNDO'}
478 index: IntProperty(name="Index")
479 row: IntProperty(name="Row")
480 select: BoolProperty(name="Select")
482 @classmethod
483 def poll(cls, context):
484 return context.object and context.object.type == 'ARMATURE'
486 @classmethod
487 def description(cls, context, properties: Any):
488 if properties.row == 0:
489 return "Remove this button from the UI panel"
490 else:
491 return "Move the active button to this UI panel row"
493 def execute(self, context):
494 obj = verify_armature_obj(context.object)
495 if self.select:
496 obj.data.collections.active_index = self.index
497 obj.data.collections_all[self.index].rigify_ui_row = self.row
498 return {'FINISHED'}
501 # noinspection PyPep8Naming
502 class DATA_OT_rigify_collection_add_ui_row(bpy.types.Operator):
503 bl_idname = "armature.rigify_collection_add_ui_row"
504 bl_label = "Add/Remove UI Rows"
505 bl_options = {'UNDO'}
507 row: IntProperty(name="Row")
508 add: BoolProperty(name="Add")
510 @classmethod
511 def poll(cls, context):
512 return context.object and context.object.type == 'ARMATURE'
514 @classmethod
515 def description(cls, context, properties: Any):
516 if properties.add:
517 return "Insert a new row before this one, shifting buttons down"
518 else:
519 return "Remove this row, shifting buttons up"
521 def execute(self, context):
522 obj = verify_armature_obj(context.object)
523 for coll in obj.data.collections_all:
524 if coll.rigify_ui_row >= self.row:
525 coll.rigify_ui_row += (1 if self.add else -1)
526 return {'FINISHED'}
529 # noinspection PyPep8Naming
530 class DATA_OT_rigify_add_color_sets(bpy.types.Operator):
531 bl_idname = "armature.rigify_add_color_sets"
532 bl_label = "Rigify Add Standard Color Sets"
533 bl_options = {'UNDO'}
535 @classmethod
536 def poll(cls, context):
537 return context.object and context.object.type == 'ARMATURE'
539 def execute(self, context):
540 obj = verify_armature_obj(context.object)
541 armature = obj.data
543 if not hasattr(armature, 'rigify_colors'):
544 return {'FINISHED'}
546 rigify_colors = get_rigify_colors(armature)
547 groups = ['Root', 'IK', 'Special', 'Tweak', 'FK', 'Extra']
549 for g in groups:
550 if g in rigify_colors:
551 continue
553 color = rigify_colors.add()
554 color.name = g
556 color.select = Color((0.3140000104904175, 0.7839999794960022, 1.0))
557 color.active = Color((0.5490000247955322, 1.0, 1.0))
558 color.standard_colors_lock = True
560 if g == "Root":
561 color.normal = Color((0.43529415130615234, 0.18431372940540314, 0.41568630933761597))
562 if g == "IK":
563 color.normal = Color((0.6039215922355652, 0.0, 0.0))
564 if g == "Special":
565 color.normal = Color((0.9568628072738647, 0.7882353663444519, 0.0470588281750679))
566 if g == "Tweak":
567 color.normal = Color((0.03921568766236305, 0.21176472306251526, 0.5803921818733215))
568 if g == "FK":
569 color.normal = Color((0.11764706671237946, 0.5686274766921997, 0.03529411926865578))
570 if g == "Extra":
571 color.normal = Color((0.9686275124549866, 0.250980406999588, 0.0941176563501358))
573 return {'FINISHED'}
576 # noinspection PyPep8Naming
577 class DATA_OT_rigify_use_standard_colors(bpy.types.Operator):
578 bl_idname = "armature.rigify_use_standard_colors"
579 bl_label = "Rigify Get active/select colors from current theme"
580 bl_options = {'UNDO'}
582 @classmethod
583 def poll(cls, context):
584 return context.object and context.object.type == 'ARMATURE'
586 def execute(self, context):
587 obj = verify_armature_obj(context.object)
588 armature = obj.data
589 if not hasattr(armature, 'rigify_colors'):
590 return {'FINISHED'}
592 current_theme = bpy.context.preferences.themes.items()[0][0]
593 theme = bpy.context.preferences.themes[current_theme]
595 selection_colors = get_selection_colors(armature)
596 selection_colors.select = theme.view_3d.bone_pose
597 selection_colors.active = theme.view_3d.bone_pose_active
599 # for col in armature.rigify_colors:
600 # col.select = theme.view_3d.bone_pose
601 # col.active = theme.view_3d.bone_pose_active
603 return {'FINISHED'}
606 # noinspection PyPep8Naming
607 class DATA_OT_rigify_apply_selection_colors(bpy.types.Operator):
608 bl_idname = "armature.rigify_apply_selection_colors"
609 bl_label = "Rigify Apply user defined active/select colors"
610 bl_options = {'UNDO'}
612 @classmethod
613 def poll(cls, context):
614 return context.object and context.object.type == 'ARMATURE'
616 def execute(self, context):
617 obj = verify_armature_obj(context.object)
618 armature = obj.data
620 if not hasattr(armature, 'rigify_colors'):
621 return {'FINISHED'}
623 # current_theme = bpy.context.preferences.themes.items()[0][0]
624 # theme = bpy.context.preferences.themes[current_theme]
626 rigify_colors = get_rigify_colors(armature)
627 selection_colors = get_selection_colors(armature)
629 for col in rigify_colors:
630 col.select = selection_colors.select
631 col.active = selection_colors.active
633 return {'FINISHED'}
636 # noinspection PyPep8Naming
637 class DATA_OT_rigify_color_set_add(bpy.types.Operator):
638 bl_idname = "armature.rigify_color_set_add"
639 bl_label = "Rigify Add Color Set"
640 bl_options = {'UNDO'}
642 @classmethod
643 def poll(cls, context):
644 return context.object and context.object.type == 'ARMATURE'
646 def execute(self, context):
647 obj = context.object
648 armature = obj.data
650 if hasattr(armature, 'rigify_colors'):
651 armature.rigify_colors.add()
652 armature.rigify_colors[-1].name = unique_name(armature.rigify_colors, 'Group')
654 current_theme = bpy.context.preferences.themes.items()[0][0]
655 theme = bpy.context.preferences.themes[current_theme]
657 armature.rigify_colors[-1].normal = theme.view_3d.wire
658 armature.rigify_colors[-1].normal.hsv = theme.view_3d.wire.hsv
659 armature.rigify_colors[-1].select = theme.view_3d.bone_pose
660 armature.rigify_colors[-1].select.hsv = theme.view_3d.bone_pose.hsv
661 armature.rigify_colors[-1].active = theme.view_3d.bone_pose_active
662 armature.rigify_colors[-1].active.hsv = theme.view_3d.bone_pose_active.hsv
664 return {'FINISHED'}
667 # noinspection PyPep8Naming
668 class DATA_OT_rigify_color_set_add_theme(bpy.types.Operator):
669 bl_idname = "armature.rigify_color_set_add_theme"
670 bl_label = "Rigify Add Color Set from Theme"
671 bl_options = {"REGISTER", "UNDO"}
673 theme: EnumProperty(items=(
674 ('THEME01', 'THEME01', ''),
675 ('THEME02', 'THEME02', ''),
676 ('THEME03', 'THEME03', ''),
677 ('THEME04', 'THEME04', ''),
678 ('THEME05', 'THEME05', ''),
679 ('THEME06', 'THEME06', ''),
680 ('THEME07', 'THEME07', ''),
681 ('THEME08', 'THEME08', ''),
682 ('THEME09', 'THEME09', ''),
683 ('THEME10', 'THEME10', ''),
684 ('THEME11', 'THEME11', ''),
685 ('THEME12', 'THEME12', ''),
686 ('THEME13', 'THEME13', ''),
687 ('THEME14', 'THEME14', ''),
688 ('THEME15', 'THEME15', ''),
689 ('THEME16', 'THEME16', ''),
690 ('THEME17', 'THEME17', ''),
691 ('THEME18', 'THEME18', ''),
692 ('THEME19', 'THEME19', ''),
693 ('THEME20', 'THEME20', '')
695 name='Theme')
697 @classmethod
698 def poll(cls, context):
699 return context.object and context.object.type == 'ARMATURE'
701 def execute(self, context):
702 obj = verify_armature_obj(context.object)
703 armature = obj.data
705 if hasattr(armature, 'rigify_colors'):
706 rigify_colors = get_rigify_colors(armature)
708 if self.theme in rigify_colors.keys():
709 return {'FINISHED'}
711 rigify_colors.add()
712 rigify_colors[-1].name = self.theme
714 color_id = int(self.theme[-2:]) - 1
716 theme_color_set = bpy.context.preferences.themes[0].bone_color_sets[color_id]
718 rigify_colors[-1].normal = theme_color_set.normal
719 rigify_colors[-1].select = theme_color_set.select
720 rigify_colors[-1].active = theme_color_set.active
722 return {'FINISHED'}
725 # noinspection PyPep8Naming
726 class DATA_OT_rigify_color_set_remove(bpy.types.Operator):
727 bl_idname = "armature.rigify_color_set_remove"
728 bl_label = "Rigify Remove Color Set"
729 bl_options = {'UNDO'}
731 idx: IntProperty()
733 @classmethod
734 def poll(cls, context):
735 return context.object and context.object.type == 'ARMATURE'
737 def execute(self, context):
738 obj = verify_armature_obj(context.object)
740 rigify_colors = get_rigify_colors(obj.data)
741 rigify_colors.remove(self.idx)
743 # set layers references to 0
744 for coll in obj.data.collections_all:
745 idx = coll.rigify_color_set_id
747 if idx == self.idx + 1:
748 coll.rigify_color_set_id = 0
749 elif idx > self.idx + 1:
750 coll.rigify_color_set_id = idx - 1
752 return {'FINISHED'}
755 # noinspection PyPep8Naming
756 class DATA_OT_rigify_color_set_remove_all(bpy.types.Operator):
757 bl_idname = "armature.rigify_color_set_remove_all"
758 bl_label = "Rigify Remove All Color Sets"
759 bl_options = {'UNDO'}
761 @classmethod
762 def poll(cls, context):
763 return context.object and context.object.type == 'ARMATURE'
765 def execute(self, context):
766 obj = verify_armature_obj(context.object)
768 rigify_colors = get_rigify_colors(obj.data)
769 while len(rigify_colors) > 0:
770 rigify_colors.remove(0)
772 # set layers references to 0
773 for coll in obj.data.collections_all:
774 coll.rigify_color_set_id = 0
776 return {'FINISHED'}
779 # noinspection PyPep8Naming
780 class DATA_UL_rigify_color_sets(bpy.types.UIList):
781 def draw_item(self, context, layout, data, item, icon, active_data, active_prop_name, index=0, flt_flag=0):
782 row = layout.row(align=True)
783 row = row.split(factor=0.1)
784 row.label(text=str(index+1))
785 row = row.split(factor=0.7)
786 row.prop(item, "name", text='', emboss=False)
787 row = row.row(align=True)
788 # icon = 'LOCKED' if item.standard_colors_lock else 'UNLOCKED'
789 # row.prop(item, "standard_colors_lock", text='', icon=icon)
790 row.prop(item, "normal", text='')
791 row2 = row.row(align=True)
792 row2.prop(item, "select", text='')
793 row2.prop(item, "active", text='')
794 # row2.enabled = not item.standard_colors_lock
795 arm = verify_armature_obj(context.object).data
796 row2.enabled = not get_colors_lock(arm)
799 # noinspection PyPep8Naming
800 class DATA_MT_rigify_color_sets_context_menu(bpy.types.Menu):
801 bl_label = 'Rigify Color Sets Specials'
803 def draw(self, context):
804 layout = self.layout
806 layout.operator('armature.rigify_color_set_remove_all')
809 # noinspection SpellCheckingInspection
810 # noinspection PyPep8Naming
811 class DATA_PT_rigify_color_sets(bpy.types.Panel):
812 bl_label = "Color Sets"
813 bl_space_type = 'PROPERTIES'
814 bl_region_type = 'WINDOW'
815 bl_context = "data"
816 bl_options = {'DEFAULT_CLOSED'}
817 bl_parent_id = "DATA_PT_rigify"
819 @classmethod
820 def poll(cls, context):
821 return is_valid_metarig(context)
823 def draw(self, context):
824 obj = verify_armature_obj(context.object)
825 armature = obj.data
826 idx = get_colors_index(armature)
827 selection_colors = get_selection_colors(armature)
828 is_locked = get_colors_lock(armature)
829 theme = get_theme_to_add(armature)
831 layout = self.layout
832 row = layout.row()
833 row.operator("armature.rigify_use_standard_colors", icon='FILE_REFRESH', text='')
834 row = row.row(align=True)
835 row.prop(selection_colors, 'select', text='')
836 row.prop(selection_colors, 'active', text='')
837 row = layout.row(align=True)
838 icon = 'LOCKED' if is_locked else 'UNLOCKED'
839 row.prop(armature, 'rigify_colors_lock', text='Unified select/active colors', icon=icon)
840 row.operator("armature.rigify_apply_selection_colors", icon='FILE_REFRESH', text='Apply')
841 row = layout.row()
842 row.template_list("DATA_UL_rigify_color_sets", "", obj.data, "rigify_colors", obj.data, "rigify_colors_index")
844 col = row.column(align=True)
845 col.operator("armature.rigify_color_set_add", icon='ADD', text="")
846 col.operator("armature.rigify_color_set_remove", icon='REMOVE', text="").idx = idx
847 col.menu("DATA_MT_rigify_color_sets_context_menu", icon='DOWNARROW_HLT', text="")
848 row = layout.row()
849 row.prop(armature, 'rigify_theme_to_add', text='Theme')
850 op = row.operator("armature.rigify_color_set_add_theme", text="Add From Theme")
851 op.theme = theme
852 row = layout.row()
853 row.operator("armature.rigify_add_color_sets", text="Add Standard")
856 # noinspection PyPep8Naming
857 class BONE_PT_rigify_buttons(bpy.types.Panel):
858 bl_label = "Rigify Type"
859 bl_space_type = 'PROPERTIES'
860 bl_region_type = 'WINDOW'
861 bl_context = "bone"
862 # bl_options = {'DEFAULT_OPEN'}
864 @classmethod
865 def poll(cls, context):
866 return is_valid_metarig(context) and context.active_pose_bone
868 def draw(self, context):
869 C = context
870 id_store = C.window_manager
871 bone = context.active_pose_bone
872 rig_name = get_rigify_type(bone)
874 layout = self.layout
875 rig_types = get_rigify_types(id_store)
877 # Build types list
878 build_type_list(context, rig_types)
880 # Rig type field
881 if len(feature_set_list.get_enabled_modules_names()) > 0:
882 row = layout.row()
883 row.prop(context.object.data, "active_feature_set")
884 row = layout.row()
885 row.prop_search(bone, "rigify_type", id_store, "rigify_types", text="Rig type")
887 # Rig type parameters / Rig type non-exist alert
888 if rig_name != "":
889 try:
890 rig = rig_lists.rigs[rig_name]['module']
891 except (ImportError, AttributeError, KeyError):
892 row = layout.row()
893 box = row.box()
894 box.label(text="ERROR: type \"%s\" does not exist!" % rig_name, icon='ERROR')
895 else:
896 if hasattr(rig.Rig, 'parameters_ui'):
897 rig = rig.Rig
899 try:
900 param_cb = rig.parameters_ui
902 # Ignore the known empty base method
903 if getattr(param_cb, '__func__', None) == \
904 getattr(base_rig.BaseRig.parameters_ui, '__func__'):
905 param_cb = None
906 except AttributeError:
907 param_cb = None
909 if param_cb is None:
910 col = layout.column()
911 col.label(text="No options")
912 else:
913 col = layout.column()
914 col.label(text="Options:")
915 box = layout.box()
916 param_cb(box, get_rigify_params(bone))
919 # noinspection PyPep8Naming
920 class VIEW3D_PT_tools_rigify_dev(bpy.types.Panel):
921 bl_label = "Rigify Dev Tools"
922 bl_space_type = 'VIEW_3D'
923 bl_region_type = 'UI'
924 bl_category = "Rigify"
926 @classmethod
927 def poll(cls, context):
928 return context.mode in ['EDIT_ARMATURE', 'EDIT_MESH']
930 def draw(self, context):
931 obj = context.active_object
932 if obj is not None:
933 if context.mode == 'EDIT_ARMATURE':
934 r = self.layout.row()
935 r.operator("armature.rigify_encode_metarig", text="Encode Metarig to Python")
936 r = self.layout.row()
937 r.operator("armature.rigify_encode_metarig_sample", text="Encode Sample to Python")
939 if context.mode == 'EDIT_MESH':
940 r = self.layout.row()
941 r.operator("mesh.rigify_encode_mesh_widget", text="Encode Mesh Widget to Python")
944 # noinspection PyPep8Naming
945 class VIEW3D_PT_rigify_animation_tools(bpy.types.Panel):
946 bl_label = "Rigify Animation Tools"
947 bl_context = "posemode" # noqa
948 bl_space_type = 'VIEW_3D'
949 bl_region_type = 'UI'
950 bl_category = "Rigify"
952 @classmethod
953 def poll(cls, context):
954 obj = context.active_object
955 if obj and obj.type == 'ARMATURE':
956 rig_id = obj.data.get("rig_id")
957 if rig_id is not None:
958 has_arm = hasattr(bpy.types, 'POSE_OT_rigify_arm_ik2fk_' + rig_id)
959 has_leg = hasattr(bpy.types, 'POSE_OT_rigify_leg_ik2fk_' + rig_id)
960 return has_arm or has_leg
962 return False
964 def draw(self, context):
965 obj = context.active_object
966 id_store = context.window_manager
967 if obj is not None:
968 row = self.layout.row()
970 only_selected = get_transfer_only_selected(id_store)
972 if only_selected:
973 icon = 'OUTLINER_DATA_ARMATURE'
974 else:
975 icon = 'ARMATURE_DATA'
977 row.prop(id_store, 'rigify_transfer_only_selected', toggle=True, icon=icon)
979 row = self.layout.row(align=True)
980 row.operator("rigify.ik2fk", text='IK2FK Pose', icon='SNAP_ON')
981 row.operator("rigify.fk2ik", text='FK2IK Pose', icon='SNAP_ON')
983 row = self.layout.row(align=True)
984 row.operator("rigify.transfer_fk_to_ik", text='IK2FK Action', icon='ACTION_TWEAK')
985 row.operator("rigify.transfer_ik_to_fk", text='FK2IK Action', icon='ACTION_TWEAK')
987 row = self.layout.row(align=True)
988 row.operator("rigify.clear_animation", text="Clear IK Action", icon='CANCEL').anim_type = "IK"
989 row.operator("rigify.clear_animation", text="Clear FK Action", icon='CANCEL').anim_type = "FK"
991 row = self.layout.row(align=True)
992 op = row.operator("rigify.rotation_pole", icon='FORCE_HARMONIC', text='Switch to pole')
993 op.value = True
994 op.toggle = False
995 op.bake = True
996 op = row.operator("rigify.rotation_pole", icon='FORCE_MAGNETIC', text='Switch to rotation')
997 op.value = False
998 op.toggle = False
999 op.bake = True
1000 RIGIFY_OT_get_frame_range.draw_range_ui(context, self.layout)
1003 def rigify_report_exception(operator, exception):
1004 import traceback
1005 import sys
1006 import os
1007 # find the non-utils module name where the error happened
1008 # hint, this is the metarig type!
1009 _exception_type, _exception_value, exception_traceback = sys.exc_info()
1010 fns = [item.filename for item in traceback.extract_tb(exception_traceback)]
1011 fns_rig = [fn for fn in fns if os.path.basename(os.path.dirname(fn)) != 'utils']
1012 fn = fns_rig[-1]
1013 fn = os.path.basename(fn)
1014 fn = os.path.splitext(fn)[0]
1015 message = []
1016 if fn.startswith("__"):
1017 message.append("Incorrect armature...")
1018 else:
1019 message.append("Incorrect armature for type '%s'" % fn)
1020 message.append(exception.message)
1022 message.reverse() # XXX - stupid! menu's are upside down!
1024 operator.report({'ERROR'}, '\n'.join(message))
1027 def is_metarig(obj):
1028 if not (obj and obj.data and obj.type == 'ARMATURE'):
1029 return False
1030 if 'rig_id' in obj.data:
1031 return False
1032 for b in obj.pose.bones:
1033 if b.rigify_type != "":
1034 return True
1035 return False
1038 class Generate(bpy.types.Operator):
1039 """Generates a rig from the active metarig armature"""
1041 bl_idname = "pose.rigify_generate"
1042 bl_label = "Rigify Generate Rig"
1043 bl_options = {'UNDO'}
1044 bl_description = 'Generates a rig from the active metarig armature'
1046 @classmethod
1047 def poll(cls, context):
1048 return is_metarig(context.object)
1050 def execute(self, context):
1051 metarig = verify_armature_obj(context.object)
1053 for bcoll in metarig.data.collections_all:
1054 if bcoll.rigify_ui_row > 0 and bcoll.name not in SPECIAL_COLLECTIONS:
1055 break
1056 else:
1057 self.report(
1058 {'ERROR'},
1059 'No bone collections have UI buttons assigned - please check the Bone Collections UI sub-panel.'
1061 return {'CANCELLED'}
1063 try:
1064 generate.generate_rig(context, metarig)
1065 except MetarigError as rig_exception:
1066 import traceback
1067 traceback.print_exc()
1069 rigify_report_exception(self, rig_exception)
1070 except Exception as rig_exception:
1071 import traceback
1072 traceback.print_exc()
1074 self.report({'ERROR'}, 'Generation has thrown an exception: ' + str(rig_exception))
1075 else:
1076 target_rig = get_rigify_target_rig(metarig.data)
1077 self.report({'INFO'}, f'Successfully generated: "{target_rig.name}"')
1078 finally:
1079 bpy.ops.object.mode_set(mode='OBJECT')
1081 return {'FINISHED'}
1084 class UpgradeMetarigTypes(bpy.types.Operator):
1085 """Upgrades metarig bones rigify_types"""
1087 bl_idname = "pose.rigify_upgrade_types"
1088 bl_label = "Rigify Upgrade Metarig Types"
1089 bl_description = 'Upgrades the rigify types on the active metarig armature'
1090 bl_options = {'UNDO'}
1092 def execute(self, context):
1093 upgrade_metarig_types(verify_armature_obj(context.active_object))
1094 return {'FINISHED'}
1097 class UpgradeMetarigLayers(bpy.types.Operator):
1098 """Upgrades the metarig from bone layers to bone collections"""
1100 bl_idname = "armature.rigify_upgrade_layers"
1101 bl_label = "Rigify Upgrade Metarig Layers"
1102 bl_description = 'Upgrades the metarig from bone layers to bone collections'
1103 bl_options = {'UNDO'}
1105 def execute(self, context):
1106 upgrade_metarig_layers(verify_armature_obj(context.active_object))
1107 return {'FINISHED'}
1110 class ValidateMetarigLayers(bpy.types.Operator):
1111 """Validates references from rig component settings to bone collections"""
1113 bl_idname = "armature.rigify_validate_layers"
1114 bl_label = "Validate Collection References"
1115 bl_description = 'Validate references from rig component settings to bone collections. Always run this both '\
1116 'before and after joining two metarig armature objects into one to avoid glitches'
1117 bl_options = {'UNDO'}
1119 @classmethod
1120 def poll(cls, context):
1121 return is_valid_metarig(context) and context.object.mode != 'EDIT'
1123 def execute(self, context):
1124 obj = verify_armature_obj(context.object)
1125 messages = validate_collection_references(obj)
1126 for msg in messages:
1127 self.report({'WARNING'}, msg)
1128 if not messages:
1129 self.report({'INFO'}, "No issues detected.")
1130 return {'FINISHED'}
1133 class Sample(bpy.types.Operator):
1134 """Create a sample metarig to be modified before generating the final rig"""
1136 bl_idname = "armature.metarig_sample_add"
1137 bl_label = "Add Metarig Sample"
1138 bl_options = {'UNDO'}
1140 metarig_type: StringProperty(
1141 name="Type",
1142 description="Name of the rig type to generate a sample of",
1143 maxlen=128,
1144 options={'SKIP_SAVE'}
1147 @classmethod
1148 def poll(cls, context):
1149 return context.mode == 'EDIT_ARMATURE'
1151 def draw(self, context):
1152 layout = self.layout
1153 layout.use_property_split = True
1154 layout.use_property_decorate = False
1155 col = layout.column()
1156 build_type_list(context, get_rigify_types(context.window_manager))
1157 col.prop(context.object.data, "active_feature_set")
1158 col.prop_search(self, "metarig_type", context.window_manager, "rigify_types")
1160 def invoke(self, context, event):
1161 if self.metarig_type == "":
1162 return context.window_manager.invoke_props_dialog(self)
1163 return self.execute(context)
1165 def execute(self, context):
1166 if self.metarig_type == "":
1167 self.report({'ERROR'}, "You must select a rig type to create a sample of.")
1168 return {'CANCELLED'}
1169 try:
1170 rig = rig_lists.rigs[self.metarig_type]["module"]
1171 create_sample = rig.create_sample
1172 except (ImportError, AttributeError, KeyError):
1173 raise Exception("rig type '" + self.metarig_type + "' has no sample.")
1174 else:
1175 create_sample(context.active_object)
1176 finally:
1177 bpy.ops.object.mode_set(mode='EDIT')
1179 return {'FINISHED'}
1182 class EncodeMetarig(bpy.types.Operator):
1183 """Creates Python code that will generate the selected metarig"""
1184 bl_idname = "armature.rigify_encode_metarig"
1185 bl_label = "Rigify Encode Metarig"
1186 bl_options = {'UNDO'}
1188 @classmethod
1189 def poll(cls, context):
1190 return context.mode == 'EDIT_ARMATURE' and is_metarig(context.object)
1192 def execute(self, context):
1193 name = "metarig.py"
1195 if name in bpy.data.texts:
1196 text_block = bpy.data.texts[name]
1197 text_block.clear()
1198 else:
1199 text_block = bpy.data.texts.new(name)
1201 obj = verify_armature_obj(context.active_object)
1202 text = write_metarig(obj, layers=True, func_name="create", groups=True, widgets=True)
1203 text_block.write(text)
1204 bpy.ops.object.mode_set(mode='EDIT')
1205 self.report({'INFO'}, f"Metarig written to text datablock: {text_block.name}")
1206 return {'FINISHED'}
1209 class EncodeMetarigSample(bpy.types.Operator):
1210 """Creates Python code that will generate the selected metarig as a sample"""
1211 bl_idname = "armature.rigify_encode_metarig_sample"
1212 bl_label = "Rigify Encode Metarig Sample"
1213 bl_options = {'UNDO'}
1215 @classmethod
1216 def poll(cls, context):
1217 return context.mode == 'EDIT_ARMATURE' and is_metarig(context.object)
1219 def execute(self, context):
1220 name = "metarig_sample.py"
1222 if name in bpy.data.texts:
1223 text_block = bpy.data.texts[name]
1224 text_block.clear()
1225 else:
1226 text_block = bpy.data.texts.new(name)
1228 obj = verify_armature_obj(context.active_object)
1229 text = write_metarig(obj, layers=False, func_name="create_sample")
1230 text_block.write(text)
1231 bpy.ops.object.mode_set(mode='EDIT')
1233 self.report({'INFO'}, f"Metarig Sample written to text datablock: {text_block.name}")
1234 return {'FINISHED'}
1237 # noinspection PyPep8Naming
1238 class VIEW3D_MT_rigify(bpy.types.Menu):
1239 bl_label = "Rigify"
1240 bl_idname = "VIEW3D_MT_rigify"
1242 append: Callable
1243 remove: Callable
1245 def draw(self, context):
1246 layout = self.layout
1247 obj = verify_armature_obj(context.object)
1248 target_rig = get_rigify_target_rig(obj.data)
1250 text = "Re-Generate Rig" if target_rig else "Generate Rig"
1251 layout.operator(Generate.bl_idname, text=text)
1253 if context.mode == 'EDIT_ARMATURE':
1254 layout.separator()
1255 layout.operator(Sample.bl_idname)
1256 layout.separator()
1257 layout.operator(EncodeMetarig.bl_idname, text="Encode Metarig")
1258 layout.operator(EncodeMetarigSample.bl_idname, text="Encode Metarig Sample")
1261 def draw_rigify_menu(self, context):
1262 if is_metarig(context.object):
1263 self.layout.menu(VIEW3D_MT_rigify.bl_idname)
1266 class EncodeWidget(bpy.types.Operator):
1267 """ Creates Python code that will generate the selected metarig.
1269 bl_idname = "mesh.rigify_encode_mesh_widget"
1270 bl_label = "Rigify Encode Widget"
1271 bl_options = {'UNDO'}
1273 @classmethod
1274 def poll(cls, context):
1275 return context.mode == 'EDIT_MESH'
1277 def execute(self, context):
1278 name = "widget.py"
1280 if name in bpy.data.texts:
1281 text_block = bpy.data.texts[name]
1282 text_block.clear()
1283 else:
1284 text_block = bpy.data.texts.new(name)
1286 text = write_widget(context.active_object)
1287 text_block.write(text)
1288 bpy.ops.object.mode_set(mode='EDIT')
1290 return {'FINISHED'}
1293 def draw_mesh_edit_menu(self, _context: bpy.types.Context):
1294 self.layout.operator(EncodeWidget.bl_idname)
1295 self.layout.separator()
1298 def fk_to_ik(rig: ArmatureObject, window='ALL'):
1299 scn = bpy.context.scene
1300 id_store = bpy.context.window_manager
1302 rig_id = rig.data['rig_id']
1303 leg_ik2fk = eval('bpy.ops.pose.rigify_leg_ik2fk_' + rig_id)
1304 arm_ik2fk = eval('bpy.ops.pose.rigify_arm_ik2fk_' + rig_id)
1305 limb_generated_names = get_limb_generated_names(rig)
1307 if window == 'ALL':
1308 frames = get_keyed_frames_in_range(bpy.context, rig)
1309 elif window == 'CURRENT':
1310 frames = [scn.frame_current]
1311 else:
1312 frames = [scn.frame_current]
1314 only_selected = get_transfer_only_selected(id_store)
1316 if not only_selected:
1317 pose_bones = rig.pose.bones
1318 bpy.ops.pose.select_all(action='DESELECT')
1319 else:
1320 pose_bones = bpy.context.selected_pose_bones
1321 bpy.ops.pose.select_all(action='DESELECT')
1323 for b in pose_bones:
1324 for group in limb_generated_names:
1325 if b.name in limb_generated_names[group].values() or b.name in limb_generated_names[group]['controls']\
1326 or b.name in limb_generated_names[group]['ik_ctrl']:
1327 names = limb_generated_names[group]
1328 if names['limb_type'] == 'arm':
1329 func = arm_ik2fk
1330 controls = names['controls']
1331 ik_ctrl = names['ik_ctrl']
1332 # fk_ctrl = names['fk_ctrl']
1333 parent = names['parent']
1334 pole = names['pole']
1335 rig.pose.bones[controls[0]].bone.select = True
1336 rig.pose.bones[controls[4]].bone.select = True
1337 rig.pose.bones[pole].bone.select = True
1338 rig.pose.bones[parent].bone.select = True
1339 kwargs = {'uarm_fk': controls[1], 'farm_fk': controls[2], 'hand_fk': controls[3],
1340 'uarm_ik': controls[0], 'farm_ik': ik_ctrl[1], 'hand_ik': controls[4],
1341 'pole': pole, 'main_parent': parent}
1342 args = (controls[0], controls[1], controls[2], controls[3],
1343 controls[4], pole, parent)
1344 else:
1345 func = leg_ik2fk
1346 controls = names['controls']
1347 ik_ctrl = names['ik_ctrl']
1348 # fk_ctrl = names['fk_ctrl']
1349 parent = names['parent']
1350 pole = names['pole']
1351 rig.pose.bones[controls[0]].bone.select = True
1352 rig.pose.bones[controls[6]].bone.select = True
1353 rig.pose.bones[controls[5]].bone.select = True
1354 rig.pose.bones[pole].bone.select = True
1355 rig.pose.bones[parent].bone.select = True
1356 # noinspection SpellCheckingInspection
1357 kwargs = {'thigh_fk': controls[1], 'shin_fk': controls[2], 'foot_fk': controls[3],
1358 'mfoot_fk': controls[7], 'thigh_ik': controls[0], 'shin_ik': ik_ctrl[1],
1359 'foot_ik': controls[6], 'pole': pole, 'footroll': controls[5], 'mfoot_ik': ik_ctrl[2],
1360 'main_parent': parent}
1361 args = (controls[0], controls[1], controls[2], controls[3],
1362 controls[6], controls[5], pole, parent)
1364 for f in frames:
1365 if not bones_in_frame(f, rig, *args):
1366 continue
1367 scn.frame_set(f)
1368 func(**kwargs)
1369 bpy.ops.anim.keyframe_insert_menu(type='BUILTIN_KSI_VisualLocRot')
1370 bpy.ops.anim.keyframe_insert_menu(type='Scaling')
1372 bpy.ops.pose.select_all(action='DESELECT')
1373 limb_generated_names.pop(group)
1374 break
1377 def ik_to_fk(rig: ArmatureObject, window='ALL'):
1378 scn = bpy.context.scene
1379 id_store = bpy.context.window_manager
1381 rig_id = rig.data['rig_id']
1382 leg_fk2ik = eval('bpy.ops.pose.rigify_leg_fk2ik_' + rig_id)
1383 arm_fk2ik = eval('bpy.ops.pose.rigify_arm_fk2ik_' + rig_id)
1384 limb_generated_names = get_limb_generated_names(rig)
1386 if window == 'ALL':
1387 frames = get_keyed_frames_in_range(bpy.context, rig)
1388 elif window == 'CURRENT':
1389 frames = [scn.frame_current]
1390 else:
1391 frames = [scn.frame_current]
1393 only_selected = get_transfer_only_selected(id_store)
1395 if not only_selected:
1396 bpy.ops.pose.select_all(action='DESELECT')
1397 pose_bones = rig.pose.bones
1398 else:
1399 pose_bones = bpy.context.selected_pose_bones
1400 bpy.ops.pose.select_all(action='DESELECT')
1402 for b in pose_bones:
1403 for group in limb_generated_names:
1404 if b.name in limb_generated_names[group].values() or b.name in limb_generated_names[group]['controls']\
1405 or b.name in limb_generated_names[group]['ik_ctrl']:
1406 names = limb_generated_names[group]
1407 if names['limb_type'] == 'arm':
1408 func = arm_fk2ik
1409 controls = names['controls']
1410 ik_ctrl = names['ik_ctrl']
1411 # fk_ctrl = names['fk_ctrl']
1412 parent = names['parent']
1413 pole = names['pole']
1414 rig.pose.bones[controls[1]].bone.select = True
1415 rig.pose.bones[controls[2]].bone.select = True
1416 rig.pose.bones[controls[3]].bone.select = True
1417 kwargs = {'uarm_fk': controls[1], 'farm_fk': controls[2], 'hand_fk': controls[3],
1418 'uarm_ik': controls[0], 'farm_ik': ik_ctrl[1],
1419 'hand_ik': controls[4]}
1420 args = (controls[0], controls[1], controls[2], controls[3],
1421 controls[4], pole, parent)
1422 else:
1423 func = leg_fk2ik
1424 controls = names['controls']
1425 ik_ctrl = names['ik_ctrl']
1426 # fk_ctrl = names['fk_ctrl']
1427 parent = names['parent']
1428 pole = names['pole']
1429 rig.pose.bones[controls[1]].bone.select = True
1430 rig.pose.bones[controls[2]].bone.select = True
1431 rig.pose.bones[controls[3]].bone.select = True
1432 # noinspection SpellCheckingInspection
1433 kwargs = {'thigh_fk': controls[1], 'shin_fk': controls[2], 'foot_fk': controls[3],
1434 'mfoot_fk': controls[7], 'thigh_ik': controls[0], 'shin_ik': ik_ctrl[1],
1435 'foot_ik': ik_ctrl[2], 'mfoot_ik': ik_ctrl[2]}
1436 args = (controls[0], controls[1], controls[2], controls[3],
1437 controls[6], controls[5], pole, parent)
1439 for f in frames:
1440 if not bones_in_frame(f, rig, *args):
1441 continue
1442 scn.frame_set(f)
1443 func(**kwargs)
1444 bpy.ops.anim.keyframe_insert_menu(type='BUILTIN_KSI_VisualLocRot')
1445 bpy.ops.anim.keyframe_insert_menu(type='Scaling')
1447 bpy.ops.pose.select_all(action='DESELECT')
1448 limb_generated_names.pop(group)
1449 break
1452 def clear_animation(act, anim_type, names):
1453 bones = []
1454 for group in names:
1455 if names[group]['limb_type'] == 'arm':
1456 if anim_type == 'IK':
1457 bones.extend([names[group]['controls'][0], names[group]['controls'][4]])
1458 elif anim_type == 'FK':
1459 bones.extend([names[group]['controls'][1], names[group]['controls'][2], names[group]['controls'][3]])
1460 else:
1461 if anim_type == 'IK':
1462 bones.extend([names[group]['controls'][0], names[group]['controls'][6], names[group]['controls'][5],
1463 names[group]['controls'][4]])
1464 elif anim_type == 'FK':
1465 bones.extend([names[group]['controls'][1], names[group]['controls'][2], names[group]['controls'][3],
1466 names[group]['controls'][4]])
1467 f_curves = []
1468 for fcu in act.fcurves:
1469 words = fcu.data_path.split('"')
1470 if words[0] == "pose.bones[" and words[1] in bones:
1471 f_curves.append(fcu)
1473 if not f_curves:
1474 return
1476 for fcu in f_curves:
1477 act.fcurves.remove(fcu)
1479 # Put cleared bones back to rest pose
1480 bpy.ops.pose.loc_clear()
1481 bpy.ops.pose.rot_clear()
1482 bpy.ops.pose.scale_clear()
1484 # updateView3D()
1487 def rot_pole_toggle(rig: ArmatureObject, window='ALL', value=False, toggle=False, bake=False):
1488 scn = bpy.context.scene
1489 id_store = bpy.context.window_manager
1491 rig_id = rig.data['rig_id']
1492 leg_fk2ik = eval('bpy.ops.pose.rigify_leg_fk2ik_' + rig_id)
1493 arm_fk2ik = eval('bpy.ops.pose.rigify_arm_fk2ik_' + rig_id)
1494 leg_ik2fk = eval('bpy.ops.pose.rigify_leg_ik2fk_' + rig_id)
1495 arm_ik2fk = eval('bpy.ops.pose.rigify_arm_ik2fk_' + rig_id)
1496 limb_generated_names = get_limb_generated_names(rig)
1498 if window == 'ALL':
1499 frames = get_keyed_frames_in_range(bpy.context, rig)
1500 elif window == 'CURRENT':
1501 frames = [scn.frame_current]
1502 else:
1503 frames = [scn.frame_current]
1505 only_selected = get_transfer_only_selected(id_store)
1507 if not only_selected:
1508 bpy.ops.pose.select_all(action='DESELECT')
1509 pose_bones = rig.pose.bones
1510 else:
1511 pose_bones = bpy.context.selected_pose_bones
1512 bpy.ops.pose.select_all(action='DESELECT')
1514 for b in pose_bones:
1515 for group in limb_generated_names:
1516 names = limb_generated_names[group]
1518 if toggle:
1519 new_pole_vector_value = not rig.pose.bones[names['parent']]['pole_vector']
1520 else:
1521 new_pole_vector_value = value
1523 if b.name in names.values() or b.name in names['controls'] or b.name in names['ik_ctrl']:
1524 if names['limb_type'] == 'arm':
1525 func1 = arm_fk2ik
1526 func2 = arm_ik2fk
1527 controls = names['controls']
1528 ik_ctrl = names['ik_ctrl']
1529 # fk_ctrl = names['fk_ctrl']
1530 parent = names['parent']
1531 pole = names['pole']
1532 rig.pose.bones[controls[0]].bone.select = not new_pole_vector_value
1533 rig.pose.bones[controls[4]].bone.select = not new_pole_vector_value
1534 rig.pose.bones[parent].bone.select = not new_pole_vector_value
1535 rig.pose.bones[pole].bone.select = new_pole_vector_value
1537 kwargs1 = {'uarm_fk': controls[1], 'farm_fk': controls[2], 'hand_fk': controls[3],
1538 'uarm_ik': controls[0], 'farm_ik': ik_ctrl[1],
1539 'hand_ik': controls[4]}
1540 kwargs2 = {'uarm_fk': controls[1], 'farm_fk': controls[2], 'hand_fk': controls[3],
1541 'uarm_ik': controls[0], 'farm_ik': ik_ctrl[1], 'hand_ik': controls[4],
1542 'pole': pole, 'main_parent': parent}
1543 args = (controls[0], controls[4], pole, parent)
1544 else:
1545 func1 = leg_fk2ik
1546 func2 = leg_ik2fk
1547 controls = names['controls']
1548 ik_ctrl = names['ik_ctrl']
1549 # fk_ctrl = names['fk_ctrl']
1550 parent = names['parent']
1551 pole = names['pole']
1552 rig.pose.bones[controls[0]].bone.select = not new_pole_vector_value
1553 rig.pose.bones[controls[6]].bone.select = not new_pole_vector_value
1554 rig.pose.bones[controls[5]].bone.select = not new_pole_vector_value
1555 rig.pose.bones[parent].bone.select = not new_pole_vector_value
1556 rig.pose.bones[pole].bone.select = new_pole_vector_value
1558 # noinspection SpellCheckingInspection
1559 kwargs1 = {'thigh_fk': controls[1], 'shin_fk': controls[2], 'foot_fk': controls[3],
1560 'mfoot_fk': controls[7], 'thigh_ik': controls[0], 'shin_ik': ik_ctrl[1],
1561 'foot_ik': ik_ctrl[2], 'mfoot_ik': ik_ctrl[2]}
1562 # noinspection SpellCheckingInspection
1563 kwargs2 = {'thigh_fk': controls[1], 'shin_fk': controls[2], 'foot_fk': controls[3],
1564 'mfoot_fk': controls[7], 'thigh_ik': controls[0], 'shin_ik': ik_ctrl[1],
1565 'foot_ik': controls[6], 'pole': pole, 'footroll': controls[5], 'mfoot_ik': ik_ctrl[2],
1566 'main_parent': parent}
1567 args = (controls[0], controls[6], controls[5], pole, parent)
1569 for f in frames:
1570 if bake and not bones_in_frame(f, rig, *args):
1571 continue
1572 scn.frame_set(f)
1573 func1(**kwargs1)
1574 rig.pose.bones[names['parent']]['pole_vector'] = new_pole_vector_value
1575 func2(**kwargs2)
1576 if bake:
1577 bpy.ops.anim.keyframe_insert_menu(type='BUILTIN_KSI_VisualLocRot')
1578 bpy.ops.anim.keyframe_insert_menu(type='Scaling')
1579 overwrite_prop_animation(rig, rig.pose.bones[parent], 'pole_vector', new_pole_vector_value, [f])
1581 bpy.ops.pose.select_all(action='DESELECT')
1582 limb_generated_names.pop(group)
1583 break
1584 scn.frame_set(0)
1587 # noinspection PyPep8Naming
1588 class OBJECT_OT_IK2FK(bpy.types.Operator):
1589 """ Snaps IK limb on FK limb at current frame"""
1590 bl_idname = "rigify.ik2fk"
1591 bl_label = "IK2FK"
1592 bl_description = "Snaps IK limb on FK"
1593 bl_options = {'INTERNAL'}
1595 def execute(self, context):
1596 rig = verify_armature_obj(context.object)
1598 fk_to_ik(rig, window='CURRENT')
1600 return {'FINISHED'}
1603 # noinspection PyPep8Naming
1604 class OBJECT_OT_FK2IK(bpy.types.Operator):
1605 """ Snaps FK limb on IK limb at current frame"""
1606 bl_idname = "rigify.fk2ik"
1607 bl_label = "FK2IK"
1608 bl_description = "Snaps FK limb on IK"
1609 bl_options = {'INTERNAL'}
1611 def execute(self, context):
1612 rig = verify_armature_obj(context.object)
1614 ik_to_fk(rig, window='CURRENT')
1616 return {'FINISHED'}
1619 # noinspection PyPep8Naming
1620 class OBJECT_OT_TransferFKtoIK(bpy.types.Operator):
1621 """Transfers FK animation to IK"""
1622 bl_idname = "rigify.transfer_fk_to_ik"
1623 bl_label = "Transfer FK anim to IK"
1624 bl_description = "Transfer FK animation to IK bones"
1625 bl_options = {'INTERNAL'}
1627 def execute(self, context):
1628 rig = verify_armature_obj(context.object)
1630 fk_to_ik(rig)
1632 return {'FINISHED'}
1635 # noinspection PyPep8Naming
1636 class OBJECT_OT_TransferIKtoFK(bpy.types.Operator):
1637 """Transfers FK animation to IK"""
1638 bl_idname = "rigify.transfer_ik_to_fk"
1639 bl_label = "Transfer IK anim to FK"
1640 bl_description = "Transfer IK animation to FK bones"
1641 bl_options = {'INTERNAL'}
1643 def execute(self, context):
1644 rig = verify_armature_obj(context.object)
1646 ik_to_fk(rig)
1648 return {'FINISHED'}
1651 # noinspection PyPep8Naming
1652 class OBJECT_OT_ClearAnimation(bpy.types.Operator):
1653 bl_idname = "rigify.clear_animation"
1654 bl_label = "Clear Animation"
1655 bl_description = "Clear Animation For FK or IK Bones"
1656 bl_options = {'INTERNAL'}
1658 anim_type: StringProperty()
1660 def execute(self, context):
1661 rig = verify_armature_obj(context.object)
1663 if not rig.animation_data:
1664 return {'FINISHED'}
1666 act = rig.animation_data.action
1667 if not act:
1668 return {'FINISHED'}
1670 clear_animation(act, self.anim_type, names=get_limb_generated_names(rig))
1671 return {'FINISHED'}
1674 # noinspection PyPep8Naming
1675 class OBJECT_OT_Rot2Pole(bpy.types.Operator):
1676 bl_idname = "rigify.rotation_pole"
1677 bl_label = "Rotation - Pole toggle"
1678 bl_description = "Toggles IK chain between rotation and pole target"
1679 bl_options = {'INTERNAL'}
1681 bone_name: StringProperty(default='')
1682 window: StringProperty(default='ALL')
1683 toggle: BoolProperty(default=True)
1684 value: BoolProperty(default=True)
1685 bake: BoolProperty(default=True)
1687 def execute(self, context):
1688 rig = verify_armature_obj(context.object)
1690 if self.bone_name:
1691 bpy.ops.pose.select_all(action='DESELECT')
1692 rig.pose.bones[self.bone_name].bone.select = True
1694 rot_pole_toggle(rig, window=self.window, toggle=self.toggle, value=self.value, bake=self.bake)
1695 return {'FINISHED'}
1698 # noinspection PyPep8Naming
1699 class POSE_OT_rigify_collection_ref_add(bpy.types.Operator):
1700 bl_idname = "pose.rigify_collection_ref_add"
1701 bl_label = "Add Bone Collection Reference"
1702 bl_description = "Add a new row to the bone collection reference list"
1703 bl_options = {'UNDO'}
1705 prop_name: StringProperty(name="Property Name")
1707 @classmethod
1708 def poll(cls, context):
1709 return is_valid_metarig(context) and context.active_pose_bone
1711 def execute(self, context):
1712 params = get_rigify_params(context.active_pose_bone)
1713 getattr(params, self.prop_name).add()
1714 return {'FINISHED'}
1717 # noinspection PyPep8Naming
1718 class POSE_OT_rigify_collection_ref_remove(bpy.types.Operator):
1719 bl_idname = "pose.rigify_collection_ref_remove"
1720 bl_label = "Remove Bone Collection Reference"
1721 bl_description = "Remove this row from the bone collection reference list"
1722 bl_options = {'UNDO'}
1724 prop_name: StringProperty(name="Property Name")
1725 index: IntProperty(name="Entry Index")
1727 @classmethod
1728 def poll(cls, context):
1729 return is_valid_metarig(context) and context.active_pose_bone
1731 def execute(self, context):
1732 params = get_rigify_params(context.active_pose_bone)
1733 getattr(params, self.prop_name).remove(self.index)
1734 return {'FINISHED'}
1737 ###############
1738 # Registering
1740 classes = (
1741 DATA_OT_rigify_add_color_sets,
1742 DATA_OT_rigify_use_standard_colors,
1743 DATA_OT_rigify_apply_selection_colors,
1744 DATA_OT_rigify_color_set_add,
1745 DATA_OT_rigify_color_set_add_theme,
1746 DATA_OT_rigify_color_set_remove,
1747 DATA_OT_rigify_color_set_remove_all,
1748 DATA_UL_rigify_color_sets,
1749 DATA_MT_rigify_color_sets_context_menu,
1750 DATA_PT_rigify,
1751 DATA_PT_rigify_advanced,
1752 DATA_PT_rigify_color_sets,
1753 DATA_UL_rigify_bone_collections,
1754 DATA_PT_rigify_collection_list,
1755 DATA_PT_rigify_collection_ui,
1756 DATA_OT_rigify_collection_select,
1757 DATA_OT_rigify_collection_set_ui_row,
1758 DATA_OT_rigify_collection_add_ui_row,
1759 DATA_PT_rigify_samples,
1760 BONE_PT_rigify_buttons,
1761 VIEW3D_PT_rigify_animation_tools,
1762 VIEW3D_PT_tools_rigify_dev,
1763 Generate,
1764 UpgradeMetarigTypes,
1765 UpgradeMetarigLayers,
1766 ValidateMetarigLayers,
1767 Sample,
1768 VIEW3D_MT_rigify,
1769 EncodeMetarig,
1770 EncodeMetarigSample,
1771 EncodeWidget,
1772 OBJECT_OT_FK2IK,
1773 OBJECT_OT_IK2FK,
1774 OBJECT_OT_TransferFKtoIK,
1775 OBJECT_OT_TransferIKtoFK,
1776 OBJECT_OT_ClearAnimation,
1777 OBJECT_OT_Rot2Pole,
1778 POSE_OT_rigify_collection_ref_add,
1779 POSE_OT_rigify_collection_ref_remove,
1783 def register():
1784 from bpy.utils import register_class
1786 animation_register()
1788 # Classes.
1789 for cls in classes:
1790 register_class(cls)
1792 bpy.types.VIEW3D_MT_editor_menus.append(draw_rigify_menu)
1793 bpy.types.VIEW3D_MT_edit_mesh.prepend(draw_mesh_edit_menu)
1795 # Sub-modules.
1796 rot_mode.register()
1799 def unregister():
1800 from bpy.utils import unregister_class
1802 # Sub-modules.
1803 rot_mode.unregister()
1805 # Classes.
1806 for cls in classes:
1807 unregister_class(cls)
1809 bpy.types.VIEW3D_MT_editor_menus.remove(draw_rigify_menu)
1810 bpy.types.VIEW3D_MT_edit_mesh.remove(draw_mesh_edit_menu)
1812 animation_unregister()