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 strip_org, make_mechanism_name, insert_before_lr | from .naming import get_name, strip_org, make_mechanism_name, insert_before_lr | ||||
| 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 31 Lines | class BoneDict(dict): | ||||
| 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_simple(obj, bone_name, assign_name=''): | ||||
| """ 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 | but only copies head, tail positions and roll. Does not | ||||
| address parenting either. | address parenting either. | ||||
| """ | """ | ||||
| #if bone_name not in obj.data.bones: | return copy_bone(obj, bone_name, assign_name, parent=False, bbone=False) | ||||
| 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 | |||||
| # 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 | |||||
| return bone_name_2 | |||||
| else: | |||||
| raise MetarigError("Cannot copy bones outside of edit mode") | |||||
| def copy_bone(obj, bone_name, assign_name=''): | def copy_bone(obj, bone_name, assign_name='', parent=False, bbone=False): | ||||
| """ Makes a copy of the given bone in the given armature object. | """ Makes a copy of the given bone in the given armature object. | ||||
| Returns the resulting bone's name. | Returns the resulting bone's name. | ||||
| """ | """ | ||||
| #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 | ||||
| edit_bone_2.parent = edit_bone_1.parent | |||||
| edit_bone_2.use_connect = edit_bone_1.use_connect | |||||
| # 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 | ||||
| if parent: | |||||
| edit_bone_2.parent = edit_bone_1.parent | |||||
| edit_bone_2.use_connect = edit_bone_1.use_connect | |||||
| 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 | edit_bone_2.bbone_segments = edit_bone_1.bbone_segments | ||||
| edit_bone_2.bbone_easein = edit_bone_1.bbone_easein | edit_bone_2.bbone_easein = edit_bone_1.bbone_easein | ||||
| edit_bone_2.bbone_easeout = edit_bone_1.bbone_easeout | edit_bone_2.bbone_easeout = edit_bone_1.bbone_easeout | ||||
| bpy.ops.object.mode_set(mode='OBJECT') | return bone_name_2 | ||||
| else: | |||||
| raise MetarigError("Cannot copy bones outside of edit mode") | |||||
| 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 10 Lines | if obj.mode in {'OBJECT','POSE'}: | ||||
| if key != "_RNA_UI" \ | if key != "_RNA_UI" \ | ||||
| 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) | ||||
| prop2 = rna_idprop_ui_prop_get(pose_bone_2, key, create=True) | prop2 = rna_idprop_ui_prop_get(pose_bone_2, key, create=True) | ||||
| pose_bone_2[key] = pose_bone_1[key] | pose_bone_2[key] = pose_bone_1[key] | ||||
| 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 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_mechanism_name(strip_org(insert_before_lr(bone_name, child_name_postfix + "_ns_ch"))) | name1 = make_mechanism_name(strip_org(insert_before_lr(bone_name, child_name_postfix + "_ns_ch"))) | ||||
| name2 = make_mechanism_name(strip_org(insert_before_lr(bone_name, child_name_postfix + "_ns_intr"))) | name2 = make_mechanism_name(strip_org(insert_before_lr(bone_name, 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): | |||||
| """Copy the bone with the given name, returning the new name.""" | |||||
| name = copy_bone(self.obj, bone_name, new_name, parent=parent, bbone=bbone) | |||||
| 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, bones, use_connect=None): | |||||
| """Link bones into a chain with parenting. First bone may be None.""" | |||||
| for parent, child in pairwise(bones): | |||||
| self.set_bone_parent(child, parent, use_connect=use_connect) | |||||
| #============================================= | #============================================= | ||||
| # Math | # Math | ||||
| #============================================= | #============================================= | ||||
| def is_same_position(obj, bone1, bone2): | |||||
| head1 = get_bone(obj, bone1).head | |||||
| head2 = get_bone(obj, bone2).head | |||||
| return (head1 - head2).length < 1e-5 | |||||
| def is_connected_position(obj, bone1, bone2): | |||||
| tail1 = get_bone(obj, bone1).tail | |||||
| head2 = get_bone(obj, bone2).head | |||||
| return (tail1 - head2).length < 1e-5 | |||||
| def align_bone_orientation(obj, bone, target_bone): | |||||
| """ Aligns the orientation of bone to target bone. """ | |||||
| bone1_e = obj.data.edit_bones[bone] | |||||
| bone2_e = obj.data.edit_bones[target_bone] | |||||
| 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 align_chain_x_axis(obj, bones): | |||||
| """ | |||||
| Aligns 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(bones) > 1) | |||||
| first_bone = eb[bones[0]] | |||||
| last_bone = eb[bones[-1]] | |||||
| # Orient uarm farm bones | |||||
| chain_y_axis = last_bone.tail - first_bone.head | |||||
| chain_rot_axis = first_bone.y_axis.cross(chain_y_axis) # ik-plane normal axis (rotation) | |||||
| if chain_rot_axis.length < first_bone.length/100: | |||||
| chain_rot_axis = first_bone.x_axis.normalized() | |||||
| else: | |||||
| chain_rot_axis = chain_rot_axis.normalized() | |||||
| for bone in bones: | |||||
| align_bone_x_axis(obj, bone, chain_rot_axis) | |||||
| def align_bone_to_axis(obj, bone, 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] | |||||
| 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: | |||||
| bone_e.head = bone_e.tail | |||||
| bone_e.tail = bone_e.head + vec | |||||
| bone_e.roll = roll | |||||