From 9b5dcf82b5972037daf9f114e79dee6618de88e7 Mon Sep 17 00:00:00 2001 From: Scott Murray Date: Thu, 16 Dec 2021 20:46:12 -0800 Subject: [PATCH 1/7] updates extend editor_entity_utils.py functionality Signed-off-by: Scott Murray --- .../editor_entity_utils.py | 200 +++++++++++++++--- 1 file changed, 172 insertions(+), 28 deletions(-) diff --git a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py index 72530325e0..b9f7576f1c 100644 --- a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py +++ b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py @@ -29,6 +29,11 @@ class EditorComponent: which also assigns self.id and self.type_id to the EditorComponent object. """ + def __init__(self, type_id): + self.type_id = type_id + self.id = None + self.property_tree = None + def get_component_name(self) -> str: """ Used to get name of component @@ -50,6 +55,9 @@ class EditorComponent: 7. prop_tree.get_container_item(path, key) :return: Property tree object of a component """ + if self.property_tree is not None: + return self.property_tree + build_prop_tree_outcome = editor.EditorComponentAPIBus( bus.Broadcast, "BuildComponentPropertyTreeEditor", self.id ) @@ -58,7 +66,114 @@ class EditorComponent: ), f"Failure: Could not build property tree 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 = prop_tree + return self.property_tree + + def is_property_container(self, component_property_path: str) -> bool: + """ + + """ + if self.property_tree is None: + self.get_property_tree() + result = self.property_tree.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 + """ + if self.is_property_container(component_property_path): + container_count_outcome = self.property_tree.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) -> bool: + """ + Used to rest a container to empty + :param component_property_path: String of component property. (e.g. 'Settings|Visible') + :return: Boolean success + """ + if self.is_property_container(component_property_path): + reset_outcome = self.property_tree.reset_container(component_property_path) + return reset_outcome.IsSuccess() + else: + return False + + def append_container_item(self, component_property_path: str, value: any) -> bool: + """ + Used to append a container item without providing an index key. + :param component_property_path: String of component property. (e.g. 'Settings|Visible') + :param value: Value to be set + :return: Boolean success + """ + if self.is_property_container(component_property_path): + append_outcome = self.property_tree.append_container_item(component_property_path, value) + return append_outcome.IsSuccess() + else: + return False + + def add_container_item(self, component_property_path: str, key: any, value: any) -> bool: + """ + Used to add a container item at a specified key. In practice key should be an integer index. + :param component_property_path: String of component property. (e.g. 'Settings|Visible') + :param key: Zero index integer key, although this could be any unique unused key value + :param value: Value to be set + :return: Boolean success + """ + if self.is_property_container(component_property_path): + add_outcome = self.property_tree.add_container_item(component_property_path, key, value) + return add_outcome.IsSuccess() + else: + return False + + def get_container_item(self, component_property_path: str, key: any) -> any: + """ + Used to retrieve a container item value at the specified key. In practice key should be an integer index. + :param component_property_path: String of component property. (e.g. 'Settings|Visible') + :param key: Zero index integer key + :return: Value stored at the key specified + """ + if self.is_property_container(component_property_path): + get_outcome = self.property_tree.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}]" + return get_outcome.GetValue() + else: + return None + + def remove_container_item(self, component_property_path: str, key: any) -> bool: + """ + Used to remove a container item value at the specified key. In practice key should be an integer index. + :param component_property_path: String of component property. (e.g. 'Settings|Visible') + :param key: Zero index integer key + :return: Boolean success + """ + if self.is_property_container(component_property_path): + remove_outcome = self.property_tree.remove_container_item(component_property_path, key) + return remove_outcome.IsSuccess() + else: + return False + + def update_container_item(self, component_property_path: str, key: any, value: any): + """ + Used to update a container item at a specified key. In practice key should be an integer index. + :param component_property_path: String of component property. (e.g. 'Settings|Visible') + :param key: Zero index integer key + :param value: Value to be set + :return: Boolean success + """ + if self.is_property_container(component_property_path): + update_outcome = self.property_tree.update_container_item(component_property_path, key, value) + return update_outcome.IsSuccess() + else: + return False def get_component_property_value(self, component_property_path: str): """ @@ -101,16 +216,25 @@ class EditorComponent: """ editor.EditorComponentAPIBus(bus.Broadcast, "DisableComponents", [self.id]) + def enable_component(self): + """ + used to enable the componet using its id value + """ + editor.EditorComponentAPIBus(bus.Broadcast, "EnabledComponents", [self.id]) + @staticmethod - def get_type_ids(component_names: list) -> list: + def get_type_ids(component_names: list, entity_type: str ='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. """ + if entity_type.lower() == 'level': + entity_type = azlmbr.entity.EntityType().Level + else: + entity_type = azlmbr.entity.EntityType().Game type_ids = editor.EditorComponentAPIBus( - bus.Broadcast, "FindComponentTypeIdsByEntityType", component_names, azlmbr.entity.EntityType().Game - ) + bus.Broadcast, "FindComponentTypeIdsByEntityType", component_names, entity_type) return type_ids @@ -278,8 +402,7 @@ class EditorEntity: components = [] type_ids = EditorComponent.get_type_ids(component_names) 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 +414,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) + 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 @@ -300,8 +444,7 @@ class EditorEntity: component_list = [] type_ids = EditorComponent.get_type_ids(component_names) 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 ) @@ -359,6 +502,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 +646,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 +664,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, '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 +685,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, '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 +706,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], 'level') return editor.EditorLevelComponentAPIBus(bus.Broadcast, "HasComponentOfType", type_ids[0]) @staticmethod @@ -572,5 +716,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], 'level') return editor.EditorLevelComponentAPIBus(bus.Broadcast, "CountComponentsOfType", type_ids[0]) From 291e172f9e13285f4e5c6a90eea7740a804ab88a Mon Sep 17 00:00:00 2001 From: Scott Murray Date: Thu, 16 Dec 2021 21:01:39 -0800 Subject: [PATCH 2/7] fixing some docstrings Signed-off-by: Scott Murray --- .../editor_python_test_tools/editor_entity_utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py index b9f7576f1c..58d9e30f81 100644 --- a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py +++ b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py @@ -71,7 +71,9 @@ class EditorComponent: def is_property_container(self, component_property_path: str) -> bool: """ - + Used to determine if a component property is a container. Containers are similar to a dictionary with int keys. + :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 is None: self.get_property_tree() @@ -255,7 +257,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() From a7d173db3d530dbf0d5f0067e05e4bd8ea422406 Mon Sep 17 00:00:00 2001 From: Scott Murray Date: Mon, 3 Jan 2022 16:15:35 -0800 Subject: [PATCH 3/7] changes from review and switching to assert model instead of return bool Signed-off-by: Scott Murray --- .../editor_entity_utils.py | 164 ++++++++++-------- 1 file changed, 93 insertions(+), 71 deletions(-) diff --git a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py index 58d9e30f81..0878431054 100644 --- a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py +++ b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py @@ -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,15 +22,21 @@ import azlmbr.legacy.general as general from editor_python_test_tools.utils import Report +class Entity_Type(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): + def __init__(self, type_id: uuid): self.type_id = type_id self.id = None self.property_tree = None @@ -88,51 +95,59 @@ class EditorComponent: :param component_property_path: String of component property. (e.g. 'Settings|Visible') :return: Count of items in the container as unsigned integer """ - if self.is_property_container(component_property_path): - container_count_outcome = self.property_tree.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() + assert ( + self.is_property_container(component_property_path) + ), f"Failure: '{component_property_path}' is not a property container" + container_count_outcome = self.property_tree.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) -> bool: + def reset_container(self, component_property_path: str): """ Used to rest a container to empty :param component_property_path: String of component property. (e.g. 'Settings|Visible') - :return: Boolean success + :return: None """ - if self.is_property_container(component_property_path): - reset_outcome = self.property_tree.reset_container(component_property_path) - return reset_outcome.IsSuccess() - else: - return False + assert ( + self.is_property_container(component_property_path) + ), f"Failure: '{component_property_path}' is not a property container" + reset_outcome = self.property_tree.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) -> bool: + def append_container_item(self, component_property_path: str, value: any): """ Used to append a container item without providing an index key. :param component_property_path: String of component property. (e.g. 'Settings|Visible') :param value: Value to be set - :return: Boolean success + :return: None """ - if self.is_property_container(component_property_path): - append_outcome = self.property_tree.append_container_item(component_property_path, value) - return append_outcome.IsSuccess() - else: - return False + assert ( + self.is_property_container(component_property_path) + ), f"Failure: '{component_property_path}' is not a property container" + append_outcome = self.property_tree.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) -> bool: + def add_container_item(self, component_property_path: str, key: any, value: any): """ Used to add a container item at a specified key. In practice key should be an integer index. :param component_property_path: String of component property. (e.g. 'Settings|Visible') :param key: Zero index integer key, although this could be any unique unused key value :param value: Value to be set - :return: Boolean success + :return: None """ - if self.is_property_container(component_property_path): - add_outcome = self.property_tree.add_container_item(component_property_path, key, value) - return add_outcome.IsSuccess() - else: - return False + assert ( + self.is_property_container(component_property_path) + ), f"Failure: '{component_property_path}' is not a property container" + add_outcome = self.property_tree.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: """ @@ -141,27 +156,29 @@ class EditorComponent: :param key: Zero index integer key :return: Value stored at the key specified """ - if self.is_property_container(component_property_path): - get_outcome = self.property_tree.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}]" - return get_outcome.GetValue() - else: - return None + assert ( + self.is_property_container(component_property_path) + ), f"Failure: '{component_property_path}' is not a property container" + get_outcome = self.property_tree.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}]" + return get_outcome.GetValue() - def remove_container_item(self, component_property_path: str, key: any) -> bool: + def remove_container_item(self, component_property_path: str, key: any): """ Used to remove a container item value at the specified key. In practice key should be an integer index. :param component_property_path: String of component property. (e.g. 'Settings|Visible') :param key: Zero index integer key - :return: Boolean success + :return: None """ - if self.is_property_container(component_property_path): - remove_outcome = self.property_tree.remove_container_item(component_property_path, key) - return remove_outcome.IsSuccess() - else: - return False + assert ( + self.is_property_container(component_property_path) + ), f"Failure: '{component_property_path}' is not a property container" + remove_outcome = self.property_tree.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): """ @@ -169,13 +186,15 @@ class EditorComponent: :param component_property_path: String of component property. (e.g. 'Settings|Visible') :param key: Zero index integer key :param value: Value to be set - :return: Boolean success + :return: None """ - if self.is_property_container(component_property_path): - update_outcome = self.property_tree.update_container_item(component_property_path, key, value) - return update_outcome.IsSuccess() - else: - return False + assert ( + self.is_property_container(component_property_path) + ), f"Failure: '{component_property_path}' is not a property container" + update_outcome = self.property_tree.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): """ @@ -211,30 +230,33 @@ 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. :return: None """ + warnings.warn("disable_component is deprecated, use set_enabled(False) instead.", DeprecationWarning) editor.EditorComponentAPIBus(bus.Broadcast, "DisableComponents", [self.id]) - def enable_component(self): - """ - used to enable the componet using its id value - """ - editor.EditorComponentAPIBus(bus.Broadcast, "EnabledComponents", [self.id]) - @staticmethod - def get_type_ids(component_names: list, entity_type: str ='Game') -> list: + def get_type_ids(component_names: list, entity_type: Entity_Type = Entity_Type.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 """ - if entity_type.lower() == 'level': - entity_type = azlmbr.entity.EntityType().Level - else: - entity_type = azlmbr.entity.EntityType().Game type_ids = editor.EditorComponentAPIBus( bus.Broadcast, "FindComponentTypeIdsByEntityType", component_names, entity_type) return type_ids @@ -402,7 +424,7 @@ 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, Entity_Type.GAME) for type_id in type_ids: new_comp = EditorComponent(type_id) add_component_outcome = editor.EditorComponentAPIBus( @@ -430,7 +452,7 @@ class EditorEntity: :param component_names: List of component names to remove :return: None """ - type_ids = EditorComponent.get_type_ids(component_names) + type_ids = EditorComponent.get_type_ids(component_names, Entity_Type.GAME) for type_id in type_ids: remove_outcome = editor.EditorComponentAPIBus(bus.Broadcast, "RemoveComponents", self.id, [type_id]) assert ( @@ -444,7 +466,7 @@ 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, Entity_Type.GAME) for type_id in type_ids: component = EditorComponent(type_id) get_component_of_type_outcome = editor.EditorComponentAPIBus( @@ -464,7 +486,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], Entity_Type.GAME) return editor.EditorComponentAPIBus(bus.Broadcast, "HasComponentOfType", self.id, type_ids[0]) def get_start_status(self) -> int: @@ -666,7 +688,7 @@ class EditorLevelEntity: :return: List of newly added components to the level """ components = [] - type_ids = EditorComponent.get_type_ids(component_names, 'level') + type_ids = EditorComponent.get_type_ids(component_names, Entity_Type.LEVEL) for type_id in type_ids: new_comp = EditorComponent(type_id) add_component_outcome = editor.EditorLevelComponentAPIBus( @@ -687,7 +709,7 @@ class EditorLevelEntity: :return: List of Level Component objects of given component name """ component_list = [] - type_ids = EditorComponent.get_type_ids(component_names, 'level') + type_ids = EditorComponent.get_type_ids(component_names, Entity_Type.LEVEL) for type_id in type_ids: component = EditorComponent(type_id) get_component_of_type_outcome = editor.EditorLevelComponentAPIBus( @@ -708,7 +730,7 @@ class EditorLevelEntity: :param component_name: Name of component to check for :return: True, if level has specified component. Else, False """ - type_ids = EditorComponent.get_type_ids([component_name], 'level') + type_ids = EditorComponent.get_type_ids([component_name], Entity_Type.LEVEL) return editor.EditorLevelComponentAPIBus(bus.Broadcast, "HasComponentOfType", type_ids[0]) @staticmethod @@ -718,5 +740,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 = EditorComponent.get_type_ids([component_name], 'level') + type_ids = EditorComponent.get_type_ids([component_name], Entity_Type.LEVEL) return editor.EditorLevelComponentAPIBus(bus.Broadcast, "CountComponentsOfType", type_ids[0]) From d343caa3f2d846610b4b1a29adac682d9e567867 Mon Sep 17 00:00:00 2001 From: Scott Murray Date: Tue, 4 Jan 2022 11:58:17 -0800 Subject: [PATCH 4/7] add optional force_get to get_property_tree Signed-off-by: Scott Murray --- .../editor_python_test_tools/editor_entity_utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py index 0878431054..4dd7ec1446 100644 --- a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py +++ b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py @@ -50,7 +50,7 @@ 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: 1. prop_tree.is_container(path) @@ -60,9 +60,10 @@ 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) + :param force_get: Force a fresh property tree to be returned rather than using an existing self.property_tree :return: Property tree object of a component """ - if self.property_tree is not None: + if (not force_get) and (self.property_tree is not None): return self.property_tree build_prop_tree_outcome = editor.EditorComponentAPIBus( @@ -244,6 +245,7 @@ class EditorComponent: def disable_component(self): """ Used to disable the component using its id value. + Deprecation warning! Use set_enable(False) instead as this method is in deprecation :return: None """ warnings.warn("disable_component is deprecated, use set_enabled(False) instead.", DeprecationWarning) From 5f9a4a5eed421480094884b2b78ac309d2d77fde Mon Sep 17 00:00:00 2001 From: Scott Murray Date: Wed, 5 Jan 2022 17:21:55 -0800 Subject: [PATCH 5/7] docstring clarification and addressing review comments Signed-off-by: Scott Murray --- .../editor_entity_utils.py | 95 +++++++++++-------- 1 file changed, 56 insertions(+), 39 deletions(-) diff --git a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py index 4dd7ec1446..d354c4ece6 100644 --- a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py +++ b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py @@ -22,7 +22,7 @@ import azlmbr.legacy.general as general from editor_python_test_tools.utils import Report -class Entity_Type(Enum): +class EditorEntityType(Enum): GAME = azlmbr.entity.EntityType().Game LEVEL = azlmbr.entity.EntityType().Level @@ -39,7 +39,7 @@ class EditorComponent: def __init__(self, type_id: uuid): self.type_id = type_id self.id = None - self.property_tree = None + self.property_tree_editor = None def get_component_name(self) -> str: """ @@ -52,7 +52,7 @@ class EditorComponent: 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) @@ -60,32 +60,36 @@ 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) - :param force_get: Force a fresh property tree to be returned rather than using an existing self.property_tree - :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 is not None): - return self.property_tree + 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()) - self.property_tree = prop_tree - return self.property_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 similar to a dictionary with int keys. + 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 is None: + if self.property_tree_editor is None: self.get_property_tree() - result = self.property_tree.is_container(component_property_path) + 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 @@ -99,7 +103,7 @@ class EditorComponent: assert ( self.is_property_container(component_property_path) ), f"Failure: '{component_property_path}' is not a property container" - container_count_outcome = self.property_tree.get_container_count(component_property_path) + 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}'" @@ -107,21 +111,22 @@ class EditorComponent: def reset_container(self, component_property_path: str): """ - Used to rest a container to empty + 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.reset_container(component_property_path) + 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 container item without providing an index key. + 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 @@ -129,38 +134,44 @@ class EditorComponent: assert ( self.is_property_container(component_property_path) ), f"Failure: '{component_property_path}' is not a property container" - append_outcome = self.property_tree.append_container_item(component_property_path, value) + 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. In practice key should be an integer index. + 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, although this could be any unique unused key value + :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.add_container_item(component_property_path, key, value) + 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. In practice key should be an integer index. + 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 + :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.get_container_item(component_property_path, key) + 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}]" @@ -168,31 +179,37 @@ class EditorComponent: def remove_container_item(self, component_property_path: str, key: any): """ - Used to remove a container item value at the specified key. In practice key should be an integer index. + 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 + :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.remove_container_item(component_property_path, key) + 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. In practice key should be an integer index. + 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 + :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.update_container_item(component_property_path, key, value) + 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}'" @@ -252,7 +269,7 @@ class EditorComponent: editor.EditorComponentAPIBus(bus.Broadcast, "DisableComponents", [self.id]) @staticmethod - def get_type_ids(component_names: list, entity_type: Entity_Type = Entity_Type.GAME) -> 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 @@ -426,7 +443,7 @@ class EditorEntity: :return: List of newly added components to the entity """ components = [] - type_ids = EditorComponent.get_type_ids(component_names, Entity_Type.GAME) + type_ids = EditorComponent.get_type_ids(component_names, EditorEntityType.GAME) for type_id in type_ids: new_comp = EditorComponent(type_id) add_component_outcome = editor.EditorComponentAPIBus( @@ -454,7 +471,7 @@ class EditorEntity: :param component_names: List of component names to remove :return: None """ - type_ids = EditorComponent.get_type_ids(component_names, Entity_Type.GAME) + 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 ( @@ -468,7 +485,7 @@ class EditorEntity: :return: List of Entity Component objects of given component name """ component_list = [] - type_ids = EditorComponent.get_type_ids(component_names, Entity_Type.GAME) + type_ids = EditorComponent.get_type_ids(component_names, EditorEntityType.GAME) for type_id in type_ids: component = EditorComponent(type_id) get_component_of_type_outcome = editor.EditorComponentAPIBus( @@ -488,7 +505,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], Entity_Type.GAME) + 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: @@ -690,7 +707,7 @@ class EditorLevelEntity: :return: List of newly added components to the level """ components = [] - type_ids = EditorComponent.get_type_ids(component_names, Entity_Type.LEVEL) + type_ids = EditorComponent.get_type_ids(component_names, EditorEntityType.LEVEL) for type_id in type_ids: new_comp = EditorComponent(type_id) add_component_outcome = editor.EditorLevelComponentAPIBus( @@ -711,7 +728,7 @@ class EditorLevelEntity: :return: List of Level Component objects of given component name """ component_list = [] - type_ids = EditorComponent.get_type_ids(component_names, Entity_Type.LEVEL) + type_ids = EditorComponent.get_type_ids(component_names, EditorEntityType.LEVEL) for type_id in type_ids: component = EditorComponent(type_id) get_component_of_type_outcome = editor.EditorLevelComponentAPIBus( @@ -732,7 +749,7 @@ class EditorLevelEntity: :param component_name: Name of component to check for :return: True, if level has specified component. Else, False """ - type_ids = EditorComponent.get_type_ids([component_name], Entity_Type.LEVEL) + type_ids = EditorComponent.get_type_ids([component_name], EditorEntityType.LEVEL) return editor.EditorLevelComponentAPIBus(bus.Broadcast, "HasComponentOfType", type_ids[0]) @staticmethod @@ -742,5 +759,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 = EditorComponent.get_type_ids([component_name], Entity_Type.LEVEL) + type_ids = EditorComponent.get_type_ids([component_name], EditorEntityType.LEVEL) return editor.EditorLevelComponentAPIBus(bus.Broadcast, "CountComponentsOfType", type_ids[0]) From 2492c0a4f4db175268742fe9dcf4191d4ad44bea Mon Sep 17 00:00:00 2001 From: Scott Murray Date: Thu, 6 Jan 2022 17:56:00 -0800 Subject: [PATCH 6/7] enum must be dereferenced with .value also added get_outcome.GetError() to pull more information from any get_container_item failures Signed-off-by: Scott Murray --- .../editor_python_test_tools/editor_entity_utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py index d354c4ece6..bf27ec06e0 100644 --- a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py +++ b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py @@ -174,7 +174,9 @@ class EditorComponent: 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"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): @@ -277,7 +279,7 @@ class EditorComponent: :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, entity_type) + bus.Broadcast, "FindComponentTypeIdsByEntityType", component_names, entity_type.value) return type_ids From aebf93d8824a8b1d8b8e31d7a808161b21357dd8 Mon Sep 17 00:00:00 2001 From: Scott Murray Date: Wed, 12 Jan 2022 13:13:52 -0800 Subject: [PATCH 7/7] fixing a typo in docstring Signed-off-by: Scott Murray --- .../editor_python_test_tools/editor_entity_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py index bf27ec06e0..c3398406ab 100644 --- a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py +++ b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py @@ -264,7 +264,7 @@ class EditorComponent: def disable_component(self): """ Used to disable the component using its id value. - Deprecation warning! Use set_enable(False) instead as this method is in deprecation + 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)