Merge branch 'upstream/development' into LYN-6769_TestingRPCs

monroegm-disable-blank-issue-2
Gene Walters 4 years ago
commit 8b044e23ce

@ -0,0 +1,131 @@
{
"ContainerEntity": {
"Id": "ContainerEntity",
"Name": "PinkFlower",
"Components": {
"Component_[10444337162843472597]": {
"$type": "EditorLockComponent",
"Id": 10444337162843472597
},
"Component_[14431042590323756177]": {
"$type": "EditorEntitySortComponent",
"Id": 14431042590323756177,
"ChildEntityOrderEntryArray": [
{
"EntityId": "Entity_[491002114939]"
}
]
},
"Component_[14577735453176806353]": {
"$type": "EditorDisabledCompositionComponent",
"Id": 14577735453176806353
},
"Component_[15674021346798629563]": {
"$type": "EditorInspectorComponent",
"Id": 15674021346798629563
},
"Component_[16784074985702513600]": {
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
"Id": 16784074985702513600,
"Parent Entity": ""
},
"Component_[3351614541100572773]": {
"$type": "EditorOnlyEntityComponent",
"Id": 3351614541100572773
},
"Component_[3600658560328167663]": {
"$type": "EditorPrefabComponent",
"Id": 3600658560328167663
},
"Component_[6155673728651558934]": {
"$type": "EditorEntityIconComponent",
"Id": 6155673728651558934
},
"Component_[8458684662170321289]": {
"$type": "EditorVisibilityComponent",
"Id": 8458684662170321289
},
"Component_[8705192117416252351]": {
"$type": "SelectionComponent",
"Id": 8705192117416252351
},
"Component_[8801280051488695852]": {
"$type": "EditorPendingCompositionComponent",
"Id": 8801280051488695852
}
}
},
"Entities": {
"Entity_[491002114939]": {
"Id": "Entity_[491002114939]",
"Name": "PinkFlower",
"Components": {
"Component_[11083205340162142682]": {
"$type": "EditorLockComponent",
"Id": 11083205340162142682
},
"Component_[11327363779873517]": {
"$type": "EditorOnlyEntityComponent",
"Id": 11327363779873517
},
"Component_[12717389211269537921]": {
"$type": "EditorEntitySortComponent",
"Id": 12717389211269537921
},
"Component_[12826285685970138542]": {
"$type": "AZ::Render::EditorMeshComponent",
"Id": 12826285685970138542,
"Controller": {
"Configuration": {
"ModelAsset": {
"assetId": {
"guid": "{549F4C4D-A7D9-5F2A-A6BD-F24C8BC43BBF}",
"subId": 280086017
},
"assetHint": "assets/objects/foliage/grass_flower_pink.azmodel"
}
}
}
},
"Component_[14101419970865342875]": {
"$type": "EditorPendingCompositionComponent",
"Id": 14101419970865342875
},
"Component_[14770464075286403207]": {
"$type": "EditorVisibilityComponent",
"Id": 14770464075286403207
},
"Component_[15205142653091190082]": {
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
"Id": 15205142653091190082,
"Parent Entity": "ContainerEntity"
},
"Component_[15510898811147803772]": {
"$type": "EditorInspectorComponent",
"Id": 15510898811147803772,
"ComponentOrderEntryArray": [
{
"ComponentId": 15205142653091190082
},
{
"ComponentId": 12826285685970138542,
"SortIndex": 1
}
]
},
"Component_[15988401742428977134]": {
"$type": "EditorDisabledCompositionComponent",
"Id": 15988401742428977134
},
"Component_[4300550837037679336]": {
"$type": "EditorEntityIconComponent",
"Id": 4300550837037679336
},
"Component_[996914988793716659]": {
"$type": "SelectionComponent",
"Id": 996914988793716659
}
}
}
}
}

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:27f87510ae07771dbad3e430e31502b1d1d13dee868f143b7f1de2a7febc8eb9
size 7046400

@ -0,0 +1,8 @@
{
"values": [
{
"$type": "ScriptProcessorRule",
"scriptFilename": "Assets/TestAnim/scene_export_actor.py"
}
]
}

@ -0,0 +1,221 @@
#
# Copyright (c) Contributors to the Open 3D Engine Project.
# For complete copyright and license terms please see the LICENSE at the root of this distribution.
#
# SPDX-License-Identifier: Apache-2.0 OR MIT
#
#
import traceback, sys, uuid, os, json
#
# Example for exporting ActorGroup scene rules
#
def log_exception_traceback():
exc_type, exc_value, exc_tb = sys.exc_info()
data = traceback.format_exception(exc_type, exc_value, exc_tb)
print(str(data))
def get_node_names(sceneGraph, nodeTypeName, testEndPoint = False, validList = None):
import azlmbr.scene.graph
import scene_api.scene_data
node = sceneGraph.get_root()
nodeList = []
children = []
paths = []
while node.IsValid():
# store children to process after siblings
if sceneGraph.has_node_child(node):
children.append(sceneGraph.get_node_child(node))
nodeName = scene_api.scene_data.SceneGraphName(sceneGraph.get_node_name(node))
paths.append(nodeName.get_path())
include = True
if (validList is not None):
include = False # if a valid list filter provided, assume to not include node name
name_parts = nodeName.get_path().split('.')
for valid in validList:
if (valid in name_parts[-1]):
include = True
break
# store any node that has provides specifc data content
nodeContent = sceneGraph.get_node_content(node)
if include and nodeContent.CastWithTypeName(nodeTypeName):
if testEndPoint is not None:
include = sceneGraph.is_node_end_point(node) is testEndPoint
if include:
if (len(nodeName.get_path())):
nodeList.append(scene_api.scene_data.SceneGraphName(sceneGraph.get_node_name(node)))
# advance to next node
if sceneGraph.has_node_sibling(node):
node = sceneGraph.get_node_sibling(node)
elif children:
node = children.pop()
else:
node = azlmbr.scene.graph.NodeIndex()
return nodeList, paths
def generate_mesh_group(scene, sceneManifest, meshDataList, paths):
# Compute the name of the scene file
clean_filename = scene.sourceFilename.replace('.', '_')
mesh_group_name = os.path.basename(clean_filename)
# make the mesh group
mesh_group = sceneManifest.add_mesh_group(mesh_group_name)
mesh_group['id'] = '{' + str(uuid.uuid5(uuid.NAMESPACE_DNS, clean_filename)) + '}'
# add all nodes to this mesh group
for activeMeshIndex in range(len(meshDataList)):
mesh_name = meshDataList[activeMeshIndex]
mesh_path = mesh_name.get_path()
sceneManifest.mesh_group_select_node(mesh_group, mesh_path)
def create_shape_configuration(nodeName):
import scene_api.physics_data
if(nodeName in ['_foot_','_wrist_']):
shapeConfiguration = scene_api.physics_data.BoxShapeConfiguration()
shapeConfiguration.scale = [1.1, 1.1, 1.1]
shapeConfiguration.dimensions = [2.1, 3.1, 4.1]
return shapeConfiguration
else:
shapeConfiguration = scene_api.physics_data.CapsuleShapeConfiguration()
shapeConfiguration.scale = [1.0, 1.0, 1.0]
shapeConfiguration.height = 1.0
shapeConfiguration.radius = 1.0
return shapeConfiguration
def create_collider_configuration(nodeName):
import scene_api.physics_data
colliderConfiguration = scene_api.physics_data.ColliderConfiguration()
colliderConfiguration.Position = [0.1, 0.1, 0.2]
colliderConfiguration.Rotation = [45.0, 35.0, 25.0]
return colliderConfiguration
def generate_physics_nodes(actorPhysicsSetupRule, nodeNameList):
import scene_api.physics_data
hitDetectionConfig = scene_api.physics_data.CharacterColliderConfiguration()
simulatedObjectColliderConfig = scene_api.physics_data.CharacterColliderConfiguration()
clothConfig = scene_api.physics_data.CharacterColliderConfiguration()
ragdollConfig = scene_api.physics_data.RagdollConfiguration()
for nodeName in nodeNameList:
shapeConfiguration = create_shape_configuration(nodeName)
colliderConfiguration = create_collider_configuration(nodeName)
hitDetectionConfig.add_character_collider_node_configuration_node(nodeName, colliderConfiguration, shapeConfiguration)
simulatedObjectColliderConfig.add_character_collider_node_configuration_node(nodeName, colliderConfiguration, shapeConfiguration)
clothConfig.add_character_collider_node_configuration_node(nodeName, colliderConfiguration, shapeConfiguration)
#
ragdollNode = scene_api.physics_data.RagdollNodeConfiguration()
ragdollNode.JointConfig.Name = nodeName
ragdollConfig.add_ragdoll_node_configuration(ragdollNode)
ragdollConfig.colliders.add_character_collider_node_configuration_node(nodeName, colliderConfiguration, shapeConfiguration)
actorPhysicsSetupRule.set_simulated_object_collider_config(simulatedObjectColliderConfig)
actorPhysicsSetupRule.set_hit_detection_config(hitDetectionConfig)
actorPhysicsSetupRule.set_cloth_config(clothConfig)
actorPhysicsSetupRule.set_ragdoll_config(ragdollConfig)
def generate_actor_group(scene, sceneManifest, meshDataList, paths):
import scene_api.scene_data
import scene_api.physics_data
import scene_api.actor_group
# fetch bone data
validNames = ['_neck_','_pelvis_','_leg_','_knee_','_spine_','_arm_','_clavicle_','_head_','_elbow_','_wrist_']
graph = scene_api.scene_data.SceneGraph(scene.graph)
nodeList, allNodePaths = get_node_names(graph, 'BoneData', validList = validNames)
nodeNameList = []
for activeMeshIndex, nodeName in enumerate(nodeList):
nodeNameList.append(nodeName.get_name())
# add comment
commentRule = scene_api.actor_group.CommentRule()
commentRule.text = str(nodeNameList)
# ActorPhysicsSetupRule
actorPhysicsSetupRule = scene_api.actor_group.ActorPhysicsSetupRule()
generate_physics_nodes(actorPhysicsSetupRule, nodeNameList)
# add scale of the Actor rule
actorScaleRule = scene_api.actor_group.ActorScaleRule()
actorScaleRule.scaleFactor = 2.0
# add coordinate system rule
coordinateSystemRule = scene_api.actor_group.CoordinateSystemRule()
coordinateSystemRule.useAdvancedData = False
# add morph target rule
morphTargetRule = scene_api.actor_group.MorphTargetRule()
morphTargetRule.targets.select_targets([nodeNameList[0]], nodeNameList)
# add skeleton optimization rule
skeletonOptimizationRule = scene_api.actor_group.SkeletonOptimizationRule()
skeletonOptimizationRule.autoSkeletonLOD = True
skeletonOptimizationRule.criticalBonesList.select_targets([nodeNameList[0:2]], nodeNameList)
# add LOD rule
lodRule = scene_api.actor_group.LodRule()
lodRule0 = lodRule.add_lod_level(0)
lodRule0.select_targets([nodeNameList[1:4]], nodeNameList)
actorGroup = scene_api.actor_group.ActorGroup()
actorGroup.name = os.path.basename(scene.sourceFilename)
actorGroup.add_rule(actorScaleRule)
actorGroup.add_rule(coordinateSystemRule)
actorGroup.add_rule(skeletonOptimizationRule)
actorGroup.add_rule(morphTargetRule)
actorGroup.add_rule(lodRule)
actorGroup.add_rule(actorPhysicsSetupRule)
actorGroup.add_rule(commentRule)
sceneManifest.manifest['values'].append(actorGroup.to_dict())
def update_manifest(scene):
import json, uuid, os
import azlmbr.scene.graph
import scene_api.scene_data
graph = scene_api.scene_data.SceneGraph(scene.graph)
mesh_name_list, all_node_paths = get_node_names(graph, 'MeshData')
scene_manifest = scene_api.scene_data.SceneManifest()
generate_actor_group(scene, scene_manifest, mesh_name_list, all_node_paths)
generate_mesh_group(scene, scene_manifest, mesh_name_list, all_node_paths)
# Convert the manifest to a JSON string and return it
return scene_manifest.export()
sceneJobHandler = None
def on_update_manifest(args):
try:
scene = args[0]
return update_manifest(scene)
except RuntimeError as err:
print (f'ERROR - {err}')
log_exception_traceback()
except:
log_exception_traceback()
global sceneJobHandler
sceneJobHandler.disconnect()
sceneJobHandler = None
# try to create SceneAPI handler for processing
try:
import azlmbr.scene
sceneJobHandler = azlmbr.scene.ScriptBuildingNotificationBusHandler()
sceneJobHandler.connect()
sceneJobHandler.add_callback('OnUpdateManifest', on_update_manifest)
except:
sceneJobHandler = None

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:011454252e40c927343cce16296412f02f45d1f345c75c036651bdcca473bda5
size 2672

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7bafcc4aefab827e1414e64bcdde235500b51392e52c9ccd588b2d7a24b865a0
size 20214

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ac841c02e81c9ae23476522b7e517889d746d4ff32b3251ac4360168f39b30a3
size 450708

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3653ca7fb42c7e5991815c17fc9ebeab14a1aa861bfb1d37e233ada66a835f00
size 7188

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a30b85428202c548e77cdcc0a595f9f26e82d29622be26891d569f4779a75830
size 1802388

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a3d76b7c93f8873ca7c4013dcc4eff887c25552c9c5847290481306656b22069
size 3668

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:219f57137c5bd44093762ea9d0fc24308679956b980f26bf194edd3a1798fcda
size 3668

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:96ed61c66d1d1f71e940ab75899ee253007e704004e1157c900c6308151a8bf1
size 1802388

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:70a7dc4e2455624067e842b059954f6c4bb9b0debc3cb1ffa783b14bffc61786
size 7188

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7f5a945c61f92f3da77dfd9fdd2c95aa257f4df6bcb82483c608ab660bb72e5f
size 450708

@ -47,18 +47,4 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_BUILD_TESTS_SUPPORTED)
COMPONENT
Atom
)
ly_add_pytest(
NAME AutomatedTesting::Atom_TestSuite_Main_GPU_Optimized
TEST_SUITE main
TEST_REQUIRES gpu
TEST_SERIAL
TIMEOUT 1200
PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Main_GPU_Optimized.py
RUNTIME_DEPENDENCIES
AssetProcessor
AutomatedTesting.Assets
Editor
COMPONENT
Atom
)
endif()

@ -4,129 +4,82 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
import datetime
import logging
import os
import zipfile
import pytest
import editor_python_test_tools.hydra_test_utils as hydra
import ly_test_tools.environment.file_system as file_system
from ly_test_tools.benchmark.data_aggregator import BenchmarkDataAggregator
from ly_test_tools.o3de.editor_test import EditorSharedTest, EditorTestSuite
from Atom.atom_utils.atom_component_helper import compare_screenshot_to_golden_image, golden_images_directory
import editor_python_test_tools.hydra_test_utils as hydra
from .atom_utils.atom_component_helper import compare_screenshot_similarity, ImageComparisonTestFailure
logger = logging.getLogger(__name__)
DEFAULT_SUBFOLDER_PATH = 'user/PythonTests/Automated/Screenshots'
TEST_DIRECTORY = os.path.join(os.path.dirname(__file__), "tests")
def golden_images_directory():
"""
Uses this file location to return the valid location for golden image files.
:return: The path to the golden_images directory, but raises an IOError if the golden_images directory is missing.
"""
current_file_directory = os.path.join(os.path.dirname(__file__))
golden_images_dir = os.path.join(current_file_directory, 'golden_images')
if not os.path.exists(golden_images_dir):
raise IOError(
f'golden_images" directory was not found at path "{golden_images_dir}"'
f'Please add a "golden_images" directory inside: "{current_file_directory}"'
)
return golden_images_dir
def create_screenshots_archive(screenshot_path):
"""
Creates a new zip file archive at archive_path containing all files listed within archive_path.
:param screenshot_path: location containing the files to archive, the zip archive file will also be saved here.
:return: None, but creates a new zip file archive inside path containing all of the files inside archive_path.
"""
files_to_archive = []
# Search for .png and .ppm files to add to the zip archive file.
for (folder_name, sub_folders, file_names) in os.walk(screenshot_path):
for file_name in file_names:
if file_name.endswith(".png") or file_name.endswith(".ppm"):
file_path = os.path.join(folder_name, file_name)
files_to_archive.append(file_path)
# Setup variables for naming the zip archive file.
timestamp = datetime.datetime.now().timestamp()
formatted_timestamp = datetime.datetime.utcfromtimestamp(timestamp).strftime("%Y-%m-%d_%H-%M-%S")
screenshots_file = os.path.join(screenshot_path, f'zip_archive_{formatted_timestamp}.zip')
# Write all of the valid .png and .ppm files to the archive file.
with zipfile.ZipFile(screenshots_file, 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=True) as zip_archive:
for file_path in files_to_archive:
file_name = os.path.basename(file_path)
zip_archive.write(file_path, file_name)
logger = logging.getLogger(__name__)
@pytest.mark.parametrize("project", ["AutomatedTesting"])
@pytest.mark.parametrize("launcher_platform", ["windows_editor"])
@pytest.mark.parametrize("level", ["Base"])
class TestAllComponentsIndepthTests(object):
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
class TestAutomation(EditorTestSuite):
# Remove -autotest_mode from global_extra_cmdline_args since we need rendering for these tests.
global_extra_cmdline_args = ["-BatchMode"] # Default is ["-BatchMode", "-autotest_mode"]
enable_prefab_system = False
@pytest.mark.parametrize("screenshot_name", ["AtomBasicLevelSetup.ppm"])
@pytest.mark.test_case_id("C34603773")
def test_BasicLevelSetup_SetsUpLevel(
self, request, editor, workspace, project, launcher_platform, level, screenshot_name):
"""
Please review the hydra script run by this test for more specific test info.
Tests that a basic rendering level setup can be created (lighting, meshes, materials, etc.).
"""
class AtomGPU_BasicLevelSetup_SetsUpLevel(EditorSharedTest):
use_null_renderer = False # Default is True
screenshot_name = "AtomBasicLevelSetup.ppm"
test_screenshots = [] # Gets set by setup()
screenshot_directory = "" # Gets set by setup()
# Clear existing test screenshots before starting test.
screenshot_directory = os.path.join(workspace.paths.project(), DEFAULT_SUBFOLDER_PATH)
test_screenshots = [os.path.join(screenshot_directory, screenshot_name)]
file_system.delete(test_screenshots, True, True)
def setup(self, workspace):
screenshot_directory = os.path.join(workspace.paths.project(), DEFAULT_SUBFOLDER_PATH)
test_screenshots = [os.path.join(screenshot_directory, self.screenshot_name)]
file_system.delete(test_screenshots, True, True)
golden_images = [os.path.join(golden_images_directory(), screenshot_name)]
level_creation_expected_lines = [
"Viewport is set to the expected size: True",
"Exited game mode"
]
unexpected_lines = ["Traceback (most recent call last):"]
from Atom.tests import hydra_AtomGPU_BasicLevelSetup as test_module
hydra.launch_and_validate_results(
request,
TEST_DIRECTORY,
editor,
"hydra_GPUTest_BasicLevelSetup.py",
timeout=180,
expected_lines=level_creation_expected_lines,
unexpected_lines=unexpected_lines,
halt_on_unexpected=True,
cfg_args=[level],
null_renderer=False,
enable_prefab_system=False,
)
similarity_threshold = 0.99
for test_screenshot, golden_image in zip(test_screenshots, golden_images):
screenshot_comparison_result = compare_screenshot_similarity(
test_screenshot, golden_image, similarity_threshold, True, screenshot_directory)
if screenshot_comparison_result != "Screenshots match":
raise Exception(f"Screenshot test failed: {screenshot_comparison_result}")
assert compare_screenshot_to_golden_image(screenshot_directory, test_screenshots, golden_images, 0.99) is True
@pytest.mark.test_case_id("C34525095")
def test_LightComponent_ScreenshotMatchesGoldenImage(
self, request, editor, workspace, project, launcher_platform, level):
"""
Please review the hydra script run by this test for more specific test info.
Tests that the Light component screenshots in a rendered level appear the same as the golden images.
"""
class AtomGPU_LightComponent_AreaLightScreenshotsMatchGoldenImages(EditorSharedTest):
use_null_renderer = False # Default is True
screenshot_names = [
"AreaLight_1.ppm",
"AreaLight_2.ppm",
"AreaLight_3.ppm",
"AreaLight_4.ppm",
"AreaLight_5.ppm",
]
test_screenshots = [] # Gets set by setup()
screenshot_directory = "" # Gets set by setup()
# Clear existing test screenshots before starting test.
def setup(self, workspace):
screenshot_directory = os.path.join(workspace.paths.project(), DEFAULT_SUBFOLDER_PATH)
for screenshot in self.screenshot_names:
screenshot_path = os.path.join(screenshot_directory, screenshot)
self.test_screenshots.append(screenshot_path)
file_system.delete(self.test_screenshots, True, True)
golden_images = []
for golden_image in screenshot_names:
golden_image_path = os.path.join(golden_images_directory(), golden_image)
golden_images.append(golden_image_path)
from Atom.tests import hydra_AtomGPU_AreaLightScreenshotTest as test_module
assert compare_screenshot_to_golden_image(screenshot_directory, test_screenshots, golden_images, 0.99) is True
@pytest.mark.test_case_id("C34525110")
class AtomGPU_LightComponent_SpotLightScreenshotsMatchGoldenImages(EditorSharedTest):
use_null_renderer = False # Default is True
screenshot_names = [
"SpotLight_1.ppm",
"SpotLight_2.ppm",
"SpotLight_3.ppm",
@ -134,40 +87,25 @@ class TestAllComponentsIndepthTests(object):
"SpotLight_5.ppm",
"SpotLight_6.ppm",
]
screenshot_directory = os.path.join(workspace.paths.project(), DEFAULT_SUBFOLDER_PATH)
test_screenshots = []
for screenshot in screenshot_names:
screenshot_path = os.path.join(screenshot_directory, screenshot)
test_screenshots.append(screenshot_path)
file_system.delete(test_screenshots, True, True)
test_screenshots = [] # Gets set by setup()
screenshot_directory = "" # Gets set by setup()
# Clear existing test screenshots before starting test.
def setup(self, workspace):
screenshot_directory = os.path.join(workspace.paths.project(), DEFAULT_SUBFOLDER_PATH)
for screenshot in self.screenshot_names:
screenshot_path = os.path.join(screenshot_directory, screenshot)
self.test_screenshots.append(screenshot_path)
file_system.delete(self.test_screenshots, True, True)
golden_images = []
for golden_image in screenshot_names:
golden_image_path = os.path.join(golden_images_directory(), golden_image)
golden_images.append(golden_image_path)
expected_lines = ["spot_light Controller|Configuration|Shadows|Shadowmap size: SUCCESS"]
unexpected_lines = ["Traceback (most recent call last):"]
hydra.launch_and_validate_results(
request,
TEST_DIRECTORY,
editor,
"hydra_GPUTest_LightComponent.py",
timeout=180,
expected_lines=expected_lines,
unexpected_lines=unexpected_lines,
halt_on_unexpected=True,
cfg_args=[level],
null_renderer=False,
enable_prefab_system=False,
)
from Atom.tests import hydra_AtomGPU_SpotLightScreenshotTest as test_module
similarity_threshold = 0.99
for test_screenshot, golden_image in zip(test_screenshots, golden_images):
screenshot_comparison_result = compare_screenshot_similarity(
test_screenshot, golden_image, similarity_threshold, True, screenshot_directory)
if screenshot_comparison_result != "Screenshots match":
raise ImageComparisonTestFailure(f"Screenshot test failed: {screenshot_comparison_result}")
assert compare_screenshot_to_golden_image(screenshot_directory, test_screenshots, golden_images, 0.99) is True
@pytest.mark.parametrize('rhi', ['dx12', 'vulkan'])
@ -198,7 +136,7 @@ class TestPerformanceBenchmarkSuite(object):
hydra.launch_and_validate_results(
request,
TEST_DIRECTORY,
os.path.join(os.path.dirname(__file__), "tests"),
editor,
"hydra_GPUTest_AtomFeatureIntegrationBenchmark.py",
timeout=600,
@ -235,7 +173,7 @@ class TestMaterialEditor(object):
hydra.launch_and_validate_results(
request,
TEST_DIRECTORY,
os.path.join(os.path.dirname(__file__), "tests"),
generic_launcher,
editor_script="",
run_python="--runpython",

@ -1,46 +0,0 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
import os
import pytest
import ly_test_tools.environment.file_system as file_system
from ly_test_tools.o3de.editor_test import EditorSharedTest, EditorTestSuite
from ly_test_tools.image.screenshot_compare_qssim import qssim as compare_screenshots
from .atom_utils.atom_component_helper import create_screenshots_archive, golden_images_directory
DEFAULT_SUBFOLDER_PATH = 'user/PythonTests/Automated/Screenshots'
@pytest.mark.xfail(reason="Optimized tests are experimental, we will enable xfail and monitor them temporarily.")
@pytest.mark.parametrize("project", ["AutomatedTesting"])
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
class TestAutomation(EditorTestSuite):
# Remove -autotest_mode from global_extra_cmdline_args since we need rendering for these tests.
global_extra_cmdline_args = ["-BatchMode"] # Default is ["-BatchMode", "-autotest_mode"]
enable_prefab_system = False
@pytest.mark.test_case_id("C34603773")
class AtomGPU_BasicLevelSetup_SetsUpLevel(EditorSharedTest):
use_null_renderer = False # Default is True
screenshot_name = "AtomBasicLevelSetup.ppm"
test_screenshots = [] # Gets set by setup()
screenshot_directory = "" # Gets set by setup()
# Clear existing test screenshots before starting test.
def setup(self, workspace):
screenshot_directory = os.path.join(workspace.paths.project(), DEFAULT_SUBFOLDER_PATH)
test_screenshots = [os.path.join(screenshot_directory, self.screenshot_name)]
file_system.delete(test_screenshots, True, True)
from Atom.tests import hydra_AtomGPU_BasicLevelSetup as test_module
golden_images = [os.path.join(golden_images_directory(), screenshot_name)]
for test_screenshot, golden_screenshot in zip(test_screenshots, golden_images):
compare_screenshots(test_screenshot, golden_screenshot)
create_screenshots_archive(screenshot_directory)

@ -93,161 +93,165 @@ def compare_screenshot_similarity(
return result
def create_basic_atom_level(level_name):
def compare_screenshot_to_golden_image(
screenshot_directory, test_screenshots, golden_images, similarity_threshold=0.99):
"""
Creates a new level inside the Editor matching level_name & adds the following:
1. "default_level" entity to hold all other entities.
2. Adds Grid, Global Skylight (IBL), ground Mesh, Directional Light, Sphere w/ material+mesh, & Camera components.
3. Each of these components has its settings tweaked slightly to match the ideal scene to test Atom rendering.
:param level_name: name of the level to create and apply this basic setup to.
Compares a list of test_screenshots to a list of golden_images and return True if they match within the
similarity threshold set. Otherwise, it will raise ImageComparisonTestFailure with a failure message.
:param screenshot_directory: path to the directory containing screenshots for creating .zip archives.
:param test_screenshots: list of test screenshot path strings.
:param golden_images: list of golden image path strings.
:param similarity_threshold: float threshold tolerance to set when comparing screenshots to golden images.
"""
for test_screenshot, golden_image in zip(test_screenshots, golden_images):
screenshot_comparison_result = compare_screenshot_similarity(
test_screenshot, golden_image, similarity_threshold, True, screenshot_directory)
if screenshot_comparison_result != "Screenshots match":
raise ImageComparisonTestFailure(f"Screenshot test failed: {screenshot_comparison_result}")
return True
def initial_viewport_setup(screen_width=1280, screen_height=720):
"""
For setting up the initial viewport resolution to expected default values before running a screenshot test.
Defaults to 1280 x 720 resolution (in pixels).
:param screen_width: Width in pixels to set the viewport width size to.
:param screen_height: Height in pixels to set the viewport height size to.
:return: None
"""
import azlmbr.legacy.general as general
general.set_viewport_size(screen_width, screen_height)
general.update_viewport()
def enter_exit_game_mode_take_screenshot(screenshot_name, enter_game_tuple, exit_game_tuple, timeout_in_seconds=4):
"""
Enters game mode, takes a screenshot named screenshot_name (must include file extension), and exits game mode.
:param screenshot_name: string representing the name of the screenshot file, including file extension.
:param enter_game_tuple: tuple where the 1st string is success & 2nd string is failure for entering the game.
:param exit_game_tuple: tuple where the 1st string is success & 2nd string is failure for exiting the game.
:param timeout_in_seconds: int or float seconds to wait for entering/exiting game mode.
:return: None
"""
import azlmbr.asset as asset
import azlmbr.bus as bus
import azlmbr.camera as camera
import azlmbr.editor as editor
import azlmbr.entity as entity
import azlmbr.legacy.general as general
from editor_python_test_tools.utils import TestHelper
from Atom.atom_utils.screenshot_utils import ScreenshotHelper
TestHelper.enter_game_mode(enter_game_tuple)
TestHelper.wait_for_condition(function=lambda: general.is_in_game_mode(), timeout_in_seconds=timeout_in_seconds)
ScreenshotHelper(general.idle_wait_frames).capture_screenshot_blocking(screenshot_name)
TestHelper.exit_game_mode(exit_game_tuple)
TestHelper.wait_for_condition(function=lambda: not general.is_in_game_mode(), timeout_in_seconds=timeout_in_seconds)
def create_basic_atom_rendering_scene():
"""
Sets up a new scene inside the Editor for testing Atom rendering GPU output.
Setup: Deletes all existing entities before creating the scene.
The created scene includes:
1. "Default Level" entity that holds all of the other entities.
2. "Grid" entity: Contains a Grid component.
3. "Global Skylight (IBL)" entity: Contains HDRI Skybox & Global Skylight (IBL) components.
4. "Ground Plane" entity: Contains Material & Mesh components.
5. "Directional Light" entity: Contains Directional Light component.
6. "Sphere" entity: Contains Material & Mesh components.
7. "Camera" entity: Contains Camera component.
:return: None
"""
import azlmbr.math as math
import azlmbr.object
import azlmbr.paths
import editor_python_test_tools.hydra_editor_utils as hydra
from editor_python_test_tools.editor_test_helper import EditorTestHelper
from editor_python_test_tools.asset_utils import Asset
from editor_python_test_tools.editor_entity_utils import EditorEntity
helper = EditorTestHelper(log_prefix="Atom_EditorTestHelper")
from Atom.atom_utils.atom_constants import AtomComponentProperties
# Wait for Editor idle loop before executing Python hydra scripts.
general.idle_enable(True)
DEGREE_RADIAN_FACTOR = 0.0174533
# Basic setup for opened level.
helper.open_level(level_name="Base")
general.idle_wait(1.0)
general.update_viewport()
general.idle_wait(0.5) # half a second is more than enough for updating the viewport.
# Close out problematic windows, FPS meters, and anti-aliasing.
if general.is_helpers_shown(): # Turn off the helper gizmos if visible
general.toggle_helpers()
general.idle_wait(1.0)
if general.is_pane_visible("Error Report"): # Close Error Report windows that block focus.
general.close_pane("Error Report")
if general.is_pane_visible("Error Log"): # Close Error Log windows that block focus.
general.close_pane("Error Log")
general.idle_wait(1.0)
general.run_console("r_displayInfo=0")
general.idle_wait(1.0)
# Delete all existing entities & create default_level entity
# Setup: Deletes all existing entities before creating the scene.
search_filter = azlmbr.entity.SearchFilter()
all_entities = entity.SearchBus(azlmbr.bus.Broadcast, "SearchEntities", search_filter)
editor.ToolsApplicationRequestBus(bus.Broadcast, "DeleteEntities", all_entities)
default_level = hydra.Entity("default_level")
default_position = math.Vector3(0.0, 0.0, 0.0)
default_level.create_entity(default_position, ["Grid"])
default_level.get_set_test(0, "Controller|Configuration|Secondary Grid Spacing", 1.0)
# Set the viewport up correctly after adding the parent default_level entity.
screen_width = 1280
screen_height = 720
degree_radian_factor = 0.0174533 # Used by "Rotation" property for the Transform component.
general.set_viewport_size(screen_width, screen_height)
general.update_viewport()
helper.wait_for_condition(
function=lambda: helper.isclose(a=general.get_viewport_size().x, b=screen_width, rel_tol=0.1)
and helper.isclose(a=general.get_viewport_size().y, b=screen_height, rel_tol=0.1),
timeout_in_seconds=4.0
)
result = helper.isclose(a=general.get_viewport_size().x, b=screen_width, rel_tol=0.1) and helper.isclose(
a=general.get_viewport_size().y, b=screen_height, rel_tol=0.1)
general.log(general.get_viewport_size().x)
general.log(general.get_viewport_size().y)
general.log(general.get_viewport_size().z)
general.log(f"Viewport is set to the expected size: {result}")
general.log("Basic level created")
general.run_console("r_DisplayInfo = 0")
# Create global_skylight entity and set the properties
global_skylight = hydra.Entity("global_skylight")
global_skylight.create_entity(
entity_position=default_position,
components=["HDRi Skybox", "Global Skylight (IBL)"],
parent_id=default_level.id)
global_skylight_asset_path = os.path.join("LightingPresets", "default_iblskyboxcm.exr.streamingimage")
global_skylight_asset_value = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", global_skylight_asset_path, math.Uuid(), False)
global_skylight.get_set_test(0, "Controller|Configuration|Cubemap Texture", global_skylight_asset_value)
global_skylight.get_set_test(1, "Controller|Configuration|Diffuse Image", global_skylight_asset_value)
global_skylight.get_set_test(1, "Controller|Configuration|Specular Image", global_skylight_asset_value)
# Create ground_plane entity and set the properties
ground_plane = hydra.Entity("ground_plane")
ground_plane.create_entity(
entity_position=default_position,
components=["Material"],
parent_id=default_level.id)
azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalUniformScale", ground_plane.id, 32.0)
# Work around to add the correct Atom Mesh component and asset.
mesh_type_id = azlmbr.globals.property.EditorMeshComponentTypeId
ground_plane.components.append(
editor.EditorComponentAPIBus(
bus.Broadcast, "AddComponentsOfType", ground_plane.id, [mesh_type_id]
).GetValue()[0]
)
all_entities = azlmbr.entity.SearchBus(azlmbr.bus.Broadcast, "SearchEntities", search_filter)
azlmbr.editor.ToolsApplicationRequestBus(azlmbr.bus.Broadcast, "DeleteEntities", all_entities)
# 1. "Default Level" entity that holds all of the other entities.
default_level_entity_name = "Default Level"
default_level_entity = EditorEntity.create_editor_entity_at(math.Vector3(0.0, 0.0, 0.0), default_level_entity_name)
# 2. "Grid" entity: Contains a Grid component.
grid_entity = EditorEntity.create_editor_entity(AtomComponentProperties.grid(), default_level_entity.id)
grid_component = grid_entity.add_component(AtomComponentProperties.grid())
secondary_grid_spacing_value = 1.0
grid_component.set_component_property_value(
AtomComponentProperties.grid('Secondary Grid Spacing'), secondary_grid_spacing_value)
# 3. "Global Skylight (IBL)" entity: Contains HDRI Skybox & Global Skylight (IBL) components.
global_skylight_entity = EditorEntity.create_editor_entity(
AtomComponentProperties.global_skylight(), default_level_entity.id)
hdri_skybox_component = global_skylight_entity.add_component(AtomComponentProperties.hdri_skybox())
global_skylight_component = global_skylight_entity.add_component(AtomComponentProperties.global_skylight())
global_skylight_image_asset_path = os.path.join("LightingPresets", "default_iblskyboxcm.exr.streamingimage")
global_skylight_image_asset = Asset.find_asset_by_path(global_skylight_image_asset_path, False)
hdri_skybox_component.set_component_property_value(
AtomComponentProperties.hdri_skybox('Cubemap Texture'), global_skylight_image_asset.id)
global_skylight_diffuse_image_asset_path = os.path.join(
"LightingPresets", "default_iblskyboxcm_ibldiffuse.exr.streamingimage")
global_skylight_diffuse_image_asset = Asset.find_asset_by_path(global_skylight_diffuse_image_asset_path, False)
global_skylight_component.set_component_property_value(
AtomComponentProperties.global_skylight('Diffuse Image'), global_skylight_diffuse_image_asset.id)
global_skylight_specular_image_asset_path = os.path.join(
"LightingPresets", "default_iblskyboxcm_iblspecular.exr.streamingimage")
global_skylight_specular_image_asset = Asset.find_asset_by_path(
global_skylight_specular_image_asset_path, False)
global_skylight_component.set_component_property_value(
AtomComponentProperties.global_skylight('Specular Image'), global_skylight_specular_image_asset.id)
# 4. "Ground Plane" entity: Contains Material & Mesh components.
ground_plane_name = "Ground Plane"
ground_plane_entity = EditorEntity.create_editor_entity(ground_plane_name, default_level_entity.id)
ground_plane_material_component = ground_plane_entity.add_component(AtomComponentProperties.material())
ground_plane_entity.set_local_uniform_scale(32.0)
ground_plane_mesh_component = ground_plane_entity.add_component(AtomComponentProperties.mesh())
ground_plane_mesh_asset_path = os.path.join("TestData", "Objects", "plane.azmodel")
ground_plane_mesh_asset_value = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", ground_plane_mesh_asset_path, math.Uuid(), False)
ground_plane.get_set_test(1, "Controller|Configuration|Mesh Asset", ground_plane_mesh_asset_value)
# Add Atom Material component and asset.
ground_plane_mesh_asset = Asset.find_asset_by_path(ground_plane_mesh_asset_path, False)
ground_plane_mesh_component.set_component_property_value(
AtomComponentProperties.mesh('Mesh Asset'), ground_plane_mesh_asset.id)
ground_plane_material_asset_path = os.path.join("Materials", "Presets", "PBR", "metal_chrome.azmaterial")
ground_plane_material_asset_value = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", ground_plane_material_asset_path, math.Uuid(), False)
ground_plane.get_set_test(0, "Default Material|Material Asset", ground_plane_material_asset_value)
# Create directional_light entity and set the properties
directional_light = hydra.Entity("directional_light")
directional_light.create_entity(
entity_position=math.Vector3(0.0, 0.0, 10.0),
components=["Directional Light"],
parent_id=default_level.id)
directional_light_rotation = math.Vector3(degree_radian_factor * -90.0, 0.0, 0.0)
azlmbr.components.TransformBus(
azlmbr.bus.Event, "SetLocalRotation", directional_light.id, directional_light_rotation)
# Create sphere entity and set the properties
sphere_entity = hydra.Entity("sphere")
sphere_entity.create_entity(
entity_position=math.Vector3(0.0, 0.0, 1.0),
components=["Material"],
parent_id=default_level.id)
# Work around to add the correct Atom Mesh component and asset.
sphere_entity.components.append(
editor.EditorComponentAPIBus(
bus.Broadcast, "AddComponentsOfType", sphere_entity.id, [mesh_type_id]
).GetValue()[0]
)
ground_plane_material_asset = Asset.find_asset_by_path(ground_plane_material_asset_path, False)
ground_plane_material_component.set_component_property_value(
AtomComponentProperties.material('Material Asset'), ground_plane_material_asset.id)
# 5. "Directional Light" entity: Contains Directional Light component.
directional_light_entity = EditorEntity.create_editor_entity_at(
math.Vector3(0.0, 0.0, 10.0), AtomComponentProperties.directional_light(), default_level_entity.id)
directional_light_entity.add_component(AtomComponentProperties.directional_light())
directional_light_entity_rotation = math.Vector3(DEGREE_RADIAN_FACTOR * -90.0, 0.0, 0.0)
directional_light_entity.set_local_rotation(directional_light_entity_rotation)
# 6. "Sphere" entity: Contains Material & Mesh components.
sphere_entity = EditorEntity.create_editor_entity_at(
math.Vector3(0.0, 0.0, 1.0), "Sphere", default_level_entity.id)
sphere_mesh_component = sphere_entity.add_component(AtomComponentProperties.mesh())
sphere_mesh_asset_path = os.path.join("Models", "sphere.azmodel")
sphere_mesh_asset_value = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", sphere_mesh_asset_path, math.Uuid(), False)
sphere_entity.get_set_test(1, "Controller|Configuration|Mesh Asset", sphere_mesh_asset_value)
# Add Atom Material component and asset.
sphere_mesh_asset = Asset.find_asset_by_path(sphere_mesh_asset_path, False)
sphere_mesh_component.set_component_property_value(
AtomComponentProperties.mesh('Mesh Asset'), sphere_mesh_asset.id)
sphere_material_component = sphere_entity.add_component(AtomComponentProperties.material())
sphere_material_asset_path = os.path.join("Materials", "Presets", "PBR", "metal_brass_polished.azmaterial")
sphere_material_asset_value = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", sphere_material_asset_path, math.Uuid(), False)
sphere_entity.get_set_test(0, "Default Material|Material Asset", sphere_material_asset_value)
# Create camera component and set the properties
camera_entity = hydra.Entity("camera")
camera_entity.create_entity(
entity_position=math.Vector3(5.5, -12.0, 9.0),
components=["Camera"],
parent_id=default_level.id)
rotation = math.Vector3(
degree_radian_factor * -27.0, degree_radian_factor * -12.0, degree_radian_factor * 25.0
)
azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalRotation", camera_entity.id, rotation)
camera_entity.get_set_test(0, "Controller|Configuration|Field of view", 60.0)
camera.EditorCameraViewRequestBus(azlmbr.bus.Event, "ToggleCameraAsActiveView", camera_entity.id)
sphere_material_asset = Asset.find_asset_by_path(sphere_material_asset_path, False)
sphere_material_component.set_component_property_value(
AtomComponentProperties.material('Material Asset'), sphere_material_asset.id)
# 7. "Camera" entity: Contains Camera component.
camera_entity = EditorEntity.create_editor_entity_at(
math.Vector3(5.5, -12.0, 9.0), AtomComponentProperties.camera(), default_level_entity.id)
camera_component = camera_entity.add_component(AtomComponentProperties.camera())
camera_entity_rotation = math.Vector3(
DEGREE_RADIAN_FACTOR * -27.0, DEGREE_RADIAN_FACTOR * -12.0, DEGREE_RADIAN_FACTOR * 25.0)
camera_entity.set_local_rotation(camera_entity_rotation)
camera_fov_value = 60.0
camera_component.set_component_property_value(AtomComponentProperties.camera('Field of view'), camera_fov_value)
azlmbr.camera.EditorCameraViewRequestBus(azlmbr.bus.Event, "ToggleCameraAsActiveView", camera_entity.id)

@ -18,6 +18,13 @@ LIGHT_TYPES = {
'simple_spot': 7,
}
# Attenuation Radius Mode options for the Light component.
ATTENUATION_RADIUS_MODE = {
'automatic': 1,
'explicit': 0,
}
# Qualiity Level settings for Diffuse Global Illumination level component
GLOBAL_ILLUMINATION_QUALITY = {
'Low': 0,
@ -276,13 +283,27 @@ class AtomComponentProperties:
def light(property: str = 'name') -> str:
"""
Light component properties.
- 'Attenuation Radius Mode' controls whether the attenuation radius is calculated automatically or explicitly.
- 'Color' the RGB value to set for the color of the light.
- 'Enable shadow' toggle for enabling shadows for the light.
- 'Enable shutters' toggle for enabling shutters for the light.
- 'Inner angle' inner angle value for the shutters (in degrees)
- 'Intensity' the intensity of the light in the set photometric unit (float with no ceiling).
- 'Light type' from atom_constants.py LIGHT_TYPES
- 'Outer angle' outer angle value for the shutters (in degrees)
:param property: From the last element of the property tree path. Default 'name' for component name string.
:return: Full property path OR component name if no property specified.
"""
properties = {
'name': 'Light',
'Attenuation Radius Mode': 'Controller|Configuration|Attenuation radius|Mode',
'Color': 'Controller|Configuration|Color',
'Enable shadow': 'Controller|Configuration|Shadows|Enable shadow',
'Enable shutters': 'Controller|Configuration|Shutters|Enable shutters',
'Inner angle': 'Controller|Configuration|Shutters|Inner angle',
'Intensity': 'Controller|Configuration|Intensity',
'Light type': 'Controller|Configuration|Light type',
'Outer angle': 'Controller|Configuration|Shutters|Outer angle',
}
return properties[property]

@ -0,0 +1,199 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
class Tests:
area_light_entity_created = (
"Area Light entity successfully created",
"Area Light entity failed to be created")
area_light_entity_deleted = (
"Area Light entity was deleted",
"Area Light entity was not deleted")
enter_game_mode = (
"Entered game mode",
"Failed to enter game mode")
exit_game_mode = (
"Exited game mode",
"Couldn't exit game mode")
light_component_added = (
"Light component was added",
"Light component wasn't added")
light_component_attenuation_radius_property_set = (
"Light component Attenuation Radius Property was set",
"Light component Attenuation Radius Property was not set")
light_component_color_property_set = (
"Light component Color property was set",
"Light component Color property was not set")
light_component_intensity_property_set = (
"Light component Intensity property was set",
"Light component Intensity property was not set")
light_component_light_type_property_set = (
"Light type property was set",
"Light type property was not set")
def AtomGPU_LightComponent_AreaLightScreenshotsMatchGoldenImages():
"""
Summary:
Light component test using the Capsule, Spot (disk), and Point (sphere) Light type property options.
Sets each scene up and then takes a screenshot of each scene for test comparison.
Test setup:
- Wait for Editor idle loop.
- Open the "Base" level.
- Close error windows and display helpers then update the viewport size.
- Runs the create_basic_atom_rendering_scene() function to setup the test scene.
Expected Behavior:
The test scripts sets up the scenes correctly and takes accurate screenshots.
Test Steps:
1. Create Area Light entity with no components.
2. Add a Light component to the Area Light entity.
3. Set the Light type property to Capsule for the Light component.
4. Set the Light component's Color property to 255, 0, 0.
5. Enter game mode and take a screenshot then exit game mode.
6. Set the Intensity property of the Light component to 0.0.
7. Set the Attenuation Radius Mode property of the Light component to 1 (automatic).
8. Enter game mode and take a screenshot then exit game mode.
9. Set the Intensity property of the Light component to 1000.0
10. Enter game mode and take a screenshot then exit game mode.
11. Set the Light type property to Spot (disk) for the Light component & rotate DEGREE_RADIAN_FACTOR * 90 degrees.
12. Enter game mode and take a screenshot then exit game mode.
13. Set the Light type property to Point (sphere) instead of Spot (disk) for the Light component.
14. Enter game mode and take a screenshot then exit game mode.
15. Delete the Area Light entity.
16. Look for errors.
:return: None
"""
import azlmbr.legacy.general as general
import azlmbr.paths
from editor_python_test_tools.editor_entity_utils import EditorEntity
from editor_python_test_tools.utils import Report, Tracer, TestHelper
from Atom.atom_utils.atom_constants import AtomComponentProperties, ATTENUATION_RADIUS_MODE, LIGHT_TYPES
from Atom.atom_utils.atom_component_helper import (
initial_viewport_setup, create_basic_atom_rendering_scene, enter_exit_game_mode_take_screenshot)
DEGREE_RADIAN_FACTOR = 0.0174533
with Tracer() as error_tracer:
# Test setup begins.
# Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level.
TestHelper.init_idle()
TestHelper.open_level("", "Base")
# Setup: Close error windows and display helpers then update the viewport size.
TestHelper.close_error_windows()
TestHelper.close_display_helpers()
initial_viewport_setup()
general.update_viewport()
# Setup: Runs the create_basic_atom_rendering_scene() function to setup the test scene.
create_basic_atom_rendering_scene()
# Test steps begin.
# 1. Create Area Light entity with no components.
area_light_entity_name = "Area Light"
area_light_entity = EditorEntity.create_editor_entity_at(
azlmbr.math.Vector3(0.0, 0.0, 0.0), area_light_entity_name)
Report.critical_result(Tests.area_light_entity_created, area_light_entity.exists())
# 2. Add a Light component to the Area Light entity.
light_component = area_light_entity.add_component(AtomComponentProperties.light())
Report.critical_result(
Tests.light_component_added, area_light_entity.has_component(AtomComponentProperties.light()))
# 3. Set the Light type property to Capsule for the Light component.
light_component.set_component_property_value(
AtomComponentProperties.light('Light type'), LIGHT_TYPES['capsule'])
Report.result(
Tests.light_component_light_type_property_set,
light_component.get_component_property_value(
AtomComponentProperties.light('Light type')) == LIGHT_TYPES['capsule'])
# 4. Set the Light component's Color property to 255, 0, 0.
light_component_color_value = azlmbr.math.Color(255.0, 0.0, 0.0, 0.0)
light_component.set_component_property_value(
AtomComponentProperties.light('Color'), light_component_color_value)
Report.result(
Tests.light_component_color_property_set,
light_component.get_component_property_value(
AtomComponentProperties.light('Color')) == light_component_color_value)
# 5. Enter game mode and take a screenshot then exit game mode.
enter_exit_game_mode_take_screenshot("AreaLight_1.ppm", Tests.enter_game_mode, Tests.exit_game_mode)
# 6. Set the Intensity property of the Light component to 0.0.
light_component.set_component_property_value(AtomComponentProperties.light('Intensity'), 0.0)
Report.result(
Tests.light_component_intensity_property_set,
light_component.get_component_property_value(AtomComponentProperties.light('Intensity')) == 0.0)
# 7. Set the Attenuation Radius Mode property of the Light component to 1 (automatic).
light_component.set_component_property_value(
AtomComponentProperties.light('Attenuation Radius Mode'), ATTENUATION_RADIUS_MODE['automatic'])
Report.result(
Tests.light_component_attenuation_radius_property_set,
light_component.get_component_property_value(
AtomComponentProperties.light('Attenuation Radius Mode')) == ATTENUATION_RADIUS_MODE['automatic'])
# 8. Enter game mode and take a screenshot then exit game mode.
enter_exit_game_mode_take_screenshot("AreaLight_2.ppm", Tests.enter_game_mode, Tests.exit_game_mode)
# 9. Set the Intensity property of the Light component to 1000.0
light_component.set_component_property_value(AtomComponentProperties.light('Intensity'), 1000.0)
Report.result(
Tests.light_component_intensity_property_set,
light_component.get_component_property_value(AtomComponentProperties.light('Intensity')) == 1000.0)
# 10. Enter game mode and take a screenshot then exit game mode.
enter_exit_game_mode_take_screenshot("AreaLight_3.ppm", Tests.enter_game_mode, Tests.exit_game_mode)
# 11. Set the Light type property to Spot (disk) for the Light component &
# rotate DEGREE_RADIAN_FACTOR * 90 degrees.
light_component.set_component_property_value(
AtomComponentProperties.light('Light type'), LIGHT_TYPES['spot_disk'])
area_light_rotation = azlmbr.math.Vector3(DEGREE_RADIAN_FACTOR * 90.0, 0.0, 0.0)
azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalRotation", area_light_entity.id, area_light_rotation)
Report.result(
Tests.light_component_light_type_property_set,
light_component.get_component_property_value(
AtomComponentProperties.light('Light type')) == LIGHT_TYPES['spot_disk'])
# 12. Enter game mode and take a screenshot then exit game mode.
enter_exit_game_mode_take_screenshot("AreaLight_4.ppm", Tests.enter_game_mode, Tests.exit_game_mode)
# 13. Set the Light type property to Point (sphere) instead of Spot (disk) for the Light component.
light_component.set_component_property_value(
AtomComponentProperties.light('Light type'), LIGHT_TYPES['sphere'])
Report.result(
Tests.light_component_light_type_property_set,
light_component.get_component_property_value(
AtomComponentProperties.light('Light type')) == LIGHT_TYPES['sphere'])
# 14. Enter game mode and take a screenshot then exit game mode.
enter_exit_game_mode_take_screenshot("AreaLight_5.ppm", Tests.enter_game_mode, Tests.exit_game_mode)
# 15. Delete the Area Light entity.
area_light_entity.delete()
Report.result(Tests.area_light_entity_deleted, not area_light_entity.exists())
# 16. Look for errors.
TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0)
for error_info in error_tracer.errors:
Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}")
for assert_info in error_tracer.asserts:
Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}")
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(AtomGPU_LightComponent_AreaLightScreenshotsMatchGoldenImages)

@ -80,6 +80,7 @@ def AtomGPU_BasicLevelSetup_SetsUpLevel():
Test setup:
- Wait for Editor idle loop.
- Open the "Base" level.
- Deletes all existing entities before creating the scene.
Expected Behavior:
The scene can be setup for a basic level.
@ -115,7 +116,6 @@ def AtomGPU_BasicLevelSetup_SetsUpLevel():
"""
import os
from math import isclose
import azlmbr.legacy.general as general
import azlmbr.math as math
@ -126,21 +126,11 @@ def AtomGPU_BasicLevelSetup_SetsUpLevel():
from editor_python_test_tools.utils import Report, Tracer, TestHelper
from Atom.atom_utils.atom_constants import AtomComponentProperties
from Atom.atom_utils.atom_component_helper import initial_viewport_setup
from Atom.atom_utils.screenshot_utils import ScreenshotHelper
SCREENSHOT_NAME = "AtomBasicLevelSetup"
SCREEN_WIDTH = 1280
SCREEN_HEIGHT = 720
DEGREE_RADIAN_FACTOR = 0.0174533
def initial_viewport_setup(screen_width, screen_height):
general.set_viewport_size(screen_width, screen_height)
general.update_viewport()
TestHelper.wait_for_condition(
function=lambda: isclose(a=general.get_viewport_size().x, b=SCREEN_WIDTH, rel_tol=0.1)
and isclose(a=general.get_viewport_size().y, b=SCREEN_HEIGHT, rel_tol=0.1),
timeout_in_seconds=4.0
)
SCREENSHOT_NAME = "AtomBasicLevelSetup"
with Tracer() as error_tracer:
# Test setup begins.
@ -148,11 +138,16 @@ def AtomGPU_BasicLevelSetup_SetsUpLevel():
TestHelper.init_idle()
TestHelper.open_level("", "Base")
# Setup: Deletes all existing entities before creating the scene.
search_filter = azlmbr.entity.SearchFilter()
all_entities = azlmbr.entity.SearchBus(azlmbr.bus.Broadcast, "SearchEntities", search_filter)
azlmbr.editor.ToolsApplicationRequestBus(azlmbr.bus.Broadcast, "DeleteEntities", all_entities)
# Test steps begin.
# 1. Close error windows and display helpers then update the viewport size.
TestHelper.close_error_windows()
TestHelper.close_display_helpers()
initial_viewport_setup(SCREEN_WIDTH, SCREEN_HEIGHT)
initial_viewport_setup()
general.update_viewport()
# 2. Create Default Level Entity.

@ -0,0 +1,248 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
class Tests:
directional_light_component_disabled = (
"Disabled Directional Light component",
"Couldn't disable Directional Light component")
enter_game_mode = (
"Entered game mode",
"Failed to enter game mode")
exit_game_mode = (
"Exited game mode",
"Couldn't exit game mode")
global_skylight_component_disabled = (
"Disabled Global Skylight (IBL) component",
"Couldn't disable Global Skylight (IBL) component")
hdri_skybox_component_disabled = (
"Disabled HDRi Skybox component",
"Couldn't disable HDRi Skybox component")
light_component_added = (
"Light component added",
"Couldn't add Light component")
light_component_color_property_set = (
"Color property was set",
"Color property was not set")
light_component_enable_shadow_property_set = (
"Enable shadow property was set",
"Enable shadow property was not set")
light_component_enable_shutters_property_set = (
"Enable shutters property was set",
"Enable shutters property was not set")
light_component_inner_angle_property_set = (
"Inner angle property was set",
"Inner angle property was not set")
light_component_intensity_property_set = (
"Intensity property was set",
"Intensity property was not set")
light_component_light_type_property_set = (
"Light type property was set",
"Light type property was not set")
light_component_outer_angle_property_set = (
"Outer angle property was set",
"Outer angle property was not set")
material_component_material_asset_property_set = (
"Material Asset property was set",
"Material Asset property was not set")
spot_light_entity_created = (
"Spot Light entity created",
"Couldn't create Spot Light entity")
def AtomGPU_LightComponent_SpotLightScreenshotsMatchGoldenImages():
"""
Summary:
Light component test using the Spot (disk) Light type property option and modifying the shadows and colors.
Sets each scene up and then takes a screenshot of each scene for test comparison.
Test setup:
- Wait for Editor idle loop.
- Open the "Base" level.
- Close error windows and display helpers then update the viewport size.
- Runs the create_basic_atom_rendering_scene() function to setup the test scene.
Expected Behavior:
The test scripts sets up the scenes correctly and takes accurate screenshots.
Test Steps:
1. Find the Directional Light entity then disable its Directional Light component.
2. Disable Global Skylight (IBL) component on the Global Skylight (IBL) entity.
3. Disable HDRi Skybox component on the Global Skylight (IBL) entity.
4. Create a Spot Light entity and rotate it.
5. Attach a Light component to the Spot Light entity.
6. Set the Light component Light Type to Spot (disk).
7. Enter game mode and take a screenshot then exit game mode.
8. Change the default material asset for the Ground Plane entity.
9. Enter game mode and take a screenshot then exit game mode.
10. Increase the Intensity value of the Light component.
11. Enter game mode and take a screenshot then exit game mode.
12. Change the Light component Color property value.
13. Enter game mode and take a screenshot then exit game mode.
14. Change the Light component Enable shutters, Inner angle, and Outer angle property values.
15. Enter game mode and take a screenshot then exit game mode.
16. Change the Light component Enable shadow and Shadowmap size property values then move Spot Light entity.
17. Enter game mode and take a screenshot then exit game mode.
18. Look for errors.
:return: None
"""
import os
import azlmbr.legacy.general as general
import azlmbr.paths
from editor_python_test_tools.asset_utils import Asset
from editor_python_test_tools.editor_entity_utils import EditorEntity
from editor_python_test_tools.utils import Report, Tracer, TestHelper
from Atom.atom_utils.atom_constants import AtomComponentProperties, LIGHT_TYPES
from Atom.atom_utils.atom_component_helper import (
initial_viewport_setup, create_basic_atom_rendering_scene, enter_exit_game_mode_take_screenshot)
DEGREE_RADIAN_FACTOR = 0.0174533
with Tracer() as error_tracer:
# Test setup begins.
# Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level.
TestHelper.init_idle()
TestHelper.open_level("", "Base")
# Setup: Close error windows and display helpers then update the viewport size.
TestHelper.close_error_windows()
TestHelper.close_display_helpers()
initial_viewport_setup()
general.update_viewport()
# Setup: Runs the create_basic_atom_rendering_scene() function to setup the test scene.
create_basic_atom_rendering_scene()
# Test steps begin.
# 1. Find the Directional Light entity then disable its Directional Light component.
directional_light_entity = EditorEntity.find_editor_entity(AtomComponentProperties.directional_light())
directional_light_component = directional_light_entity.get_components_of_type(
[AtomComponentProperties.directional_light()])[0]
directional_light_component.disable_component()
Report.critical_result(Tests.directional_light_component_disabled, not directional_light_component.is_enabled())
# 2. Disable Global Skylight (IBL) component on the Global Skylight (IBL) entity.
global_skylight_entity = EditorEntity.find_editor_entity(AtomComponentProperties.global_skylight())
global_skylight_component = global_skylight_entity.get_components_of_type(
[AtomComponentProperties.global_skylight()])[0]
global_skylight_component.disable_component()
Report.critical_result(Tests.global_skylight_component_disabled, not global_skylight_component.is_enabled())
# 3. Disable HDRi Skybox component on the Global Skylight (IBL) entity.
hdri_skybox_component = global_skylight_entity.get_components_of_type(
[AtomComponentProperties.hdri_skybox()])[0]
hdri_skybox_component.disable_component()
Report.critical_result(Tests.hdri_skybox_component_disabled, not hdri_skybox_component.is_enabled())
# 4. Create a Spot Light entity and rotate it.
spot_light_name = "Spot Light"
spot_light_entity = EditorEntity.create_editor_entity_at(
azlmbr.math.Vector3(0.7, -2.0, 1.0), spot_light_name)
rotation = azlmbr.math.Vector3(DEGREE_RADIAN_FACTOR * 300.0, 0.0, 0.0)
spot_light_entity.set_local_rotation(rotation)
Report.critical_result(Tests.spot_light_entity_created, spot_light_entity.exists())
# 5. Attach a Light component to the Spot Light entity.
light_component = spot_light_entity.add_component(AtomComponentProperties.light())
Report.critical_result(Tests.light_component_added, light_component.is_enabled())
# 6. Set the Light component Light Type to Spot (disk).
light_component.set_component_property_value(
AtomComponentProperties.light('Light type'), LIGHT_TYPES['spot_disk'])
Report.result(
Tests.light_component_light_type_property_set,
light_component.get_component_property_value(
AtomComponentProperties.light('Light type')) == LIGHT_TYPES['spot_disk'])
# 7. Enter game mode and take a screenshot then exit game mode.
enter_exit_game_mode_take_screenshot("SpotLight_1.ppm", Tests.enter_game_mode, Tests.exit_game_mode)
# 8. Change the default material asset for the Ground Plane entity.
ground_plane_name = "Ground Plane"
ground_plane_entity = EditorEntity.find_editor_entity(ground_plane_name)
ground_plane_material_component_name = AtomComponentProperties.material()
ground_plane_material_component = ground_plane_entity.get_components_of_type(
[ground_plane_material_component_name])[0]
ground_plane_material_asset_path = os.path.join(
"Materials", "Presets", "Macbeth", "22_neutral_5-0_0-70d.azmaterial")
ground_plane_material_asset = Asset.find_asset_by_path(ground_plane_material_asset_path, False)
ground_plane_material_component.set_component_property_value(
AtomComponentProperties.material('Material Asset'), ground_plane_material_asset.id)
Report.result(
Tests.material_component_material_asset_property_set,
ground_plane_material_component.get_component_property_value(
AtomComponentProperties.material('Material Asset')) == ground_plane_material_asset.id)
# 9. Enter game mode and take a screenshot then exit game mode.
enter_exit_game_mode_take_screenshot("SpotLight_2.ppm", Tests.enter_game_mode, Tests.exit_game_mode)
# 10. Increase the Intensity value of the Light component.
light_component.set_component_property_value(AtomComponentProperties.light('Intensity'), 800.0)
Report.result(
Tests.light_component_intensity_property_set,
light_component.get_component_property_value(
AtomComponentProperties.light('Intensity')) == 800.0)
# 11. Enter game mode and take a screenshot then exit game mode.
enter_exit_game_mode_take_screenshot("SpotLight_3.ppm", Tests.enter_game_mode, Tests.exit_game_mode)
# 12. Change the Light component Color property value.
color_value = azlmbr.math.Color(47.0 / 255.0, 75.0 / 255.0, 37.0 / 255.0, 255.0 / 255.0)
light_component.set_component_property_value(AtomComponentProperties.light('Color'), color_value)
Report.result(
Tests.light_component_color_property_set,
light_component.get_component_property_value(AtomComponentProperties.light('Color')) == color_value)
# 13. Enter game mode and take a screenshot then exit game mode.
enter_exit_game_mode_take_screenshot("SpotLight_4.ppm", Tests.enter_game_mode, Tests.exit_game_mode)
# 14. Change the Light component Enable shutters, Inner angle, and Outer angle property values.
enable_shutters = True
inner_angle = 60.0
outer_angle = 75.0
light_component.set_component_property_value(AtomComponentProperties.light('Enable shutters'), enable_shutters)
light_component.set_component_property_value(AtomComponentProperties.light('Inner angle'), inner_angle)
light_component.set_component_property_value(AtomComponentProperties.light('Outer angle'), outer_angle)
Report.result(
Tests.light_component_enable_shutters_property_set,
light_component.get_component_property_value(
AtomComponentProperties.light('Enable shutters')) == enable_shutters)
Report.result(
Tests.light_component_inner_angle_property_set,
light_component.get_component_property_value(AtomComponentProperties.light('Inner angle')) == inner_angle)
Report.result(
Tests.light_component_outer_angle_property_set,
light_component.get_component_property_value(AtomComponentProperties.light('Outer angle')) == outer_angle)
# 15. Enter game mode and take a screenshot then exit game mode.
enter_exit_game_mode_take_screenshot("SpotLight_5.ppm", Tests.enter_game_mode, Tests.exit_game_mode)
# 16. Change the Light component Enable shadow and slightly move Spot Light entity.
light_component.set_component_property_value(AtomComponentProperties.light('Enable shadow'), True)
Report.result(
Tests.light_component_enable_shadow_property_set,
light_component.get_component_property_value(AtomComponentProperties.light('Enable shadow')) is True)
spot_light_entity.set_world_rotation(azlmbr.math.Vector3(0.7, -2.0, 1.9))
# 17. Enter game mode and take a screenshot then exit game mode.
enter_exit_game_mode_take_screenshot("SpotLight_6.ppm", Tests.enter_game_mode, Tests.exit_game_mode)
# 18. Look for errors.
TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0)
for error_info in error_tracer.errors:
Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}")
for assert_info in error_tracer.asserts:
Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}")
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(AtomGPU_LightComponent_SpotLightScreenshotsMatchGoldenImages)

@ -1,200 +0,0 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
import os
import editor_python_test_tools.hydra_editor_utils as hydra
from editor_python_test_tools.editor_test_helper import EditorTestHelper
from Atom.atom_utils.screenshot_utils import ScreenshotHelper
SCREEN_WIDTH = 1280
SCREEN_HEIGHT = 720
DEGREE_RADIAN_FACTOR = 0.0174533
helper = EditorTestHelper(log_prefix="Test_Atom_BasicLevelSetup")
def run():
"""
1. View -> Layouts -> Restore Default Layout, sets the viewport to ratio 16:9 @ 1280 x 720
2. Runs console command r_DisplayInfo = 0
3. Deletes all entities currently present in the level.
4. Creates a "default_level" entity to hold all other entities, setting the translate values to x:0, y:0, z:0
5. Adds a Grid component to the "default_level" & updates its Grid Spacing to 1.0m
6. Adds a "global_skylight" entity to "default_level", attaching an HDRi Skybox w/ a Cubemap Texture.
7. Adds a Global Skylight (IBL) component w/ diffuse image and specular image to "global_skylight" entity.
8. Adds a "ground_plane" entity to "default_level", attaching a Mesh component & Material component.
9. Adds a "directional_light" entity to "default_level" & adds a Directional Light component.
10. Adds a "sphere" entity to "default_level" & adds a Mesh component with a Material component to it.
11. Adds a "camera" entity to "default_level" & adds a Camera component with 80 degree FOV and Transform values:
Translate - x:5.5m, y:-12.0m, z:9.0m
Rotate - x:-27.0, y:-12.0, z:25.0
12. Finally enters game mode, takes a screenshot, & exits game mode.
:return: None
"""
import azlmbr.asset as asset
import azlmbr.bus as bus
import azlmbr.camera as camera
import azlmbr.entity as entity
import azlmbr.legacy.general as general
import azlmbr.math as math
import azlmbr.paths
import azlmbr.editor as editor
def initial_viewport_setup(screen_width, screen_height):
general.set_viewport_size(screen_width, screen_height)
general.update_viewport()
helper.wait_for_condition(
function=lambda: helper.isclose(a=general.get_viewport_size().x, b=SCREEN_WIDTH, rel_tol=0.1)
and helper.isclose(a=general.get_viewport_size().y, b=SCREEN_HEIGHT, rel_tol=0.1),
timeout_in_seconds=4.0
)
result = helper.isclose(a=general.get_viewport_size().x, b=SCREEN_WIDTH, rel_tol=0.1) and helper.isclose(
a=general.get_viewport_size().y, b=SCREEN_HEIGHT, rel_tol=0.1)
general.log(general.get_viewport_size().x)
general.log(general.get_viewport_size().y)
general.log(general.get_viewport_size().z)
general.log(f"Viewport is set to the expected size: {result}")
general.run_console("r_DisplayInfo = 0")
def after_level_load():
"""Function to call after creating/opening a level to ensure it loads."""
# Give everything a second to initialize.
general.idle_enable(True)
general.idle_wait(1.0)
general.update_viewport()
general.idle_wait(0.5) # half a second is more than enough for updating the viewport.
# Close out problematic windows, FPS meters, and anti-aliasing.
if general.is_helpers_shown(): # Turn off the helper gizmos if visible
general.toggle_helpers()
general.idle_wait(1.0)
if general.is_pane_visible("Error Report"): # Close Error Report windows that block focus.
general.close_pane("Error Report")
if general.is_pane_visible("Error Log"): # Close Error Log windows that block focus.
general.close_pane("Error Log")
general.idle_wait(1.0)
general.run_console("r_displayInfo=0")
general.idle_wait(1.0)
# Wait for Editor idle loop before executing Python hydra scripts.
general.idle_enable(True)
# Basic setup for opened level.
helper.open_level(level_name="Base")
after_level_load()
initial_viewport_setup(SCREEN_WIDTH, SCREEN_HEIGHT)
# Create default_level entity
search_filter = azlmbr.entity.SearchFilter()
all_entities = entity.SearchBus(azlmbr.bus.Broadcast, "SearchEntities", search_filter)
editor.ToolsApplicationRequestBus(bus.Broadcast, "DeleteEntities", all_entities)
default_level = hydra.Entity("default_level")
position = math.Vector3(0.0, 0.0, 0.0)
default_level.create_entity(position, ["Grid"])
default_level.get_set_test(0, "Controller|Configuration|Secondary Grid Spacing", 1.0)
# Create global_skylight entity and set the properties
global_skylight = hydra.Entity("global_skylight")
global_skylight.create_entity(
entity_position=math.Vector3(0.0, 0.0, 0.0),
components=["HDRi Skybox", "Global Skylight (IBL)"],
parent_id=default_level.id
)
global_skylight_image_asset_path = os.path.join("LightingPresets", "default_iblskyboxcm.exr.streamingimage")
global_skylight_image_asset = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", global_skylight_image_asset_path, math.Uuid(), False)
global_skylight.get_set_test(0, "Controller|Configuration|Cubemap Texture", global_skylight_image_asset)
hydra.get_set_test(global_skylight, 1, "Controller|Configuration|Diffuse Image", global_skylight_image_asset)
hydra.get_set_test(global_skylight, 1, "Controller|Configuration|Specular Image", global_skylight_image_asset)
# Create ground_plane entity and set the properties
ground_plane = hydra.Entity("ground_plane")
ground_plane.create_entity(
entity_position=math.Vector3(0.0, 0.0, 0.0),
components=["Material"],
parent_id=default_level.id
)
azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalUniformScale", ground_plane.id, 32.0)
# Work around to add the correct Atom Mesh component and asset.
mesh_type_id = azlmbr.globals.property.EditorMeshComponentTypeId
ground_plane.components.append(
editor.EditorComponentAPIBus(
bus.Broadcast, "AddComponentsOfType", ground_plane.id, [mesh_type_id]
).GetValue()[0]
)
ground_plane_mesh_asset_path = os.path.join("TestData", "Objects", "plane.azmodel")
ground_plane_mesh_asset = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", ground_plane_mesh_asset_path, math.Uuid(), False)
hydra.get_set_test(ground_plane, 1, "Controller|Configuration|Mesh Asset", ground_plane_mesh_asset)
# Add Atom Material component and asset.
ground_plane_material_asset_path = os.path.join("Materials", "Presets", "PBR", "metal_chrome.azmaterial")
ground_plane_material_asset = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", ground_plane_material_asset_path, math.Uuid(), False)
ground_plane.get_set_test(0, "Default Material|Material Asset", ground_plane_material_asset)
# Create directional_light entity and set the properties
directional_light = hydra.Entity("directional_light")
directional_light.create_entity(
entity_position=math.Vector3(0.0, 0.0, 10.0),
components=["Directional Light"],
parent_id=default_level.id
)
rotation = math.Vector3(DEGREE_RADIAN_FACTOR * -90.0, 0.0, 0.0)
azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalRotation", directional_light.id, rotation)
# Create sphere entity and set the properties
sphere = hydra.Entity("sphere")
sphere.create_entity(
entity_position=math.Vector3(0.0, 0.0, 1.0),
components=["Material"],
parent_id=default_level.id
)
# Work around to add the correct Atom Mesh component and asset.
sphere.components.append(
editor.EditorComponentAPIBus(
bus.Broadcast, "AddComponentsOfType", sphere.id, [mesh_type_id]
).GetValue()[0]
)
sphere_mesh_asset_path = os.path.join("Models", "sphere.azmodel")
sphere_mesh_asset = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", sphere_mesh_asset_path, math.Uuid(), False)
hydra.get_set_test(sphere, 1, "Controller|Configuration|Mesh Asset", sphere_mesh_asset)
# Add Atom Material component and asset.
sphere_material_asset_path = os.path.join("Materials", "Presets", "PBR", "metal_brass_polished.azmaterial")
sphere_material_asset = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", sphere_material_asset_path, math.Uuid(), False)
sphere.get_set_test(0, "Default Material|Material Asset", sphere_material_asset)
# Create camera component and set the properties
camera_entity = hydra.Entity("camera")
position = math.Vector3(5.5, -12.0, 9.0)
camera_entity.create_entity(components=["Camera"], entity_position=position, parent_id=default_level.id)
rotation = math.Vector3(
DEGREE_RADIAN_FACTOR * -27.0, DEGREE_RADIAN_FACTOR * -12.0, DEGREE_RADIAN_FACTOR * 25.0
)
azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalRotation", camera_entity.id, rotation)
camera_entity.get_set_test(0, "Controller|Configuration|Field of view", 60.0)
camera.EditorCameraViewRequestBus(azlmbr.bus.Event, "ToggleCameraAsActiveView", camera_entity.id)
# Enter game mode, take screenshot, & exit game mode.
general.idle_wait(0.5)
general.enter_game_mode()
general.idle_wait(1.0)
helper.wait_for_condition(function=lambda: general.is_in_game_mode(), timeout_in_seconds=2.0)
ScreenshotHelper(general.idle_wait_frames).capture_screenshot_blocking(f"{'AtomBasicLevelSetup'}.ppm")
general.exit_game_mode()
helper.wait_for_condition(function=lambda: not general.is_in_game_mode(), timeout_in_seconds=2.0)
if __name__ == "__main__":
run()

@ -1,261 +0,0 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
import os
import sys
import azlmbr.asset as asset
import azlmbr.bus as bus
import azlmbr.editor as editor
import azlmbr.math as math
import azlmbr.paths
import azlmbr.legacy.general as general
sys.path.append(os.path.join(azlmbr.paths.projectroot, "Gem", "PythonTests"))
import editor_python_test_tools.hydra_editor_utils as hydra
from Atom.atom_utils import atom_component_helper, atom_constants, screenshot_utils
from editor_python_test_tools.editor_test_helper import EditorTestHelper
helper = EditorTestHelper(log_prefix="Atom_EditorTestHelper")
LEVEL_NAME = "Base"
LIGHT_COMPONENT = "Light"
LIGHT_TYPE_PROPERTY = 'Controller|Configuration|Light type'
DEGREE_RADIAN_FACTOR = 0.0174533
def run():
"""
Sets up the tests by making sure the required level is created & setup correctly.
It then executes 2 test cases - see each associated test function's docstring for more info.
Finally prints the string "Light component tests completed" after completion
Tests will fail immediately if any of these log lines are found:
1. Trace::Assert
2. Trace::Error
3. Traceback (most recent call last):
:return: None
"""
atom_component_helper.create_basic_atom_level(level_name=LEVEL_NAME)
# Run tests.
area_light_test()
spot_light_test()
general.log("Light component tests completed.")
def area_light_test():
"""
Basic test for the "Light" component attached to an "area_light" entity.
Test Case - Light Component: Capsule, Spot (disk), and Point (sphere):
1. Creates "area_light" entity w/ a Light component that has a Capsule Light type w/ the color set to 255, 0, 0
2. Enters game mode to take a screenshot for comparison, then exits game mode.
3. Sets the Light component Intensity Mode to Lumens (default).
4. Ensures the Light component Mode is Automatic (default).
5. Sets the Intensity value of the Light component to 0.0
6. Enters game mode again, takes another screenshot for comparison, then exits game mode.
7. Updates the Intensity value of the Light component to 1000.0
8. Enters game mode again, takes another screenshot for comparison, then exits game mode.
9. Swaps the Capsule light type option to Spot (disk) light type on the Light component
10. Updates "area_light" entity Transform rotate value to x: 90.0, y:0.0, z:0.0
11. Enters game mode again, takes another screenshot for comparison, then exits game mode.
12. Swaps the Spot (disk) light type for the Point (sphere) light type in the Light component.
13. Enters game mode again, takes another screenshot for comparison, then exits game mode.
14. Deletes the Light component from the "area_light" entity and verifies its successful.
"""
# Create an "area_light" entity with "Light" component using Light type of "Capsule"
area_light_entity_name = "area_light"
area_light = hydra.Entity(area_light_entity_name)
area_light.create_entity(math.Vector3(-1.0, -2.0, 3.0), [LIGHT_COMPONENT])
general.log(
f"{area_light_entity_name}_test: Component added to the entity: "
f"{hydra.has_components(area_light.id, [LIGHT_COMPONENT])}")
light_component_id_pair = hydra.attach_component_to_entity(area_light.id, LIGHT_COMPONENT)
# Select the "Capsule" light type option.
azlmbr.editor.EditorComponentAPIBus(
azlmbr.bus.Broadcast,
'SetComponentProperty',
light_component_id_pair,
LIGHT_TYPE_PROPERTY,
atom_constants.LIGHT_TYPES['capsule']
)
# Update color and take screenshot in game mode
color = math.Color(255.0, 0.0, 0.0, 0.0)
area_light.get_set_test(0, "Controller|Configuration|Color", color)
general.idle_wait(1.0)
screenshot_utils.take_screenshot_game_mode("AreaLight_1", area_light_entity_name)
# Update intensity value to 0.0 and take screenshot in game mode
area_light.get_set_test(0, "Controller|Configuration|Attenuation Radius|Mode", 1)
area_light.get_set_test(0, "Controller|Configuration|Intensity", 0.0)
general.idle_wait(1.0)
screenshot_utils.take_screenshot_game_mode("AreaLight_2", area_light_entity_name)
# Update intensity value to 1000.0 and take screenshot in game mode
area_light.get_set_test(0, "Controller|Configuration|Intensity", 1000.0)
general.idle_wait(1.0)
screenshot_utils.take_screenshot_game_mode("AreaLight_3", area_light_entity_name)
# Swap the "Capsule" light type option to "Spot (disk)" light type
azlmbr.editor.EditorComponentAPIBus(
azlmbr.bus.Broadcast,
'SetComponentProperty',
light_component_id_pair,
LIGHT_TYPE_PROPERTY,
atom_constants.LIGHT_TYPES['spot_disk']
)
area_light_rotation = math.Vector3(DEGREE_RADIAN_FACTOR * 90.0, 0.0, 0.0)
azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalRotation", area_light.id, area_light_rotation)
general.idle_wait(1.0)
screenshot_utils.take_screenshot_game_mode("AreaLight_4", area_light_entity_name)
# Swap the "Spot (disk)" light type to the "Point (sphere)" light type and take screenshot.
azlmbr.editor.EditorComponentAPIBus(
azlmbr.bus.Broadcast,
'SetComponentProperty',
light_component_id_pair,
LIGHT_TYPE_PROPERTY,
atom_constants.LIGHT_TYPES['sphere']
)
general.idle_wait(1.0)
screenshot_utils.take_screenshot_game_mode("AreaLight_5", area_light_entity_name)
editor.ToolsApplicationRequestBus(bus.Broadcast, "DeleteEntityById", area_light.id)
def spot_light_test():
"""
Basic test for the Light component attached to a "spot_light" entity.
Test Case - Light Component: Spot (disk) with shadows & colors:
1. Creates "spot_light" entity w/ a Light component attached to it.
2. Selects the "directional_light" entity already present in the level and disables it.
3. Selects the "global_skylight" entity already present in the level and disables the HDRi Skybox component,
as well as the Global Skylight (IBL) component.
4. Enters game mode to take a screenshot for comparison, then exits game mode.
5. Selects the "ground_plane" entity and changes updates the material to a new material.
6. Enters game mode to take a screenshot for comparison, then exits game mode.
7. Selects the "spot_light" entity and increases the Light component Intensity to 800 lm
8. Enters game mode to take a screenshot for comparison, then exits game mode.
9. Selects the "spot_light" entity and sets the Light component Color to 47, 75, 37
10. Enters game mode to take a screenshot for comparison, then exits game mode.
11. Selects the "spot_light" entity and modifies the Shutter controls to the following values:
- Enable shutters: True
- Inner Angle: 60.0
- Outer Angle: 75.0
12. Enters game mode to take a screenshot for comparison, then exits game mode.
13. Selects the "spot_light" entity and modifies the Shadow controls to the following values:
- Enable Shadow: True
- ShadowmapSize: 256
14. Modifies the world translate position of the "spot_light" entity to 0.7, -2.0, 1.9 (for casting shadows better)
15. Enters game mode to take a screenshot for comparison, then exits game mode.
"""
# Disable "Directional Light" component for the "directional_light" entity
# "directional_light" entity is created by the create_basic_atom_level() function by default.
directional_light_entity_id = hydra.find_entity_by_name("directional_light")
directional_light = hydra.Entity(name='directional_light', id=directional_light_entity_id)
directional_light_component_type = azlmbr.editor.EditorComponentAPIBus(
azlmbr.bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Directional Light"], 0)[0]
directional_light_component = azlmbr.editor.EditorComponentAPIBus(
azlmbr.bus.Broadcast, 'GetComponentOfType', directional_light.id, directional_light_component_type
).GetValue()
editor.EditorComponentAPIBus(bus.Broadcast, "DisableComponents", [directional_light_component])
general.idle_wait(0.5)
# Disable "Global Skylight (IBL)" and "HDRi Skybox" components for the "global_skylight" entity
global_skylight_entity_id = hydra.find_entity_by_name("global_skylight")
global_skylight = hydra.Entity(name='global_skylight', id=global_skylight_entity_id)
global_skylight_component_type = azlmbr.editor.EditorComponentAPIBus(
azlmbr.bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Global Skylight (IBL)"], 0)[0]
global_skylight_component = azlmbr.editor.EditorComponentAPIBus(
azlmbr.bus.Broadcast, 'GetComponentOfType', global_skylight.id, global_skylight_component_type
).GetValue()
editor.EditorComponentAPIBus(bus.Broadcast, "DisableComponents", [global_skylight_component])
hdri_skybox_component_type = azlmbr.editor.EditorComponentAPIBus(
azlmbr.bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["HDRi Skybox"], 0)[0]
hdri_skybox_component = azlmbr.editor.EditorComponentAPIBus(
azlmbr.bus.Broadcast, 'GetComponentOfType', global_skylight.id, hdri_skybox_component_type
).GetValue()
editor.EditorComponentAPIBus(bus.Broadcast, "DisableComponents", [hdri_skybox_component])
general.idle_wait(0.5)
# Create a "spot_light" entity with "Light" component using Light Type of "Spot (disk)"
spot_light_entity_name = "spot_light"
spot_light = hydra.Entity(spot_light_entity_name)
spot_light.create_entity(math.Vector3(0.7, -2.0, 1.0), [LIGHT_COMPONENT])
general.log(
f"{spot_light_entity_name}_test: Component added to the entity: "
f"{hydra.has_components(spot_light.id, [LIGHT_COMPONENT])}")
rotation = math.Vector3(DEGREE_RADIAN_FACTOR * 300.0, 0.0, 0.0)
azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalRotation", spot_light.id, rotation)
light_component_type = hydra.attach_component_to_entity(spot_light.id, LIGHT_COMPONENT)
editor.EditorComponentAPIBus(
azlmbr.bus.Broadcast,
'SetComponentProperty',
light_component_type,
LIGHT_TYPE_PROPERTY,
atom_constants.LIGHT_TYPES['spot_disk']
)
general.idle_wait(1.0)
screenshot_utils.take_screenshot_game_mode("SpotLight_1", spot_light_entity_name)
# Change default material of ground plane entity and take screenshot
ground_plane_entity_id = hydra.find_entity_by_name("ground_plane")
ground_plane = hydra.Entity(name='ground_plane', id=ground_plane_entity_id)
ground_plane_asset_path = os.path.join("Materials", "Presets", "MacBeth", "22_neutral_5-0_0-70d.azmaterial")
ground_plane_asset_value = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", ground_plane_asset_path, math.Uuid(), False)
material_property_path = "Default Material|Material Asset"
material_component_type = azlmbr.editor.EditorComponentAPIBus(
azlmbr.bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Material"], 0)[0]
material_component = azlmbr.editor.EditorComponentAPIBus(
azlmbr.bus.Broadcast, 'GetComponentOfType', ground_plane.id, material_component_type).GetValue()
editor.EditorComponentAPIBus(
azlmbr.bus.Broadcast,
'SetComponentProperty',
material_component,
material_property_path,
ground_plane_asset_value
)
general.idle_wait(1.0)
screenshot_utils.take_screenshot_game_mode("SpotLight_2", spot_light_entity_name)
# Increase intensity value of the Spot light and take screenshot in game mode
spot_light.get_set_test(0, "Controller|Configuration|Intensity", 800.0)
general.idle_wait(1.0)
screenshot_utils.take_screenshot_game_mode("SpotLight_3", spot_light_entity_name)
# Update the Spot light color and take screenshot in game mode
color_value = math.Color(47.0 / 255.0, 75.0 / 255.0, 37.0 / 255.0, 255.0 / 255.0)
spot_light.get_set_test(0, "Controller|Configuration|Color", color_value)
general.idle_wait(1.0)
screenshot_utils.take_screenshot_game_mode("SpotLight_4", spot_light_entity_name)
# Update the Shutter controls of the Light component and take screenshot
spot_light.get_set_test(0, "Controller|Configuration|Shutters|Enable shutters", True)
spot_light.get_set_test(0, "Controller|Configuration|Shutters|Inner angle", 60.0)
spot_light.get_set_test(0, "Controller|Configuration|Shutters|Outer angle", 75.0)
general.idle_wait(1.0)
screenshot_utils.take_screenshot_game_mode("SpotLight_5", spot_light_entity_name)
# Update the Shadow controls, move the spot_light entity world translate position and take screenshot
spot_light.get_set_test(0, "Controller|Configuration|Shadows|Enable shadow", True)
spot_light.get_set_test(0, "Controller|Configuration|Shadows|Shadowmap size", 256.0)
azlmbr.components.TransformBus(
azlmbr.bus.Event, "SetWorldTranslation", spot_light.id, math.Vector3(0.7, -2.0, 1.9))
general.idle_wait(1.0)
screenshot_utils.take_screenshot_game_mode("SpotLight_6", spot_light_entity_name)
if __name__ == "__main__":
run()

@ -25,7 +25,7 @@ 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_component_objects()
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.
"""
@ -94,6 +94,13 @@ class EditorComponent:
"""
return editor.EditorComponentAPIBus(bus.Broadcast, "IsComponentEnabled", self.id)
def disable_component(self):
"""
Used to disable the component using its id value.
:return: None
"""
editor.EditorComponentAPIBus(bus.Broadcast, "DisableComponents", [self.id])
@staticmethod
def get_type_ids(component_names: list) -> list:
"""
@ -136,10 +143,11 @@ class EditorEntity:
# Creation functions
@classmethod
def find_editor_entity(cls, entity_name: str, must_be_unique : bool = False) -> EditorEntity:
def find_editor_entity(cls, entity_name: str, must_be_unique: bool = False) -> EditorEntity:
"""
Given Entity name, outputs entity object
:param entity_name: Name of entity to find
:param must_be_unique: bool that asserts the entity_name specified is unique when set to True
:return: EditorEntity class object
"""
entities = cls.find_editor_entities([entity_name])
@ -147,14 +155,14 @@ class EditorEntity:
if must_be_unique:
assert len(entities) == 1, f"Failure: Multiple entities with name: '{entity_name}' when expected only one"
entity = cls(entities[0])
entity = entities[0]
return entity
@classmethod
def find_editor_entities(cls, entity_names: List[str]) -> EditorEntity:
def find_editor_entities(cls, entity_names: List[str]) -> List[EditorEntity]:
"""
Given Entities names, returns a list of EditorEntity
:param entity_name: Name of entity to find
:param entity_names: List of entity names to find
:return: List[EditorEntity] class object
"""
searchFilter = azlmbr.entity.SearchFilter()
@ -438,7 +446,7 @@ class EditorEntity:
def set_local_rotation(self, new_rotation) -> None:
"""
Sets the set the local rotation(relative to the parent) of the current entity.
:param vector3_rotation: The math.Vector3 value to use for rotation on the entity (uses radians).
:param new_rotation: The math.Vector3 value to use for rotation on the entity (uses radians).
:return: None
"""
new_rotation = convert_to_azvector3(new_rotation)
@ -454,7 +462,7 @@ class EditorEntity:
def set_local_translation(self, new_translation) -> None:
"""
Sets the local translation(relative to the parent) of the current entity.
:param vector3_translation: The math.Vector3 value to use for translation on the entity.
:param new_translation: The math.Vector3 value to use for translation on the entity.
:return: None
"""
new_translation = convert_to_azvector3(new_translation)

@ -46,6 +46,18 @@ def get_component_type_id(component_name):
return component_type_id
def get_level_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().Level)
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
@ -145,6 +157,20 @@ def get_component_property_value(component, component_propertyPath):
print(f'FAILURE: Could not get value from {component_propertyPath}')
return None
def set_component_property_value(component, component_propertyPath, value):
"""
Given a component name and component property path, set component property value
:param component: Component object to act on.
:param componentPropertyPath: String of component property. (e.g. 'Settings|Visible')
:param value: new value for the variable being changed in the component
"""
componentPropertyObj = editor.EditorComponentAPIBus(bus.Broadcast, 'SetComponentProperty', component,
component_propertyPath, value)
if componentPropertyObj.IsSuccess():
print(f'{component_propertyPath} set to {value}')
else:
print(f'FAILURE: Could not set value in {component_propertyPath}')
def get_property_tree(component):
"""

@ -88,8 +88,7 @@ class TestHelper:
general.idle_wait_frames(200)
@staticmethod
def enter_game_mode(msgtuple_success_fail : Tuple[str, str]):
# type: (tuple) -> None
def enter_game_mode(msgtuple_success_fail: Tuple[str, str]) -> None:
"""
:param msgtuple_success_fail: The tuple with the expected/unexpected messages for entering game mode.
@ -102,8 +101,7 @@ class TestHelper:
Report.critical_result(msgtuple_success_fail, general.is_in_game_mode())
@staticmethod
def multiplayer_enter_game_mode(msgtuple_success_fail : Tuple[str, str], sv_default_player_spawn_asset : str):
# type: (tuple) -> None
def multiplayer_enter_game_mode(msgtuple_success_fail: Tuple[str, str], sv_default_player_spawn_asset: str) -> None:
"""
:param msgtuple_success_fail: The tuple with the expected/unexpected messages for entering game mode.
:param sv_default_player_spawn_asset: The path to the network player prefab that will be automatically spawned upon entering gamemode. The engine default is "prefabs/player.network.spawnable"

@ -25,8 +25,8 @@ class TestAutomation(TestAutomationBase):
def test_NvCloth_AddClothSimulationToMesh(self, request, workspace, editor, launcher_platform):
from .tests import NvCloth_AddClothSimulationToMesh as test_module
self._run_test(request, workspace, editor, test_module, use_null_renderer = self.use_null_renderer, enable_prefab_system=False)
self._run_test(request, workspace, editor, test_module, use_null_renderer = self.use_null_renderer)
def test_NvCloth_AddClothSimulationToActor(self, request, workspace, editor, launcher_platform):
from .tests import NvCloth_AddClothSimulationToActor as test_module
self._run_test(request, workspace, editor, test_module, use_null_renderer = self.use_null_renderer, enable_prefab_system=False)
self._run_test(request, workspace, editor, test_module, use_null_renderer = self.use_null_renderer)

@ -12,7 +12,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS)
NAME AutomatedTesting::PrefabTests
TEST_SUITE main
TEST_SERIAL
PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Main.py
PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Main_Optimized.py
RUNTIME_DEPENDENCIES
Legacy::Editor
AZ::AssetProcessor

@ -0,0 +1,52 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
import pytest
from ly_test_tools.o3de.editor_test import EditorSingleTest, EditorSharedTest, EditorParallelTest, EditorTestSuite
@pytest.mark.SUITE_main
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
@pytest.mark.parametrize("project", ["AutomatedTesting"])
class TestAutomationNoAutoTestMode(EditorTestSuite):
# Enable only -BatchMode for these tests. Some tests cannot run in -autotest_mode due to UI interactions
global_extra_cmdline_args = ["-BatchMode"]
class test_CreatePrefab_UnderAnEntity(EditorSharedTest):
from .tests.create_prefab import CreatePrefab_UnderAnEntity as test_module
class test_CreatePrefab_UnderAnotherPrefab(EditorSharedTest):
from .tests.create_prefab import CreatePrefab_UnderAnotherPrefab as test_module
class test_DeleteEntity_UnderAnotherPrefab(EditorSharedTest):
from .tests.delete_entity import DeleteEntity_UnderAnotherPrefab as test_module
class test_DeleteEntity_UnderLevelPrefab(EditorSharedTest):
from .tests.delete_entity import DeleteEntity_UnderLevelPrefab as test_module
class test_ReparentPrefab_UnderAnotherPrefab(EditorSharedTest):
from .tests.reparent_prefab import ReparentPrefab_UnderAnotherPrefab as test_module
class test_DetachPrefab_UnderAnotherPrefab(EditorSharedTest):
from .tests.detach_prefab import DetachPrefab_UnderAnotherPrefab as test_module
class test_OpenLevel_ContainingTwoEntities(EditorSharedTest):
from .tests.open_level import OpenLevel_ContainingTwoEntities as test_module
class test_CreatePrefab_WithSingleEntity(EditorSharedTest):
from .tests.create_prefab import CreatePrefab_WithSingleEntity as test_module
class test_InstantiatePrefab_ContainingASingleEntity(EditorSharedTest):
from .tests.instantiate_prefab import InstantiatePrefab_ContainingASingleEntity as test_module
class test_DeletePrefab_ContainingASingleEntity(EditorSharedTest):
from .tests.delete_prefab import DeletePrefab_ContainingASingleEntity as test_module
class test_DuplicatePrefab_ContainingASingleEntity(EditorSharedTest):
from .tests.duplicate_prefab import DuplicatePrefab_ContainingASingleEntity as test_module

@ -7,8 +7,8 @@ SPDX-License-Identifier: Apache-2.0 OR MIT
def DetachPrefab_UnderAnotherPrefab():
CAR_PREFAB_FILE_NAME = 'car_prefab'
WHEEL_PREFAB_FILE_NAME = 'wheel_prefab'
CAR_PREFAB_FILE_NAME = 'car_prefab2'
WHEEL_PREFAB_FILE_NAME = 'wheel_prefab2'
import editor_python_test_tools.pyside_utils as pyside_utils

@ -0,0 +1,166 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
class MacroMaterialTests:
setup_test = (
"Setup successful",
"Setup failed"
)
material_changed_not_called_when_inactive = (
"OnTerrainMacroMaterialRegionChanged not called successfully",
"OnTerrainMacroMaterialRegionChanged called when component inactive."
)
material_created = (
"MaterialCreated called successfully",
"MaterialCreated failed"
)
material_destroyed = (
"MaterialDestroyed called successfully",
"MaterialDestroyed failed"
)
material_recreated = (
"MaterialCreated called successfully on second test",
"MaterialCreated failed on second test"
)
material_changed_call_on_aabb_change = (
"OnTerrainMacroMaterialRegionChanged called successfully",
"Timed out waiting for OnTerrainMacroMaterialRegionChanged"
)
def TerrainMacroMaterialComponent_MacroMaterialActivates():
"""
Summary:
Load an empty level, create a MacroMaterialComponent and check assigning textures results in the correct callbacks.
:return: None
"""
import os
import math as sys_math
import azlmbr.legacy.general as general
import azlmbr.asset as asset
import azlmbr.bus as bus
import azlmbr.math as math
import azlmbr.terrain as terrain
import azlmbr.editor as editor
import azlmbr.vegetation as vegetation
import azlmbr.entity as EntityId
import editor_python_test_tools.hydra_editor_utils as hydra
from editor_python_test_tools.utils import Report
from editor_python_test_tools.utils import TestHelper as helper
import editor_python_test_tools.pyside_utils as pyside_utils
from editor_python_test_tools.editor_entity_utils import EditorEntity
from editor_python_test_tools.asset_utils import Asset
material_created_called = False
material_changed_called = False
material_region_changed_called = False
material_destroyed_called = False
def create_entity_at(entity_name, components_to_add, x, y, z):
entity = EditorEntity.create_editor_entity_at([x, y, z], entity_name)
for component in components_to_add:
entity.add_component(component)
return entity
def on_macro_material_created(args):
nonlocal material_created_called
material_created_called = True
def on_macro_material_changed(args):
nonlocal material_changed_called
material_changed_called = True
def on_macro_material_region_changed(args):
nonlocal material_region_changed_called
material_region_changed_called = True
def on_macro_material_destroyed(args):
nonlocal material_destroyed_called
material_destroyed_called = True
helper.init_idle()
# Open a level.
helper.open_level("Physics", "Base")
helper.wait_for_condition(lambda: general.get_current_level_name() == "Base", 2.0)
general.idle_wait_frames(1)
# Set up a handler to wait for notifications from the TerrainSystem.
handler = terrain.TerrainMacroMaterialAutomationBusHandler()
handler.connect()
handler.add_callback("OnTerrainMacroMaterialCreated", on_macro_material_created)
handler.add_callback("OnTerrainMacroMaterialChanged", on_macro_material_changed)
handler.add_callback("OnTerrainMacroMaterialRegionChanged", on_macro_material_region_changed)
handler.add_callback("OnTerrainMacroMaterialDestroyed", on_macro_material_destroyed)
macro_material_entity = create_entity_at("macro", ["Terrain Macro Material", "Axis Aligned Box Shape"], 0.0, 0.0, 0.0)
# Check that no macro material callbacks happened. It should be "inactive" as it has no assets assigned.
setup_success = not material_created_called and not material_changed_called and not material_region_changed_called and not material_destroyed_called
Report.result(MacroMaterialTests.setup_test, setup_success)
# Find the aabb component.
aabb_component_type_id_type = azlmbr.editor.EditorComponentAPIBus(azlmbr.bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Axis Aligned Box Shape"], 0)[0]
aabb_component_id = azlmbr.editor.EditorComponentAPIBus(azlmbr.bus.Broadcast, 'GetComponentOfType', macro_material_entity.id, aabb_component_type_id_type).GetValue()
# Change the aabb dimensions
material_region_changed_called = False
box_dimensions_path = "Axis Aligned Box Shape|Box Configuration|Dimensions"
editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", aabb_component_id, box_dimensions_path, math.Vector3(1.0, 1.0, 1.0))
# Check we don't receive a callback. The macro material component should be inactive as it has no images assigned.
general.idle_wait_frames(1)
Report.result(MacroMaterialTests.material_changed_not_called_when_inactive, material_region_changed_called == False)
# Find the macro material component.
macro_material_id_type = azlmbr.editor.EditorComponentAPIBus(azlmbr.bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Terrain Macro Material"], 0)[0]
macro_material_component_id = azlmbr.editor.EditorComponentAPIBus(azlmbr.bus.Broadcast, 'GetComponentOfType', macro_material_entity.id, macro_material_id_type).GetValue()
# Find a color image asset.
color_image_path = os.path.join("assets", "textures", "image.png.streamingimage")
color_image_asset = asset.AssetCatalogRequestBus(bus.Broadcast, "GetAssetIdByPath", color_image_path, math.Uuid(), False)
# Assign the image to the MacroMaterial component, which should result in a created message.
material_created_called = False
color_texture_path = "Configuration|Color Texture"
editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", macro_material_component_id, color_texture_path, color_image_asset)
call_result = helper.wait_for_condition(lambda: material_created_called == True, 2.0)
Report.result(MacroMaterialTests.material_created, call_result)
# Find a normal image asset.
normal_image_path = os.path.join("assets", "textures", "normal.png.streamingimage")
normal_image_asset = asset.AssetCatalogRequestBus(bus.Broadcast, "GetAssetIdByPath", normal_image_path, math.Uuid(), False)
# Assign the normal image to the MacroMaterial component, which should result in a created message.
material_created_called = False
material_destroyed_called = False
normal_texture_path = "Configuration|Normal Texture"
editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", macro_material_component_id, normal_texture_path, normal_image_asset)
# Check the MacroMaterial was destroyed and recreated.
destroyed_call_result = helper.wait_for_condition(lambda: material_destroyed_called == True, 2.0)
Report.result(MacroMaterialTests.material_destroyed, destroyed_call_result)
recreated_call_result = helper.wait_for_condition(lambda: material_created_called == True, 2.0)
Report.result(MacroMaterialTests.material_recreated, recreated_call_result)
# Change the aabb dimensions.
box_dimensions_path = "Axis Aligned Box Shape|Box Configuration|Dimensions"
editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", aabb_component_id, box_dimensions_path, math.Vector3(1.0, 1.0, 1.0))
# Check that a callback is received.
region_changed_call_result = helper.wait_for_condition(lambda: material_region_changed_called == True, 2.0)
Report.result(MacroMaterialTests.material_changed_call_on_aabb_change, region_changed_call_result)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(TerrainMacroMaterialComponent_MacroMaterialActivates)

@ -0,0 +1,214 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
class VegetationTests:
vegetation_on_gradient_1 = (
"Vegetation detected at correct position on Gradient1",
"Vegetation not detected at correct position on Gradient1"
)
vegetation_on_gradient_2 = (
"Vegetation detected at correct position on Gradient2",
"Vegetation not detected at correct position on Gradient2"
)
unfiltered_vegetation_count_correct = (
"Unfiltered vegetation spawn count correct",
"Unfiltered vegetation spawn count incorrect"
)
testTag2_excluded_vegetation_count_correct = (
"TestTag2 filtered vegetation count correct",
"TestTag2 filtered vegetation count incorrect"
)
testTag2_excluded_vegetation_z_correct = (
"TestTag2 filtered vegetation spawned in correct position",
"TestTag2 filtered vegetation failed to spawn in correct position"
)
testTag3_excluded_vegetation_count_correct = (
"TestTag3 filtered vegetation count correct",
"TestTag3 filtered vegetation count incorrect"
)
testTag3_excluded_vegetation_z_correct = (
"TestTag3 filtered vegetation spawned in correct position",
"TestTag3 filtered vegetation failed to spawn in correct position"
)
cleared_exclusion_vegetation_count_correct = (
"Cleared filter vegetation count correct",
"Cleared filter vegetation count incorrect"
)
def TerrainSystem_VegetationSpawnsOnTerrainSurfaces():
"""
Summary:
Load an empty level,
Create two entities with constant gradient components with different values.
Create two entities with TerrainLayerSpawners
Create an entity to spawn vegetation
Ensure that vegetation spawns at the correct heights
Add a VegetationSurfaceMaskFilter and ensure it responds correctly to surface changes.
:return: None
"""
import os
import sys
import math as sys_math
import azlmbr.legacy.general as general
import azlmbr.bus as bus
import azlmbr.math as math
import azlmbr.areasystem as areasystem
import azlmbr.editor as editor
import azlmbr.vegetation as vegetation
import azlmbr.terrain as terrain
import azlmbr.entity as EntityId
import azlmbr.surface_data as surface_data
import editor_python_test_tools.hydra_editor_utils as hydra
from editor_python_test_tools.utils import Report
from editor_python_test_tools.utils import TestHelper as helper
def create_entity_at(entity_name, components_to_add, x, y, z):
entity = hydra.Entity(entity_name)
entity.create_entity(math.Vector3(x, y, z), components_to_add)
return entity
def FindHighestAndLowestZValuesInArea(aabb):
vegetation_items = areasystem.AreaSystemRequestBus(bus.Broadcast, 'GetInstancesInAabb', aabb)
lowest_z = min([item.position.z for item in vegetation_items])
highest_z = max([item.position.z for item in vegetation_items])
return highest_z, lowest_z
helper.init_idle()
# Open an empty level.
helper.open_level("Physics", "Base")
helper.wait_for_condition(lambda: general.get_current_level_name() == "Base", 2.0)
general.idle_wait_frames(1)
box_height = 20.0
box_y_position = 10.0
box_dimensions = math.Vector3(20.0, 20.0, box_height)
# Add Terrain Rendering
hydra.add_level_component("Terrain World")
hydra.add_level_component("Terrain World Renderer")
# Create two terrain entities at adjoining positions
terrain_entity_1 = create_entity_at("Terrain1", ["Terrain Layer Spawner", "Axis Aligned Box Shape", "Terrain Height Gradient List", "Terrain Surface Gradient List"], 0.0, box_y_position, box_height/2.0)
terrain_entity_1.get_set_test(1, "Axis Aligned Box Shape|Box Configuration|Dimensions", box_dimensions)
terrain_entity_2 = create_entity_at("Terrain2", ["Terrain Layer Spawner", "Axis Aligned Box Shape", "Terrain Height Gradient List", "Terrain Surface Gradient List"], 20.0, box_y_position, box_height/2.0)
terrain_entity_2.get_set_test(1, "Axis Aligned Box Shape|Box Configuration|Dimensions", box_dimensions)
# Create two gradient entities.
gradient_value_1 = 0.25
gradient_value_2 = 0.5
gradient_entity_1 = create_entity_at("Gradient1", ["Constant Gradient"], 0.0, 0.0, 0.0)
gradient_entity_1.get_set_test(0, "Configuration|Value", gradient_value_1)
gradient_entity_2 = create_entity_at("Gradient2", ["Constant Gradient"], 0.0, 0.0, 0.0)
gradient_entity_2.get_set_test(0, "Configuration|Value", gradient_value_2)
mapping = terrain.TerrainSurfaceGradientMapping()
mapping.gradientEntityId = gradient_entity_1.id
pte = hydra.get_property_tree(terrain_entity_1.components[3])
pte.add_container_item("Configuration|Gradient to Surface Mappings", 0, mapping)
mapping = terrain.TerrainSurfaceGradientMapping()
mapping.gradientEntityId = gradient_entity_2.id
pte = hydra.get_property_tree(terrain_entity_2.components[3])
pte.add_container_item("Configuration|Gradient to Surface Mappings", 0, mapping)
# create a vegetation entity that overlaps both terrain entities.
vegetation_entity = create_entity_at("Vegetation", ["Vegetation Layer Spawner", "Axis Aligned Box Shape", "Vegetation Asset List", "Vegetation Surface Mask Filter"], 10.0, box_y_position, box_height/2.0)
vegetation_entity.get_set_test(1, "Axis Aligned Box Shape|Box Configuration|Dimensions", box_dimensions)
# Set the vegetation area to a PrefabInstanceSpawner with a specific prefab asset selected.
prefab_spawner = vegetation.PrefabInstanceSpawner()
prefab_spawner.SetPrefabAssetPath(os.path.join("Prefabs", "PinkFlower.spawnable"))
descriptor = hydra.get_component_property_value(vegetation_entity.components[2], 'Configuration|Embedded Assets|[0]')
descriptor.spawner = prefab_spawner
vegetation_entity.get_set_test(2, "Configuration|Embedded Assets|[0]", descriptor)
# Assign gradients to layer spawners.
terrain_entity_1.get_set_test(2, "Configuration|Gradient Entities", [gradient_entity_1.id])
terrain_entity_2.get_set_test(2, "Configuration|Gradient Entities", [gradient_entity_2.id])
# Move view so that the entities are visible.
general.set_current_view_position(17.0, -66.0, 41.0)
general.set_current_view_rotation(-15, 0, 0)
# Expected item counts under conditions to be tested.
# By default, vegetation spawns at a density of 20 items per 16 meters,
# so in a 20m square, there should be around 25 ^ 2 items depending on whether area edges are included.
# In this case there are 26 ^ 2 items.
expected_surface_tag_excluded_item_count = 338
expected_no_exclusions_item_count = 676
# Wait for the vegetation to spawn
helper.wait_for_condition(lambda: vegetation.VegetationSpawnerRequestBus(bus.Event, "GetAreaProductCount", vegetation_entity.id) == expected_no_exclusions_item_count, 5.0)
# Check the spawn count is correct.
item_count = vegetation.VegetationSpawnerRequestBus(bus.Event, "GetAreaProductCount", vegetation_entity.id)
Report.result(VegetationTests.unfiltered_vegetation_count_correct, item_count == expected_no_exclusions_item_count)
test_aabb = math.Aabb_CreateFromMinMax(math.Vector3(-10.0, -10.0, 0.0), math.Vector3(30.0, 10.0, box_height))
# Find the z positions of the items with the lowest and highest x values, this will avoid the overlap area where z values are blended between the surface heights.
highest_z, lowest_z = FindHighestAndLowestZValuesInArea(test_aabb)
# Check that the z values are as expected.
Report.result(VegetationTests.vegetation_on_gradient_1, sys_math.isclose(lowest_z, box_height * gradient_value_1, abs_tol=0.01))
Report.result(VegetationTests.vegetation_on_gradient_2, sys_math.isclose(highest_z, box_height * gradient_value_2, abs_tol=0.01))
# Assign SurfaceTags to the SurfaceGradientLists
terrain_entity_1.get_set_test(3, "Configuration|Gradient to Surface Mappings|[0]|Surface Tag", surface_data.SurfaceTag("test_tag2"))
terrain_entity_2.get_set_test(3, "Configuration|Gradient to Surface Mappings|[0]|Surface Tag", surface_data.SurfaceTag("test_tag3"))
# Give the VegetationSurfaceFilter an exclusion list, set it to exclude test_tag2 which should remove all the lower items which are in terrain_entity_1.
vegetation_entity.get_set_test(3, "Configuration|Exclusion|Surface Tags", [surface_data.SurfaceTag()])
vegetation_entity.get_set_test(3, "Configuration|Exclusion|Surface Tags|[0]", surface_data.SurfaceTag("test_tag2"))
# Wait for the vegetation to respawn and check z values.
helper.wait_for_condition(lambda: vegetation.VegetationSpawnerRequestBus(bus.Event, "GetAreaProductCount", vegetation_entity.id) == expected_surface_tag_excluded_item_count, 5.0)
item_count = vegetation.VegetationSpawnerRequestBus(bus.Event, "GetAreaProductCount", vegetation_entity.id)
Report.result(VegetationTests.testTag2_excluded_vegetation_count_correct, item_count == expected_surface_tag_excluded_item_count)
highest_z, lowest_z = FindHighestAndLowestZValuesInArea(test_aabb)
Report.result(VegetationTests.testTag2_excluded_vegetation_z_correct, lowest_z > box_height * gradient_value_1)
# Clear the filter and ensure vegetation respawns.
vegetation_entity.get_set_test(3, "Configuration|Exclusion|Surface Tags|[0]", surface_data.SurfaceTag("invalid"))
helper.wait_for_condition(lambda: vegetation.VegetationSpawnerRequestBus(bus.Event, "GetAreaProductCount", vegetation_entity.id) == expected_no_exclusions_item_count, 5.0)
item_count = vegetation.VegetationSpawnerRequestBus(bus.Event, "GetAreaProductCount", vegetation_entity.id)
Report.result(VegetationTests.cleared_exclusion_vegetation_count_correct, item_count == expected_no_exclusions_item_count)
# Exclude test_tag3 to exclude the higher items in terrain_entity_2 and recheck.
vegetation_entity.get_set_test(3, "Configuration|Exclusion|Surface Tags|[0]", surface_data.SurfaceTag("test_tag3"))
helper.wait_for_condition(lambda: vegetation.VegetationSpawnerRequestBus(bus.Event, "GetAreaProductCount", vegetation_entity.id) == expected_surface_tag_excluded_item_count, 5.0)
item_count = vegetation.VegetationSpawnerRequestBus(bus.Event, "GetAreaProductCount", vegetation_entity.id)
Report.result(VegetationTests.testTag3_excluded_vegetation_count_correct, item_count == expected_surface_tag_excluded_item_count)
highest_z, lowest_z = FindHighestAndLowestZValuesInArea(test_aabb)
Report.result(VegetationTests.testTag3_excluded_vegetation_z_correct, highest_z < box_height * gradient_value_2)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(TerrainSystem_VegetationSpawnsOnTerrainSurfaces)

@ -0,0 +1,168 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
#fmt: off
class Tests():
level_components_added = ("Level components added correctly", "Failed to create level components")
create_terrain_spawner_entity = ("Terrain_spawner_entity created successfully", "Failed to create terrain_spawner_entity")
create_height_provider_entity = ("Height_provider_entity created successfully", "Failed to create height_provider_entity")
bounds_max_changed = ("Terrain World Bounds Max changed successfully", "Failed to change Terrain World Bounds Max")
bounds_min_changed = ("Terrain World Bounds Min changed successfully", "Failed to change Terrain World Bounds Min")
height_query_changed = ("Terrain World Height Query Resolution changed successfully", "Failed to change Height Query Resolution")
box_dimensions_changed = ("Aabb dimensions changed successfully", "Failed to change Aabb dimensions")
shape_changed = ("Shape changed successfully", "Failed Shape change")
frequency_changed = ("Frequency changed successfully", "Failed Frequency change")
entity_added = ("Entity added successfully", "Failed Entity add")
terrain_exists = ("Terrain exists at the provided point", "Terrain does not exist at the provided point")
terrain_does_not_exist = ("Terrain does not exist at the provided point", "Terrain exists at the provided point")
values_not_the_same = ("The tested values are not the same", "The tested values are the same")
no_errors_and_warnings_found = ("No errors and warnings found", "Found errors and warnings")
#fmt: on
def Terrain_World_ConfigurationWorks():
"""
Summary:
Test the Terrain World configuration changes when parameters are changed in the component
Test Steps:
Expected Behavior:
The Editor is stable there are no warnings or errors.
Test Steps:
1) Start the Tracer to catch any errors and warnings
2) Load the base level
3) Load the level components
4) Create 2 test entities, one parent at 512.0, 512.0, 50.0 and one child at the default position and add the required components
5) Set the base Terrain World values
6) Change the Axis Aligned Box Shape dimensions
7) Set the Shape Reference to terrain_spawner_entity
8) Set the FastNoise Gradient frequency to 0.01
9) Set the Gradient List to height_provider_entity
10) Disable and Enable the Terrain Gradient List so that it is recognised
11) Check terrain exists at a known position in the world
12) Check terrain does not exist at a known position outside the world
13) Check height value is the expected one when query resolution is changed
"""
from editor_python_test_tools.editor_entity_utils import EditorEntity
from editor_python_test_tools.utils import TestHelper as helper, Report
from editor_python_test_tools.utils import Report, Tracer
import editor_python_test_tools.hydra_editor_utils as hydra
import azlmbr.math as azmath
import azlmbr.legacy.general as general
import azlmbr.bus as bus
import azlmbr.editor as editor
import azlmbr.terrain as terrain
import math
SET_BOX_X_SIZE = 2048.0
SET_BOX_Y_SIZE = 2048.0
SET_BOX_Z_SIZE = 100.0
CLAMP = 1
helper.init_idle()
# 1) Start the Tracer to catch any errors and warnings
with Tracer() as section_tracer:
# 2) Load the level
helper.open_level("", "Base")
helper.wait_for_condition(lambda: general.get_current_level_name() == "Base", 2.0)
# 3) Load the level components
terrain_world_component = hydra.add_level_component("Terrain World")
terrain_world_renderer = hydra.add_level_component("Terrain World Renderer")
Report.critical_result(Tests.level_components_added,
terrain_world_component is not None and terrain_world_renderer is not None)
# 4) Create 2 test entities, one parent at 512.0, 512.0, 50.0 and one child at the default position and add the required components
entity1_components_to_add = ["Axis Aligned Box Shape", "Terrain Layer Spawner", "Terrain Height Gradient List", "Terrain Physics Heightfield Collider"]
entity2_components_to_add = ["Shape Reference", "Gradient Transform Modifier", "FastNoise Gradient"]
terrain_spawner_entity = hydra.Entity("TerrainEntity")
terrain_spawner_entity.create_entity(azmath.Vector3(512.0, 512.0, 50.0), entity1_components_to_add)
Report.result(Tests.create_terrain_spawner_entity, terrain_spawner_entity.id.IsValid())
height_provider_entity = hydra.Entity("HeightProviderEntity")
height_provider_entity.create_entity(azmath.Vector3(0.0, 0.0, 0.0), entity2_components_to_add,terrain_spawner_entity.id)
Report.result(Tests.create_height_provider_entity, height_provider_entity.id.IsValid())
# Give everything a chance to finish initializing.
general.idle_wait_frames(1)
# 5) Set the base Terrain World values
world_bounds_max = azmath.Vector3(1100.0, 1100.0, 1100.0)
world_bounds_min = azmath.Vector3(10.0, 10.0, 10.0)
height_query_resolution = azmath.Vector2(1.0, 1.0)
hydra.set_component_property_value(terrain_world_component, "Configuration|World Bounds (Max)", world_bounds_max)
hydra.set_component_property_value(terrain_world_component, "Configuration|World Bounds (Min)", world_bounds_min)
hydra.set_component_property_value(terrain_world_component, "Configuration|Height Query Resolution (m)", height_query_resolution)
world_max = hydra.get_component_property_value(terrain_world_component, "Configuration|World Bounds (Max)")
world_min = hydra.get_component_property_value(terrain_world_component, "Configuration|World Bounds (Min)")
world_query = hydra.get_component_property_value(terrain_world_component, "Configuration|Height Query Resolution (m)")
Report.result(Tests.bounds_max_changed, world_max == world_bounds_max)
Report.result(Tests.bounds_min_changed, world_min == world_bounds_min)
Report.result(Tests.height_query_changed, world_query == height_query_resolution)
# 6) Change the Axis Aligned Box Shape dimensions
box_dimensions = azmath.Vector3(SET_BOX_X_SIZE, SET_BOX_Y_SIZE, SET_BOX_Z_SIZE)
terrain_spawner_entity.get_set_test(0, "Axis Aligned Box Shape|Box Configuration|Dimensions", box_dimensions)
box_shape_dimensions = hydra.get_component_property_value(terrain_spawner_entity.components[0], "Axis Aligned Box Shape|Box Configuration|Dimensions")
Report.result(Tests.box_dimensions_changed, box_dimensions == box_shape_dimensions)
# 7) Set the Shape Reference to terrain_spawner_entity
height_provider_entity.get_set_test(0, "Configuration|Shape Entity Id", terrain_spawner_entity.id)
entityId = hydra.get_component_property_value(height_provider_entity.components[0], "Configuration|Shape Entity Id")
Report.result(Tests.shape_changed, entityId == terrain_spawner_entity.id)
# 8) Set the FastNoise Gradient frequency to 0.01
frequency = 0.01
height_provider_entity.get_set_test(2, "Configuration|Frequency", frequency)
frequencyVal = hydra.get_component_property_value(height_provider_entity.components[2], "Configuration|Frequency")
Report.result(Tests.frequency_changed, math.isclose(frequency, frequencyVal, abs_tol = 0.00001))
# 9) Set the Gradient List to height_provider_entity
propertyTree = hydra.get_property_tree(terrain_spawner_entity.components[2])
propertyTree.add_container_item("Configuration|Gradient Entities", 0, height_provider_entity.id)
checkID = propertyTree.get_container_item("Configuration|Gradient Entities", 0)
Report.result(Tests.entity_added, checkID.GetValue() == height_provider_entity.id)
general.idle_wait_frames(1)
# 10) Disable and Enable the Terrain Gradient List so that it is recognised, EnableComponents performs both actions.
editor.EditorComponentAPIBus(bus.Broadcast, 'EnableComponents', [terrain_spawner_entity.components[2]])
# 11) Check terrain exists at a known position in the world
terrainExists = not terrain.TerrainDataRequestBus(bus.Broadcast, 'GetIsHoleFromFloats', 10.0, 10.0, CLAMP)
Report.result(Tests.terrain_exists, terrainExists)
terrainExists = not terrain.TerrainDataRequestBus(bus.Broadcast, 'GetIsHoleFromFloats', 1100.0, 1100.0, CLAMP)
Report.result(Tests.terrain_exists, terrainExists)
# 12) Check terrain does not exist at a known position outside the world
terrainDoesNotExist = terrain.TerrainDataRequestBus(bus.Broadcast, 'GetIsHoleFromFloats', 1101.0, 1101.0, CLAMP)
Report.result(Tests.terrain_does_not_exist, terrainDoesNotExist)
terrainDoesNotExist = terrain.TerrainDataRequestBus(bus.Broadcast, 'GetIsHoleFromFloats', 9.0, 9.0, CLAMP)
Report.result(Tests.terrain_does_not_exist, terrainDoesNotExist)
# 13) Check height value is the expected one when query resolution is changed
testpoint = terrain.TerrainDataRequestBus(bus.Broadcast, 'GetHeightFromFloats', 10.5, 10.5, CLAMP)
height_query_resolution = azmath.Vector2(0.5, 0.5)
hydra.set_component_property_value(terrain_world_component, "Configuration|Height Query Resolution (m)", height_query_resolution)
general.idle_wait_frames(1)
testpoint2 = terrain.TerrainDataRequestBus(bus.Broadcast, 'GetHeightFromFloats', 10.5, 10.5, CLAMP)
Report.result(Tests.values_not_the_same, not math.isclose(testpoint, testpoint2, abs_tol = 0.000000001))
helper.wait_for_condition(lambda: section_tracer.has_errors or section_tracer.has_asserts, 1.0)
for error_info in section_tracer.errors:
Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}")
for assert_info in section_tracer.asserts:
Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}")
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(Terrain_World_ConfigurationWorks)

@ -27,6 +27,15 @@ class TestAutomation(EditorTestSuite):
class test_Terrain_SupportsPhysics(EditorSharedTest):
from .EditorScripts import Terrain_SupportsPhysics as test_module
class test_TerrainHeightGradientList_AddRemoveGradientWorks(EditorSharedTest):
from .EditorScripts import TerrainHeightGradientList_AddRemoveGradientWorks as test_module
class test_TerrainSystem_VegetationSpawnsOnTerrainSurfaces(EditorSharedTest):
from .EditorScripts import TerrainSystem_VegetationSpawnsOnTerrainSurfaces as test_module
class test_TerrainMacroMaterialComponent_MacroMaterialActivates(EditorSharedTest):
from .EditorScripts import TerrainMacroMaterialComponent_MacroMaterialActivates as test_module
class test_TerrainWorld_ConfigurationWorks(EditorSharedTest):
from .EditorScripts import Terrain_World_ConfigurationWorks as test_module

@ -0,0 +1,4 @@
/.maya_data/*
/.mayaSwatches/*
*.swatch
[Uu]ser_env.bat

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:57848334af0220b7348a8f2583080acf1d9c139a78c9fb1a93a7d2bce61f3c40
size 41335413

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9b936b72b5b45b52c188bf9f930d6066af65f0d79ff8abf5dc08927d97ac465e
size 55264

@ -0,0 +1,35 @@
{
"description": "",
"materialType": "Materials/Types/StandardPBR.materialtype",
"parentMaterial": "",
"propertyLayoutVersion": 3,
"properties": {
"baseColor": {
"color": [
0.0,
0.0,
0.0,
1.0
]
},
"emissive": {
"color": [
0.0,
0.0,
0.0,
1.0
]
},
"irradiance": {
"color": [
0.0,
0.0,
0.0,
1.0
]
},
"opacity": {
"factor": 1.0
}
}
}

@ -0,0 +1,35 @@
{
"description": "",
"materialType": "Materials/Types/StandardPBR.materialtype",
"parentMaterial": "",
"propertyLayoutVersion": 3,
"properties": {
"baseColor": {
"color": [
0.0,
1.0,
0.0,
1.0
]
},
"emissive": {
"color": [
0.0,
0.0,
0.0,
1.0
]
},
"irradiance": {
"color": [
0.0,
1.0,
0.0,
1.0
]
},
"opacity": {
"factor": 1.0
}
}
}

@ -0,0 +1,49 @@
{
"description": "",
"materialType": "Materials/Types/StandardPBR.materialtype",
"parentMaterial": "",
"propertyLayoutVersion": 3,
"properties": {
"baseColor": {
"color": [
0.800000011920929,
0.800000011920929,
0.800000011920929,
1.0
],
"textureMap": "Textures/arch_1k_basecolor.png"
},
"general": {
"applySpecularAA": true
},
"irradiance": {
"color": [
1.0,
0.885053813457489,
0.801281750202179,
1.0
]
},
"metallic": {
"textureMap": "Textures/arch_1k_metallic.png"
},
"normal": {
"textureMap": "Textures/arch_1k_normal.jpg"
},
"occlusion": {
"diffuseTextureMap": "Textures/arch_1k_ao.png"
},
"opacity": {
"factor": 1.0
},
"parallax": {
"factor": 0.050999999046325687,
"pdo": true,
"quality": "High",
"useTexture": false
},
"roughness": {
"textureMap": "Textures/arch_1k_roughness.png"
}
}
}

@ -0,0 +1,54 @@
{
"description": "",
"materialType": "Materials/Types/StandardPBR.materialtype",
"parentMaterial": "",
"propertyLayoutVersion": 3,
"properties": {
"baseColor": {
"color": [
0.800000011920929,
0.800000011920929,
0.800000011920929,
1.0
],
"textureMap": "Textures/bricks_1k_basecolor.png"
},
"clearCoat": {
"factor": 0.5,
"normalMap": "Textures/bricks_1k_normal.jpg",
"roughness": 0.5
},
"general": {
"applySpecularAA": true
},
"irradiance": {
"color": [
1.0,
0.9703211784362793,
0.9703211784362793,
1.0
]
},
"metallic": {
"textureMap": "Textures/bricks_1k_metallic.png"
},
"normal": {
"textureMap": "Textures/bricks_1k_normal.jpg"
},
"occlusion": {
"diffuseTextureMap": "Textures/bricks_1k_ao.png"
},
"opacity": {
"factor": 1.0
},
"parallax": {
"algorithm": "ContactRefinement",
"factor": 0.03500000014901161,
"quality": "Medium",
"useTexture": false
},
"roughness": {
"textureMap": "Textures/bricks_1k_roughness.png"
}
}
}

@ -0,0 +1,51 @@
{
"description": "",
"materialType": "Materials/Types/StandardPBR.materialtype",
"parentMaterial": "",
"propertyLayoutVersion": 3,
"properties": {
"baseColor": {
"color": [
0.800000011920929,
0.800000011920929,
0.800000011920929,
1.0
],
"textureMap": "Textures/floor_1k_basecolor.png"
},
"clearCoat": {
"enable": true,
"influenceMap": "Textures/floor_1k_ao.png",
"normalMap": "Textures/floor_1k_normal.png",
"roughness": 0.25
},
"general": {
"applySpecularAA": true
},
"irradiance": {
"color": [
1.0,
0.9404135346412659,
0.8688944578170776,
1.0
]
},
"normal": {
"textureMap": "Textures/floor_1k_normal.png"
},
"occlusion": {
"diffuseTextureMap": "Textures/floor_1k_ao.png"
},
"opacity": {
"factor": 1.0
},
"parallax": {
"factor": 0.012000000104308129,
"pdo": true,
"useTexture": false
},
"roughness": {
"textureMap": "Textures/floor_1k_roughness.png"
}
}
}

@ -0,0 +1,44 @@
{
"description": "",
"materialType": "Materials/Types/StandardPBR.materialtype",
"parentMaterial": "",
"propertyLayoutVersion": 3,
"properties": {
"baseColor": {
"color": [
0.800000011920929,
0.800000011920929,
0.800000011920929,
1.0
],
"textureBlendMode": "Lerp",
"textureMap": "Textures/roof_1k_basecolor.png"
},
"general": {
"applySpecularAA": true
},
"metallic": {
"useTexture": false
},
"normal": {
"factor": 0.5,
"flipY": true,
"textureMap": "Textures/roof_1k_normal.jpg"
},
"occlusion": {
"diffuseTextureMap": "Textures/roof_1k_ao.png"
},
"opacity": {
"factor": 1.0
},
"parallax": {
"algorithm": "ContactRefinement",
"factor": 0.019999999552965165,
"quality": "Medium",
"useTexture": false
},
"roughness": {
"textureMap": "Textures/roof_1k_roughness.png"
}
}
}

@ -0,0 +1,35 @@
{
"description": "",
"materialType": "Materials/Types/StandardPBR.materialtype",
"parentMaterial": "",
"propertyLayoutVersion": 3,
"properties": {
"baseColor": {
"color": [
0.0,
0.0,
1.0,
1.0
]
},
"emissive": {
"color": [
0.0,
0.0,
0.0,
1.0
]
},
"irradiance": {
"color": [
0.0,
0.0,
1.0,
1.0
]
},
"opacity": {
"factor": 1.0
}
}
}

@ -0,0 +1,35 @@
{
"description": "",
"materialType": "Materials/Types/StandardPBR.materialtype",
"parentMaterial": "",
"propertyLayoutVersion": 3,
"properties": {
"baseColor": {
"color": [
0.800000011920929,
0.0,
0.0,
1.0
]
},
"emissive": {
"color": [
0.0,
0.0,
0.0,
1.0
]
},
"irradiance": {
"color": [
1.0,
0.0,
0.0,
1.0
]
},
"opacity": {
"factor": 1.0
}
}
}

@ -0,0 +1,19 @@
{
"description": "",
"materialType": "Materials/Types/StandardPBR.materialtype",
"parentMaterial": "",
"propertyLayoutVersion": 3,
"properties": {
"emissive": {
"color": [
0.0,
0.0,
0.0,
1.0
]
},
"opacity": {
"factor": 1.0
}
}
}

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d9c14dc81887be7647d9afa9e5481634eded0b1d0285b59bb76bb2a91326ca8a
size 3369

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9f21e251c50a657956ba8b401f102b8a3237a0c9c78eab0427332eb3ad8f5952
size 281259

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1b37ec99d6f8d26b8313315d801b9c378456ed9ec31cc2e0566c17ea3287b4fa
size 1643629

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:57657925d24ceb83881e614b3056b82dfc7197a3eebbb707a47f6721249d47c3
size 108869

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:fc5a747fef8c4d830b72e250645925f41125ea75c18b6fb094cafe7b19850ca5
size 70950

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8438ee2f06842f54e493fe1eda34b6ca39e6db19d2e0f21342f0f4e52fc3f1e3
size 689118

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0e59f003d7c8d26e7ec8460acec442f31cf7a06eb76008989c1ae6fccd8e2f15
size 537441

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b1c69c97ffc402d63130b680af75128b431504c4dedcbd15a5fbb41c142317fe
size 1305835

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6650f449128dee04f513961b9d65d81105f2bd4fa580f4dbd9d6fb7b0af932bc
size 275302

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f482b7b57d180c6df76b750d05547151731fc6c61d2b9674f3b9daa5ab0ac225
size 440844

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:955f60ebbe10948e9d47cb90e0e0359c4ee26bd8e76d95292a673a2adb68f410
size 82102

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d5d0f40ab7b5153a44b87ee0c83c65ddc69cf845d16c8c9c3c2c043f85c1f04f
size 542782

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e3d169b73a2852e404eaa113e91d32b66e0d45a1b55598cc58f64e16c9565bf9
size 729137

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f34b38f6e205b6bffec72ddfd1305a66d0146d61bcc2ef901fd146a0ec2708de
size 1934899

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9745b60c53a4b9fa33a6a3713b9b06ec7679883524533de44628cfe0d26ae038
size 377230

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:03eba2d92ed70e04d5278152e945cf3a170750b71a04fd0f835fd6030b731720
size 166767

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e35858a269d42405702102e7f7972a6330c3eaacfe637148c676e14d5c901c29
size 784675

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:22b80bfc1226fb587b230c51e9dac7fce6e295a4f0674326e96dc1232f40548b
size 901463

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b6743770a3428721a42a25a331fdb1e9895f71848c8215377e690b7cf9b7397f
size 1756093

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:756105faa27e722d6cc5915f33f8bcf9fd7afc022c41276a8c0e3b1f122303a3
size 501917

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:08fa78ce8e7840189d22442ed46863b4eaf5a4a99c0655690a2c47b1b0ebd9ce
size 1399902

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4c24b269cd413b86da8cdd44bb639f3b95cfd1a8c53a3a941cb5a22a1042f85d
size 689214

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:30a1308326a5b0288147d0fb92230ebd1cef107180e38f7d5795bdc13a62e368
size 3389

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:51c661aa2ee3fb4b4735005a71e734eb25e433ef73ded5131b110e4df2e94acf
size 384316

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4619ec32e2de143db42639e0a4e0dfb174eb85057eb20f9e0d0af59e40977d9a
size 11229

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a505d441c79f3ab674fd5a6b879a430970400fb9f56b379dde00900da296a47e
size 599094

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:552608851205bde18e2bc2f7cbf475ccefbb182c7bfcb9430fa931da22363c80
size 1836310

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:79f1c3430a193ee78c72b84a461612ffd79aa23374949af13a200ec9a301ba2d
size 274678

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d280d5676ccfd6e4b6efdc53e828cc6823f44339a87ec9cee8a79d989ad46175
size 127305

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5c4fb7c8012891c7af4ff371c8e6a8ef85ef1160a5b93012b964158ab48b8e4a
size 760602

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a9fe904a58b248937adde2b48ba320da5139700d03e3183fbadfaaa0ac8bb6f5
size 651508

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:37d461594dca7eb3da8f6f135511d38efc7c1f333afb98d286fb36a0034f3100
size 2158620

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8e95e08eed20fb4d44a6461f38f27f21fd3ae3f024550afed95ecf3bc30929b1
size 297329

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1b00b684b53854ec24e86441e59220e6c49948429bd6fb7edab7058a11e8f92e
size 139998

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5a6d428bf17ecee32d47c3feab27567a882b97dfa733dcf6ca8bf1b0bd0a025a
size 908698

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ab2c93badd7b72f45990c3859d6ddc7f2436a4aa5e93c0de65a139ea99120a97
size 628789

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:97ec8bd597b0805a8a36bcd90ccdba2ae9430ed3a8631b0f7f87796fb2ceefc5
size 2128105

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f8bbbc46f0f1ccfefec202b1e26ecbd418d3283037746bd63a892d1a930fb36d
size 282637

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:44db84371fb81e1712b5f6aa9777fcfe76728dc25a51f0423e07c294b062c724
size 137528

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c07d4a1af570a6b3817e97c92daa17f2593ea2b88e0e167ed43f7869b77971ab
size 881064

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4310237ce83738635b3880ccd896f78ea2c70bebadd620cb6ffaaccc78ba7cbf
size 9231896

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e093cf9538bb40615d32bec6fa9c60d4308bc017c5ab02a7c388310159bc87fe
size 8342440

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:465efab4da8d2b6698c83844d2fbe662e38a92f278aa27d356bbc1bca1e23f8b
size 8879938

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b4faebdc849479a6b5d10db4cac8b6e9823bf447aeb4b45945f6acd43333def8
size 4557863

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:064fbdba14e1ca04e438f4ec85d3f9a0fe3203620ee2a014113ec99454fbee65
size 2709058

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2c8159b1ad888143ec3cbf5e2535f87abbe238a1474619696355d11bc4e4bb49
size 812014

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0da92f21f46502a7c58c1a7ce7280354b4b41e553a5bf181d9962cacf8ed2191
size 2327816

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2a701afeb01e19efdff72393370c86ee148f6d81c1551bcbd211937df503be9b
size 3694284

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:368262bb832ab61b4649aadffe4158b64d46b70985e99106300fa0c7a7989c35
size 666377

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2021b64706bc328959d521f056dcb9b9acf3062a5f9ad35da9e93b2351dcab97
size 1371185

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:fdbb251cf483ffccc6cb1b344eba1b3262fa8087c37a36aa843f0c0e9d222f8f
size 403018

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8b67d6bea4c9fbc8412944b261a0c0f2dc1dc2e4103a6742bfb57d3c2d36429c
size 499028

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save