# 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[a-zA-Z0-9]+)?(?P_[lr]{1}_)(?P[a-zA-Z0-9]+)", # Regular expression to validate that the joint follows the correct naming convention for mirroring "transform_expression": "(?P[a-zA-Z0-9]+)?(?P_[lr]{1}_)(?P[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