Skip to content
Snippets Groups Projects
pose_usage.py 5.09 KiB
Newer Older
  • Learn to ignore specific revisions
  • # ##### BEGIN GPL LICENSE BLOCK #####
    #
    #  This program is free software; you can redistribute it and/or
    #  modify it under the terms of the GNU General Public License
    #  as published by the Free Software Foundation; either version 2
    #  of the License, or (at your option) any later version.
    #
    #  This program is distributed in the hope that it will be useful,
    #  but WITHOUT ANY WARRANTY; without even the implied warranty of
    #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    #  GNU General Public License for more details.
    #
    #  You should have received a copy of the GNU General Public License
    #  along with this program; if not, write to the Free Software Foundation,
    #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
    #
    # ##### END GPL LICENSE BLOCK #####
    
    """
    Pose Library - usage functions.
    """
    
    from typing import Set
    import re
    
    from bpy.types import (
        Action,
        Object,
    )
    
    
    def select_bones(arm_object: Object, action: Action, *, select: bool, flipped: bool) -> None:
        pose_bone_re = re.compile(r'pose.bones\["([^"]+)"\]')
        pose = arm_object.pose
    
        seen_bone_names: Set[str] = set()
    
        for fcurve in action.fcurves:
            data_path: str = fcurve.data_path
            match = pose_bone_re.match(data_path)
            if not match:
                continue
    
            bone_name = match.group(1)
    
            if bone_name in seen_bone_names:
                continue
            seen_bone_names.add(bone_name)
    
            if flipped:
                bone_name = flip_side_name(bone_name)
    
            try:
                pose_bone = pose.bones[bone_name]
            except KeyError:
                continue
    
            pose_bone.bone.select = select
    
    
    _FLIP_SEPARATORS = set(". -_")
    
    # These are single-character replacements, others are handled differently.
    _FLIP_REPLACEMENTS = {
        "l": "r",
        "L": "R",
        "r": "l",
        "R": "L",
    }
    
    
    def flip_side_name(to_flip: str) -> str:
        """Flip left and right indicators in the name.
    
        Basically a Python implementation of BLI_string_flip_side_name.
    
        >>> flip_side_name('bone_L.004')
        'bone_R.004'
        >>> flip_side_name('left_bone')
        'right_bone'
        >>> flip_side_name('Left_bone')
        'Right_bone'
        >>> flip_side_name('LEFT_bone')
        'RIGHT_bone'
        >>> flip_side_name('some.bone-RIGHT.004')
        'some.bone-LEFT.004'
        >>> flip_side_name('some.bone-right.004')
        'some.bone-left.004'
        >>> flip_side_name('some.bone-Right.004')
        'some.bone-Left.004'
        >>> flip_side_name('some.bone-LEFT.004')
        'some.bone-RIGHT.004'
        >>> flip_side_name('some.bone-left.004')
        'some.bone-right.004'
        >>> flip_side_name('some.bone-Left.004')
        'some.bone-Right.004'
        >>> flip_side_name('.004')
        '.004'
        >>> flip_side_name('L.004')
        'R.004'
        """
        import string
    
        if len(to_flip) < 3:
            # we don't flip names like .R or .L
            return to_flip
    
        # We first check the case with a .### extension, let's find the last period.
        number = ""
        replace = to_flip
        if to_flip[-1] in string.digits:
            try:
                index = to_flip.rindex(".")
            except ValueError:
                pass
            else:
                if to_flip[index + 1] in string.digits:
                    # TODO(Sybren): this doesn't handle "bone.1abc2" correctly.
                    number = to_flip[index:]
                    replace = to_flip[:index]
    
        if not replace:
            # Nothing left after the number, so no flips necessary.
            return replace + number
    
        if len(replace) == 1:
            replace = _FLIP_REPLACEMENTS.get(replace, replace)
            return replace + number
    
        # First case; separator . - _ with extensions r R l L.
        if replace[-2] in _FLIP_SEPARATORS and replace[-1] in _FLIP_REPLACEMENTS:
            replace = replace[:-1] + _FLIP_REPLACEMENTS[replace[-1]]
            return replace + number
    
        # Second case; beginning with r R l L, with separator after it.
        if replace[1] in _FLIP_SEPARATORS and replace[0] in _FLIP_REPLACEMENTS:
            replace = _FLIP_REPLACEMENTS[replace[0]] + replace[1:]
            return replace + number
    
        lower = replace.lower()
        prefix = suffix = ""
        if lower.startswith("right"):
            bit = replace[0:2]
            if bit == "Ri":
                prefix = "Left"
            elif bit == "RI":
                prefix = "LEFT"
            else:
                prefix = "left"
            replace = replace[5:]
        elif lower.startswith("left"):
            bit = replace[0:2]
            if bit == "Le":
                prefix = "Right"
            elif bit == "LE":
                prefix = "RIGHT"
            else:
                prefix = "right"
            replace = replace[4:]
        elif lower.endswith("right"):
            bit = replace[-5:-3]
            if bit == "Ri":
                suffix = "Left"
            elif bit == "RI":
                suffix = "LEFT"
            else:
                suffix = "left"
            replace = replace[:-5]
        elif lower.endswith("left"):
            bit = replace[-4:-2]
            if bit == "Le":
                suffix = "Right"
            elif bit == "LE":
                suffix = "RIGHT"
            else:
                suffix = "right"
            replace = replace[:-4]
    
        return prefix + replace + suffix + number
    
    
    if __name__ == '__main__':
        import doctest
    
        print(f"Test result: {doctest.testmod()}")