MetaBox/Scripts/Animation/epic_pose_wrangler/model/mirror_mapping.py
2025-01-14 03:03:51 +08:00

127 lines
4.4 KiB
Python

# Copyright Epic Games, Inc. All Rights Reserved.
"""
Example Mapping (MetaHuman)
{
# Regular expression to validate that the solver follows the correct naming convention for mirroring
"solver_expression": "(?P<prefix>[a-zA-Z0-9]+)?(?P<side>_[lr]{1}_)(?P<suffix>[a-zA-Z0-9]+)",
# Regular expression to validate that the joint follows the correct naming convention for mirroring
"transform_expression": "(?P<prefix>[a-zA-Z0-9]+)?(?P<side>_[lr]{1}_)(?P<suffix>[a-zA-Z0-9]+)",
"left": {
# Left side syntax for the solver
"solver_syntax": "_l_",
# Left side syntax for the joint
"transform_syntax": "_l_"
},
"right": {
# Right side syntax for the solver
"solver_syntax": "_r_",
# Right side syntax for the joint
"transform_syntax": "_r_"
}
}
"""
import json
import os
from epic_pose_wrangler.log import LOG
class MirrorMapping(object):
"""
Class for managing mirror settings
"""
LEFT = "left"
RIGHT = "right"
def __init__(self, file_path=None, source_side="left"):
# Make a list of valid mappings that should exist in the mirror mapping file
self._valid_mappings = [MirrorMapping.LEFT, MirrorMapping.RIGHT]
# If no file path is specified, use the MetaHuman config as the fallback
if file_path is None:
LOG.debug("No mirror mapping specified, using default MetaHuman conventions")
file_path = os.path.join(
os.path.dirname(os.path.dirname(__file__)),
'resources',
'mirror_mappings',
'metahuman.json'
)
self._file_path = file_path
# Load the json mapping data
with open(file_path, 'r') as f:
self._mapping_data = json.loads(f.read())
# Set the solver expression from the file
self._solver_expression = self._mapping_data['solver_expression']
# Set the transform expression from the file
self._transform_expression = self._mapping_data['transform_expression']
# Set the source side and create defaults
self._source_side = source_side
self._source_mapping_data = {}
self._source_solver_syntax = ""
self._source_transform_syntax = ""
self._target_mapping_data = {}
self._target_solver_syntax = ""
self._target_transform_syntax = ""
# Set the source side property to trigger the default values to be updated
self.source_side = source_side
@property
def file_path(self):
return self._file_path
@property
def solver_expression(self):
return self._solver_expression
@property
def transform_expression(self):
return self._transform_expression
@property
def source_side(self):
return self._source_side
@source_side.setter
def source_side(self, side):
"""
Sets the source side and updates the source/target values accordingly
:param side: MirrorMapping.LEFT or MirrorMapping.RIGHT
"""
if side not in self._valid_mappings:
raise ValueError("Invalid side specified, options are: {}".format(", ".join(self._valid_mappings)))
self._source_side = side
self._source_mapping_data = self._mapping_data[self._source_side]
self._source_solver_syntax = self._source_mapping_data['solver_syntax']
self._source_transform_syntax = self._source_mapping_data['transform_syntax']
self._target_mapping_data = self._mapping_data[
MirrorMapping.RIGHT if self._source_side == MirrorMapping.LEFT else MirrorMapping.LEFT]
self._target_solver_syntax = self._target_mapping_data['solver_syntax']
self._target_transform_syntax = self._target_mapping_data['transform_syntax']
@property
def source_solver_syntax(self):
return self._source_solver_syntax
@property
def source_transform_syntax(self):
return self._source_transform_syntax
@property
def target_solver_syntax(self):
return self._target_solver_syntax
@property
def target_transform_syntax(self):
return self._target_transform_syntax
def swap_sides(self):
"""
Swap the source side to the opposite of the current side.
"""
new_target = MirrorMapping.LEFT if self.source_side == MirrorMapping.RIGHT else MirrorMapping.RIGHT
self.source_side = new_target