Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'rigify/utils/bones.py')
-rw-r--r--rigify/utils/bones.py165
1 files changed, 103 insertions, 62 deletions
diff --git a/rigify/utils/bones.py b/rigify/utils/bones.py
index 82b9451f..8419eb90 100644
--- a/rigify/utils/bones.py
+++ b/rigify/utils/bones.py
@@ -2,15 +2,18 @@
import bpy
import math
-from mathutils import Vector, Matrix, Color
+
+from mathutils import Vector, Matrix
+from typing import Optional, Callable
from .errors import MetarigError
from .naming import get_name, make_derived_name, is_control_bone
-from .misc import pairwise
+from .misc import pairwise, ArmatureObject
+
-#=======================
+########################
# Bone collection
-#=======================
+########################
class BoneDict(dict):
"""
@@ -18,23 +21,22 @@ class BoneDict(dict):
Allows access to contained items as attributes, and only
accepts certain types of values.
+
+ @DynamicAttrs
"""
@staticmethod
def __sanitize_attr(key, value):
if hasattr(BoneDict, key):
- raise KeyError("Invalid BoneDict key: %s" % (key))
+ raise KeyError(f"Invalid BoneDict key: {key}")
- if (value is None or
- isinstance(value, str) or
- isinstance(value, list) or
- isinstance(value, BoneDict)):
+ if value is None or isinstance(value, (str, list, BoneDict)):
return value
if isinstance(value, dict):
return BoneDict(value)
- raise ValueError("Invalid BoneDict value: %r" % (value))
+ raise ValueError(f"Invalid BoneDict value: {repr(value)}")
def __init__(self, *args, **kwargs):
super().__init__()
@@ -71,13 +73,14 @@ class BoneDict(dict):
return all_bones
-#=======================
+
+########################
# Bone manipulation
-#=======================
+########################
#
# NOTE: PREFER USING BoneUtilityMixin IN NEW STYLE RIGS!
-def get_bone(obj, bone_name):
+def get_bone(obj: ArmatureObject, bone_name: Optional[str]):
"""Get EditBone or PoseBone by name, depending on the current mode."""
if not bone_name:
return None
@@ -87,7 +90,7 @@ def get_bone(obj, bone_name):
return bones[bone_name]
-def new_bone(obj, bone_name):
+def new_bone(obj: ArmatureObject, bone_name: str):
""" Adds a new bone to the given armature object.
Returns the resulting bone's name.
"""
@@ -102,11 +105,13 @@ def new_bone(obj, bone_name):
raise MetarigError("Can't add new bone '%s' outside of edit mode" % bone_name)
-def copy_bone(obj, bone_name, assign_name='', *, parent=False, inherit_scale=False, bbone=False, length=None, scale=None):
+def copy_bone(obj: ArmatureObject, bone_name: str, assign_name='', *,
+ parent=False, inherit_scale=False, bbone=False,
+ length: Optional[float] = None, scale: Optional[float] = None):
""" Makes a copy of the given bone in the given armature object.
Returns the resulting bone's name.
"""
- #if bone_name not in obj.data.bones:
+
if bone_name not in obj.data.edit_bones:
raise MetarigError("copy_bone(): bone '%s' not found, cannot copy it" % bone_name)
@@ -116,7 +121,6 @@ def copy_bone(obj, bone_name, assign_name='', *, parent=False, inherit_scale=Fal
# Copy the edit bone
edit_bone_1 = obj.data.edit_bones[bone_name]
edit_bone_2 = obj.data.edit_bones.new(assign_name)
- bone_name_1 = bone_name
bone_name_2 = edit_bone_2.name
# Copy edit bone attributes
@@ -137,6 +141,7 @@ def copy_bone(obj, bone_name, assign_name='', *, parent=False, inherit_scale=Fal
edit_bone_2.inherit_scale = edit_bone_1.inherit_scale
if bbone:
+ # noinspection SpellCheckingInspection
for name in ['bbone_segments',
'bbone_easein', 'bbone_easeout',
'bbone_rollin', 'bbone_rollout',
@@ -155,9 +160,10 @@ def copy_bone(obj, bone_name, assign_name='', *, parent=False, inherit_scale=Fal
raise MetarigError("Cannot copy bones outside of edit mode")
-def copy_bone_properties(obj, bone_name_1, bone_name_2, transforms=True, props=True, widget=True):
+def copy_bone_properties(obj: ArmatureObject, bone_name_1: str, bone_name_2: str,
+ transforms=True, props=True, widget=True):
""" Copy transform and custom properties from bone 1 to bone 2. """
- if obj.mode in {'OBJECT','POSE'}:
+ if obj.mode in {'OBJECT', 'POSE'}:
# Get the pose bones
pose_bone_1 = obj.pose.bones[bone_name_1]
pose_bone_2 = obj.pose.bones[bone_name_2]
@@ -197,7 +203,7 @@ def _legacy_copy_bone(obj, bone_name, assign_name=''):
return new_name
-def flip_bone(obj, bone_name):
+def flip_bone(obj: ArmatureObject, bone_name: str):
""" Flips an edit bone.
"""
if bone_name not in obj.data.edit_bones:
@@ -214,11 +220,11 @@ def flip_bone(obj, bone_name):
raise MetarigError("Cannot flip bones outside of edit mode")
-def flip_bone_chain(obj, bone_names):
+def flip_bone_chain(obj: ArmatureObject, bone_names: list[str]):
"""Flips a connected bone chain."""
assert obj.mode == 'EDIT'
- bones = [ obj.data.edit_bones[name] for name in bone_names ]
+ bones = [obj.data.edit_bones[name] for name in bone_names]
# Verify chain and unparent
for prev_bone, bone in pairwise(bones):
@@ -242,7 +248,9 @@ def flip_bone_chain(obj, bone_names):
bone.use_connect = True
-def put_bone(obj, bone_name, pos, *, matrix=None, length=None, scale=None):
+def put_bone(obj: ArmatureObject, bone_name: str, pos: Optional[Vector], *,
+ matrix: Optional[Matrix] = None,
+ length: Optional[float] = None, scale: Optional[float] = None):
""" Places a bone at the given position.
"""
if bone_name not in obj.data.edit_bones:
@@ -274,13 +282,14 @@ def put_bone(obj, bone_name, pos, *, matrix=None, length=None, scale=None):
raise MetarigError("Cannot 'put' bones outside of edit mode")
-def disable_bbones(obj, bone_names):
+def disable_bbones(obj: ArmatureObject, bone_names: list[str]):
"""Disables B-Bone segments on the specified bones."""
assert(obj.mode != 'EDIT')
for bone in bone_names:
obj.data.bones[bone].bbone_segments = 1
+# noinspection SpellCheckingInspection
def _legacy_make_nonscaling_child(obj, bone_name, location, child_name_postfix=""):
""" Takes the named bone and creates a non-scaling child of it at
the given location. The returned bone (returned by name) is not
@@ -345,48 +354,65 @@ def _legacy_make_nonscaling_child(obj, bone_name, location, child_name_postfix="
raise MetarigError("Cannot make nonscaling child outside of edit mode")
-#===================================
+####################################
# Bone manipulation as rig methods
-#===================================
+####################################
class BoneUtilityMixin(object):
+ obj: ArmatureObject
+ register_new_bone: Callable[[str, Optional[str]], None]
+
"""
Provides methods for more convenient creation of bones.
Requires self.obj to be the armature object being worked on.
"""
- def register_new_bone(self, new_name, old_name=None):
+ def register_new_bone(self, new_name: str, old_name: Optional[str] = None):
"""Registers creation or renaming of a bone based on old_name"""
pass
- def new_bone(self, new_name):
+ def new_bone(self, new_name: str) -> str:
"""Create a new bone with the specified name."""
name = new_bone(self.obj, new_name)
- self.register_new_bone(name)
+ self.register_new_bone(name, None)
return name
- def copy_bone(self, bone_name, new_name='', *, parent=False, inherit_scale=False, bbone=False, length=None, scale=None):
+ def copy_bone(self, bone_name: str, new_name='', *,
+ parent=False, inherit_scale=False, bbone=False,
+ length: Optional[float] = None,
+ scale: Optional[float] = None) -> str:
"""Copy the bone with the given name, returning the new name."""
- name = copy_bone(self.obj, bone_name, new_name, parent=parent, inherit_scale=inherit_scale, bbone=bbone, length=length, scale=scale)
+ name = copy_bone(self.obj, bone_name, new_name,
+ parent=parent, inherit_scale=inherit_scale,
+ bbone=bbone, length=length, scale=scale)
self.register_new_bone(name, bone_name)
return name
- def copy_bone_properties(self, src_name, tgt_name, *, props=True, ui_controls=None, **kwargs):
- """Copy pose-mode properties of the bone."""
+ def copy_bone_properties(self, src_name: str, tgt_name: str, *,
+ props=True,
+ ui_controls: list[str] | bool | None = None,
+ **kwargs):
+ """Copy pose-mode properties of the bone. For using ui_controls, self must be a Rig."""
+
+ if ui_controls:
+ from ..base_rig import BaseRig
+ assert isinstance(self, BaseRig)
+
if props:
if ui_controls is None and is_control_bone(tgt_name) and hasattr(self, 'script'):
ui_controls = [tgt_name]
elif ui_controls is True:
ui_controls = self.bones.flatten('ctrl')
- copy_bone_properties(self.obj, src_name, tgt_name, props=props and not ui_controls, **kwargs)
+ copy_bone_properties(
+ self.obj, src_name, tgt_name, props=props and not ui_controls, **kwargs)
if props and ui_controls:
from .mechanism import copy_custom_properties_with_ui
copy_custom_properties_with_ui(self, src_name, tgt_name, ui_controls=ui_controls)
- def rename_bone(self, old_name, new_name):
+ def rename_bone(self, old_name: str, new_name: str) -> str:
"""Rename the bone, returning the actual new name."""
bone = self.get_bone(old_name)
bone.name = new_name
@@ -394,15 +420,17 @@ class BoneUtilityMixin(object):
self.register_new_bone(bone.name, old_name)
return bone.name
- def get_bone(self, bone_name):
+ def get_bone(self, bone_name: Optional[str])\
+ -> Optional[bpy.types.EditBone | bpy.types.PoseBone]:
"""Get EditBone or PoseBone by name, depending on the current mode."""
return get_bone(self.obj, bone_name)
- def get_bone_parent(self, bone_name):
+ def get_bone_parent(self, bone_name: str) -> Optional[str]:
"""Get the name of the parent bone, or None."""
return get_name(self.get_bone(bone_name).parent)
- def set_bone_parent(self, bone_name, parent_name, use_connect=False, inherit_scale=None):
+ def set_bone_parent(self, bone_name: str, parent_name: Optional[str],
+ use_connect=False, inherit_scale: Optional[str] = None):
"""Set the parent of the bone."""
eb = self.obj.data.edit_bones
bone = eb[bone_name]
@@ -412,16 +440,20 @@ class BoneUtilityMixin(object):
bone.inherit_scale = inherit_scale
bone.parent = (eb[parent_name] if parent_name else None)
- def parent_bone_chain(self, bone_names, use_connect=None, inherit_scale=None):
+ def parent_bone_chain(self, bone_names: list[str],
+ use_connect: Optional[bool] = None,
+ inherit_scale: Optional[str] = None):
"""Link bones into a chain with parenting. First bone may be None."""
for parent, child in pairwise(bone_names):
- self.set_bone_parent(child, parent, use_connect=use_connect, inherit_scale=inherit_scale)
+ self.set_bone_parent(
+ child, parent, use_connect=use_connect, inherit_scale=inherit_scale)
+
-#=============================================
+##############################################
# B-Bones
-#=============================================
+##############################################
-def connect_bbone_chain_handles(obj, bone_names):
+def connect_bbone_chain_handles(obj: ArmatureObject, bone_names: list[str]):
assert obj.mode == 'EDIT'
for prev_name, next_name in pairwise(bone_names):
@@ -434,26 +466,28 @@ def connect_bbone_chain_handles(obj, bone_names):
next_bone.bbone_handle_type_start = 'ABSOLUTE'
next_bone.bbone_custom_handle_start = prev_bone
-#=============================================
-# Math
-#=============================================
+##############################################
+# Math
+##############################################
-def is_same_position(obj, bone_name1, bone_name2):
+def is_same_position(obj: ArmatureObject, bone_name1: str, bone_name2: str):
head1 = get_bone(obj, bone_name1).head
head2 = get_bone(obj, bone_name2).head
return (head1 - head2).length < 1e-5
-def is_connected_position(obj, bone_name1, bone_name2):
+def is_connected_position(obj: ArmatureObject, bone_name1: str, bone_name2: str):
tail1 = get_bone(obj, bone_name1).tail
head2 = get_bone(obj, bone_name2).head
return (tail1 - head2).length < 1e-5
-def copy_bone_position(obj, bone_name, target_bone_name, *, length=None, scale=None):
+def copy_bone_position(obj: ArmatureObject, bone_name: str, target_bone_name: str, *,
+ length: Optional[float] = None,
+ scale: Optional[float] = None):
""" Completely copies the position and orientation of the bone. """
bone1_e = obj.data.edit_bones[bone_name]
bone2_e = obj.data.edit_bones[target_bone_name]
@@ -469,7 +503,7 @@ def copy_bone_position(obj, bone_name, target_bone_name, *, length=None, scale=N
bone2_e.length *= scale
-def align_bone_orientation(obj, bone_name, target_bone_name):
+def align_bone_orientation(obj: ArmatureObject, bone_name: str, target_bone_name: str):
""" Aligns the orientation of bone to target bone. """
bone1_e = obj.data.edit_bones[bone_name]
bone2_e = obj.data.edit_bones[target_bone_name]
@@ -480,7 +514,7 @@ def align_bone_orientation(obj, bone_name, target_bone_name):
bone1_e.roll = bone2_e.roll
-def set_bone_orientation(obj, bone_name, orientation):
+def set_bone_orientation(obj: ArmatureObject, bone_name: str, orientation: str | Matrix):
""" Aligns the orientation of bone to target bone or matrix. """
if isinstance(orientation, str):
align_bone_orientation(obj, bone_name, orientation)
@@ -494,7 +528,7 @@ def set_bone_orientation(obj, bone_name, orientation):
bone_e.matrix = matrix
-def align_bone_roll(obj, bone1, bone2):
+def align_bone_roll(obj: ArmatureObject, bone1: str, bone2: str):
""" Aligns the roll of two bones.
"""
bone1_e = obj.data.edit_bones[bone1]
@@ -539,7 +573,7 @@ def align_bone_roll(obj, bone1, bone2):
bone1_e.roll = -roll
-def align_bone_x_axis(obj, bone, vec):
+def align_bone_x_axis(obj: ArmatureObject, bone: str, vec: Vector):
""" Rolls the bone to align its x-axis as closely as possible to
the given vector.
Must be in edit mode.
@@ -564,7 +598,7 @@ def align_bone_x_axis(obj, bone, vec):
bone_e.roll += angle * 2
-def align_bone_z_axis(obj, bone, vec):
+def align_bone_z_axis(obj: ArmatureObject, bone: str, vec: Vector):
""" Rolls the bone to align its z-axis as closely as possible to
the given vector.
Must be in edit mode.
@@ -589,7 +623,7 @@ def align_bone_z_axis(obj, bone, vec):
bone_e.roll += angle * 2
-def align_bone_y_axis(obj, bone, vec):
+def align_bone_y_axis(obj: ArmatureObject, bone: str, vec: Vector):
""" Matches the bone y-axis to
the given vector.
Must be in edit mode.
@@ -597,14 +631,15 @@ def align_bone_y_axis(obj, bone, vec):
bone_e = obj.data.edit_bones[bone]
vec.normalize()
+
vec = vec * bone_e.length
bone_e.tail = bone_e.head + vec
-def compute_chain_x_axis(obj, bone_names):
+def compute_chain_x_axis(obj: ArmatureObject, bone_names: list[str]):
"""
- Compute the x axis of all bones to be perpendicular
+ Compute the X axis of all bones to be perpendicular
to the primary plane in which the bones lie.
"""
eb = obj.data.edit_bones
@@ -615,6 +650,7 @@ def compute_chain_x_axis(obj, bone_names):
# Compute normal to the plane defined by the first bone,
# and the end of the last bone in the chain
+
chain_y_axis = last_bone.tail - first_bone.head
chain_rot_axis = first_bone.y_axis.cross(chain_y_axis)
@@ -624,9 +660,9 @@ def compute_chain_x_axis(obj, bone_names):
return chain_rot_axis.normalized()
-def align_chain_x_axis(obj, bone_names):
+def align_chain_x_axis(obj: ArmatureObject, bone_names: list[str]):
"""
- Aligns the x axis of all bones to be perpendicular
+ Aligns the X axis of all bones to be perpendicular
to the primary plane in which the bones lie.
"""
chain_rot_axis = compute_chain_x_axis(obj, bone_names)
@@ -635,7 +671,10 @@ def align_chain_x_axis(obj, bone_names):
align_bone_x_axis(obj, name, chain_rot_axis)
-def align_bone_to_axis(obj, bone_name, axis, *, length=None, roll=0, flip=False):
+def align_bone_to_axis(obj: ArmatureObject, bone_name: str, axis: str, *,
+ length: Optional[float] = None,
+ roll: Optional[float] = 0.0,
+ flip=False):
"""
Aligns the Y axis of the bone to the global axis (x,y,z,-x,-y,-z),
optionally adjusting length and initially flipping the bone.
@@ -651,7 +690,7 @@ def align_bone_to_axis(obj, bone_name, axis, *, length=None, roll=0, flip=False)
length = -length
axis = axis[1:]
- vec = Vector((0,0,0))
+ vec = Vector((0, 0, 0))
setattr(vec, axis, length)
if flip:
@@ -664,7 +703,9 @@ def align_bone_to_axis(obj, bone_name, axis, *, length=None, roll=0, flip=False)
bone_e.roll = roll
-def set_bone_widget_transform(obj, bone_name, transform_bone, use_size=True, scale=1.0, target_size=False):
+def set_bone_widget_transform(obj: ArmatureObject, bone_name: str,
+ transform_bone: Optional[str], *,
+ use_size=True, scale=1.0, target_size=False):
assert obj.mode != 'EDIT'
bone = obj.pose.bones[bone_name]