Cleanup: strip trailing space, remove BOM
[blender-addons.git] / pose_library / pose_usage.py
blobdc496d9c98c4bb84db2348d529b619360c2cee8a
1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
19 """
20 Pose Library - usage functions.
21 """
23 from typing import Set
24 import re
26 from bpy.types import (
27 Action,
28 Object,
32 def select_bones(arm_object: Object, action: Action, *, select: bool, flipped: bool) -> None:
33 pose_bone_re = re.compile(r'pose.bones\["([^"]+)"\]')
34 pose = arm_object.pose
36 seen_bone_names: Set[str] = set()
38 for fcurve in action.fcurves:
39 data_path: str = fcurve.data_path
40 match = pose_bone_re.match(data_path)
41 if not match:
42 continue
44 bone_name = match.group(1)
46 if bone_name in seen_bone_names:
47 continue
48 seen_bone_names.add(bone_name)
50 if flipped:
51 bone_name = flip_side_name(bone_name)
53 try:
54 pose_bone = pose.bones[bone_name]
55 except KeyError:
56 continue
58 pose_bone.bone.select = select
61 _FLIP_SEPARATORS = set(". -_")
63 # These are single-character replacements, others are handled differently.
64 _FLIP_REPLACEMENTS = {
65 "l": "r",
66 "L": "R",
67 "r": "l",
68 "R": "L",
72 def flip_side_name(to_flip: str) -> str:
73 """Flip left and right indicators in the name.
75 Basically a Python implementation of BLI_string_flip_side_name.
77 >>> flip_side_name('bone_L.004')
78 'bone_R.004'
79 >>> flip_side_name('left_bone')
80 'right_bone'
81 >>> flip_side_name('Left_bone')
82 'Right_bone'
83 >>> flip_side_name('LEFT_bone')
84 'RIGHT_bone'
85 >>> flip_side_name('some.bone-RIGHT.004')
86 'some.bone-LEFT.004'
87 >>> flip_side_name('some.bone-right.004')
88 'some.bone-left.004'
89 >>> flip_side_name('some.bone-Right.004')
90 'some.bone-Left.004'
91 >>> flip_side_name('some.bone-LEFT.004')
92 'some.bone-RIGHT.004'
93 >>> flip_side_name('some.bone-left.004')
94 'some.bone-right.004'
95 >>> flip_side_name('some.bone-Left.004')
96 'some.bone-Right.004'
97 >>> flip_side_name('.004')
98 '.004'
99 >>> flip_side_name('L.004')
100 'R.004'
102 import string
104 if len(to_flip) < 3:
105 # we don't flip names like .R or .L
106 return to_flip
108 # We first check the case with a .### extension, let's find the last period.
109 number = ""
110 replace = to_flip
111 if to_flip[-1] in string.digits:
112 try:
113 index = to_flip.rindex(".")
114 except ValueError:
115 pass
116 else:
117 if to_flip[index + 1] in string.digits:
118 # TODO(Sybren): this doesn't handle "bone.1abc2" correctly.
119 number = to_flip[index:]
120 replace = to_flip[:index]
122 if not replace:
123 # Nothing left after the number, so no flips necessary.
124 return replace + number
126 if len(replace) == 1:
127 replace = _FLIP_REPLACEMENTS.get(replace, replace)
128 return replace + number
130 # First case; separator . - _ with extensions r R l L.
131 if replace[-2] in _FLIP_SEPARATORS and replace[-1] in _FLIP_REPLACEMENTS:
132 replace = replace[:-1] + _FLIP_REPLACEMENTS[replace[-1]]
133 return replace + number
135 # Second case; beginning with r R l L, with separator after it.
136 if replace[1] in _FLIP_SEPARATORS and replace[0] in _FLIP_REPLACEMENTS:
137 replace = _FLIP_REPLACEMENTS[replace[0]] + replace[1:]
138 return replace + number
140 lower = replace.lower()
141 prefix = suffix = ""
142 if lower.startswith("right"):
143 bit = replace[0:2]
144 if bit == "Ri":
145 prefix = "Left"
146 elif bit == "RI":
147 prefix = "LEFT"
148 else:
149 prefix = "left"
150 replace = replace[5:]
151 elif lower.startswith("left"):
152 bit = replace[0:2]
153 if bit == "Le":
154 prefix = "Right"
155 elif bit == "LE":
156 prefix = "RIGHT"
157 else:
158 prefix = "right"
159 replace = replace[4:]
160 elif lower.endswith("right"):
161 bit = replace[-5:-3]
162 if bit == "Ri":
163 suffix = "Left"
164 elif bit == "RI":
165 suffix = "LEFT"
166 else:
167 suffix = "left"
168 replace = replace[:-5]
169 elif lower.endswith("left"):
170 bit = replace[-4:-2]
171 if bit == "Le":
172 suffix = "Right"
173 elif bit == "LE":
174 suffix = "RIGHT"
175 else:
176 suffix = "right"
177 replace = replace[:-4]
179 return prefix + replace + suffix + number
182 if __name__ == '__main__':
183 import doctest
185 print(f"Test result: {doctest.testmod()}")