Merge pull request #6470 from aws-lumberyard-dev/Atom/scottmur/lbc_helper

Extension of editor_entity_utils.py to expose more functionality
This commit is contained in:
jromnoa
2022-01-14 11:26:14 -08:00
committed by GitHub
@@ -8,7 +8,8 @@ SPDX-License-Identifier: Apache-2.0 OR MIT
# Built-in Imports
from __future__ import annotations
from typing import List, Tuple, Union
from enum import Enum
import warnings
# Open 3D Engine Imports
import azlmbr
@@ -21,14 +22,25 @@ import azlmbr.legacy.general as general
from editor_python_test_tools.utils import Report
class EditorEntityType(Enum):
GAME = azlmbr.entity.EntityType().Game
LEVEL = azlmbr.entity.EntityType().Level
class EditorComponent:
"""
EditorComponent class used to set and get the component property value using path
EditorComponent object is returned from either of
EditorEntity.add_component() or Entity.add_components() or EditorEntity.get_components_of_type()
which also assigns self.id and self.type_id to the EditorComponent object.
self.type_id is the UUID for the component type as provided by an ebus call.
"""
def __init__(self, type_id: uuid):
self.type_id = type_id
self.id = None
self.property_tree_editor = None
def get_component_name(self) -> str:
"""
Used to get name of component
@@ -38,9 +50,9 @@ class EditorComponent:
assert len(type_names) != 0, "Component object does not have type id"
return type_names[0]
def get_property_tree(self):
def get_property_tree(self, force_get: bool = False):
"""
Used to get the property tree object of component that has following functions associated with it:
Used to get and cache the property tree editor of component that has following functions associated with it:
1. prop_tree.is_container(path)
2. prop_tree.get_container_count(path)
3. prop_tree.reset_container(path)
@@ -48,17 +60,161 @@ class EditorComponent:
5. prop_tree.remove_container_item(path, key)
6. prop_tree.update_container_item(path, key, value)
7. prop_tree.get_container_item(path, key)
:return: Property tree object of a component
:param force_get: Force a fresh property tree editor rather than the cached self.property_tree_editor
:return: Property tree editor of the component
"""
if (not force_get) and (self.property_tree_editor is not None):
return self.property_tree_editor
build_prop_tree_outcome = editor.EditorComponentAPIBus(
bus.Broadcast, "BuildComponentPropertyTreeEditor", self.id
)
assert (
build_prop_tree_outcome.IsSuccess()
), f"Failure: Could not build property tree of component: '{self.get_component_name()}'"
), f"Failure: Could not build property tree editor of component: '{self.get_component_name()}'"
prop_tree = build_prop_tree_outcome.GetValue()
Report.info(prop_tree.build_paths_list())
return prop_tree
self.property_tree_editor = prop_tree
return self.property_tree_editor
def is_property_container(self, component_property_path: str) -> bool:
"""
Used to determine if a component property is a container.
Containers are a collection of same typed values that can expand/shrink to contain more or less.
There are two types of containers; indexed and associative.
Indexed containers use integer key and are something like a linked list
Associative containers utilize keys of the same type which could be any supported type.
:param component_property_path: String of component property. (e.g. 'Settings|Visible')
:return: Boolean True if the property is a container False if it is not.
"""
if self.property_tree_editor is None:
self.get_property_tree()
result = self.property_tree_editor.is_container(component_property_path)
if not result:
Report.info(f"{self.get_component_name()}: '{component_property_path}' is not a container")
return result
def get_container_count(self, component_property_path: str) -> int:
"""
Used to get the count of items in the container.
:param component_property_path: String of component property. (e.g. 'Settings|Visible')
:return: Count of items in the container as unsigned integer
"""
assert (
self.is_property_container(component_property_path)
), f"Failure: '{component_property_path}' is not a property container"
container_count_outcome = self.property_tree_editor.get_container_count(component_property_path)
assert (
container_count_outcome.IsSuccess()
), f"Failure: get_container_count did not return success for '{component_property_path}'"
return container_count_outcome.GetValue()
def reset_container(self, component_property_path: str):
"""
Used to reset a container to empty
:param component_property_path: String of component property. (e.g. 'Settings|Visible')
:return: None
"""
assert (
self.is_property_container(component_property_path)
), f"Failure: '{component_property_path}' is not a property container"
reset_outcome = self.property_tree_editor.reset_container(component_property_path)
assert (
reset_outcome.IsSuccess()
), f"Failure: could not reset_container on '{component_property_path}'"
def append_container_item(self, component_property_path: str, value: any):
"""
Used to append a value to an indexed container item without providing an index key.
Append will fail on an associative container
:param component_property_path: String of component property. (e.g. 'Settings|Visible')
:param value: Value to be set
:return: None
"""
assert (
self.is_property_container(component_property_path)
), f"Failure: '{component_property_path}' is not a property container"
append_outcome = self.property_tree_editor.append_container_item(component_property_path, value)
assert (
append_outcome.IsSuccess()
), f"Failure: could not append_container_item to '{component_property_path}'"
def add_container_item(self, component_property_path: str, key: any, value: any):
"""
Used to add a container item at a specified key.
There are two types of containers; indexed and associative.
Indexed containers use integer key.
Associative containers utilize keys of the same type which could be any supported type.
:param component_property_path: String of component property. (e.g. 'Settings|Visible')
:param key: Zero index integer key or any supported type for associative container
:param value: Value to be set
:return: None
"""
assert (
self.is_property_container(component_property_path)
), f"Failure: '{component_property_path}' is not a property container"
add_outcome = self.property_tree_editor.add_container_item(component_property_path, key, value)
assert (
add_outcome.IsSuccess()
), f"Failure: could not add_container_item '{key}' to '{component_property_path}'"
def get_container_item(self, component_property_path: str, key: any) -> any:
"""
Used to retrieve a container item value at the specified key.
There are two types of containers; indexed and associative.
Indexed containers use integer key.
Associative containers utilize keys of the same type which could be any supported type.
:param component_property_path: String of component property. (e.g. 'Settings|Visible')
:param key: Zero index integer key or any supported type for associative container
:return: Value stored at the key specified
"""
assert (
self.is_property_container(component_property_path)
), f"Failure: '{component_property_path}' is not a property container"
get_outcome = self.property_tree_editor.get_container_item(component_property_path, key)
assert (
get_outcome.IsSuccess()
), (
f"Failure: could not get a value for {self.get_component_name()}: '{component_property_path}' [{key}]. "
f"Error returned by get_container_item: {get_outcome.GetError()}")
return get_outcome.GetValue()
def remove_container_item(self, component_property_path: str, key: any):
"""
Used to remove a container item value at the specified key.
There are two types of containers; indexed and associative.
Indexed containers use integer key.
Associative containers utilize keys of the same type which could be any supported type.
:param component_property_path: String of component property. (e.g. 'Settings|Visible')
:param key: Zero index integer key or any supported type for associative container
:return: None
"""
assert (
self.is_property_container(component_property_path)
), f"Failure: '{component_property_path}' is not a property container"
remove_outcome = self.property_tree_editor.remove_container_item(component_property_path, key)
assert (
remove_outcome.IsSuccess()
), f"Failure: could not remove_container_item '{key}' from '{component_property_path}'"
def update_container_item(self, component_property_path: str, key: any, value: any):
"""
Used to update a container item at a specified key.
There are two types of containers; indexed and associative.
Indexed containers use integer key.
Associative containers utilize keys of the same type which could be any supported type.
:param component_property_path: String of component property. (e.g. 'Settings|Visible')
:param key: Zero index integer key or any supported type for associative container
:param value: Value to be set
:return: None
"""
assert (
self.is_property_container(component_property_path)
), f"Failure: '{component_property_path}' is not a property container"
update_outcome = self.property_tree_editor.update_container_item(component_property_path, key, value)
assert (
update_outcome.IsSuccess()
), f"Failure: could not update '{key}' in '{component_property_path}'"
def get_component_property_value(self, component_property_path: str):
"""
@@ -94,23 +250,36 @@ class EditorComponent:
"""
return editor.EditorComponentAPIBus(bus.Broadcast, "IsComponentEnabled", self.id)
def set_enabled(self, new_state: bool):
"""
Used to set the component enabled state
:param new_state: Boolean enabled True, disabled False
:return: None
"""
if new_state:
editor.EditorComponentAPIBus(bus.Broadcast, "EnableComponents", [self.id])
else:
editor.EditorComponentAPIBus(bus.Broadcast, "DisableComponents", [self.id])
def disable_component(self):
"""
Used to disable the component using its id value.
Deprecation warning! Use set_enabled(False) instead as this method is in deprecation
:return: None
"""
warnings.warn("disable_component is deprecated, use set_enabled(False) instead.", DeprecationWarning)
editor.EditorComponentAPIBus(bus.Broadcast, "DisableComponents", [self.id])
@staticmethod
def get_type_ids(component_names: list) -> list:
def get_type_ids(component_names: list, entity_type: EditorEntityType = EditorEntityType.GAME) -> list:
"""
Used to get type ids of given components list
:param: component_names: List of components to get type ids
:return: List of type ids of given components.
:param component_names: List of components to get type ids
:param entity_type: Entity_Type enum value Entity_Type.GAME is the default
:return: List of type ids of given components. Type id is a UUID as provided by the ebus call
"""
type_ids = editor.EditorComponentAPIBus(
bus.Broadcast, "FindComponentTypeIdsByEntityType", component_names, azlmbr.entity.EntityType().Game
)
bus.Broadcast, "FindComponentTypeIdsByEntityType", component_names, entity_type.value)
return type_ids
@@ -131,7 +300,7 @@ class EditorEntity:
"""
Entity class is used to create and interact with Editor Entities.
Example: To create Editor Entity, Use the code:
test_entity = Entity.create_editor_entity("TestEntity")
test_entity = EditorEntity.create_editor_entity("TestEntity")
# This creates a python object with 'test_entity' linked to entity name "TestEntity" in Editor.
# To add component, use:
test_entity.add_component(<COMPONENT_NAME>)
@@ -276,10 +445,9 @@ class EditorEntity:
:return: List of newly added components to the entity
"""
components = []
type_ids = EditorComponent.get_type_ids(component_names)
type_ids = EditorComponent.get_type_ids(component_names, EditorEntityType.GAME)
for type_id in type_ids:
new_comp = EditorComponent()
new_comp.type_id = type_id
new_comp = EditorComponent(type_id)
add_component_outcome = editor.EditorComponentAPIBus(
bus.Broadcast, "AddComponentsOfType", self.id, [type_id]
)
@@ -291,6 +459,27 @@ class EditorEntity:
self.components.append(new_comp)
return components
def remove_component(self, component_name: str) -> None:
"""
Used to remove a component from Entity
:param component_name: String of component name to remove
:return: None
"""
self.remove_components([component_name])
def remove_components(self, component_names: list):
"""
Used to remove a list of components from Entity
:param component_names: List of component names to remove
:return: None
"""
type_ids = EditorComponent.get_type_ids(component_names, EditorEntityType.GAME)
for type_id in type_ids:
remove_outcome = editor.EditorComponentAPIBus(bus.Broadcast, "RemoveComponents", self.id, [type_id])
assert (
remove_outcome.IsSuccess()
), f"Failure: could not remove component from '{self.get_name()}'"
def get_components_of_type(self, component_names: list) -> List[EditorComponent]:
"""
Used to get components of type component_name that already exists on Entity
@@ -298,10 +487,9 @@ class EditorEntity:
:return: List of Entity Component objects of given component name
"""
component_list = []
type_ids = EditorComponent.get_type_ids(component_names)
type_ids = EditorComponent.get_type_ids(component_names, EditorEntityType.GAME)
for type_id in type_ids:
component = EditorComponent()
component.type_id = type_id
component = EditorComponent(type_id)
get_component_of_type_outcome = editor.EditorComponentAPIBus(
bus.Broadcast, "GetComponentOfType", self.id, type_id
)
@@ -319,7 +507,7 @@ class EditorEntity:
:param component_name: Name of component to check for
:return: True, if entity has specified component. Else, False
"""
type_ids = EditorComponent.get_type_ids([component_name])
type_ids = EditorComponent.get_type_ids([component_name], EditorEntityType.GAME)
return editor.EditorComponentAPIBus(bus.Broadcast, "HasComponentOfType", self.id, type_ids[0])
def get_start_status(self) -> int:
@@ -359,6 +547,21 @@ class EditorEntity:
set_status = self.get_start_status()
assert set_status == status_to_set, f"Failed to set start status of {desired_start_status} to {self.get_name}"
def is_locked(self) -> bool:
"""
Used to get the locked status of the entity
:return: Boolean True if locked False if not locked
"""
return editor.EditorEntityInfoRequestBus(bus.Event, "IsLocked", self.id)
def set_lock_state(self, is_locked: bool) -> None:
"""
Sets the lock state on the object to locked or not locked.
:param is_locked: True for locking, False to unlock.
:return: None
"""
editor.EditorEntityAPIBus(bus.Event, "SetLockState", self.id, is_locked)
def delete(self) -> None:
"""
Used to delete the Entity.
@@ -488,18 +691,6 @@ class EditorLevelEntity:
EditorLevelComponentAPIBus requests.
"""
@staticmethod
def get_type_ids(component_names: list) -> list:
"""
Used to get type ids of given components list for EntityType Level
:param: component_names: List of components to get type ids
:return: List of type ids of given components.
"""
type_ids = editor.EditorComponentAPIBus(
bus.Broadcast, "FindComponentTypeIdsByEntityType", component_names, azlmbr.entity.EntityType().Level
)
return type_ids
@staticmethod
def add_component(component_name: str) -> EditorComponent:
"""
@@ -518,10 +709,9 @@ class EditorLevelEntity:
:return: List of newly added components to the level
"""
components = []
type_ids = EditorLevelEntity.get_type_ids(component_names)
type_ids = EditorComponent.get_type_ids(component_names, EditorEntityType.LEVEL)
for type_id in type_ids:
new_comp = EditorComponent()
new_comp.type_id = type_id
new_comp = EditorComponent(type_id)
add_component_outcome = editor.EditorLevelComponentAPIBus(
bus.Broadcast, "AddComponentsOfType", [type_id]
)
@@ -540,10 +730,9 @@ class EditorLevelEntity:
:return: List of Level Component objects of given component name
"""
component_list = []
type_ids = EditorLevelEntity.get_type_ids(component_names)
type_ids = EditorComponent.get_type_ids(component_names, EditorEntityType.LEVEL)
for type_id in type_ids:
component = EditorComponent()
component.type_id = type_id
component = EditorComponent(type_id)
get_component_of_type_outcome = editor.EditorLevelComponentAPIBus(
bus.Broadcast, "GetComponentOfType", type_id
)
@@ -562,7 +751,7 @@ class EditorLevelEntity:
:param component_name: Name of component to check for
:return: True, if level has specified component. Else, False
"""
type_ids = EditorLevelEntity.get_type_ids([component_name])
type_ids = EditorComponent.get_type_ids([component_name], EditorEntityType.LEVEL)
return editor.EditorLevelComponentAPIBus(bus.Broadcast, "HasComponentOfType", type_ids[0])
@staticmethod
@@ -572,5 +761,5 @@ class EditorLevelEntity:
:param component_name: Name of component to check for
:return: integer count of occurences of level component attached to level or zero if none are present
"""
type_ids = EditorLevelEntity.get_type_ids([component_name])
type_ids = EditorComponent.get_type_ids([component_name], EditorEntityType.LEVEL)
return editor.EditorLevelComponentAPIBus(bus.Broadcast, "CountComponentsOfType", type_ids[0])