Changeset View
Changeset View
Standalone View
Standalone View
rigify/utils/bones.py
| Show All 18 Lines | |||||
| # <pep8 compliant> | # <pep8 compliant> | ||||
| import bpy | import bpy | ||||
| import math | import math | ||||
| from mathutils import Vector, Matrix, Color | from mathutils import Vector, Matrix, Color | ||||
| from rna_prop_ui import rna_idprop_ui_prop_get | from rna_prop_ui import rna_idprop_ui_prop_get | ||||
| from .errors import MetarigError | from .errors import MetarigError | ||||
| from .naming import make_derived_name | from .naming import get_name, make_derived_name | ||||
| from .misc import pairwise | |||||
| #======================= | #======================= | ||||
| # Bone collection | # Bone collection | ||||
| #======================= | #======================= | ||||
| class BoneDict(dict): | class BoneDict(dict): | ||||
| """ | """ | ||||
| Special dictionary for holding bone names in a structured way. | Special dictionary for holding bone names in a structured way. | ||||
| Show All 14 Lines | def __sanitize_attr(key, value): | ||||
| return value | return value | ||||
| if isinstance(value, dict): | if isinstance(value, dict): | ||||
| return BoneDict(value) | return BoneDict(value) | ||||
| raise ValueError("Invalid BoneDict value: %r" % (value)) | raise ValueError("Invalid BoneDict value: %r" % (value)) | ||||
| def __init__(self, *args, **kwargs): | def __init__(self, *args, **kwargs): | ||||
| super(BoneDict, self).__init__() | super().__init__() | ||||
| for key, value in dict(*args, **kwargs).items(): | for key, value in dict(*args, **kwargs).items(): | ||||
| dict.__setitem__(self, key, BoneDict.__sanitize_attr(key, value)) | dict.__setitem__(self, key, BoneDict.__sanitize_attr(key, value)) | ||||
| self.__dict__ = self | self.__dict__ = self | ||||
| def __repr__(self): | def __repr__(self): | ||||
| return "BoneDict(%s)" % (dict.__repr__(self)) | return "BoneDict(%s)" % (dict.__repr__(self)) | ||||
| def __setitem__(self, key, value): | def __setitem__(self, key, value): | ||||
| dict.__setitem__(self, key, BoneDict.__sanitize_attr(key, value)) | dict.__setitem__(self, key, BoneDict.__sanitize_attr(key, value)) | ||||
| def update(self, *args, **kwargs): | def update(self, *args, **kwargs): | ||||
| for key, value in dict(*args, **kwargs).items(): | for key, value in dict(*args, **kwargs).items(): | ||||
| dict.__setitem__(self, key, BoneDict.__sanitize_attr(key, value)) | dict.__setitem__(self, key, BoneDict.__sanitize_attr(key, value)) | ||||
| def flatten(self): | def flatten(self, key=None): | ||||
| """Return all contained bones as a list.""" | """Return all contained bones or a single key as a list.""" | ||||
| items = [self[key]] if key is not None else self.values() | |||||
| all_bones = [] | all_bones = [] | ||||
| for item in self.values(): | for item in items: | ||||
| if isinstance(item, BoneDict): | if isinstance(item, BoneDict): | ||||
| all_bones.extend(item.flatten()) | all_bones.extend(item.flatten()) | ||||
| elif isinstance(item, list): | elif isinstance(item, list): | ||||
| all_bones.extend(item) | all_bones.extend(item) | ||||
| elif item is not None: | elif item is not None: | ||||
| all_bones.append(item) | all_bones.append(item) | ||||
| return all_bones | return all_bones | ||||
| #======================= | #======================= | ||||
| # Bone manipulation | # Bone manipulation | ||||
| #======================= | #======================= | ||||
| # | |||||
| # NOTE: PREFER USING BoneUtilityMixin IN NEW STYLE RIGS! | |||||
| def get_bone(obj, bone_name): | |||||
| """Get EditBone or PoseBone by name, depending on the current mode.""" | |||||
| if not bone_name: | |||||
| return None | |||||
| bones = obj.data.edit_bones if obj.mode == 'EDIT' else obj.pose.bones | |||||
| if bone_name not in bones: | |||||
| raise MetarigError("bone '%s' not found" % bone_name) | |||||
| return bones[bone_name] | |||||
| def new_bone(obj, bone_name): | def new_bone(obj, bone_name): | ||||
| """ Adds a new bone to the given armature object. | """ Adds a new bone to the given armature object. | ||||
| Returns the resulting bone's name. | Returns the resulting bone's name. | ||||
| """ | """ | ||||
| if obj == bpy.context.active_object and bpy.context.mode == 'EDIT_ARMATURE': | if obj == bpy.context.active_object and bpy.context.mode == 'EDIT_ARMATURE': | ||||
| edit_bone = obj.data.edit_bones.new(bone_name) | edit_bone = obj.data.edit_bones.new(bone_name) | ||||
| name = edit_bone.name | name = edit_bone.name | ||||
| edit_bone.head = (0, 0, 0) | edit_bone.head = (0, 0, 0) | ||||
| edit_bone.tail = (0, 1, 0) | edit_bone.tail = (0, 1, 0) | ||||
| edit_bone.roll = 0 | edit_bone.roll = 0 | ||||
| bpy.ops.object.mode_set(mode='OBJECT') | |||||
| bpy.ops.object.mode_set(mode='EDIT') | |||||
| return name | return name | ||||
| else: | else: | ||||
| raise MetarigError("Can't add new bone '%s' outside of edit mode" % bone_name) | raise MetarigError("Can't add new bone '%s' outside of edit mode" % bone_name) | ||||
| def copy_bone_simple(obj, bone_name, assign_name=''): | def copy_bone(obj, bone_name, assign_name='', *, parent=False, bbone=False, length=None, scale=None): | ||||
| """ Makes a copy of the given bone in the given armature object. | """ Makes a copy of the given bone in the given armature object. | ||||
| but only copies head, tail positions and roll. Does not | Returns the resulting bone's name. | ||||
| address parenting either. | |||||
| """ | """ | ||||
| #if bone_name not in obj.data.bones: | #if bone_name not in obj.data.bones: | ||||
| if bone_name not in obj.data.edit_bones: | if bone_name not in obj.data.edit_bones: | ||||
| raise MetarigError("copy_bone(): bone '%s' not found, cannot copy it" % bone_name) | raise MetarigError("copy_bone(): bone '%s' not found, cannot copy it" % bone_name) | ||||
| if obj == bpy.context.active_object and bpy.context.mode == 'EDIT_ARMATURE': | if obj == bpy.context.active_object and bpy.context.mode == 'EDIT_ARMATURE': | ||||
| if assign_name == '': | if assign_name == '': | ||||
| assign_name = bone_name | assign_name = bone_name | ||||
| # Copy the edit bone | # Copy the edit bone | ||||
| edit_bone_1 = obj.data.edit_bones[bone_name] | edit_bone_1 = obj.data.edit_bones[bone_name] | ||||
| edit_bone_2 = obj.data.edit_bones.new(assign_name) | edit_bone_2 = obj.data.edit_bones.new(assign_name) | ||||
| bone_name_1 = bone_name | bone_name_1 = bone_name | ||||
| bone_name_2 = edit_bone_2.name | bone_name_2 = edit_bone_2.name | ||||
| # Copy edit bone attributes | # Copy edit bone attributes | ||||
| edit_bone_2.layers = list(edit_bone_1.layers) | edit_bone_2.layers = list(edit_bone_1.layers) | ||||
| edit_bone_2.head = Vector(edit_bone_1.head) | edit_bone_2.head = Vector(edit_bone_1.head) | ||||
| edit_bone_2.tail = Vector(edit_bone_1.tail) | edit_bone_2.tail = Vector(edit_bone_1.tail) | ||||
| edit_bone_2.roll = edit_bone_1.roll | edit_bone_2.roll = edit_bone_1.roll | ||||
| return bone_name_2 | if parent: | ||||
| else: | |||||
| raise MetarigError("Cannot copy bones outside of edit mode") | |||||
| def copy_bone(obj, bone_name, assign_name=''): | |||||
| """ 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) | |||||
| if obj == bpy.context.active_object and bpy.context.mode == 'EDIT_ARMATURE': | |||||
| if assign_name == '': | |||||
| assign_name = bone_name | |||||
| # 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 | |||||
| edit_bone_2.parent = edit_bone_1.parent | edit_bone_2.parent = edit_bone_1.parent | ||||
| edit_bone_2.use_connect = edit_bone_1.use_connect | edit_bone_2.use_connect = edit_bone_1.use_connect | ||||
| # Copy edit bone attributes | |||||
| edit_bone_2.layers = list(edit_bone_1.layers) | |||||
| edit_bone_2.head = Vector(edit_bone_1.head) | |||||
| edit_bone_2.tail = Vector(edit_bone_1.tail) | |||||
| edit_bone_2.roll = edit_bone_1.roll | |||||
| edit_bone_2.use_inherit_rotation = edit_bone_1.use_inherit_rotation | edit_bone_2.use_inherit_rotation = edit_bone_1.use_inherit_rotation | ||||
| edit_bone_2.use_inherit_scale = edit_bone_1.use_inherit_scale | edit_bone_2.use_inherit_scale = edit_bone_1.use_inherit_scale | ||||
| edit_bone_2.use_local_location = edit_bone_1.use_local_location | edit_bone_2.use_local_location = edit_bone_1.use_local_location | ||||
| edit_bone_2.use_deform = edit_bone_1.use_deform | if bbone: | ||||
| edit_bone_2.bbone_segments = edit_bone_1.bbone_segments | for name in ['bbone_segments', | ||||
| edit_bone_2.bbone_easein = edit_bone_1.bbone_easein | 'bbone_easein', 'bbone_easeout', | ||||
| edit_bone_2.bbone_easeout = edit_bone_1.bbone_easeout | 'bbone_rollin', 'bbone_rollout', | ||||
| 'bbone_curveinx', 'bbone_curveiny', 'bbone_curveoutx', 'bbone_curveouty', | |||||
| 'bbone_scaleinx', 'bbone_scaleiny', 'bbone_scaleoutx', 'bbone_scaleouty']: | |||||
| setattr(edit_bone_2, name, getattr(edit_bone_1, name)) | |||||
| # Resize the bone after copy if requested | |||||
| if length is not None: | |||||
| edit_bone_2.length = length | |||||
| elif scale is not None: | |||||
| edit_bone_2.length *= scale | |||||
| return bone_name_2 | |||||
| else: | |||||
| raise MetarigError("Cannot copy bones outside of edit mode") | |||||
| bpy.ops.object.mode_set(mode='OBJECT') | |||||
| def copy_bone_properties(obj, bone_name_1, bone_name_2): | |||||
| """ Copy transform and custom properties from bone 1 to bone 2. """ | |||||
| if obj.mode in {'OBJECT','POSE'}: | |||||
| # Get the pose bones | # Get the pose bones | ||||
| pose_bone_1 = obj.pose.bones[bone_name_1] | pose_bone_1 = obj.pose.bones[bone_name_1] | ||||
| pose_bone_2 = obj.pose.bones[bone_name_2] | pose_bone_2 = obj.pose.bones[bone_name_2] | ||||
| # Copy pose bone attributes | # Copy pose bone attributes | ||||
| pose_bone_2.rotation_mode = pose_bone_1.rotation_mode | pose_bone_2.rotation_mode = pose_bone_1.rotation_mode | ||||
| pose_bone_2.rotation_axis_angle = tuple(pose_bone_1.rotation_axis_angle) | pose_bone_2.rotation_axis_angle = tuple(pose_bone_1.rotation_axis_angle) | ||||
| pose_bone_2.rotation_euler = tuple(pose_bone_1.rotation_euler) | pose_bone_2.rotation_euler = tuple(pose_bone_1.rotation_euler) | ||||
| Show All 11 Lines | if obj.mode in {'OBJECT','POSE'}: | ||||
| and key != "rigify_parameters" \ | and key != "rigify_parameters" \ | ||||
| and key != "rigify_type": | and key != "rigify_type": | ||||
| prop1 = rna_idprop_ui_prop_get(pose_bone_1, key, create=False) | prop1 = rna_idprop_ui_prop_get(pose_bone_1, key, create=False) | ||||
| pose_bone_2[key] = pose_bone_1[key] | pose_bone_2[key] = pose_bone_1[key] | ||||
| if prop1 is not None: | if prop1 is not None: | ||||
| prop2 = rna_idprop_ui_prop_get(pose_bone_2, key, create=True) | prop2 = rna_idprop_ui_prop_get(pose_bone_2, key, create=True) | ||||
| for key in prop1.keys(): | for key in prop1.keys(): | ||||
| prop2[key] = prop1[key] | prop2[key] = prop1[key] | ||||
| else: | |||||
| raise MetarigError("Cannot copy bone properties in edit mode") | |||||
| bpy.ops.object.mode_set(mode='EDIT') | |||||
| return bone_name_2 | def _legacy_copy_bone(obj, bone_name, assign_name=''): | ||||
| else: | """LEGACY ONLY, DON'T USE""" | ||||
| raise MetarigError("Cannot copy bones outside of edit mode") | new_name = copy_bone(obj, bone_name, assign_name, parent=True, bbone=True) | ||||
| # Mode switch PER BONE CREATION?! | |||||
| bpy.ops.object.mode_set(mode='OBJECT') | |||||
| copy_bone_properties(obj, bone_name, new_name) | |||||
| bpy.ops.object.mode_set(mode='EDIT') | |||||
| return new_name | |||||
| def flip_bone(obj, bone_name): | def flip_bone(obj, bone_name): | ||||
| """ Flips an edit bone. | """ Flips an edit bone. | ||||
| """ | """ | ||||
| if bone_name not in obj.data.bones: | if bone_name not in obj.data.edit_bones: | ||||
| raise MetarigError("flip_bone(): bone '%s' not found, cannot copy it" % bone_name) | raise MetarigError("flip_bone(): bone '%s' not found, cannot copy it" % bone_name) | ||||
| if obj == bpy.context.active_object and bpy.context.mode == 'EDIT_ARMATURE': | if obj == bpy.context.active_object and bpy.context.mode == 'EDIT_ARMATURE': | ||||
| bone = obj.data.edit_bones[bone_name] | bone = obj.data.edit_bones[bone_name] | ||||
| head = Vector(bone.head) | head = Vector(bone.head) | ||||
| tail = Vector(bone.tail) | tail = Vector(bone.tail) | ||||
| bone.tail = head + tail | bone.tail = head + tail | ||||
| bone.head = tail | bone.head = tail | ||||
| bone.tail = head | bone.tail = head | ||||
| else: | else: | ||||
| raise MetarigError("Cannot flip bones outside of edit mode") | raise MetarigError("Cannot flip bones outside of edit mode") | ||||
| def flip_bone_chain(obj, bone_names): | |||||
| """Flips a connected bone chain.""" | |||||
| assert obj.mode == 'EDIT' | |||||
| bones = [ obj.data.edit_bones[name] for name in bone_names ] | |||||
| # Verify chain and unparent | |||||
| for prev_bone, bone in pairwise(bones): | |||||
| assert bone.parent == prev_bone and bone.use_connect | |||||
| for bone in bones: | |||||
| bone.parent = None | |||||
| bone.use_connect = False | |||||
| for child in bone.children: | |||||
| child.use_connect = False | |||||
| # Flip bones | |||||
| for bone in bones: | |||||
| head, tail = Vector(bone.head), Vector(bone.tail) | |||||
| bone.tail = head + tail | |||||
| bone.head, bone.tail = tail, head | |||||
| # Re-parent | |||||
| for bone, next_bone in pairwise(bones): | |||||
| bone.parent = next_bone | |||||
| bone.use_connect = True | |||||
| def put_bone(obj, bone_name, pos): | def put_bone(obj, bone_name, pos): | ||||
| """ Places a bone at the given position. | """ Places a bone at the given position. | ||||
| """ | """ | ||||
| if bone_name not in obj.data.bones: | if bone_name not in obj.data.edit_bones: | ||||
| raise MetarigError("put_bone(): bone '%s' not found, cannot move it" % bone_name) | raise MetarigError("put_bone(): bone '%s' not found, cannot move it" % bone_name) | ||||
| if obj == bpy.context.active_object and bpy.context.mode == 'EDIT_ARMATURE': | if obj == bpy.context.active_object and bpy.context.mode == 'EDIT_ARMATURE': | ||||
| bone = obj.data.edit_bones[bone_name] | bone = obj.data.edit_bones[bone_name] | ||||
| delta = pos - bone.head | delta = pos - bone.head | ||||
| bone.translate(delta) | bone.translate(delta) | ||||
| else: | else: | ||||
| raise MetarigError("Cannot 'put' bones outside of edit mode") | raise MetarigError("Cannot 'put' bones outside of edit mode") | ||||
| def make_nonscaling_child(obj, bone_name, location, child_name_postfix=""): | def disable_bbones(obj, bone_names): | ||||
| """Disables B-Bone segments on the specified bones.""" | |||||
| assert(obj.mode != 'EDIT') | |||||
| for bone in bone_names: | |||||
| obj.data.bones[bone].bbone_segments = 1 | |||||
| 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 | """ Takes the named bone and creates a non-scaling child of it at | ||||
| the given location. The returned bone (returned by name) is not | the given location. The returned bone (returned by name) is not | ||||
| a true child, but behaves like one sans inheriting scaling. | a true child, but behaves like one sans inheriting scaling. | ||||
| It is intended as an intermediate construction to prevent rig types | It is intended as an intermediate construction to prevent rig types | ||||
| from scaling with their parents. The named bone is assumed to be | from scaling with their parents. The named bone is assumed to be | ||||
| an ORG bone. | an ORG bone. | ||||
| LEGACY ONLY, DON'T USE | |||||
| """ | """ | ||||
| if bone_name not in obj.data.bones: | if bone_name not in obj.data.edit_bones: | ||||
| raise MetarigError("make_nonscaling_child(): bone '%s' not found, cannot copy it" % bone_name) | raise MetarigError("make_nonscaling_child(): bone '%s' not found, cannot copy it" % bone_name) | ||||
| if obj == bpy.context.active_object and bpy.context.mode == 'EDIT_ARMATURE': | if obj == bpy.context.active_object and bpy.context.mode == 'EDIT_ARMATURE': | ||||
| # Create desired names for bones | # Create desired names for bones | ||||
| name1 = make_derived_name(bone_name, 'mch', child_name_postfix + "_ns_ch") | name1 = make_derived_name(bone_name, 'mch', child_name_postfix + "_ns_ch") | ||||
| name2 = make_derived_name(bone_name, 'mch', child_name_postfix + "_ns_intr") | name2 = make_derived_name(bone_name, 'mch', child_name_postfix + "_ns_intr") | ||||
| # Create bones | # Create bones | ||||
| Show All 36 Lines | if obj == bpy.context.active_object and bpy.context.mode == 'EDIT_ARMATURE': | ||||
| bpy.ops.object.mode_set(mode='EDIT') | bpy.ops.object.mode_set(mode='EDIT') | ||||
| return child | return child | ||||
| else: | else: | ||||
| raise MetarigError("Cannot make nonscaling child outside of edit mode") | raise MetarigError("Cannot make nonscaling child outside of edit mode") | ||||
| #=================================== | |||||
| # Bone manipulation as rig methods | |||||
| #=================================== | |||||
| class BoneUtilityMixin(object): | |||||
| """ | |||||
| 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): | |||||
| """Registers creation or renaming of a bone based on old_name""" | |||||
| pass | |||||
| def new_bone(self, new_name): | |||||
| """Create a new bone with the specified name.""" | |||||
| name = new_bone(self.obj, bone_name) | |||||
| self.register_new_bone(self, name) | |||||
| return name | |||||
| def copy_bone(self, bone_name, new_name='', *, parent=False, bbone=False, length=None, scale=None): | |||||
| """Copy the bone with the given name, returning the new name.""" | |||||
| name = copy_bone(self.obj, bone_name, new_name, parent=parent, bbone=bbone, length=length, scale=scale) | |||||
| self.register_new_bone(name, bone_name) | |||||
| return name | |||||
| def copy_bone_properties(self, src_name, tgt_name): | |||||
| """Copy pose-mode properties of the bone.""" | |||||
| copy_bone_properties(self.obj, src_name, tgt_name) | |||||
| def rename_bone(self, old_name, new_name): | |||||
| """Rename the bone, returning the actual new name.""" | |||||
| bone = self.get_bone(old_name) | |||||
| bone.name = new_name | |||||
| if bone.name != old_name: | |||||
| self.register_new_bone(bone.name, old_name) | |||||
| return bone.name | |||||
| def get_bone(self, bone_name): | |||||
| """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): | |||||
| """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): | |||||
| """Set the parent of the bone.""" | |||||
| eb = self.obj.data.edit_bones | |||||
| bone = eb[bone_name] | |||||
| if use_connect is not None: | |||||
| bone.use_connect = use_connect | |||||
| bone.parent = (eb[parent_name] if parent_name else None) | |||||
| def parent_bone_chain(self, bone_names, use_connect=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) | |||||
| #============================================= | |||||
| # B-Bones | |||||
| #============================================= | |||||
| def connect_bbone_chain_handles(obj, bone_names): | |||||
| assert obj.mode == 'EDIT' | |||||
| for prev_name, next_name in pairwise(bone_names): | |||||
| prev_bone = get_bone(obj, prev_name) | |||||
| next_bone = get_bone(obj, next_name) | |||||
| prev_bone.bbone_handle_type_end = 'ABSOLUTE' | |||||
| prev_bone.bbone_custom_handle_end = next_bone | |||||
| 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): | |||||
| 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): | |||||
| 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): | |||||
| """ 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] | |||||
| bone2_e.head = bone1_e.head | |||||
| bone2_e.tail = bone1_e.tail | |||||
| bone2_e.roll = bone1_e.roll | |||||
| # Resize the bone after copy if requested | |||||
| if length is not None: | |||||
| bone2_e.length = length | |||||
| elif scale is not None: | |||||
| bone2_e.length *= scale | |||||
| def align_bone_orientation(obj, bone_name, target_bone_name): | |||||
| """ 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] | |||||
| axis = bone2_e.y_axis.normalized() * bone1_e.length | |||||
| bone1_e.tail = bone1_e.head + axis | |||||
| bone1_e.roll = bone2_e.roll | |||||
| def align_bone_roll(obj, bone1, bone2): | def align_bone_roll(obj, bone1, bone2): | ||||
| """ Aligns the roll of two bones. | """ Aligns the roll of two bones. | ||||
| """ | """ | ||||
| bone1_e = obj.data.edit_bones[bone1] | bone1_e = obj.data.edit_bones[bone1] | ||||
| bone2_e = obj.data.edit_bones[bone2] | bone2_e = obj.data.edit_bones[bone2] | ||||
| bone1_e.roll = 0.0 | bone1_e.roll = 0.0 | ||||
| ▲ Show 20 Lines • Show All 90 Lines • ▼ Show 20 Lines | """ Matches the bone y-axis to | ||||
| Must be in edit mode. | Must be in edit mode. | ||||
| """ | """ | ||||
| bone_e = obj.data.edit_bones[bone] | bone_e = obj.data.edit_bones[bone] | ||||
| vec.normalize() | vec.normalize() | ||||
| vec = vec * bone_e.length | vec = vec * bone_e.length | ||||
| bone_e.tail = bone_e.head + vec | bone_e.tail = bone_e.head + vec | ||||
| def compute_chain_x_axis(obj, bone_names): | |||||
| """ | |||||
| Compute the x axis of all bones to be perpendicular | |||||
| to the primary plane in which the bones lie. | |||||
| """ | |||||
| eb = obj.data.edit_bones | |||||
| assert(len(bone_names) > 1) | |||||
| first_bone = eb[bone_names[0]] | |||||
| last_bone = eb[bone_names[-1]] | |||||
| # 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) | |||||
| if chain_rot_axis.length < first_bone.length/100: | |||||
| return first_bone.x_axis.normalized() | |||||
| else: | |||||
| return chain_rot_axis.normalized() | |||||
| def align_chain_x_axis(obj, bone_names): | |||||
| """ | |||||
| 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) | |||||
| for name in 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): | |||||
| """ | |||||
| 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. | |||||
| """ | |||||
| bone_e = obj.data.edit_bones[bone_name] | |||||
| if length is None: | |||||
| length = bone_e.length | |||||
| if roll is None: | |||||
| roll = bone_e.roll | |||||
| if axis[0] == '-': | |||||
| length = -length | |||||
| axis = axis[1:] | |||||
| vec = Vector((0,0,0)) | |||||
| setattr(vec, axis, length) | |||||
| if flip: | |||||
| base = Vector(bone_e.tail) | |||||
| bone_e.tail = base + vec | |||||
| bone_e.head = base | |||||
| else: | |||||
| bone_e.tail = bone_e.head + vec | |||||
| bone_e.roll = roll | |||||