Sybren A. Stüvel d1e7346c63 Add-ons: Rigify, reformat code
Run `make format` to reformat the Rigify code. It now adheres to the
global Blender code style standard, rather than having its own style.

Most of the changes are simply adding spaces around operators, newlines
below docstrings, and changing some indentation.

Note that this does not reformat any stored-as-multiline-strings code
blocks.

No functional changes.

Pull Request: https://projects.blender.org/blender/blender/pulls/123833
2024-06-28 11:38:31 +02:00

367 lines
9.7 KiB
Python

# SPDX-FileCopyrightText: 2019-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
import random
import time
import re
import collections
import enum
from typing import Optional, TYPE_CHECKING
if TYPE_CHECKING:
from ..base_generate import BaseGenerator
ORG_PREFIX = "ORG-" # Prefix of original bones.
MCH_PREFIX = "MCH-" # Prefix of mechanism bones.
DEF_PREFIX = "DEF-" # Prefix of deformation bones.
ROOT_NAME = "root" # Name of the root bone.
_PREFIX_TABLE = {'org': "ORG", 'mch': "MCH", 'def': "DEF", 'ctrl': ''}
########################################################################
# Name structure
########################################################################
NameParts = collections.namedtuple('NameParts', ['prefix', 'base', 'side_z', 'side', 'number'])
def split_name(name: str):
name_parts = re.match(
r'^(?:(ORG|MCH|DEF)-)?(.*?)([._-][tTbB])?([._-][lLrR])?(?:\.(\d+))?$', name)
return NameParts(*name_parts.groups())
def is_control_bone(name: str):
return not split_name(name).prefix
def combine_name(parts: NameParts, *, prefix=None, base=None, side_z=None, side=None, number=None):
eff_prefix = prefix if prefix is not None else parts.prefix
eff_number = number if number is not None else parts.number
if isinstance(eff_number, int):
eff_number = '%03d' % eff_number
return ''.join([
eff_prefix + '-' if eff_prefix else '',
base if base is not None else parts.base,
side_z if side_z is not None else parts.side_z or '',
side if side is not None else parts.side or '',
'.' + eff_number if eff_number else '',
])
def insert_before_lr(name: str, text: str) -> str:
parts = split_name(name)
if parts.side:
return combine_name(parts, base=parts.base + text)
else:
return name + text
def make_derived_name(name: str, subtype: str, suffix: Optional[str] = None):
""" Replaces the name prefix, and optionally adds the suffix (before .LR if found).
"""
assert(subtype in _PREFIX_TABLE)
parts = split_name(name)
new_base = parts.base + (suffix or '')
return combine_name(parts, prefix=_PREFIX_TABLE[subtype], base=new_base)
########################################################################
# Name mirroring
########################################################################
class Side(enum.IntEnum):
LEFT = -1
MIDDLE = 0
RIGHT = 1
@staticmethod
def from_parts(parts: NameParts):
if parts.side:
if parts.side[1].lower() == 'l':
return Side.LEFT
else:
return Side.RIGHT
else:
return Side.MIDDLE
@staticmethod
def to_string(parts: NameParts, side: 'Side'):
if side != Side.MIDDLE:
side_char = 'L' if side == Side.LEFT else 'R'
side_str = parts.side or parts.side_z
if side_str:
sep, side_char2 = side_str[0:2]
if side_char2.lower() == side_char2:
side_char = side_char.lower()
else:
sep = '.'
return sep + side_char
else:
return ''
@staticmethod
def to_name(parts: NameParts, side: 'Side'):
new_side = Side.to_string(parts, side)
return combine_name(parts, side=new_side)
class SideZ(enum.IntEnum):
TOP = 2
MIDDLE = 0
BOTTOM = -2
@staticmethod
def from_parts(parts: NameParts):
if parts.side_z:
if parts.side_z[1].lower() == 't':
return SideZ.TOP
else:
return SideZ.BOTTOM
else:
return SideZ.MIDDLE
@staticmethod
def to_string(parts: NameParts, side: 'SideZ'):
if side != SideZ.MIDDLE:
side_char = 'T' if side == SideZ.TOP else 'B'
side_str = parts.side_z or parts.side
if side_str:
sep, side_char2 = side_str[0:2]
if side_char2.lower() == side_char2:
side_char = side_char.lower()
else:
sep = '.'
return sep + side_char
else:
return ''
@staticmethod
def to_name(parts: NameParts, side: 'SideZ'):
new_side = SideZ.to_string(parts, side)
return combine_name(parts, side_z=new_side)
NameSides = collections.namedtuple('NameSides', ['base', 'side', 'side_z'])
def get_name_side(name: str):
return Side.from_parts(split_name(name))
def get_name_side_z(name: str):
return SideZ.from_parts(split_name(name))
def get_name_base_and_sides(name: str):
parts = split_name(name)
base = combine_name(parts, side='', side_z='')
return NameSides(base, Side.from_parts(parts), SideZ.from_parts(parts))
def change_name_side(name: str,
side: Optional[Side] = None, *,
side_z: Optional[SideZ] = None):
parts = split_name(name)
new_side = None if side is None else Side.to_string(parts, side)
new_side_z = None if side_z is None else SideZ.to_string(parts, side_z)
return combine_name(parts, side=new_side, side_z=new_side_z)
def mirror_name(name: str):
parts = split_name(name)
side = Side.from_parts(parts)
if side != Side.MIDDLE:
return Side.to_name(parts, -side)
else:
return name
def mirror_name_z(name: str):
parts = split_name(name)
side = SideZ.from_parts(parts)
if side != SideZ.MIDDLE:
return SideZ.to_name(parts, -side)
else:
return name
########################################################################
# Name manipulation
########################################################################
def get_name(bone) -> Optional[str]:
return bone.name if bone else None
def strip_trailing_number(name: str):
return combine_name(split_name(name), number='')
def strip_prefix(name: str):
return combine_name(split_name(name), prefix='')
def unique_name(collection, base_name: str):
parts = split_name(base_name)
name = combine_name(parts, number='')
count = 1
while name in collection:
name = combine_name(parts, number=count)
count += 1
return name
def strip_org(name: str):
""" Returns the name with ORG_PREFIX stripped from it.
"""
if name.startswith(ORG_PREFIX):
return name[len(ORG_PREFIX):]
else:
return name
org_name = strip_org
def strip_mch(name: str):
""" Returns the name with MCH_PREFIX stripped from it.
"""
if name.startswith(MCH_PREFIX):
return name[len(MCH_PREFIX):]
else:
return name
def strip_def(name: str):
""" Returns the name with DEF_PREFIX stripped from it.
"""
if name.startswith(DEF_PREFIX):
return name[len(DEF_PREFIX):]
else:
return name
def org(name: str):
""" Prepends the ORG_PREFIX to a name if it doesn't already have
it, and returns it.
"""
if name.startswith(ORG_PREFIX):
return name
else:
return ORG_PREFIX + name
make_original_name = org
def mch(name: str):
""" Prepends the MCH_PREFIX to a name if it doesn't already have
it, and returns it.
"""
if name.startswith(MCH_PREFIX):
return name
else:
return MCH_PREFIX + name
make_mechanism_name = mch
def deformer(name: str):
""" Prepends the DEF_PREFIX to a name if it doesn't already have
it, and returns it.
"""
if name.startswith(DEF_PREFIX):
return name
else:
return DEF_PREFIX + name
make_deformer_name = deformer
def random_id(length=8):
""" Generates a random alphanumeric id string.
"""
t_length = int(length / 2)
r_length = int(length / 2) + int(length % 2)
chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
'x', 'y', 'z']
text = ""
for i in range(0, r_length):
text += random.choice(chars)
text += str(hex(int(time.time())))[2:][-t_length:].rjust(t_length, '0')[::-1]
return text
def choose_derived_bone(generator: 'BaseGenerator', original: str, subtype: str, *,
by_owner=True, recursive=True):
bones = generator.obj.pose.bones
names = generator.find_derived_bones(original, by_owner=by_owner, recursive=recursive)
direct = make_derived_name(original, subtype)
if direct in names and direct in bones:
return direct
prefix = _PREFIX_TABLE[subtype] + '-'
matching = [name for name in names if name.startswith(prefix) and name in bones]
if len(matching) > 0:
return matching[0]
# Try matching bones created by legacy rigs just by name - there is no origin data
from ..base_generate import LegacyRig
if isinstance(generator.bone_owners.get(direct), LegacyRig):
if not by_owner or generator.bone_owners.get(original) is generator.bone_owners[direct]:
assert direct in bones
return direct
return None
_MIRROR_MAP_RAW = [
("Left", "Right"),
("L", "R"),
]
_MIRROR_MAP = {
**{a: b for a, b in _MIRROR_MAP_RAW},
**{b: a for a, b in _MIRROR_MAP_RAW},
**{a.lower(): b.lower() for a, b in _MIRROR_MAP_RAW},
**{b.lower(): a.lower() for a, b in _MIRROR_MAP_RAW},
}
_MIRROR_RE = [
r"(?<![a-z])(left|light)(?![a-z])",
r"(?<=[\._])(l|r)(?![a-z])",
]
def mirror_name_fuzzy(name: str) -> str:
"""Try to mirror a name by trying various patterns without expecting any rigid structure."""
for reg in _MIRROR_RE:
new_name = re.sub(reg, lambda m: _MIRROR_MAP.get(m[0], m[0]), name, flags=re.IGNORECASE)
if new_name != name:
return new_name
return name