You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
426 lines
18 KiB
Python
426 lines
18 KiB
Python
"""
|
|
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
|
its licensors.
|
|
|
|
For complete copyright and license terms please see the LICENSE at the root of this
|
|
distribution (the "License"). All use of this software is governed by the License,
|
|
or, if provided, by the license below or the license accompanying this file. Do not
|
|
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
"""
|
|
|
|
import azlmbr.bus as bus
|
|
import azlmbr.editor as editor
|
|
import azlmbr.entity as entity
|
|
import azlmbr.object
|
|
|
|
from typing import List
|
|
from math import isclose
|
|
import collections.abc
|
|
|
|
|
|
def find_entity_by_name(entity_name):
|
|
"""
|
|
Gets an entity ID from the entity with the given entity_name
|
|
:param entity_name: String of entity name to search for
|
|
:return entity ID
|
|
"""
|
|
search_filter = entity.SearchFilter()
|
|
search_filter.names = [entity_name]
|
|
matching_entity_list = entity.SearchBus(bus.Broadcast, 'SearchEntities', search_filter)
|
|
if matching_entity_list:
|
|
matching_entity = matching_entity_list[0]
|
|
if matching_entity.IsValid():
|
|
print(f'{entity_name} entity found with ID {matching_entity.ToString()}')
|
|
return matching_entity
|
|
else:
|
|
return matching_entity_list
|
|
|
|
|
|
def get_component_type_id(component_name):
|
|
"""
|
|
Gets the component_type_id from a given component name
|
|
:param component_name: String of component name to search for
|
|
:return component type ID
|
|
"""
|
|
type_ids_list = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', [component_name],
|
|
entity.EntityType().Game)
|
|
component_type_id = type_ids_list[0]
|
|
return component_type_id
|
|
|
|
|
|
def add_level_component(component_name):
|
|
"""
|
|
Adds the specified component to the Level Inspector
|
|
:param component_name: String of component name to search for
|
|
:return Component object.
|
|
"""
|
|
level_component_list = [component_name]
|
|
level_component_type_ids_list = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType',
|
|
level_component_list, entity.EntityType().Level)
|
|
level_component_outcome = editor.EditorLevelComponentAPIBus(bus.Broadcast, 'AddComponentsOfType',
|
|
[level_component_type_ids_list[0]])
|
|
level_component = level_component_outcome.GetValue()[0]
|
|
return level_component
|
|
|
|
|
|
def add_component(componentName, entityId):
|
|
"""
|
|
Given a component name, finds component TypeId, adds to given entity, and verifies successful add/active state.
|
|
:param componentName: String of component name to add.
|
|
:param entityId: Entity to add component to.
|
|
:return: Component object.
|
|
"""
|
|
typeIdsList = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', [componentName],
|
|
entity.EntityType().Game)
|
|
typeNamesList = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeNames', typeIdsList)
|
|
componentOutcome = editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', entityId, typeIdsList)
|
|
isActive = editor.EditorComponentAPIBus(bus.Broadcast, 'IsComponentEnabled', componentOutcome.GetValue()[0])
|
|
hasComponent = editor.EditorComponentAPIBus(bus.Broadcast, 'HasComponentOfType', entityId, typeIdsList[0])
|
|
if componentOutcome.IsSuccess() and isActive:
|
|
print('{} component was added to entity'.format(typeNamesList[0]))
|
|
elif componentOutcome.IsSuccess() and not isActive:
|
|
print('{} component was added to entity, but the component is disabled'.format(typeNamesList[0]))
|
|
elif not componentOutcome.IsSuccess():
|
|
print('Failed to add {} component to entity'.format(typeNamesList[0]))
|
|
if hasComponent:
|
|
print('Entity has a {} component'.format(typeNamesList[0]))
|
|
return componentOutcome.GetValue()[0]
|
|
|
|
|
|
def add_component_of_type(componentTypeId, entityId):
|
|
typeIdsList = [componentTypeId]
|
|
componentOutcome = editor.EditorComponentAPIBus(
|
|
azlmbr.bus.Broadcast, 'AddComponentsOfType', entityId, typeIdsList)
|
|
return componentOutcome.GetValue()[0]
|
|
|
|
|
|
def remove_component(component_name, entity_id):
|
|
"""
|
|
Removes the specified component from the specified entity.
|
|
:param component_name: String of component name to remove.
|
|
:param entity_id: Entity to remove component from.
|
|
:return: EntityComponentIdPair if removal was successful, else None.
|
|
"""
|
|
type_ids_list = [get_component_type_id(component_name)]
|
|
outcome = editor.EditorComponentAPIBus(bus.Broadcast, 'GetComponentOfType', entity_id, type_ids_list[0])
|
|
if outcome.IsSuccess():
|
|
component_entity_pair = outcome.GetValue()
|
|
editor.EditorComponentAPIBus(bus.Broadcast, 'RemoveComponents', [component_entity_pair])
|
|
has_component = editor.EditorComponentAPIBus(bus.Broadcast, 'HasComponentOfType', entity_id, type_ids_list[0])
|
|
if has_component:
|
|
print(f"Failed to remove {component_name}")
|
|
return None
|
|
else:
|
|
print(f"{component_name} was successfully removed")
|
|
return component_entity_pair
|
|
else:
|
|
print(f"{component_name} not found on entity")
|
|
return None
|
|
|
|
|
|
def get_component_property_value(component, component_propertyPath):
|
|
"""
|
|
Given a component name and component property path, outputs the property's value.
|
|
:param component: Component object to act on.
|
|
:param componentPropertyPath: String of component property. (e.g. 'Settings|Visible')
|
|
:return: Value set in given componentPropertyPath
|
|
"""
|
|
componentPropertyObj = editor.EditorComponentAPIBus(bus.Broadcast, 'GetComponentProperty', component,
|
|
component_propertyPath)
|
|
if componentPropertyObj.IsSuccess():
|
|
componentProperty = componentPropertyObj.GetValue()
|
|
print(f'{component_propertyPath} set to {componentProperty}')
|
|
return componentProperty
|
|
else:
|
|
print(f'FAILURE: Could not get value from {component_propertyPath}')
|
|
return None
|
|
|
|
|
|
def get_property_tree(component):
|
|
"""
|
|
Given a configured component object, prints the property tree info from that component
|
|
:param component: Component object to act on.
|
|
"""
|
|
pteObj = editor.EditorComponentAPIBus(bus.Broadcast, 'BuildComponentPropertyTreeEditor', component)
|
|
pte = pteObj.GetValue()
|
|
print(pte.build_paths_list())
|
|
return pte
|
|
|
|
|
|
def compare_values(first_object: object, second_object: object, name: str) -> bool:
|
|
# Quick case - can we just directly compare the two objects successfully?
|
|
if (first_object == second_object):
|
|
result = True
|
|
# No, so get a lot more specific
|
|
elif isinstance(first_object, collections.abc.Container):
|
|
# If they aren't both containers, they're different
|
|
if not isinstance(second_object, collections.abc.Container):
|
|
result = False
|
|
# If they have different lengths, they're different
|
|
elif len(first_object) != len (second_object):
|
|
result = False
|
|
# If they're different strings, they're containers but they failed the == check so
|
|
# we know they're different
|
|
elif isinstance(first_object, str):
|
|
result = False
|
|
else:
|
|
# It's a collection of values, so iterate through them all...
|
|
collection_idx = 0
|
|
result = True
|
|
for val1, val2 in zip(first_object, second_object):
|
|
result = result and compare_values(val1, val2, f"{name} (index [{collection_idx}])")
|
|
collection_idx = collection_idx + 1
|
|
|
|
else:
|
|
# Do approximate comparisons for floats
|
|
if isinstance(first_object, float) and isclose(first_object, second_object, rel_tol=0.001):
|
|
result = True
|
|
# We currently don't have a generic way to compare PythonProxyObject contents, so return a
|
|
# false positive result for now.
|
|
elif isinstance(first_object, azlmbr.object.PythonProxyObject):
|
|
print(f"{name}: validation inconclusive, the two objects cannot be directly compared.")
|
|
result = True
|
|
else:
|
|
result = False
|
|
|
|
if not result:
|
|
print(f"compare_values failed: {first_object} ({type(first_object)}) vs {second_object} ({type(second_object)})")
|
|
|
|
print(f"{name}: {'SUCCESS' if result else 'FAILURE'}")
|
|
return result
|
|
|
|
|
|
class Entity:
|
|
"""
|
|
Entity class used to create entity objects
|
|
:param name: String for the name of the Entity
|
|
:param id: The ID of the entity
|
|
"""
|
|
|
|
def __init__(self, name: str, id: object = entity.EntityId()):
|
|
self.name: str = name
|
|
self.id: object = id
|
|
self.components: List[object] = None
|
|
self.parent_id = None
|
|
self.parent_name = None
|
|
|
|
def create_entity(self, entity_position, components, parent_id=entity.EntityId()):
|
|
self.id = editor.ToolsApplicationRequestBus(
|
|
bus.Broadcast, "CreateNewEntityAtPosition", entity_position, parent_id
|
|
)
|
|
if self.id.IsValid():
|
|
print(f"{self.name} Entity successfully created")
|
|
editor.EditorEntityAPIBus(bus.Event, 'SetName', self.id, self.name)
|
|
self.components = []
|
|
for component in components:
|
|
self.add_component(component)
|
|
|
|
def add_component(self, component):
|
|
new_component = add_component(component, self.id)
|
|
self.components.append(new_component)
|
|
|
|
def add_component_of_type(self, componentTypeId):
|
|
new_component = add_component_of_type(componentTypeId, self.id)
|
|
self.components.append(new_component)
|
|
|
|
def remove_component(self, component):
|
|
removed_component = remove_component(component, self.id)
|
|
if removed_component is not None:
|
|
self.components.remove(removed_component)
|
|
|
|
def get_parent_info(self):
|
|
"""
|
|
Sets the value for parent_id and parent_name on the entity (self)
|
|
Prints the string for papertrail
|
|
:return: None
|
|
"""
|
|
self.parent_id = editor.EditorEntityInfoRequestBus(bus.Event, "GetParent", self.id)
|
|
self.parent_name = editor.EditorEntityInfoRequestBus(bus.Event, "GetName", self.parent_id)
|
|
print(f"The parent entity of {self.name} is {self.parent_name}")
|
|
|
|
def set_test_parent_entity(self, parent_entity_obj):
|
|
editor.EditorEntityAPIBus(bus.Event, "SetParent", self.id, parent_entity_obj.id)
|
|
self.get_parent_info()
|
|
|
|
def get_set_test(self, component_index: int, path: str, value: object, expected_result: object = None) -> bool:
|
|
"""
|
|
Used to set and validate changes in component values
|
|
:param component_index: Index location in the self.components list
|
|
:param path: asset path in the component
|
|
:param value: new value for the variable being changed in the component
|
|
:param expected_result: (optional) check the result against a specific expected value
|
|
"""
|
|
|
|
if expected_result is None:
|
|
expected_result = value
|
|
|
|
# Test Get/Set (get old value, set new value, check that new value was set correctly)
|
|
print(f"Entity {self.name} Path {path} Component Index {component_index} ")
|
|
|
|
component = self.components[component_index]
|
|
old_value = get_component_property_value(component, path)
|
|
|
|
if old_value is not None:
|
|
print(f"SUCCESS: Retrieved property Value for {self.name}")
|
|
else:
|
|
print(f"FAILURE: Failed to find value in {self.name} {path}")
|
|
return False
|
|
|
|
if old_value == expected_result:
|
|
print((f"WARNING: get_set_test on {self.name} is setting the same value that already exists ({old_value})."
|
|
"The set results will be inconclusive."))
|
|
|
|
editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", component, path, value)
|
|
|
|
new_value = get_component_property_value(self.components[component_index], path)
|
|
|
|
if new_value is not None:
|
|
print(f"SUCCESS: Retrieved new property Value for {self.name}")
|
|
else:
|
|
print(f"FAILURE: Failed to find new value in {self.name}")
|
|
return False
|
|
|
|
return compare_values(new_value, expected_result, f"{self.name} {path}")
|
|
|
|
|
|
def get_set_test(entity: object, component_index: int, path: str, value: object) -> bool:
|
|
"""
|
|
Used to set and validate changes in component values
|
|
:param component_index: Index location in the entity.components list
|
|
:param path: asset path in the component
|
|
:param value: new value for the variable being changed in the component
|
|
"""
|
|
return entity.get_set_test(component_index, path, value)
|
|
|
|
|
|
def get_set_property_test(ly_object: object, attribute_name: str, value: object, expected_result: object = None) -> bool:
|
|
"""
|
|
Used to set and validate BehaviorContext property changes in Lumberyard objects
|
|
:param ly_object: The lumberyard object to test
|
|
:param attribute_name: property (attribute) name in the BehaviorContext
|
|
:param value: new value for the variable being changed in the component
|
|
:param expected_result: (optional) check the result against a specific expected value other than the one set
|
|
"""
|
|
|
|
if expected_result is None:
|
|
expected_result = value
|
|
|
|
# Test Get/Set (get old value, set new value, check that new value was set correctly)
|
|
print(f"Attempting to set {ly_object.typename}.{attribute_name} = {value} (expected result is {expected_result})")
|
|
|
|
if hasattr(ly_object, attribute_name):
|
|
print(f"SUCCESS: Located attribute {attribute_name} for {ly_object.typename}")
|
|
else:
|
|
print(f"FAILURE: Failed to find attribute {attribute_name} in {ly_object.typename}")
|
|
return False
|
|
|
|
old_value = getattr(ly_object, attribute_name)
|
|
|
|
if old_value is not None:
|
|
print(f"SUCCESS: Retrieved existing value {old_value} for {attribute_name} in {ly_object.typename}")
|
|
else:
|
|
print(f"FAILURE: Failed to retrieve value for {attribute_name} in {ly_object.typename}")
|
|
return False
|
|
|
|
if old_value == expected_result:
|
|
print((f"WARNING: get_set_test on {attribute_name} is setting the same value that already exists ({old_value})."
|
|
"The 'set' result for the test will be inconclusive."))
|
|
|
|
setattr(ly_object, attribute_name, expected_result)
|
|
|
|
new_value = getattr(ly_object, attribute_name)
|
|
|
|
if new_value is not None:
|
|
print(f"SUCCESS: Retrieved new value {new_value} for {attribute_name} in {ly_object.typename}")
|
|
else:
|
|
print(f"FAILURE: Failed to retrieve value for {attribute_name} in {ly_object.typename}")
|
|
return False
|
|
|
|
return compare_values(new_value, expected_result, f"{ly_object.typename}.{attribute_name}")
|
|
|
|
|
|
def has_components(entity_id: object, component_list: list) -> bool:
|
|
"""
|
|
Used to verify if a given entity has all the components of components_list. Returns True if all the
|
|
components are present, else False
|
|
:param entity_id: entity id of the entity
|
|
:param component_list: list of component names to be verified
|
|
"""
|
|
typeIdsList = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', component_list,
|
|
entity.EntityType().Game)
|
|
for type_id in typeIdsList:
|
|
if not editor.EditorComponentAPIBus(bus.Broadcast, 'HasComponentOfType', entity_id, type_id):
|
|
return False
|
|
return True
|
|
|
|
|
|
class PathNotFoundError(Exception):
|
|
def __init__(self, path):
|
|
self.path = path
|
|
|
|
def __str__(self):
|
|
return f"Path \"{self.path}\" not found in Editor Settings"
|
|
|
|
|
|
def get_editor_settings_path_list():
|
|
"""
|
|
Get the list of Editor Settings paths
|
|
"""
|
|
paths = editor.EditorSettingsAPIBus(bus.Broadcast, 'BuildSettingsList')
|
|
return paths
|
|
|
|
|
|
def get_editor_settings_by_path(path):
|
|
"""
|
|
Get the value of Editor Settings based on the path.
|
|
:param path: path to the Editor Settings to get the value
|
|
"""
|
|
if path not in get_editor_settings_path_list():
|
|
raise PathNotFoundError(path)
|
|
outcome = editor.EditorSettingsAPIBus(bus.Broadcast, 'GetValue', path)
|
|
if outcome.isSuccess():
|
|
return outcome.GetValue()
|
|
raise RuntimeError(f"GetValue for path '{path}' failed")
|
|
|
|
|
|
def set_editor_settings_by_path(path, value, is_bool = False):
|
|
"""
|
|
Set the value of Editor Settings based on the path.
|
|
# NOTE: Some Editor Settings may need an Editor restart to apply.
|
|
# Ex: Enabling or disabling New Viewport Interaction Model
|
|
:param path: path to the Editor Settings to get the value
|
|
:param value: value to be set
|
|
:param is_bool: True for Boolean settings (enable/disable), False for other settings
|
|
"""
|
|
if path not in get_editor_settings_path_list():
|
|
raise PathNotFoundError(path)
|
|
if is_bool and not isinstance(value, bool):
|
|
def ParseBoolValue(value):
|
|
if(value == "0"):
|
|
return False
|
|
return True
|
|
value = ParseBoolValue(value)
|
|
outcome = editor.EditorSettingsAPIBus(bus.Broadcast, 'SetValue', path, value)
|
|
if not outcome.isSuccess():
|
|
raise RuntimeError(f"SetValue for path '{path}' failed")
|
|
print(f"Value for path '{path}' is set to {value}")
|
|
|
|
|
|
def get_component_type_id_map(component_name_list):
|
|
"""
|
|
Given a list of component names, returns a map of component name -> component type id
|
|
:param component_name_list: The lumberyard object to test
|
|
:return: Dictionary of component name -> component type id pairs
|
|
"""
|
|
# Remove any duplicates so we don't have to query for the same TypeId
|
|
component_names = list(set(component_name_list))
|
|
|
|
type_ids_by_component = {}
|
|
type_ids = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', component_names,
|
|
entity.EntityType().Game)
|
|
for i, typeId in enumerate(type_ids):
|
|
type_ids_by_component[component_names[i]] = typeId
|
|
|
|
return type_ids_by_component
|