1 # SPDX-FileCopyrightText: 2019-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
11 from typing
import Optional
, TYPE_CHECKING
15 from ..base_generate
import BaseGenerator
18 ORG_PREFIX
= "ORG-" # Prefix of original bones.
19 MCH_PREFIX
= "MCH-" # Prefix of mechanism bones.
20 DEF_PREFIX
= "DEF-" # Prefix of deformation bones.
21 ROOT_NAME
= "root" # Name of the root bone.
23 _PREFIX_TABLE
= {'org': "ORG", 'mch': "MCH", 'def': "DEF", 'ctrl': ''}
25 ########################################################################
27 ########################################################################
29 NameParts
= collections
.namedtuple('NameParts', ['prefix', 'base', 'side_z', 'side', 'number'])
32 def split_name(name
: str):
33 name_parts
= re
.match(
34 r
'^(?:(ORG|MCH|DEF)-)?(.*?)([._-][tTbB])?([._-][lLrR])?(?:\.(\d+))?$', name
)
35 return NameParts(*name_parts
.groups())
38 def is_control_bone(name
: str):
39 return not split_name(name
).prefix
42 def combine_name(parts
: NameParts
, *, prefix
=None, base
=None, side_z
=None, side
=None, number
=None):
43 eff_prefix
= prefix
if prefix
is not None else parts
.prefix
44 eff_number
= number
if number
is not None else parts
.number
45 if isinstance(eff_number
, int):
46 eff_number
= '%03d' % eff_number
49 eff_prefix
+'-' if eff_prefix
else '',
50 base
if base
is not None else parts
.base
,
51 side_z
if side_z
is not None else parts
.side_z
or '',
52 side
if side
is not None else parts
.side
or '',
53 '.'+eff_number
if eff_number
else '',
57 def insert_before_lr(name
: str, text
: str) -> str:
58 parts
= split_name(name
)
61 return combine_name(parts
, base
=parts
.base
+ text
)
66 def make_derived_name(name
: str, subtype
: str, suffix
: Optional
[str] = None):
67 """ Replaces the name prefix, and optionally adds the suffix (before .LR if found).
69 assert(subtype
in _PREFIX_TABLE
)
71 parts
= split_name(name
)
72 new_base
= parts
.base
+ (suffix
or '')
74 return combine_name(parts
, prefix
=_PREFIX_TABLE
[subtype
], base
=new_base
)
77 ########################################################################
79 ########################################################################
81 class Side(enum
.IntEnum
):
87 def from_parts(parts
: NameParts
):
89 if parts
.side
[1].lower() == 'l':
97 def to_string(parts
: NameParts
, side
: 'Side'):
98 if side
!= Side
.MIDDLE
:
99 side_char
= 'L' if side
== Side
.LEFT
else 'R'
100 side_str
= parts
.side
or parts
.side_z
103 sep
, side_char2
= side_str
[0:2]
104 if side_char2
.lower() == side_char2
:
105 side_char
= side_char
.lower()
109 return sep
+ side_char
114 def to_name(parts
: NameParts
, side
: 'Side'):
115 new_side
= Side
.to_string(parts
, side
)
116 return combine_name(parts
, side
=new_side
)
119 class SideZ(enum
.IntEnum
):
125 def from_parts(parts
: NameParts
):
127 if parts
.side_z
[1].lower() == 't':
135 def to_string(parts
: NameParts
, side
: 'SideZ'):
136 if side
!= SideZ
.MIDDLE
:
137 side_char
= 'T' if side
== SideZ
.TOP
else 'B'
138 side_str
= parts
.side_z
or parts
.side
141 sep
, side_char2
= side_str
[0:2]
142 if side_char2
.lower() == side_char2
:
143 side_char
= side_char
.lower()
147 return sep
+ side_char
152 def to_name(parts
: NameParts
, side
: 'SideZ'):
153 new_side
= SideZ
.to_string(parts
, side
)
154 return combine_name(parts
, side_z
=new_side
)
157 NameSides
= collections
.namedtuple('NameSides', ['base', 'side', 'side_z'])
160 def get_name_side(name
: str):
161 return Side
.from_parts(split_name(name
))
164 def get_name_side_z(name
: str):
165 return SideZ
.from_parts(split_name(name
))
168 def get_name_base_and_sides(name
: str):
169 parts
= split_name(name
)
170 base
= combine_name(parts
, side
='', side_z
='')
171 return NameSides(base
, Side
.from_parts(parts
), SideZ
.from_parts(parts
))
174 def change_name_side(name
: str,
175 side
: Optional
[Side
] = None, *,
176 side_z
: Optional
[SideZ
] = None):
177 parts
= split_name(name
)
178 new_side
= None if side
is None else Side
.to_string(parts
, side
)
179 new_side_z
= None if side_z
is None else SideZ
.to_string(parts
, side_z
)
180 return combine_name(parts
, side
=new_side
, side_z
=new_side_z
)
183 def mirror_name(name
: str):
184 parts
= split_name(name
)
185 side
= Side
.from_parts(parts
)
187 if side
!= Side
.MIDDLE
:
188 return Side
.to_name(parts
, -side
)
193 def mirror_name_z(name
: str):
194 parts
= split_name(name
)
195 side
= SideZ
.from_parts(parts
)
197 if side
!= SideZ
.MIDDLE
:
198 return SideZ
.to_name(parts
, -side
)
203 ########################################################################
205 ########################################################################
207 def get_name(bone
) -> Optional
[str]:
208 return bone
.name
if bone
else None
211 def strip_trailing_number(name
: str):
212 return combine_name(split_name(name
), number
='')
215 def strip_prefix(name
: str):
216 return combine_name(split_name(name
), prefix
='')
219 def unique_name(collection
, base_name
: str):
220 parts
= split_name(base_name
)
221 name
= combine_name(parts
, number
='')
224 while name
in collection
:
225 name
= combine_name(parts
, number
=count
)
231 def strip_org(name
: str):
232 """ Returns the name with ORG_PREFIX stripped from it.
234 if name
.startswith(ORG_PREFIX
):
235 return name
[len(ORG_PREFIX
):]
243 def strip_mch(name
: str):
244 """ Returns the name with MCH_PREFIX stripped from it.
246 if name
.startswith(MCH_PREFIX
):
247 return name
[len(MCH_PREFIX
):]
252 def strip_def(name
: str):
253 """ Returns the name with DEF_PREFIX stripped from it.
255 if name
.startswith(DEF_PREFIX
):
256 return name
[len(DEF_PREFIX
):]
262 """ Prepends the ORG_PREFIX to a name if it doesn't already have
265 if name
.startswith(ORG_PREFIX
):
268 return ORG_PREFIX
+ name
271 make_original_name
= org
275 """ Prepends the MCH_PREFIX to a name if it doesn't already have
278 if name
.startswith(MCH_PREFIX
):
281 return MCH_PREFIX
+ name
284 make_mechanism_name
= mch
287 def deformer(name
: str):
288 """ Prepends the DEF_PREFIX to a name if it doesn't already have
291 if name
.startswith(DEF_PREFIX
):
294 return DEF_PREFIX
+ name
297 make_deformer_name
= deformer
300 def random_id(length
=8):
301 """ Generates a random alphanumeric id string.
303 t_length
= int(length
/ 2)
304 r_length
= int(length
/ 2) + int(length
% 2)
306 chars
= ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 'b', 'c', 'd', 'e', 'f',
307 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
310 for i
in range(0, r_length
):
311 text
+= random
.choice(chars
)
312 text
+= str(hex(int(time
.time())))[2:][-t_length
:].rjust(t_length
, '0')[::-1]
316 def choose_derived_bone(generator
: 'BaseGenerator', original
: str, subtype
: str, *,
317 by_owner
=True, recursive
=True):
318 bones
= generator
.obj
.pose
.bones
319 names
= generator
.find_derived_bones(original
, by_owner
=by_owner
, recursive
=recursive
)
321 direct
= make_derived_name(original
, subtype
)
322 if direct
in names
and direct
in bones
:
325 prefix
= _PREFIX_TABLE
[subtype
] + '-'
326 matching
= [name
for name
in names
if name
.startswith(prefix
) and name
in bones
]
328 if len(matching
) > 0:
331 # Try matching bones created by legacy rigs just by name - there is no origin data
332 from ..base_generate
import LegacyRig
334 if isinstance(generator
.bone_owners
.get(direct
), LegacyRig
):
335 if not by_owner
or generator
.bone_owners
.get(original
) is generator
.bone_owners
[direct
]:
336 assert direct
in bones
347 **{a
: b
for a
, b
in _MIRROR_MAP_RAW
},
348 **{b
: a
for a
, b
in _MIRROR_MAP_RAW
},
349 **{a
.lower(): b
.lower() for a
, b
in _MIRROR_MAP_RAW
},
350 **{b
.lower(): a
.lower() for a
, b
in _MIRROR_MAP_RAW
},
353 r
"(?<![a-z])(left|light)(?![a-z])",
354 r
"(?<=[\._])(l|r)(?![a-z])",
358 def mirror_name_fuzzy(name
: str) -> str:
359 """Try to mirror a name by trying various patterns without expecting any rigid structure."""
361 for reg
in _MIRROR_RE
:
362 new_name
= re
.sub(reg
, lambda m
: _MIRROR_MAP
.get(m
[0], m
[0]), name
, flags
=re
.IGNORECASE
)