from typing import Literal, Optional
from loguru import logger
from ..data_structure.constants import ShapeTypeEnumUnreal, Vector
from ..object.object_utils import ObjectUtilsUnreal
from ..rpc import remote_unreal
from ..utils import Validator
from ..utils.functions import unreal_functions
from .actor_base import ActorBase
try:
import unreal
from unreal_factory import XRFeitoriaUnrealFactory # defined in src/XRFeitoriaUnreal/Content/Python
except ModuleNotFoundError:
pass
[docs]
@remote_unreal(dec_class=True, suffix='_in_engine')
class ActorUnreal(ActorBase):
"""Actor class for Unreal Engine."""
_object_utils = ObjectUtilsUnreal
@property
def engine_path(self) -> str:
"""Engine path of the actor."""
return self._object_utils._get_engine_path_in_engine(self.name)
[docs]
@classmethod
def spawn_from_engine_path(
cls,
engine_path: str,
name: Optional[str] = None,
location: 'Vector' = (0, 0, 0),
rotation: 'Vector' = (0, 0, 0),
scale: 'Vector' = (1, 1, 1),
) -> 'ActorBase':
"""Spawns an actor in the engine and returns its corresponding actor.
Args:
engine_path (str): the path to the actor in the engine. For example, '/Game/Engine/BasicShapes/Cube'.
name (str, optional): the name of the actor. Defaults to None, in which case a name will be generated.
location (Vector, optional): the location of the actor. Units are in meters. Defaults to (0, 0, 0).
rotation (Vector, optional): the rotation of the actor. Units are in degrees. Defaults to (0, 0, 0).
scale (Vector, optional): the scale of the actor. Defaults to (1, 1, 1).
Returns:
ActorBase: the actor that was spawned
"""
Validator.validate_argument_type(engine_path, str)
if name is None:
name = cls._object_utils.generate_obj_name(obj_type='actor')
cls._object_utils.validate_new_name(name)
Validator.validate_vector(location, 3)
Validator.validate_vector(rotation, 3)
Validator.validate_vector(scale, 3)
_name = cls._spawn_actor_in_engine(engine_path, name=name, location=location, rotation=rotation, scale=scale)
logger.info(f'[cyan]Spawned[/cyan] actor "{_name}"')
return cls(_name)
#####################################
###### RPC METHODS (Private) ########
#####################################
@staticmethod
def _get_stencil_value_in_engine(actor_name: str) -> int:
"""Get stencil value of the actor in Unreal Engine.
Args:
actor_name (str): Name of the actor.
Returns:
int: Stencil value.
"""
actor = XRFeitoriaUnrealFactory.utils_actor.get_actor_by_name(actor_name)
return XRFeitoriaUnrealFactory.utils_actor.get_stencil_value(actor)
@staticmethod
def _get_mask_color_in_engine(actor_name: str) -> 'Vector':
"""Get mask color of the actor in Unreal Engine.
Args:
actor_name (str): Name of the actor.
Returns:
Vector: Mask color. (r, g, b) in [0, 255].
"""
actor = XRFeitoriaUnrealFactory.utils_actor.get_actor_by_name(actor_name)
return XRFeitoriaUnrealFactory.utils_actor.get_actor_mask_color(actor)
@staticmethod
def _set_stencil_value_in_engine(actor_name: str, value: int) -> int:
"""Set pass index of the actor in Unreal Engine.
Args:
actor_name (str): Name of the actor.
value (int in [0, 255]): Pass index (stencil value).
"""
actor = XRFeitoriaUnrealFactory.utils_actor.get_actor_by_name(actor_name)
XRFeitoriaUnrealFactory.utils_actor.set_stencil_value(actor, value)
@staticmethod
def _import_actor_from_file_in_engine(file_path: str, actor_name: str) -> None:
"""Imports an actor from a file in the Unreal Engine and spawns it in the world
with a given name.
Args:
file_path (str): The path to the file of the actor to import.
actor_name (str): The name to give to the spawned actor in the world.
"""
actor_paths = XRFeitoriaUnrealFactory.utils.import_asset(file_path)
actor_path = [path for path in actor_paths if path.split('/')[-1] == path.split('/')[-2]]
actor_object = unreal.load_asset(actor_path[0])
actor = XRFeitoriaUnrealFactory.utils_actor.spawn_actor_from_object(actor_object=actor_object)
actor.set_actor_label(actor_name)
if isinstance(actor, unreal.SkeletalMeshActor):
anim_asset_exist = unreal.EditorAssetLibrary.does_asset_exist(f'{actor_path[0]}_Anim')
if anim_asset_exist:
anim_asset = unreal.load_asset(f'{actor_path[0]}_Anim')
actor.skeletal_mesh_component.override_animation_data(anim_asset)
actor.skeletal_mesh_component.set_animation_mode(unreal.AnimationMode.ANIMATION_SINGLE_NODE)
@staticmethod
def _spawn_actor_in_engine(
engine_path: str,
name: str,
location: 'Vector' = (0, 0, 0),
rotation: 'Vector' = (0, 0, 0),
scale: 'Vector' = (1, 1, 1),
) -> str:
"""Spawns an actor in the engine and returns its name.
Args:
engine_path (str): the path to the actor in the engine. For example, '/Game/Engine/BasicShapes/Cube'.
name (str, optional): the name of the actor. Defaults to None, in which case a name will be generated.
location (Vector, optional): the location of the actor. Units are in meters. Defaults to (0, 0, 0).
rotation (Vector, optional): the rotation of the actor. Units are in degrees. Defaults to (0, 0, 0).
scale (Vector, optional): the scale of the actor. Defaults to (1, 1, 1).
Returns:
str: the name of the actor that was spawned
"""
unreal_functions.check_asset_in_engine(engine_path, raise_error=True)
_object = unreal.load_asset(engine_path)
# location = [loc * 100.0 for loc in location] # convert from meters to centimeters
_actor = XRFeitoriaUnrealFactory.utils_actor.spawn_actor_from_object(_object, location, rotation, scale)
_actor.set_actor_label(name)
return _actor.get_actor_label()
@staticmethod
def _import_animation_from_file_in_engine(animation_path: str, actor_name: str, action_name: str) -> None:
# get actor
actor = XRFeitoriaUnrealFactory.utils_actor.get_actor_by_name(actor_name)
if not isinstance(actor, unreal.SkeletalMeshActor):
raise TypeError(f'Actor "{actor_name}" is not a skeletal mesh actor.')
# get actor's engine path
actor_engine_path = actor.skeletal_mesh_component.skeletal_mesh.get_path_name().split('.')[0]
# import animation
animation_engine_path = XRFeitoriaUnrealFactory.utils.import_anim(
animation_path, f'{actor_engine_path}_Skeleton'
)
# set animation
anim_asset_exist = unreal.EditorAssetLibrary.does_asset_exist(animation_engine_path[0])
if anim_asset_exist:
anim_asset = unreal.load_asset(animation_engine_path[0])
actor.skeletal_mesh_component.override_animation_data(anim_asset)
actor.skeletal_mesh_component.set_animation_mode(unreal.AnimationMode.ANIMATION_SINGLE_NODE)
[docs]
@remote_unreal(dec_class=True, suffix='_in_engine')
class ShapeUnrealWrapper:
"""Wrapper class for shapes in Unreal Engine."""
_object_utils = ObjectUtilsUnreal
path_mapping = {
ShapeTypeEnumUnreal.cube.value: '/Engine/BasicShapes/Cube',
ShapeTypeEnumUnreal.sphere.value: '/Engine/BasicShapes/Sphere',
ShapeTypeEnumUnreal.cylinder.value: '/Engine/BasicShapes/Cylinder',
ShapeTypeEnumUnreal.cone.value: '/Engine/BasicShapes/Cone',
ShapeTypeEnumUnreal.plane.value: '/Engine/BasicShapes/Plane',
}
[docs]
@classmethod
def spawn(
cls,
type: Literal['cube', 'sphere', 'cylinder', 'cone', 'plane'],
name: Optional[str] = None,
location: Vector = (0, 0, 0),
rotation: Vector = (0, 0, 0),
scale: Vector = (1, 1, 1),
) -> 'ActorUnreal':
"""Spawns a shape in the engine and returns its corresponding actor.
Args:
mesh_type (Literal['cube', 'sphere', 'cylinder', 'cone', 'plane']): the type of the shape.
name (Optional[str], optional): the name of the shape. Defaults to None.
location (Vector, optional): the location of the shape. Units are in meters. Defaults to (0, 0, 0).
rotation (Vector, optional): the rotation of the shape. Units are in degrees. Defaults to (0, 0, 0).
scale (Vector, optional): the scale of the shape. Defaults to (1, 1, 1).
"""
if name is None:
name = cls._object_utils.generate_obj_name(obj_type=type)
return ActorUnreal.spawn_from_engine_path(
engine_path=cls.path_mapping[type],
name=name,
location=location,
rotation=rotation,
scale=scale,
)