Merge branch 'development' into Atom/santorac/ShaderVariantListSizeLimit
This commit is contained in:
+1
-8
@@ -2,18 +2,14 @@
|
||||
.vs/
|
||||
.vscode/
|
||||
__pycache__
|
||||
AssetProcessorTemp/**
|
||||
[Bb]uild/
|
||||
[Oo]ut/**
|
||||
CMakeUserPresets.json
|
||||
[Cc]ache/
|
||||
/[Ii]nstall/
|
||||
Editor/EditorEventLog.xml
|
||||
Editor/EditorLayout.xml
|
||||
**/*egg-info/**
|
||||
**/*egg-link
|
||||
**/[Rr]estricted
|
||||
UserSettings.xml
|
||||
[Uu]ser/
|
||||
FrameCapture/**
|
||||
.DS_Store
|
||||
@@ -22,9 +18,6 @@ client*.cfg
|
||||
server*.cfg
|
||||
.mayaSwatches/
|
||||
_savebackup/
|
||||
#Output folder for test results when running Automated Tests
|
||||
TestResults/**
|
||||
*.swatches
|
||||
/imgui.ini
|
||||
/scripts/project_manager/logs/
|
||||
/AutomatedTesting/Gem/PythonTests/scripting/TestResults
|
||||
|
||||
|
||||
@@ -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:4c1d04d687bdce69965965c290c907b3f9a730a7ea73874b734e1c7ff41426d9
|
||||
size 3832768
|
||||
@@ -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,66 @@
|
||||
#
|
||||
# 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
|
||||
|
||||
import scene_export_utils
|
||||
import scene_api.motion_group
|
||||
|
||||
#
|
||||
# Example for exporting MotionGroup scene rules
|
||||
#
|
||||
|
||||
def update_manifest(scene):
|
||||
import azlmbr.scene.graph
|
||||
import scene_api.scene_data
|
||||
|
||||
# create a SceneManifest
|
||||
sceneManifest = scene_api.scene_data.SceneManifest()
|
||||
|
||||
# create a MotionGroup
|
||||
motionGroup = scene_api.motion_group.MotionGroup()
|
||||
motionGroup.name = os.path.basename(scene.sourceFilename.replace('.', '_'))
|
||||
|
||||
motionAdditiveRule = scene_api.motion_group.MotionAdditiveRule()
|
||||
motionAdditiveRule.sampleFrame = 2
|
||||
motionGroup.add_rule(motionAdditiveRule)
|
||||
|
||||
motionScaleRule = motionGroup.create_rule(scene_api.motion_group.MotionScaleRule())
|
||||
motionScaleRule.scaleFactor = 1.1
|
||||
motionGroup.add_rule(motionScaleRule)
|
||||
|
||||
# add motion group to scene manifest
|
||||
sceneManifest.add_motion_group(motionGroup)
|
||||
|
||||
# Convert the manifest to a JSON string and return it
|
||||
return sceneManifest.export()
|
||||
|
||||
sceneJobHandler = None
|
||||
|
||||
def on_update_manifest(args):
|
||||
try:
|
||||
scene = args[0]
|
||||
return update_manifest(scene)
|
||||
except RuntimeError as err:
|
||||
print (f'ERROR - {err}')
|
||||
scene_export_utils.log_exception_traceback()
|
||||
except:
|
||||
scene_export_utils.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,8 @@
|
||||
{
|
||||
"values": [
|
||||
{
|
||||
"$type": "ScriptProcessorRule",
|
||||
"scriptFilename": "Assets/TestAnim/scene_export_motion.py"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ac841c02e81c9ae23476522b7e517889d746d4ff32b3251ac4360168f39b30a3
|
||||
size 450708
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3653ca7fb42c7e5991815c17fc9ebeab14a1aa861bfb1d37e233ada66a835f00
|
||||
size 7188
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a30b85428202c548e77cdcc0a595f9f26e82d29622be26891d569f4779a75830
|
||||
size 1802388
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a3d76b7c93f8873ca7c4013dcc4eff887c25552c9c5847290481306656b22069
|
||||
size 3668
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:219f57137c5bd44093762ea9d0fc24308679956b980f26bf194edd3a1798fcda
|
||||
size 3668
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:96ed61c66d1d1f71e940ab75899ee253007e704004e1157c900c6308151a8bf1
|
||||
size 1802388
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:70a7dc4e2455624067e842b059954f6c4bb9b0debc3cb1ffa783b14bffc61786
|
||||
size 7188
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7f5a945c61f92f3da77dfd9fdd2c95aa257f4df6bcb82483c608ab660bb72e5f
|
||||
size 450708
|
||||
@@ -0,0 +1,63 @@
|
||||
#
|
||||
# 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
|
||||
|
||||
#
|
||||
# Utility methods for processing scenes
|
||||
#
|
||||
|
||||
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
|
||||
@@ -1,10 +1,10 @@
|
||||
"""
|
||||
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
|
||||
"""
|
||||
|
||||
#
|
||||
# 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, logging, json
|
||||
from typing import Tuple, List
|
||||
|
||||
@@ -14,30 +14,44 @@ from scene_api.scene_data import SceneGraphName
|
||||
|
||||
|
||||
def log_exception_traceback():
|
||||
"""
|
||||
Outputs an exception stacktrace.
|
||||
"""
|
||||
"""Outputs an exception stacktrace."""
|
||||
data = traceback.format_exc()
|
||||
logger = logging.getLogger('python')
|
||||
logger.error(data)
|
||||
|
||||
|
||||
def sanitize_name_for_disk(name: str):
|
||||
"""
|
||||
Removes illegal filename characters from a string.
|
||||
def sanitize_name_for_disk(name: str) -> str:
|
||||
"""Removes illegal filename characters from a string.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name :
|
||||
String to clean.
|
||||
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
Name with illegal characters removed.
|
||||
|
||||
:param name: String to clean.
|
||||
:return: Name with illegal characters removed.
|
||||
"""
|
||||
return "".join(char for char in name if char not in "|<>:\"/?*\\")
|
||||
|
||||
|
||||
def get_mesh_node_names(scene_graph: sceneData.SceneGraph) -> Tuple[List[SceneGraphName], List[str]]:
|
||||
"""
|
||||
Returns a tuple of all the mesh nodes as well as all the node paths
|
||||
"""Returns a tuple of all the mesh nodes as well as all the node paths
|
||||
|
||||
Parameters
|
||||
----------
|
||||
scene_graph :
|
||||
Scene graph to search
|
||||
|
||||
|
||||
Returns
|
||||
-------
|
||||
Tuple[List[SceneGraphName], List[str]]
|
||||
Tuple of [Mesh Nodes, All Node Paths]
|
||||
|
||||
:param scene_graph: Scene graph to search
|
||||
:return: Tuple of [Mesh Nodes, All Node Paths]
|
||||
"""
|
||||
import azlmbr.scene as sceneApi
|
||||
import azlmbr.scene.graph
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import azlmbr.bus
|
||||
import azlmbr.math
|
||||
|
||||
from scene_api.scene_data import PrimitiveShape, DecompositionMode, ColorChannel, TangentSpaceSource, TangentSpaceMethod
|
||||
from scene_helpers import *
|
||||
|
||||
|
||||
@@ -41,6 +42,36 @@ def add_material_component(entity_id):
|
||||
raise RuntimeError("UpdateComponentForEntity for editor_material_component failed")
|
||||
|
||||
|
||||
def add_physx_meshes(scene_manifest: sceneData.SceneManifest, source_file_name: str, mesh_name_list: List, all_node_paths: List[str]):
|
||||
first_mesh = mesh_name_list[0].get_path()
|
||||
|
||||
# Add a Box Primitive PhysX mesh with a comment
|
||||
physx_box = scene_manifest.add_physx_primitive_mesh_group(source_file_name + "_box", PrimitiveShape.BOX, 0.0, None)
|
||||
scene_manifest.physx_mesh_group_add_comment(physx_box, "This is a box primitive")
|
||||
# Select the first mesh, unselect every other node
|
||||
scene_manifest.physx_mesh_group_add_selected_node(physx_box, first_mesh)
|
||||
|
||||
for node in all_node_paths:
|
||||
if node != first_mesh:
|
||||
scene_manifest.physx_mesh_group_add_unselected_node(physx_box, node)
|
||||
|
||||
# Add a Convex Mesh PhysX mesh with a comment
|
||||
convex_mesh = scene_manifest.add_physx_convex_mesh_group(source_file_name + "_convex", 0.08, .0004,
|
||||
True, True, True, True, True, 24, True, "Glass")
|
||||
scene_manifest.physx_mesh_group_add_comment(convex_mesh, "This is a convex mesh")
|
||||
# Select/Unselect nodes using lists
|
||||
all_except_first_mesh = [x for x in all_node_paths if x != first_mesh]
|
||||
scene_manifest.physx_mesh_group_add_selected_unselected_nodes(convex_mesh, [first_mesh], all_except_first_mesh)
|
||||
|
||||
# Configure mesh decomposition for this mesh
|
||||
scene_manifest.physx_mesh_group_decompose_meshes(convex_mesh, 512, 32, .002, 100100, DecompositionMode.TETRAHEDRON,
|
||||
0.06, 0.055, 0.00015, 3, 3, True, False)
|
||||
|
||||
# Add a Triangle mesh
|
||||
triangle = scene_manifest.add_physx_triangle_mesh_group(source_file_name + "_triangle", False, True, True, True, True, True)
|
||||
scene_manifest.physx_mesh_group_add_selected_unselected_nodes(triangle, [first_mesh], all_except_first_mesh)
|
||||
|
||||
|
||||
def update_manifest(scene):
|
||||
import uuid, os
|
||||
import azlmbr.scene.graph
|
||||
@@ -62,6 +93,8 @@ def update_manifest(scene):
|
||||
previous_entity_id = azlmbr.entity.InvalidEntityId
|
||||
first_mesh = True
|
||||
|
||||
add_physx_meshes(scene_manifest, source_filename_only, mesh_name_list, all_node_paths)
|
||||
|
||||
# Loop every mesh node in the scene
|
||||
for activeMeshIndex in range(len(mesh_name_list)):
|
||||
mesh_name = mesh_name_list[activeMeshIndex]
|
||||
@@ -82,10 +115,11 @@ def update_manifest(scene):
|
||||
if node != mesh_path:
|
||||
scene_manifest.mesh_group_unselect_node(mesh_group, node)
|
||||
|
||||
scene_manifest.mesh_group_add_cloth_rule(mesh_group, mesh_path, "Col0", 1, "Col0", 2, "Col0", 2, 3)
|
||||
scene_manifest.mesh_group_add_cloth_rule(mesh_group, mesh_path, "Col0", ColorChannel.GREEN, "Col0",
|
||||
ColorChannel.BLUE, "Col0", ColorChannel.BLUE, ColorChannel.ALPHA)
|
||||
scene_manifest.mesh_group_add_advanced_mesh_rule(mesh_group, True, False, True, "Col0")
|
||||
scene_manifest.mesh_group_add_skin_rule(mesh_group, 3, 0.002)
|
||||
scene_manifest.mesh_group_add_tangent_rule(mesh_group, 1, 0)
|
||||
scene_manifest.mesh_group_add_tangent_rule(mesh_group, TangentSpaceSource.MIKKT_GENERATION, TangentSpaceMethod.TSPACE_BASIC)
|
||||
|
||||
# Create an editor entity
|
||||
entity_id = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "CreateEditorReadyEntity", mesh_group_name)
|
||||
@@ -106,6 +140,26 @@ def update_manifest(scene):
|
||||
if not result:
|
||||
raise RuntimeError("UpdateComponentForEntity failed for Mesh component")
|
||||
|
||||
# Add a physics component referencing the triangle mesh we made for the first node
|
||||
if previous_entity_id is None:
|
||||
physx_mesh_component = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "GetOrAddComponentByTypeName",
|
||||
entity_id, "{FD429282-A075-4966-857F-D0BBF186CFE6} EditorColliderComponent")
|
||||
|
||||
json_update = json.dumps({
|
||||
"ShapeConfiguration": {
|
||||
"PhysicsAsset": {
|
||||
"Asset": {
|
||||
"assetHint": os.path.join(source_relative_path, source_filename_only + "_triangle.pxmesh")
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
result = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "UpdateComponentForEntity", entity_id, physx_mesh_component, json_update)
|
||||
|
||||
if not result:
|
||||
raise RuntimeError("UpdateComponentForEntity failed for PhysX mesh component")
|
||||
|
||||
# an example of adding a material component to override the default material
|
||||
if previous_entity_id is not None and first_mesh:
|
||||
first_mesh = False
|
||||
|
||||
+5
-5
@@ -19,8 +19,8 @@
|
||||
|
||||
<RemoteProcedure Name="AutonomousToAuthorityNoParams" InvokeFrom="Autonomous" HandleOn="Authority" IsPublic="false" IsReliable="false" GenerateEventBindings="true" Description="" />
|
||||
|
||||
<RemoteProcedure Name="AuthorityToAutonomous" InvokeFrom="Authority" HandleOn="Autonomous" IsPublic="false" IsReliable="false" GenerateEventBindings="true" Description="" >
|
||||
<Param Type="float" Name="SomeFloat" />
|
||||
<RemoteProcedure Name="AuthorityToAutonomous_PlayerNumber" InvokeFrom="Authority" HandleOn="Autonomous" IsPublic="false" IsReliable="true" GenerateEventBindings="true" Description="" >
|
||||
<Param Type="int" Name="player_number" />
|
||||
</RemoteProcedure>
|
||||
|
||||
<RemoteProcedure Name="AuthorityToAutonomousNoParams" InvokeFrom="Authority" HandleOn="Autonomous" IsPublic="false" IsReliable="false" GenerateEventBindings="true" Description="" />
|
||||
@@ -29,10 +29,10 @@
|
||||
<Param Type="float" Name="SomeFloat" />
|
||||
</RemoteProcedure>
|
||||
|
||||
<RemoteProcedure Name="AuthorityToClientNoParams" InvokeFrom="Authority" HandleOn="Client" IsPublic="false" IsReliable="false" GenerateEventBindings="true" Description="" />
|
||||
<RemoteProcedure Name="AuthorityToClientNoParams_PlayFx" InvokeFrom="Authority" HandleOn="Client" IsPublic="false" IsReliable="true" GenerateEventBindings="true" Description="" />
|
||||
|
||||
<RemoteProcedure Name="ServerToAuthority" InvokeFrom="Server" HandleOn="Authority" IsPublic="false" IsReliable="false" GenerateEventBindings="true" Description="" >
|
||||
<Param Type="float" Name="SomeFloat" />
|
||||
<RemoteProcedure Name="ServerToAuthority_DealDamage" InvokeFrom="Server" HandleOn="Authority" IsPublic="false" IsReliable="true" GenerateEventBindings="true" Description="" >
|
||||
<Param Type="float" Name="damage" />
|
||||
</RemoteProcedure>
|
||||
|
||||
<RemoteProcedure Name="ServerToAuthorityNoParam" InvokeFrom="Server" HandleOn="Authority" IsPublic="false" IsReliable="false" GenerateEventBindings="true" Description="" />
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
|
||||
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS)
|
||||
# Only enable AWS automated tests on Windows
|
||||
if(NOT "${PAL_PLATFORM_NAME}" STREQUAL "Windows")
|
||||
set(SUPPORTED_PLATFORMS "Windows" "Linux")
|
||||
if (NOT "${PAL_PLATFORM_NAME}" IN_LIST SUPPORTED_PLATFORMS)
|
||||
return()
|
||||
endif()
|
||||
|
||||
@@ -21,9 +22,8 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS)
|
||||
NAME AutomatedTesting::AWSTests
|
||||
TEST_SUITE awsi
|
||||
TEST_SERIAL
|
||||
PATH ${CMAKE_CURRENT_LIST_DIR}/${PAL_PLATFORM_NAME}/
|
||||
PATH ${CMAKE_CURRENT_LIST_DIR}/
|
||||
RUNTIME_DEPENDENCIES
|
||||
Legacy::Editor
|
||||
AZ::AssetProcessor
|
||||
AutomatedTesting.GameLauncher
|
||||
AutomatedTesting.Assets
|
||||
|
||||
@@ -2,30 +2,61 @@
|
||||
|
||||
## Prerequisites
|
||||
1. Build the O3DE Editor and AutomatedTesting.GameLauncher in Profile.
|
||||
2. AWS CLI is installed and configured following [Configuration and Credential File Settings](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html).
|
||||
3. [AWS Cloud Development Kit (CDK)](https://docs.aws.amazon.com/cdk/latest/guide/getting_started.html#getting_started_install) is installed.
|
||||
2. Install the latest version of NodeJs.
|
||||
3. AWS CLI is installed and configured following [Configuration and Credential File Settings](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html).
|
||||
4. [AWS Cloud Development Kit (CDK)](https://docs.aws.amazon.com/cdk/latest/guide/getting_started.html#getting_started_install) is installed.
|
||||
|
||||
## Deploy CDK Applications
|
||||
1. Go to the AWS IAM console and create an IAM role called o3de-automation-tests which adds your own account as as a trusted entity and uses the "AdministratorAccess" permissions policy.
|
||||
2. Copy {engine_root}\scripts\build\Platform\Windows\deploy_cdk_applications.cmd to your engine root folder.
|
||||
3. Open a new Command Prompt window at the engine root and set the following environment variables:
|
||||
```
|
||||
Set O3DE_AWS_PROJECT_NAME=AWSAUTO
|
||||
Set O3DE_AWS_DEPLOY_REGION=us-east-1
|
||||
Set O3DE_AWS_DEPLOY_ACCOUNT={your_aws_account_id}
|
||||
Set ASSUME_ROLE_ARN=arn:aws:iam::{your_aws_account_id}:role/o3de-automation-tests
|
||||
Set COMMIT_ID=HEAD
|
||||
```
|
||||
4. In the same Command Prompt window, Deploy the CDK applications for AWS gems by running deploy_cdk_applications.cmd.
|
||||
2. Copy the following deployment script to your engine root folder:
|
||||
* Windows (Command Prompt)
|
||||
```
|
||||
{engine_root}\scripts\build\Platform\Windows\deploy_cdk_applications.cmd
|
||||
```
|
||||
* Linux
|
||||
```
|
||||
{engine_root}/scripts/build/Platform/Linux/deploy_cdk_applications.sh
|
||||
```
|
||||
3. Open a new CLI window at the engine root and set the following environment variables:
|
||||
* Windows
|
||||
```
|
||||
Set O3DE_AWS_PROJECT_NAME=AWSAUTO
|
||||
Set O3DE_AWS_DEPLOY_REGION=us-east-1
|
||||
Set ASSUME_ROLE_ARN=arn:aws:iam::{your_aws_account_id}:role/o3de-automation-tests
|
||||
Set COMMIT_ID=HEAD
|
||||
```
|
||||
* Linux
|
||||
```
|
||||
export O3DE_AWS_PROJECT_NAME=AWSAUTO
|
||||
export O3DE_AWS_DEPLOY_REGION=us-east-1
|
||||
export ASSUME_ROLE_ARN=arn:aws:iam::{your_aws_account_id}:role/o3de-automation-tests
|
||||
export COMMIT_ID=HEAD
|
||||
```
|
||||
4. In the same CLI window, Deploy the CDK applications for AWS gems by running deploy_cdk_applications.cmd.
|
||||
|
||||
## Run Automation Tests
|
||||
### CLI
|
||||
In the same Command Prompt window, run the following CLI command:
|
||||
python\python.cmd -m pytest {path_to_the_test_file} --build-directory {directory_to_the_profile_build}
|
||||
1. In the same CLI window, run the following CLI command:
|
||||
* Windows
|
||||
```
|
||||
python\python.cmd -m pytest {path_to_the_test_file} --build-directory {directory_to_the_profile_build}
|
||||
```
|
||||
* Linux
|
||||
```
|
||||
python/python.sh -m pytest {path_to_the_test_file} --build-directory {directory_to_the_profile_build}
|
||||
```
|
||||
|
||||
### Pycharm
|
||||
You can also run any specific automation test directly from Pycharm by providing the "--build-directory" argument in the Run Configuration.
|
||||
|
||||
## Destroy CDK Applications
|
||||
1. Copy {engine_root}\scripts\build\Platform\Windows\destroy_cdk_applications.cmd to your engine root folder.
|
||||
2. In the same Command Prompt window, destroy the CDK applications for AWS gems by running destroy_cdk_applications.cmd.
|
||||
1. Copy the following destruction script to your engine root folder:
|
||||
* Windows
|
||||
```
|
||||
{engine_root}\scripts\build\Platform\Windows\destroy_cdk_applications.cmd
|
||||
```
|
||||
* Linux
|
||||
```
|
||||
{engine_root}/scripts/build/Platform/Linux/destroy_cdk_applications.sh
|
||||
```
|
||||
2. In the same CLI window, destroy the CDK applications for AWS gems by running destroy_cdk_applications.cmd.
|
||||
|
||||
+289
-289
@@ -1,289 +1,289 @@
|
||||
"""
|
||||
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 logging
|
||||
import os
|
||||
import pytest
|
||||
import typing
|
||||
from datetime import datetime
|
||||
|
||||
import ly_test_tools.log.log_monitor
|
||||
|
||||
from AWS.common import constants
|
||||
from AWS.common.resource_mappings import AWS_RESOURCE_MAPPINGS_ACCOUNT_ID_KEY
|
||||
from .aws_metrics_custom_thread import AWSMetricsThread
|
||||
|
||||
# fixture imports
|
||||
from assetpipeline.ap_fixtures.asset_processor_fixture import asset_processor
|
||||
from .aws_metrics_utils import aws_metrics_utils
|
||||
|
||||
AWS_METRICS_FEATURE_NAME = 'AWSMetrics'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup(launcher: pytest.fixture,
|
||||
asset_processor: pytest.fixture) -> pytest.fixture:
|
||||
"""
|
||||
Set up the resource mapping configuration and start the log monitor.
|
||||
:param launcher: Client launcher for running the test level.
|
||||
:param asset_processor: asset_processor fixture.
|
||||
:return log monitor object.
|
||||
"""
|
||||
asset_processor.start()
|
||||
asset_processor.wait_for_idle()
|
||||
|
||||
file_to_monitor = os.path.join(launcher.workspace.paths.project_log(), constants.GAME_LOG_NAME)
|
||||
|
||||
# Initialize the log monitor.
|
||||
log_monitor = ly_test_tools.log.log_monitor.LogMonitor(launcher=launcher, log_file_path=file_to_monitor)
|
||||
|
||||
return log_monitor
|
||||
|
||||
|
||||
def monitor_metrics_submission(log_monitor: pytest.fixture) -> None:
|
||||
"""
|
||||
Monitor the messages and notifications for submitting metrics.
|
||||
:param log_monitor: Log monitor to check the log messages.
|
||||
"""
|
||||
expected_lines = [
|
||||
'(Script) - Submitted metrics without buffer.',
|
||||
'(Script) - Submitted metrics with buffer.',
|
||||
'(Script) - Flushed the buffered metrics.',
|
||||
'(Script) - Metrics is sent successfully.'
|
||||
]
|
||||
|
||||
unexpected_lines = [
|
||||
'(Script) - Failed to submit metrics without buffer.',
|
||||
'(Script) - Failed to submit metrics with buffer.',
|
||||
'(Script) - Failed to send metrics.'
|
||||
]
|
||||
|
||||
result = log_monitor.monitor_log_for_lines(
|
||||
expected_lines=expected_lines,
|
||||
unexpected_lines=unexpected_lines,
|
||||
halt_on_unexpected=True)
|
||||
|
||||
# Assert the log monitor detected expected lines and did not detect any unexpected lines.
|
||||
assert result, (
|
||||
f'Log monitoring failed. Used expected_lines values: {expected_lines} & '
|
||||
f'unexpected_lines values: {unexpected_lines}')
|
||||
|
||||
|
||||
def query_metrics_from_s3(aws_metrics_utils: pytest.fixture, resource_mappings: pytest.fixture) -> None:
|
||||
"""
|
||||
Verify that the metrics events are delivered to the S3 bucket and can be queried.
|
||||
:param aws_metrics_utils: aws_metrics_utils fixture.
|
||||
:param resource_mappings: resource_mappings fixture.
|
||||
"""
|
||||
aws_metrics_utils.verify_s3_delivery(
|
||||
resource_mappings.get_resource_name_id('AWSMetrics.AnalyticsBucketName')
|
||||
)
|
||||
logger.info('Metrics are sent to S3.')
|
||||
|
||||
aws_metrics_utils.run_glue_crawler(
|
||||
resource_mappings.get_resource_name_id('AWSMetrics.EventsCrawlerName'))
|
||||
|
||||
# Remove the events_json table if exists so that the sample query can create a table with the same name.
|
||||
aws_metrics_utils.delete_table(resource_mappings.get_resource_name_id('AWSMetrics.EventDatabaseName'), 'events_json')
|
||||
aws_metrics_utils.run_named_queries(resource_mappings.get_resource_name_id('AWSMetrics.AthenaWorkGroupName'))
|
||||
logger.info('Query metrics from S3 successfully.')
|
||||
|
||||
|
||||
def verify_operational_metrics(aws_metrics_utils: pytest.fixture,
|
||||
resource_mappings: pytest.fixture, start_time: datetime) -> None:
|
||||
"""
|
||||
Verify that operational health metrics are delivered to CloudWatch.
|
||||
:param aws_metrics_utils: aws_metrics_utils fixture.
|
||||
:param resource_mappings: resource_mappings fixture.
|
||||
:param start_time: Time when the game launcher starts.
|
||||
"""
|
||||
aws_metrics_utils.verify_cloud_watch_delivery(
|
||||
'AWS/Lambda',
|
||||
'Invocations',
|
||||
[{'Name': 'FunctionName',
|
||||
'Value': resource_mappings.get_resource_name_id('AWSMetrics.AnalyticsProcessingLambdaName')}],
|
||||
start_time)
|
||||
logger.info('AnalyticsProcessingLambda metrics are sent to CloudWatch.')
|
||||
|
||||
aws_metrics_utils.verify_cloud_watch_delivery(
|
||||
'AWS/Lambda',
|
||||
'Invocations',
|
||||
[{'Name': 'FunctionName',
|
||||
'Value': resource_mappings.get_resource_name_id('AWSMetrics.EventProcessingLambdaName')}],
|
||||
start_time)
|
||||
logger.info('EventsProcessingLambda metrics are sent to CloudWatch.')
|
||||
|
||||
|
||||
def update_kinesis_analytics_application_status(aws_metrics_utils: pytest.fixture,
|
||||
resource_mappings: pytest.fixture, start_application: bool) -> None:
|
||||
"""
|
||||
Update the Kinesis analytics application to start or stop it.
|
||||
:param aws_metrics_utils: aws_metrics_utils fixture.
|
||||
:param resource_mappings: resource_mappings fixture.
|
||||
:param start_application: whether to start or stop the application.
|
||||
"""
|
||||
if start_application:
|
||||
aws_metrics_utils.start_kinesis_data_analytics_application(
|
||||
resource_mappings.get_resource_name_id('AWSMetrics.AnalyticsApplicationName'))
|
||||
else:
|
||||
aws_metrics_utils.stop_kinesis_data_analytics_application(
|
||||
resource_mappings.get_resource_name_id('AWSMetrics.AnalyticsApplicationName'))
|
||||
|
||||
@pytest.mark.SUITE_awsi
|
||||
@pytest.mark.usefixtures('automatic_process_killer')
|
||||
@pytest.mark.usefixtures('aws_credentials')
|
||||
@pytest.mark.usefixtures('resource_mappings')
|
||||
@pytest.mark.parametrize('assume_role_arn', [constants.ASSUME_ROLE_ARN])
|
||||
@pytest.mark.parametrize('feature_name', [AWS_METRICS_FEATURE_NAME])
|
||||
@pytest.mark.parametrize('profile_name', ['AWSAutomationTest'])
|
||||
@pytest.mark.parametrize('project', ['AutomatedTesting'])
|
||||
@pytest.mark.parametrize('region_name', [constants.AWS_REGION])
|
||||
@pytest.mark.parametrize('resource_mappings_filename', [constants.AWS_RESOURCE_MAPPING_FILE_NAME])
|
||||
@pytest.mark.parametrize('session_name', [constants.SESSION_NAME])
|
||||
@pytest.mark.parametrize('stacks', [[f'{constants.AWS_PROJECT_NAME}-{AWS_METRICS_FEATURE_NAME}-{constants.AWS_REGION}']])
|
||||
class TestAWSMetricsWindows(object):
|
||||
"""
|
||||
Test class to verify the real-time and batch analytics for metrics.
|
||||
"""
|
||||
@pytest.mark.parametrize('level', ['AWS/Metrics'])
|
||||
def test_realtime_and_batch_analytics(self,
|
||||
level: str,
|
||||
launcher: pytest.fixture,
|
||||
asset_processor: pytest.fixture,
|
||||
workspace: pytest.fixture,
|
||||
aws_utils: pytest.fixture,
|
||||
resource_mappings: pytest.fixture,
|
||||
aws_metrics_utils: pytest.fixture):
|
||||
"""
|
||||
Verify that the metrics events are sent to CloudWatch and S3 for analytics.
|
||||
"""
|
||||
# Start Kinesis analytics application on a separate thread to avoid blocking the test.
|
||||
kinesis_analytics_application_thread = AWSMetricsThread(target=update_kinesis_analytics_application_status,
|
||||
args=(aws_metrics_utils, resource_mappings, True))
|
||||
kinesis_analytics_application_thread.start()
|
||||
|
||||
log_monitor = setup(launcher, asset_processor)
|
||||
|
||||
# Kinesis analytics application needs to be in the running state before we start the game launcher.
|
||||
kinesis_analytics_application_thread.join()
|
||||
launcher.args = ['+LoadLevel', level]
|
||||
launcher.args.extend(['-rhi=null'])
|
||||
start_time = datetime.utcnow()
|
||||
with launcher.start(launch_ap=False):
|
||||
monitor_metrics_submission(log_monitor)
|
||||
|
||||
# Verify that real-time analytics metrics are delivered to CloudWatch.
|
||||
aws_metrics_utils.verify_cloud_watch_delivery(
|
||||
AWS_METRICS_FEATURE_NAME,
|
||||
'TotalLogins',
|
||||
[],
|
||||
start_time)
|
||||
logger.info('Real-time metrics are sent to CloudWatch.')
|
||||
|
||||
# Run time-consuming operations on separate threads to avoid blocking the test.
|
||||
operational_threads = list()
|
||||
operational_threads.append(
|
||||
AWSMetricsThread(target=query_metrics_from_s3,
|
||||
args=(aws_metrics_utils, resource_mappings)))
|
||||
operational_threads.append(
|
||||
AWSMetricsThread(target=verify_operational_metrics,
|
||||
args=(aws_metrics_utils, resource_mappings, start_time)))
|
||||
operational_threads.append(
|
||||
AWSMetricsThread(target=update_kinesis_analytics_application_status,
|
||||
args=(aws_metrics_utils, resource_mappings, False)))
|
||||
for thread in operational_threads:
|
||||
thread.start()
|
||||
for thread in operational_threads:
|
||||
thread.join()
|
||||
|
||||
@pytest.mark.parametrize('level', ['AWS/Metrics'])
|
||||
def test_realtime_and_batch_analytics_no_global_accountid(self,
|
||||
level: str,
|
||||
launcher: pytest.fixture,
|
||||
asset_processor: pytest.fixture,
|
||||
workspace: pytest.fixture,
|
||||
aws_utils: pytest.fixture,
|
||||
resource_mappings: pytest.fixture,
|
||||
aws_metrics_utils: pytest.fixture):
|
||||
"""
|
||||
Verify that the metrics events are sent to CloudWatch and S3 for analytics.
|
||||
"""
|
||||
# Remove top-level account ID from resource mappings
|
||||
resource_mappings.clear_select_keys([AWS_RESOURCE_MAPPINGS_ACCOUNT_ID_KEY])
|
||||
# Start Kinesis analytics application on a separate thread to avoid blocking the test.
|
||||
kinesis_analytics_application_thread = AWSMetricsThread(target=update_kinesis_analytics_application_status,
|
||||
args=(aws_metrics_utils, resource_mappings, True))
|
||||
kinesis_analytics_application_thread.start()
|
||||
|
||||
log_monitor = setup(launcher, asset_processor)
|
||||
|
||||
# Kinesis analytics application needs to be in the running state before we start the game launcher.
|
||||
kinesis_analytics_application_thread.join()
|
||||
launcher.args = ['+LoadLevel', level]
|
||||
launcher.args.extend(['-rhi=null'])
|
||||
start_time = datetime.utcnow()
|
||||
with launcher.start(launch_ap=False):
|
||||
monitor_metrics_submission(log_monitor)
|
||||
|
||||
# Verify that real-time analytics metrics are delivered to CloudWatch.
|
||||
aws_metrics_utils.verify_cloud_watch_delivery(
|
||||
AWS_METRICS_FEATURE_NAME,
|
||||
'TotalLogins',
|
||||
[],
|
||||
start_time)
|
||||
logger.info('Real-time metrics are sent to CloudWatch.')
|
||||
|
||||
# Run time-consuming operations on separate threads to avoid blocking the test.
|
||||
operational_threads = list()
|
||||
operational_threads.append(
|
||||
AWSMetricsThread(target=query_metrics_from_s3,
|
||||
args=(aws_metrics_utils, resource_mappings)))
|
||||
operational_threads.append(
|
||||
AWSMetricsThread(target=verify_operational_metrics,
|
||||
args=(aws_metrics_utils, resource_mappings, start_time)))
|
||||
operational_threads.append(
|
||||
AWSMetricsThread(target=update_kinesis_analytics_application_status,
|
||||
args=(aws_metrics_utils, resource_mappings, False)))
|
||||
for thread in operational_threads:
|
||||
thread.start()
|
||||
for thread in operational_threads:
|
||||
thread.join()
|
||||
|
||||
@pytest.mark.parametrize('level', ['AWS/Metrics'])
|
||||
def test_unauthorized_user_request_rejected(self,
|
||||
level: str,
|
||||
launcher: pytest.fixture,
|
||||
asset_processor: pytest.fixture,
|
||||
workspace: pytest.fixture):
|
||||
"""
|
||||
Verify that unauthorized users cannot send metrics events to the AWS backed backend.
|
||||
"""
|
||||
log_monitor = setup(launcher, asset_processor)
|
||||
|
||||
# Set invalid AWS credentials.
|
||||
launcher.args = ['+LoadLevel', level, '+cl_awsAccessKey', 'AKIAIOSFODNN7EXAMPLE',
|
||||
'+cl_awsSecretKey', 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY']
|
||||
launcher.args.extend(['-rhi=null'])
|
||||
|
||||
with launcher.start(launch_ap=False):
|
||||
result = log_monitor.monitor_log_for_lines(
|
||||
expected_lines=['(Script) - Failed to send metrics.'],
|
||||
unexpected_lines=['(Script) - Metrics is sent successfully.'],
|
||||
halt_on_unexpected=True)
|
||||
assert result, 'Metrics events are sent successfully by unauthorized user'
|
||||
logger.info('Unauthorized user is rejected to send metrics.')
|
||||
|
||||
def test_clean_up_s3_bucket(self,
|
||||
aws_utils: pytest.fixture,
|
||||
resource_mappings: pytest.fixture,
|
||||
aws_metrics_utils: pytest.fixture):
|
||||
"""
|
||||
Clear the analytics bucket objects so that the S3 bucket can be destroyed during tear down.
|
||||
"""
|
||||
aws_metrics_utils.empty_bucket(
|
||||
resource_mappings.get_resource_name_id('AWSMetrics.AnalyticsBucketName'))
|
||||
"""
|
||||
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 logging
|
||||
import os
|
||||
import pytest
|
||||
import typing
|
||||
from datetime import datetime
|
||||
|
||||
import ly_test_tools.log.log_monitor
|
||||
|
||||
from AWS.common import constants
|
||||
from AWS.common.resource_mappings import AWS_RESOURCE_MAPPINGS_ACCOUNT_ID_KEY
|
||||
from .aws_metrics_custom_thread import AWSMetricsThread
|
||||
|
||||
# fixture imports
|
||||
from assetpipeline.ap_fixtures.asset_processor_fixture import asset_processor
|
||||
from .aws_metrics_utils import aws_metrics_utils
|
||||
|
||||
AWS_METRICS_FEATURE_NAME = 'AWSMetrics'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup(launcher: pytest.fixture,
|
||||
asset_processor: pytest.fixture) -> pytest.fixture:
|
||||
"""
|
||||
Set up the resource mapping configuration and start the log monitor.
|
||||
:param launcher: Client launcher for running the test level.
|
||||
:param asset_processor: asset_processor fixture.
|
||||
:return log monitor object.
|
||||
"""
|
||||
asset_processor.start()
|
||||
asset_processor.wait_for_idle()
|
||||
|
||||
file_to_monitor = os.path.join(launcher.workspace.paths.project_log(), constants.GAME_LOG_NAME)
|
||||
|
||||
# Initialize the log monitor.
|
||||
log_monitor = ly_test_tools.log.log_monitor.LogMonitor(launcher=launcher, log_file_path=file_to_monitor)
|
||||
|
||||
return log_monitor
|
||||
|
||||
|
||||
def monitor_metrics_submission(log_monitor: pytest.fixture) -> None:
|
||||
"""
|
||||
Monitor the messages and notifications for submitting metrics.
|
||||
:param log_monitor: Log monitor to check the log messages.
|
||||
"""
|
||||
expected_lines = [
|
||||
'(Script) - Submitted metrics without buffer.',
|
||||
'(Script) - Submitted metrics with buffer.',
|
||||
'(Script) - Flushed the buffered metrics.',
|
||||
'(Script) - Metrics is sent successfully.'
|
||||
]
|
||||
|
||||
unexpected_lines = [
|
||||
'(Script) - Failed to submit metrics without buffer.',
|
||||
'(Script) - Failed to submit metrics with buffer.',
|
||||
'(Script) - Failed to send metrics.'
|
||||
]
|
||||
|
||||
result = log_monitor.monitor_log_for_lines(
|
||||
expected_lines=expected_lines,
|
||||
unexpected_lines=unexpected_lines,
|
||||
halt_on_unexpected=True)
|
||||
|
||||
# Assert the log monitor detected expected lines and did not detect any unexpected lines.
|
||||
assert result, (
|
||||
f'Log monitoring failed. Used expected_lines values: {expected_lines} & '
|
||||
f'unexpected_lines values: {unexpected_lines}')
|
||||
|
||||
|
||||
def query_metrics_from_s3(aws_metrics_utils: pytest.fixture, resource_mappings: pytest.fixture) -> None:
|
||||
"""
|
||||
Verify that the metrics events are delivered to the S3 bucket and can be queried.
|
||||
:param aws_metrics_utils: aws_metrics_utils fixture.
|
||||
:param resource_mappings: resource_mappings fixture.
|
||||
"""
|
||||
aws_metrics_utils.verify_s3_delivery(
|
||||
resource_mappings.get_resource_name_id('AWSMetrics.AnalyticsBucketName')
|
||||
)
|
||||
logger.info('Metrics are sent to S3.')
|
||||
|
||||
aws_metrics_utils.run_glue_crawler(
|
||||
resource_mappings.get_resource_name_id('AWSMetrics.EventsCrawlerName'))
|
||||
|
||||
# Remove the events_json table if exists so that the sample query can create a table with the same name.
|
||||
aws_metrics_utils.delete_table(resource_mappings.get_resource_name_id('AWSMetrics.EventDatabaseName'), 'events_json')
|
||||
aws_metrics_utils.run_named_queries(resource_mappings.get_resource_name_id('AWSMetrics.AthenaWorkGroupName'))
|
||||
logger.info('Query metrics from S3 successfully.')
|
||||
|
||||
|
||||
def verify_operational_metrics(aws_metrics_utils: pytest.fixture,
|
||||
resource_mappings: pytest.fixture, start_time: datetime) -> None:
|
||||
"""
|
||||
Verify that operational health metrics are delivered to CloudWatch.
|
||||
:param aws_metrics_utils: aws_metrics_utils fixture.
|
||||
:param resource_mappings: resource_mappings fixture.
|
||||
:param start_time: Time when the game launcher starts.
|
||||
"""
|
||||
aws_metrics_utils.verify_cloud_watch_delivery(
|
||||
'AWS/Lambda',
|
||||
'Invocations',
|
||||
[{'Name': 'FunctionName',
|
||||
'Value': resource_mappings.get_resource_name_id('AWSMetrics.AnalyticsProcessingLambdaName')}],
|
||||
start_time)
|
||||
logger.info('AnalyticsProcessingLambda metrics are sent to CloudWatch.')
|
||||
|
||||
aws_metrics_utils.verify_cloud_watch_delivery(
|
||||
'AWS/Lambda',
|
||||
'Invocations',
|
||||
[{'Name': 'FunctionName',
|
||||
'Value': resource_mappings.get_resource_name_id('AWSMetrics.EventProcessingLambdaName')}],
|
||||
start_time)
|
||||
logger.info('EventsProcessingLambda metrics are sent to CloudWatch.')
|
||||
|
||||
|
||||
def update_kinesis_analytics_application_status(aws_metrics_utils: pytest.fixture,
|
||||
resource_mappings: pytest.fixture, start_application: bool) -> None:
|
||||
"""
|
||||
Update the Kinesis analytics application to start or stop it.
|
||||
:param aws_metrics_utils: aws_metrics_utils fixture.
|
||||
:param resource_mappings: resource_mappings fixture.
|
||||
:param start_application: whether to start or stop the application.
|
||||
"""
|
||||
if start_application:
|
||||
aws_metrics_utils.start_kinesis_data_analytics_application(
|
||||
resource_mappings.get_resource_name_id('AWSMetrics.AnalyticsApplicationName'))
|
||||
else:
|
||||
aws_metrics_utils.stop_kinesis_data_analytics_application(
|
||||
resource_mappings.get_resource_name_id('AWSMetrics.AnalyticsApplicationName'))
|
||||
|
||||
@pytest.mark.SUITE_awsi
|
||||
@pytest.mark.usefixtures('automatic_process_killer')
|
||||
@pytest.mark.usefixtures('aws_credentials')
|
||||
@pytest.mark.usefixtures('resource_mappings')
|
||||
@pytest.mark.parametrize('assume_role_arn', [constants.ASSUME_ROLE_ARN])
|
||||
@pytest.mark.parametrize('feature_name', [AWS_METRICS_FEATURE_NAME])
|
||||
@pytest.mark.parametrize('profile_name', ['AWSAutomationTest'])
|
||||
@pytest.mark.parametrize('project', ['AutomatedTesting'])
|
||||
@pytest.mark.parametrize('region_name', [constants.AWS_REGION])
|
||||
@pytest.mark.parametrize('resource_mappings_filename', [constants.AWS_RESOURCE_MAPPING_FILE_NAME])
|
||||
@pytest.mark.parametrize('session_name', [constants.SESSION_NAME])
|
||||
@pytest.mark.parametrize('stacks', [[f'{constants.AWS_PROJECT_NAME}-{AWS_METRICS_FEATURE_NAME}-{constants.AWS_REGION}']])
|
||||
class TestAWSMetricsWindows(object):
|
||||
"""
|
||||
Test class to verify the real-time and batch analytics for metrics.
|
||||
"""
|
||||
@pytest.mark.parametrize('level', ['levels/aws/metrics/metrics.spawnable'])
|
||||
def test_realtime_and_batch_analytics(self,
|
||||
level: str,
|
||||
launcher: pytest.fixture,
|
||||
asset_processor: pytest.fixture,
|
||||
workspace: pytest.fixture,
|
||||
aws_utils: pytest.fixture,
|
||||
resource_mappings: pytest.fixture,
|
||||
aws_metrics_utils: pytest.fixture):
|
||||
"""
|
||||
Verify that the metrics events are sent to CloudWatch and S3 for analytics.
|
||||
"""
|
||||
# Start Kinesis analytics application on a separate thread to avoid blocking the test.
|
||||
kinesis_analytics_application_thread = AWSMetricsThread(target=update_kinesis_analytics_application_status,
|
||||
args=(aws_metrics_utils, resource_mappings, True))
|
||||
kinesis_analytics_application_thread.start()
|
||||
|
||||
log_monitor = setup(launcher, asset_processor)
|
||||
|
||||
# Kinesis analytics application needs to be in the running state before we start the game launcher.
|
||||
kinesis_analytics_application_thread.join()
|
||||
launcher.args = ['+LoadLevel', level]
|
||||
launcher.args.extend(['-rhi=null'])
|
||||
start_time = datetime.utcnow()
|
||||
with launcher.start(launch_ap=False):
|
||||
monitor_metrics_submission(log_monitor)
|
||||
|
||||
# Verify that real-time analytics metrics are delivered to CloudWatch.
|
||||
aws_metrics_utils.verify_cloud_watch_delivery(
|
||||
AWS_METRICS_FEATURE_NAME,
|
||||
'TotalLogins',
|
||||
[],
|
||||
start_time)
|
||||
logger.info('Real-time metrics are sent to CloudWatch.')
|
||||
|
||||
# Run time-consuming operations on separate threads to avoid blocking the test.
|
||||
operational_threads = list()
|
||||
operational_threads.append(
|
||||
AWSMetricsThread(target=query_metrics_from_s3,
|
||||
args=(aws_metrics_utils, resource_mappings)))
|
||||
operational_threads.append(
|
||||
AWSMetricsThread(target=verify_operational_metrics,
|
||||
args=(aws_metrics_utils, resource_mappings, start_time)))
|
||||
operational_threads.append(
|
||||
AWSMetricsThread(target=update_kinesis_analytics_application_status,
|
||||
args=(aws_metrics_utils, resource_mappings, False)))
|
||||
for thread in operational_threads:
|
||||
thread.start()
|
||||
for thread in operational_threads:
|
||||
thread.join()
|
||||
|
||||
@pytest.mark.parametrize('level', ['levels/aws/metrics/metrics.spawnable'])
|
||||
def test_realtime_and_batch_analytics_no_global_accountid(self,
|
||||
level: str,
|
||||
launcher: pytest.fixture,
|
||||
asset_processor: pytest.fixture,
|
||||
workspace: pytest.fixture,
|
||||
aws_utils: pytest.fixture,
|
||||
resource_mappings: pytest.fixture,
|
||||
aws_metrics_utils: pytest.fixture):
|
||||
"""
|
||||
Verify that the metrics events are sent to CloudWatch and S3 for analytics.
|
||||
"""
|
||||
# Remove top-level account ID from resource mappings
|
||||
resource_mappings.clear_select_keys([AWS_RESOURCE_MAPPINGS_ACCOUNT_ID_KEY])
|
||||
# Start Kinesis analytics application on a separate thread to avoid blocking the test.
|
||||
kinesis_analytics_application_thread = AWSMetricsThread(target=update_kinesis_analytics_application_status,
|
||||
args=(aws_metrics_utils, resource_mappings, True))
|
||||
kinesis_analytics_application_thread.start()
|
||||
|
||||
log_monitor = setup(launcher, asset_processor)
|
||||
|
||||
# Kinesis analytics application needs to be in the running state before we start the game launcher.
|
||||
kinesis_analytics_application_thread.join()
|
||||
launcher.args = ['+LoadLevel', level]
|
||||
launcher.args.extend(['-rhi=null'])
|
||||
start_time = datetime.utcnow()
|
||||
with launcher.start(launch_ap=False):
|
||||
monitor_metrics_submission(log_monitor)
|
||||
|
||||
# Verify that real-time analytics metrics are delivered to CloudWatch.
|
||||
aws_metrics_utils.verify_cloud_watch_delivery(
|
||||
AWS_METRICS_FEATURE_NAME,
|
||||
'TotalLogins',
|
||||
[],
|
||||
start_time)
|
||||
logger.info('Real-time metrics are sent to CloudWatch.')
|
||||
|
||||
# Run time-consuming operations on separate threads to avoid blocking the test.
|
||||
operational_threads = list()
|
||||
operational_threads.append(
|
||||
AWSMetricsThread(target=query_metrics_from_s3,
|
||||
args=(aws_metrics_utils, resource_mappings)))
|
||||
operational_threads.append(
|
||||
AWSMetricsThread(target=verify_operational_metrics,
|
||||
args=(aws_metrics_utils, resource_mappings, start_time)))
|
||||
operational_threads.append(
|
||||
AWSMetricsThread(target=update_kinesis_analytics_application_status,
|
||||
args=(aws_metrics_utils, resource_mappings, False)))
|
||||
for thread in operational_threads:
|
||||
thread.start()
|
||||
for thread in operational_threads:
|
||||
thread.join()
|
||||
|
||||
@pytest.mark.parametrize('level', ['levels/aws/metrics/metrics.spawnable'])
|
||||
def test_unauthorized_user_request_rejected(self,
|
||||
level: str,
|
||||
launcher: pytest.fixture,
|
||||
asset_processor: pytest.fixture,
|
||||
workspace: pytest.fixture):
|
||||
"""
|
||||
Verify that unauthorized users cannot send metrics events to the AWS backed backend.
|
||||
"""
|
||||
log_monitor = setup(launcher, asset_processor)
|
||||
|
||||
# Set invalid AWS credentials.
|
||||
launcher.args = ['+LoadLevel', level, '+cl_awsAccessKey', 'AKIAIOSFODNN7EXAMPLE',
|
||||
'+cl_awsSecretKey', 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY']
|
||||
launcher.args.extend(['-rhi=null'])
|
||||
|
||||
with launcher.start(launch_ap=False):
|
||||
result = log_monitor.monitor_log_for_lines(
|
||||
expected_lines=['(Script) - Failed to send metrics.'],
|
||||
unexpected_lines=['(Script) - Metrics is sent successfully.'],
|
||||
halt_on_unexpected=True)
|
||||
assert result, 'Metrics events are sent successfully by unauthorized user'
|
||||
logger.info('Unauthorized user is rejected to send metrics.')
|
||||
|
||||
def test_clean_up_s3_bucket(self,
|
||||
aws_utils: pytest.fixture,
|
||||
resource_mappings: pytest.fixture,
|
||||
aws_metrics_utils: pytest.fixture):
|
||||
"""
|
||||
Clear the analytics bucket objects so that the S3 bucket can be destroyed during tear down.
|
||||
"""
|
||||
aws_metrics_utils.empty_bucket(
|
||||
resource_mappings.get_resource_name_id('AWSMetrics.AnalyticsBucketName'))
|
||||
+29
-29
@@ -1,29 +1,29 @@
|
||||
"""
|
||||
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
|
||||
"""
|
||||
|
||||
from threading import Thread
|
||||
|
||||
|
||||
class AWSMetricsThread(Thread):
|
||||
"""
|
||||
Custom thread for raising assertion errors on the main thread.
|
||||
"""
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self._error = None
|
||||
|
||||
def run(self) -> None:
|
||||
try:
|
||||
super().run()
|
||||
except AssertionError as e:
|
||||
self._error = e
|
||||
|
||||
def join(self, **kwargs) -> None:
|
||||
super().join(**kwargs)
|
||||
|
||||
if self._error:
|
||||
raise AssertionError(self._error)
|
||||
"""
|
||||
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
|
||||
"""
|
||||
|
||||
from threading import Thread
|
||||
|
||||
|
||||
class AWSMetricsThread(Thread):
|
||||
"""
|
||||
Custom thread for raising assertion errors on the main thread.
|
||||
"""
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self._error = None
|
||||
|
||||
def run(self) -> None:
|
||||
try:
|
||||
super().run()
|
||||
except AssertionError as e:
|
||||
self._error = e
|
||||
|
||||
def join(self, **kwargs) -> None:
|
||||
super().join(**kwargs)
|
||||
|
||||
if self._error:
|
||||
raise AssertionError(self._error)
|
||||
+239
-239
@@ -1,239 +1,239 @@
|
||||
"""
|
||||
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 logging
|
||||
import pathlib
|
||||
import pytest
|
||||
import typing
|
||||
|
||||
from datetime import datetime
|
||||
from botocore.exceptions import WaiterError
|
||||
|
||||
from .aws_metrics_waiters import KinesisAnalyticsApplicationUpdatedWaiter, \
|
||||
CloudWatchMetricsDeliveredWaiter, DataLakeMetricsDeliveredWaiter, GlueCrawlerReadyWaiter
|
||||
|
||||
logging.getLogger('boto').setLevel(logging.CRITICAL)
|
||||
|
||||
# Expected directory and file extension for the S3 objects.
|
||||
EXPECTED_S3_DIRECTORY = 'firehose_events/'
|
||||
EXPECTED_S3_OBJECT_EXTENSION = '.parquet'
|
||||
|
||||
|
||||
class AWSMetricsUtils:
|
||||
"""
|
||||
Provide utils functions for the AWSMetrics gem to interact with the deployed resources.
|
||||
"""
|
||||
|
||||
def __init__(self, aws_utils: pytest.fixture):
|
||||
self._aws_util = aws_utils
|
||||
|
||||
def start_kinesis_data_analytics_application(self, application_name: str) -> None:
|
||||
"""
|
||||
Start the Kenisis Data Analytics application for real-time analytics.
|
||||
:param application_name: Name of the Kenisis Data Analytics application.
|
||||
"""
|
||||
input_id = self.get_kinesis_analytics_application_input_id(application_name)
|
||||
assert input_id, 'invalid Kinesis Data Analytics application input.'
|
||||
|
||||
client = self._aws_util.client('kinesisanalytics')
|
||||
try:
|
||||
client.start_application(
|
||||
ApplicationName=application_name,
|
||||
InputConfigurations=[
|
||||
{
|
||||
'Id': input_id,
|
||||
'InputStartingPositionConfiguration': {
|
||||
'InputStartingPosition': 'NOW'
|
||||
}
|
||||
},
|
||||
]
|
||||
)
|
||||
except client.exceptions.ResourceInUseException:
|
||||
# The application has been started.
|
||||
return
|
||||
|
||||
try:
|
||||
KinesisAnalyticsApplicationUpdatedWaiter(client, 'RUNNING').wait(application_name=application_name)
|
||||
except WaiterError as e:
|
||||
assert False, f'Failed to start the Kinesis Data Analytics application: {str(e)}.'
|
||||
|
||||
def get_kinesis_analytics_application_input_id(self, application_name: str) -> str:
|
||||
"""
|
||||
Get the input ID for the Kenisis Data Analytics application.
|
||||
:param application_name: Name of the Kenisis Data Analytics application.
|
||||
:return: Input ID for the Kenisis Data Analytics application.
|
||||
"""
|
||||
client = self._aws_util.client('kinesisanalytics')
|
||||
response = client.describe_application(
|
||||
ApplicationName=application_name
|
||||
)
|
||||
if not response:
|
||||
return ''
|
||||
input_descriptions = response.get('ApplicationDetail', {}).get('InputDescriptions', [])
|
||||
if len(input_descriptions) != 1:
|
||||
return ''
|
||||
|
||||
return input_descriptions[0].get('InputId', '')
|
||||
|
||||
def stop_kinesis_data_analytics_application(self, application_name: str) -> None:
|
||||
"""
|
||||
Stop the Kenisis Data Analytics application.
|
||||
:param application_name: Name of the Kenisis Data Analytics application.
|
||||
"""
|
||||
client = self._aws_util.client('kinesisanalytics')
|
||||
client.stop_application(
|
||||
ApplicationName=application_name
|
||||
)
|
||||
|
||||
try:
|
||||
KinesisAnalyticsApplicationUpdatedWaiter(client, 'READY').wait(application_name=application_name)
|
||||
except WaiterError as e:
|
||||
assert False, f'Failed to stop the Kinesis Data Analytics application: {str(e)}.'
|
||||
|
||||
def verify_cloud_watch_delivery(self, namespace: str, metrics_name: str,
|
||||
dimensions: typing.List[dict], start_time: datetime) -> None:
|
||||
"""
|
||||
Verify that the expected metrics is delivered to CloudWatch.
|
||||
:param namespace: Namespace of the metrics.
|
||||
:param metrics_name: Name of the metrics.
|
||||
:param dimensions: Dimensions of the metrics.
|
||||
:param start_time: Start time for generating the metrics.
|
||||
"""
|
||||
client = self._aws_util.client('cloudwatch')
|
||||
|
||||
try:
|
||||
CloudWatchMetricsDeliveredWaiter(client).wait(
|
||||
namespace=namespace,
|
||||
metrics_name=metrics_name,
|
||||
dimensions=dimensions,
|
||||
start_time=start_time
|
||||
)
|
||||
except WaiterError as e:
|
||||
assert False, f'Failed to deliver metrics to CloudWatch: {str(e)}.'
|
||||
|
||||
def verify_s3_delivery(self, analytics_bucket_name: str) -> None:
|
||||
"""
|
||||
Verify that metrics are delivered to S3 for batch analytics successfully.
|
||||
:param analytics_bucket_name: Name of the deployed S3 bucket.
|
||||
"""
|
||||
client = self._aws_util.client('s3')
|
||||
bucket_name = analytics_bucket_name
|
||||
|
||||
try:
|
||||
DataLakeMetricsDeliveredWaiter(client).wait(bucket_name=bucket_name, prefix=EXPECTED_S3_DIRECTORY)
|
||||
except WaiterError as e:
|
||||
assert False, f'Failed to find the S3 directory for storing metrics data: {str(e)}.'
|
||||
|
||||
# Check whether the data is converted to the expected data format.
|
||||
response = client.list_objects_v2(
|
||||
Bucket=bucket_name,
|
||||
Prefix=EXPECTED_S3_DIRECTORY
|
||||
)
|
||||
assert response.get('KeyCount', 0) != 0, f'Failed to deliver metrics to the S3 bucket {bucket_name}.'
|
||||
|
||||
s3_objects = response.get('Contents', [])
|
||||
for s3_object in s3_objects:
|
||||
key = s3_object.get('Key', '')
|
||||
assert pathlib.Path(key).suffix == EXPECTED_S3_OBJECT_EXTENSION, \
|
||||
f'Invalid data format is found in the S3 bucket {bucket_name}'
|
||||
|
||||
def run_glue_crawler(self, crawler_name: str) -> None:
|
||||
"""
|
||||
Run the Glue crawler and wait for it to finish.
|
||||
:param crawler_name: Name of the Glue crawler
|
||||
"""
|
||||
client = self._aws_util.client('glue')
|
||||
try:
|
||||
client.start_crawler(
|
||||
Name=crawler_name
|
||||
)
|
||||
except client.exceptions.CrawlerRunningException:
|
||||
# The crawler has already been started.
|
||||
return
|
||||
|
||||
try:
|
||||
GlueCrawlerReadyWaiter(client).wait(crawler_name=crawler_name)
|
||||
except WaiterError as e:
|
||||
assert False, f'Failed to run the Glue crawler: {str(e)}.'
|
||||
|
||||
def run_named_queries(self, work_group: str) -> None:
|
||||
"""
|
||||
Run the named queries under the specific Athena work group.
|
||||
:param work_group: Name of the Athena work group.
|
||||
"""
|
||||
client = self._aws_util.client('athena')
|
||||
# List all the named queries.
|
||||
response = client.list_named_queries(
|
||||
WorkGroup=work_group
|
||||
)
|
||||
named_query_ids = response.get('NamedQueryIds', [])
|
||||
|
||||
# Run each of the queries.
|
||||
for named_query_id in named_query_ids:
|
||||
get_named_query_response = client.get_named_query(
|
||||
NamedQueryId=named_query_id
|
||||
)
|
||||
named_query = get_named_query_response.get('NamedQuery', {})
|
||||
|
||||
start_query_execution_response = client.start_query_execution(
|
||||
QueryString=named_query.get('QueryString', ''),
|
||||
QueryExecutionContext={
|
||||
'Database': named_query.get('Database', '')
|
||||
},
|
||||
WorkGroup=work_group
|
||||
)
|
||||
|
||||
# Wait for the query to finish.
|
||||
state = 'RUNNING'
|
||||
while state == 'QUEUED' or state == 'RUNNING':
|
||||
get_query_execution_response = client.get_query_execution(
|
||||
QueryExecutionId=start_query_execution_response.get('QueryExecutionId', '')
|
||||
)
|
||||
|
||||
state = get_query_execution_response.get('QueryExecution', {}).get('Status', {}).get('State', '')
|
||||
|
||||
assert state == 'SUCCEEDED', f'Failed to run the named query {named_query.get("Name", {})}'
|
||||
|
||||
def empty_bucket(self, bucket_name: str) -> None:
|
||||
"""
|
||||
Empty the S3 bucket following:
|
||||
https://boto3.amazonaws.com/v1/documentation/api/latest/guide/migrations3.html
|
||||
|
||||
:param bucket_name: Name of the S3 bucket.
|
||||
"""
|
||||
s3 = self._aws_util.resource('s3')
|
||||
bucket = s3.Bucket(bucket_name)
|
||||
|
||||
for key in bucket.objects.all():
|
||||
key.delete()
|
||||
|
||||
def delete_table(self, database_name: str, table_name: str) -> None:
|
||||
"""
|
||||
Delete an existing Glue table.
|
||||
|
||||
:param database_name: Name of the Glue database.
|
||||
:param table_name: Name of the table to delete.
|
||||
"""
|
||||
client = self._aws_util.client('glue')
|
||||
client.delete_table(
|
||||
DatabaseName=database_name,
|
||||
Name=table_name
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def aws_metrics_utils(
|
||||
request: pytest.fixture,
|
||||
aws_utils: pytest.fixture):
|
||||
"""
|
||||
Fixture for the AWS metrics util functions.
|
||||
:param request: _pytest.fixtures.SubRequest class that handles getting
|
||||
a pytest fixture from a pytest function/fixture.
|
||||
:param aws_utils: aws_utils fixture.
|
||||
"""
|
||||
aws_utils_obj = AWSMetricsUtils(aws_utils)
|
||||
return aws_utils_obj
|
||||
"""
|
||||
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 logging
|
||||
import pathlib
|
||||
import pytest
|
||||
import typing
|
||||
|
||||
from datetime import datetime
|
||||
from botocore.exceptions import WaiterError
|
||||
|
||||
from .aws_metrics_waiters import KinesisAnalyticsApplicationUpdatedWaiter, \
|
||||
CloudWatchMetricsDeliveredWaiter, DataLakeMetricsDeliveredWaiter, GlueCrawlerReadyWaiter
|
||||
|
||||
logging.getLogger('boto').setLevel(logging.CRITICAL)
|
||||
|
||||
# Expected directory and file extension for the S3 objects.
|
||||
EXPECTED_S3_DIRECTORY = 'firehose_events/'
|
||||
EXPECTED_S3_OBJECT_EXTENSION = '.parquet'
|
||||
|
||||
|
||||
class AWSMetricsUtils:
|
||||
"""
|
||||
Provide utils functions for the AWSMetrics gem to interact with the deployed resources.
|
||||
"""
|
||||
|
||||
def __init__(self, aws_utils: pytest.fixture):
|
||||
self._aws_util = aws_utils
|
||||
|
||||
def start_kinesis_data_analytics_application(self, application_name: str) -> None:
|
||||
"""
|
||||
Start the Kenisis Data Analytics application for real-time analytics.
|
||||
:param application_name: Name of the Kenisis Data Analytics application.
|
||||
"""
|
||||
input_id = self.get_kinesis_analytics_application_input_id(application_name)
|
||||
assert input_id, 'invalid Kinesis Data Analytics application input.'
|
||||
|
||||
client = self._aws_util.client('kinesisanalytics')
|
||||
try:
|
||||
client.start_application(
|
||||
ApplicationName=application_name,
|
||||
InputConfigurations=[
|
||||
{
|
||||
'Id': input_id,
|
||||
'InputStartingPositionConfiguration': {
|
||||
'InputStartingPosition': 'NOW'
|
||||
}
|
||||
},
|
||||
]
|
||||
)
|
||||
except client.exceptions.ResourceInUseException:
|
||||
# The application has been started.
|
||||
return
|
||||
|
||||
try:
|
||||
KinesisAnalyticsApplicationUpdatedWaiter(client, 'RUNNING').wait(application_name=application_name)
|
||||
except WaiterError as e:
|
||||
assert False, f'Failed to start the Kinesis Data Analytics application: {str(e)}.'
|
||||
|
||||
def get_kinesis_analytics_application_input_id(self, application_name: str) -> str:
|
||||
"""
|
||||
Get the input ID for the Kenisis Data Analytics application.
|
||||
:param application_name: Name of the Kenisis Data Analytics application.
|
||||
:return: Input ID for the Kenisis Data Analytics application.
|
||||
"""
|
||||
client = self._aws_util.client('kinesisanalytics')
|
||||
response = client.describe_application(
|
||||
ApplicationName=application_name
|
||||
)
|
||||
if not response:
|
||||
return ''
|
||||
input_descriptions = response.get('ApplicationDetail', {}).get('InputDescriptions', [])
|
||||
if len(input_descriptions) != 1:
|
||||
return ''
|
||||
|
||||
return input_descriptions[0].get('InputId', '')
|
||||
|
||||
def stop_kinesis_data_analytics_application(self, application_name: str) -> None:
|
||||
"""
|
||||
Stop the Kenisis Data Analytics application.
|
||||
:param application_name: Name of the Kenisis Data Analytics application.
|
||||
"""
|
||||
client = self._aws_util.client('kinesisanalytics')
|
||||
client.stop_application(
|
||||
ApplicationName=application_name
|
||||
)
|
||||
|
||||
try:
|
||||
KinesisAnalyticsApplicationUpdatedWaiter(client, 'READY').wait(application_name=application_name)
|
||||
except WaiterError as e:
|
||||
assert False, f'Failed to stop the Kinesis Data Analytics application: {str(e)}.'
|
||||
|
||||
def verify_cloud_watch_delivery(self, namespace: str, metrics_name: str,
|
||||
dimensions: typing.List[dict], start_time: datetime) -> None:
|
||||
"""
|
||||
Verify that the expected metrics is delivered to CloudWatch.
|
||||
:param namespace: Namespace of the metrics.
|
||||
:param metrics_name: Name of the metrics.
|
||||
:param dimensions: Dimensions of the metrics.
|
||||
:param start_time: Start time for generating the metrics.
|
||||
"""
|
||||
client = self._aws_util.client('cloudwatch')
|
||||
|
||||
try:
|
||||
CloudWatchMetricsDeliveredWaiter(client).wait(
|
||||
namespace=namespace,
|
||||
metrics_name=metrics_name,
|
||||
dimensions=dimensions,
|
||||
start_time=start_time
|
||||
)
|
||||
except WaiterError as e:
|
||||
assert False, f'Failed to deliver metrics to CloudWatch: {str(e)}.'
|
||||
|
||||
def verify_s3_delivery(self, analytics_bucket_name: str) -> None:
|
||||
"""
|
||||
Verify that metrics are delivered to S3 for batch analytics successfully.
|
||||
:param analytics_bucket_name: Name of the deployed S3 bucket.
|
||||
"""
|
||||
client = self._aws_util.client('s3')
|
||||
bucket_name = analytics_bucket_name
|
||||
|
||||
try:
|
||||
DataLakeMetricsDeliveredWaiter(client).wait(bucket_name=bucket_name, prefix=EXPECTED_S3_DIRECTORY)
|
||||
except WaiterError as e:
|
||||
assert False, f'Failed to find the S3 directory for storing metrics data: {str(e)}.'
|
||||
|
||||
# Check whether the data is converted to the expected data format.
|
||||
response = client.list_objects_v2(
|
||||
Bucket=bucket_name,
|
||||
Prefix=EXPECTED_S3_DIRECTORY
|
||||
)
|
||||
assert response.get('KeyCount', 0) != 0, f'Failed to deliver metrics to the S3 bucket {bucket_name}.'
|
||||
|
||||
s3_objects = response.get('Contents', [])
|
||||
for s3_object in s3_objects:
|
||||
key = s3_object.get('Key', '')
|
||||
assert pathlib.Path(key).suffix == EXPECTED_S3_OBJECT_EXTENSION, \
|
||||
f'Invalid data format is found in the S3 bucket {bucket_name}'
|
||||
|
||||
def run_glue_crawler(self, crawler_name: str) -> None:
|
||||
"""
|
||||
Run the Glue crawler and wait for it to finish.
|
||||
:param crawler_name: Name of the Glue crawler
|
||||
"""
|
||||
client = self._aws_util.client('glue')
|
||||
try:
|
||||
client.start_crawler(
|
||||
Name=crawler_name
|
||||
)
|
||||
except client.exceptions.CrawlerRunningException:
|
||||
# The crawler has already been started.
|
||||
return
|
||||
|
||||
try:
|
||||
GlueCrawlerReadyWaiter(client).wait(crawler_name=crawler_name)
|
||||
except WaiterError as e:
|
||||
assert False, f'Failed to run the Glue crawler: {str(e)}.'
|
||||
|
||||
def run_named_queries(self, work_group: str) -> None:
|
||||
"""
|
||||
Run the named queries under the specific Athena work group.
|
||||
:param work_group: Name of the Athena work group.
|
||||
"""
|
||||
client = self._aws_util.client('athena')
|
||||
# List all the named queries.
|
||||
response = client.list_named_queries(
|
||||
WorkGroup=work_group
|
||||
)
|
||||
named_query_ids = response.get('NamedQueryIds', [])
|
||||
|
||||
# Run each of the queries.
|
||||
for named_query_id in named_query_ids:
|
||||
get_named_query_response = client.get_named_query(
|
||||
NamedQueryId=named_query_id
|
||||
)
|
||||
named_query = get_named_query_response.get('NamedQuery', {})
|
||||
|
||||
start_query_execution_response = client.start_query_execution(
|
||||
QueryString=named_query.get('QueryString', ''),
|
||||
QueryExecutionContext={
|
||||
'Database': named_query.get('Database', '')
|
||||
},
|
||||
WorkGroup=work_group
|
||||
)
|
||||
|
||||
# Wait for the query to finish.
|
||||
state = 'RUNNING'
|
||||
while state == 'QUEUED' or state == 'RUNNING':
|
||||
get_query_execution_response = client.get_query_execution(
|
||||
QueryExecutionId=start_query_execution_response.get('QueryExecutionId', '')
|
||||
)
|
||||
|
||||
state = get_query_execution_response.get('QueryExecution', {}).get('Status', {}).get('State', '')
|
||||
|
||||
assert state == 'SUCCEEDED', f'Failed to run the named query {named_query.get("Name", {})}'
|
||||
|
||||
def empty_bucket(self, bucket_name: str) -> None:
|
||||
"""
|
||||
Empty the S3 bucket following:
|
||||
https://boto3.amazonaws.com/v1/documentation/api/latest/guide/migrations3.html
|
||||
|
||||
:param bucket_name: Name of the S3 bucket.
|
||||
"""
|
||||
s3 = self._aws_util.resource('s3')
|
||||
bucket = s3.Bucket(bucket_name)
|
||||
|
||||
for key in bucket.objects.all():
|
||||
key.delete()
|
||||
|
||||
def delete_table(self, database_name: str, table_name: str) -> None:
|
||||
"""
|
||||
Delete an existing Glue table.
|
||||
|
||||
:param database_name: Name of the Glue database.
|
||||
:param table_name: Name of the table to delete.
|
||||
"""
|
||||
client = self._aws_util.client('glue')
|
||||
client.delete_table(
|
||||
DatabaseName=database_name,
|
||||
Name=table_name
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def aws_metrics_utils(
|
||||
request: pytest.fixture,
|
||||
aws_utils: pytest.fixture):
|
||||
"""
|
||||
Fixture for the AWS metrics util functions.
|
||||
:param request: _pytest.fixtures.SubRequest class that handles getting
|
||||
a pytest fixture from a pytest function/fixture.
|
||||
:param aws_utils: aws_utils fixture.
|
||||
"""
|
||||
aws_utils_obj = AWSMetricsUtils(aws_utils)
|
||||
return aws_utils_obj
|
||||
+139
-139
@@ -1,139 +1,139 @@
|
||||
"""
|
||||
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 botocore.client
|
||||
import logging
|
||||
|
||||
from datetime import timedelta
|
||||
from AWS.common.custom_waiter import CustomWaiter, WaitState
|
||||
|
||||
logging.getLogger('boto').setLevel(logging.CRITICAL)
|
||||
|
||||
|
||||
class KinesisAnalyticsApplicationUpdatedWaiter(CustomWaiter):
|
||||
"""
|
||||
Subclass of the base custom waiter class.
|
||||
Wait for the Kinesis analytics application being updated to a specific status.
|
||||
"""
|
||||
def __init__(self, client: botocore.client, status: str):
|
||||
"""
|
||||
Initialize the waiter.
|
||||
|
||||
:param client: Boto3 client to use.
|
||||
:param status: Expected status.
|
||||
"""
|
||||
super().__init__(
|
||||
'KinesisAnalyticsApplicationUpdated',
|
||||
'DescribeApplication',
|
||||
'ApplicationDetail.ApplicationStatus',
|
||||
{status: WaitState.SUCCESS},
|
||||
client)
|
||||
|
||||
def wait(self, application_name: str):
|
||||
"""
|
||||
Wait for the expected status.
|
||||
|
||||
:param application_name: Name of the Kinesis analytics application.
|
||||
"""
|
||||
self._wait(ApplicationName=application_name)
|
||||
|
||||
|
||||
class GlueCrawlerReadyWaiter(CustomWaiter):
|
||||
"""
|
||||
Subclass of the base custom waiter class.
|
||||
Wait for the Glue crawler to finish its processing. Return when the crawler is in the "Stopping" status
|
||||
to avoid wasting too much time in the automation tests on its shutdown process.
|
||||
"""
|
||||
def __init__(self, client: botocore.client):
|
||||
"""
|
||||
Initialize the waiter.
|
||||
|
||||
:param client: Boto3 client to use.
|
||||
"""
|
||||
super().__init__(
|
||||
'GlueCrawlerReady',
|
||||
'GetCrawler',
|
||||
'Crawler.State',
|
||||
{'STOPPING': WaitState.SUCCESS},
|
||||
client)
|
||||
|
||||
def wait(self, crawler_name):
|
||||
"""
|
||||
Wait for the expected status.
|
||||
|
||||
:param crawler_name: Name of the Glue crawler.
|
||||
"""
|
||||
self._wait(Name=crawler_name)
|
||||
|
||||
|
||||
class DataLakeMetricsDeliveredWaiter(CustomWaiter):
|
||||
"""
|
||||
Subclass of the base custom waiter class.
|
||||
Wait for the expected directory being created in the S3 bucket.
|
||||
"""
|
||||
def __init__(self, client: botocore.client):
|
||||
"""
|
||||
Initialize the waiter.
|
||||
|
||||
:param client: Boto3 client to use.
|
||||
"""
|
||||
super().__init__(
|
||||
'DataLakeMetricsDelivered',
|
||||
'ListObjectsV2',
|
||||
'KeyCount > `0`',
|
||||
{True: WaitState.SUCCESS},
|
||||
client)
|
||||
|
||||
def wait(self, bucket_name, prefix):
|
||||
"""
|
||||
Wait for the expected directory being created.
|
||||
|
||||
:param bucket_name: Name of the S3 bucket.
|
||||
:param prefix: Name of the expected directory prefix.
|
||||
"""
|
||||
self._wait(Bucket=bucket_name, Prefix=prefix)
|
||||
|
||||
|
||||
class CloudWatchMetricsDeliveredWaiter(CustomWaiter):
|
||||
"""
|
||||
Subclass of the base custom waiter class.
|
||||
Wait for the expected metrics being delivered to CloudWatch.
|
||||
"""
|
||||
def __init__(self, client: botocore.client):
|
||||
"""
|
||||
Initialize the waiter.
|
||||
|
||||
:param client: Boto3 client to use.
|
||||
"""
|
||||
super().__init__(
|
||||
'CloudWatchMetricsDelivered',
|
||||
'GetMetricStatistics',
|
||||
'length(Datapoints) > `0`',
|
||||
{True: WaitState.SUCCESS},
|
||||
client)
|
||||
|
||||
def wait(self, namespace, metrics_name, dimensions, start_time):
|
||||
"""
|
||||
Wait for the expected metrics being delivered.
|
||||
|
||||
:param namespace: Namespace of the metrics.
|
||||
:param metrics_name: Name of the metrics.
|
||||
:param dimensions: Dimensions of the metrics.
|
||||
:param start_time: Start time for generating the metrics.
|
||||
"""
|
||||
self._wait(
|
||||
Namespace=namespace,
|
||||
MetricName=metrics_name,
|
||||
Dimensions=dimensions,
|
||||
StartTime=start_time,
|
||||
EndTime=start_time + timedelta(0, self.timeout),
|
||||
Period=60,
|
||||
Statistics=[
|
||||
'SampleCount'
|
||||
],
|
||||
Unit='Count'
|
||||
)
|
||||
"""
|
||||
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 botocore.client
|
||||
import logging
|
||||
|
||||
from datetime import timedelta
|
||||
from AWS.common.custom_waiter import CustomWaiter, WaitState
|
||||
|
||||
logging.getLogger('boto').setLevel(logging.CRITICAL)
|
||||
|
||||
|
||||
class KinesisAnalyticsApplicationUpdatedWaiter(CustomWaiter):
|
||||
"""
|
||||
Subclass of the base custom waiter class.
|
||||
Wait for the Kinesis analytics application being updated to a specific status.
|
||||
"""
|
||||
def __init__(self, client: botocore.client, status: str):
|
||||
"""
|
||||
Initialize the waiter.
|
||||
|
||||
:param client: Boto3 client to use.
|
||||
:param status: Expected status.
|
||||
"""
|
||||
super().__init__(
|
||||
'KinesisAnalyticsApplicationUpdated',
|
||||
'DescribeApplication',
|
||||
'ApplicationDetail.ApplicationStatus',
|
||||
{status: WaitState.SUCCESS},
|
||||
client)
|
||||
|
||||
def wait(self, application_name: str):
|
||||
"""
|
||||
Wait for the expected status.
|
||||
|
||||
:param application_name: Name of the Kinesis analytics application.
|
||||
"""
|
||||
self._wait(ApplicationName=application_name)
|
||||
|
||||
|
||||
class GlueCrawlerReadyWaiter(CustomWaiter):
|
||||
"""
|
||||
Subclass of the base custom waiter class.
|
||||
Wait for the Glue crawler to finish its processing. Return when the crawler is in the "Stopping" status
|
||||
to avoid wasting too much time in the automation tests on its shutdown process.
|
||||
"""
|
||||
def __init__(self, client: botocore.client):
|
||||
"""
|
||||
Initialize the waiter.
|
||||
|
||||
:param client: Boto3 client to use.
|
||||
"""
|
||||
super().__init__(
|
||||
'GlueCrawlerReady',
|
||||
'GetCrawler',
|
||||
'Crawler.State',
|
||||
{'STOPPING': WaitState.SUCCESS},
|
||||
client)
|
||||
|
||||
def wait(self, crawler_name):
|
||||
"""
|
||||
Wait for the expected status.
|
||||
|
||||
:param crawler_name: Name of the Glue crawler.
|
||||
"""
|
||||
self._wait(Name=crawler_name)
|
||||
|
||||
|
||||
class DataLakeMetricsDeliveredWaiter(CustomWaiter):
|
||||
"""
|
||||
Subclass of the base custom waiter class.
|
||||
Wait for the expected directory being created in the S3 bucket.
|
||||
"""
|
||||
def __init__(self, client: botocore.client):
|
||||
"""
|
||||
Initialize the waiter.
|
||||
|
||||
:param client: Boto3 client to use.
|
||||
"""
|
||||
super().__init__(
|
||||
'DataLakeMetricsDelivered',
|
||||
'ListObjectsV2',
|
||||
'KeyCount > `0`',
|
||||
{True: WaitState.SUCCESS},
|
||||
client)
|
||||
|
||||
def wait(self, bucket_name, prefix):
|
||||
"""
|
||||
Wait for the expected directory being created.
|
||||
|
||||
:param bucket_name: Name of the S3 bucket.
|
||||
:param prefix: Name of the expected directory prefix.
|
||||
"""
|
||||
self._wait(Bucket=bucket_name, Prefix=prefix)
|
||||
|
||||
|
||||
class CloudWatchMetricsDeliveredWaiter(CustomWaiter):
|
||||
"""
|
||||
Subclass of the base custom waiter class.
|
||||
Wait for the expected metrics being delivered to CloudWatch.
|
||||
"""
|
||||
def __init__(self, client: botocore.client):
|
||||
"""
|
||||
Initialize the waiter.
|
||||
|
||||
:param client: Boto3 client to use.
|
||||
"""
|
||||
super().__init__(
|
||||
'CloudWatchMetricsDelivered',
|
||||
'GetMetricStatistics',
|
||||
'length(Datapoints) > `0`',
|
||||
{True: WaitState.SUCCESS},
|
||||
client)
|
||||
|
||||
def wait(self, namespace, metrics_name, dimensions, start_time):
|
||||
"""
|
||||
Wait for the expected metrics being delivered.
|
||||
|
||||
:param namespace: Namespace of the metrics.
|
||||
:param metrics_name: Name of the metrics.
|
||||
:param dimensions: Dimensions of the metrics.
|
||||
:param start_time: Start time for generating the metrics.
|
||||
"""
|
||||
self._wait(
|
||||
Namespace=namespace,
|
||||
MetricName=metrics_name,
|
||||
Dimensions=dimensions,
|
||||
StartTime=start_time,
|
||||
EndTime=start_time + timedelta(0, self.timeout),
|
||||
Period=60,
|
||||
Statistics=[
|
||||
'SampleCount'
|
||||
],
|
||||
Unit='Count'
|
||||
)
|
||||
+4
-4
@@ -40,7 +40,7 @@ class TestAWSClientAuthWindows(object):
|
||||
Test class to verify AWS Client Auth gem features on Windows.
|
||||
"""
|
||||
|
||||
@pytest.mark.parametrize('level', ['AWS/ClientAuth'])
|
||||
@pytest.mark.parametrize('level', ['levels/aws/clientauth/clientauth.spawnable'])
|
||||
def test_anonymous_credentials(self,
|
||||
level: str,
|
||||
launcher: pytest.fixture,
|
||||
@@ -72,7 +72,7 @@ class TestAWSClientAuthWindows(object):
|
||||
)
|
||||
assert result, 'Anonymous credentials fetched successfully.'
|
||||
|
||||
@pytest.mark.parametrize('level', ['AWS/ClientAuth'])
|
||||
@pytest.mark.parametrize('level', ['levels/aws/clientauth/clientauth.spawnable'])
|
||||
def test_anonymous_credentials_no_global_accountid(self,
|
||||
level: str,
|
||||
launcher: pytest.fixture,
|
||||
@@ -140,7 +140,7 @@ class TestAWSClientAuthWindows(object):
|
||||
except cognito_idp.exceptions.UserNotFoundException:
|
||||
pass
|
||||
|
||||
launcher.args = ['+LoadLevel', 'AWS/ClientAuthPasswordSignUp']
|
||||
launcher.args = ['+LoadLevel', 'levels/aws/clientauthpasswordsignup/clientauthpasswordsignup.spawnable']
|
||||
launcher.args.extend(['-rhi=null'])
|
||||
|
||||
with launcher.start(launch_ap=False):
|
||||
@@ -158,7 +158,7 @@ class TestAWSClientAuthWindows(object):
|
||||
Username='test1'
|
||||
)
|
||||
|
||||
launcher.args = ['+LoadLevel', 'AWS/ClientAuthPasswordSignIn']
|
||||
launcher.args = ['+LoadLevel', 'levels/aws/clientauthpasswordsignin/clientauthpasswordsignin.spawnable']
|
||||
launcher.args.extend(['-rhi=null'])
|
||||
|
||||
with launcher.start(launch_ap=False):
|
||||
-1
@@ -4,4 +4,3 @@ For complete copyright and license terms please see the LICENSE at the root of t
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
|
||||
+1
-1
@@ -84,7 +84,7 @@ def write_test_data_to_dynamodb_table(resource_mappings: pytest.fixture, aws_uti
|
||||
@pytest.mark.parametrize('session_name', [constants.SESSION_NAME])
|
||||
@pytest.mark.usefixtures('workspace')
|
||||
@pytest.mark.parametrize('project', ['AutomatedTesting'])
|
||||
@pytest.mark.parametrize('level', ['AWS/Core'])
|
||||
@pytest.mark.parametrize('level', ['levels/aws/core/core.spawnable'])
|
||||
@pytest.mark.usefixtures('resource_mappings')
|
||||
@pytest.mark.parametrize('resource_mappings_filename', [constants.AWS_RESOURCE_MAPPING_FILE_NAME])
|
||||
@pytest.mark.parametrize('stacks', [[f'{constants.AWS_PROJECT_NAME}-{AWS_CORE_FEATURE_NAME}',
|
||||
@@ -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,10 +4,18 @@ For complete copyright and license terms please see the LICENSE at the root of t
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
import pytest
|
||||
|
||||
import ly_test_tools.environment.file_system as file_system
|
||||
import editor_python_test_tools.hydra_test_utils as hydra
|
||||
|
||||
from ly_test_tools.o3de.editor_test import EditorSharedTest, EditorTestSuite
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
TEST_DIRECTORY = os.path.join(os.path.dirname(__file__), "tests")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("project", ["AutomatedTesting"])
|
||||
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
|
||||
@@ -39,10 +47,6 @@ class TestAutomation(EditorTestSuite):
|
||||
class AtomEditorComponents_DirectionalLightAdded(EditorSharedTest):
|
||||
from Atom.tests import hydra_AtomEditorComponents_DirectionalLightAdded as test_module
|
||||
|
||||
@pytest.mark.test_case_id("C36525660")
|
||||
class AtomEditorComponents_DisplayMapperAdded(EditorSharedTest):
|
||||
from Atom.tests import hydra_AtomEditorComponents_DisplayMapperAdded as test_module
|
||||
|
||||
@pytest.mark.test_case_id("C36525661")
|
||||
class AtomEditorComponents_EntityReferenceAdded(EditorSharedTest):
|
||||
from Atom.tests import hydra_AtomEditorComponents_EntityReferenceAdded as test_module
|
||||
@@ -118,3 +122,73 @@ class TestAutomation(EditorTestSuite):
|
||||
|
||||
class ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges(EditorSharedTest):
|
||||
from Atom.tests import hydra_ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges as test_module
|
||||
|
||||
|
||||
@pytest.mark.parametrize("project", ["AutomatedTesting"])
|
||||
@pytest.mark.parametrize("launcher_platform", ['windows_generic'])
|
||||
class TestMaterialEditorBasicTests(object):
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup_teardown(self, request, workspace, project):
|
||||
def delete_files():
|
||||
file_system.delete(
|
||||
[
|
||||
os.path.join(workspace.paths.project(), "Materials", "test_material.material"),
|
||||
os.path.join(workspace.paths.project(), "Materials", "test_material_1.material"),
|
||||
os.path.join(workspace.paths.project(), "Materials", "test_material_2.material"),
|
||||
],
|
||||
True,
|
||||
True,
|
||||
)
|
||||
# Cleanup our newly created materials
|
||||
delete_files()
|
||||
|
||||
def teardown():
|
||||
# Cleanup our newly created materials
|
||||
delete_files()
|
||||
|
||||
request.addfinalizer(teardown)
|
||||
|
||||
@pytest.mark.parametrize("exe_file_name", ["MaterialEditor"])
|
||||
@pytest.mark.test_case_id("C34448113") # Creating a New Asset.
|
||||
@pytest.mark.test_case_id("C34448114") # Opening an Existing Asset.
|
||||
@pytest.mark.test_case_id("C34448115") # Closing Selected Material.
|
||||
@pytest.mark.test_case_id("C34448116") # Closing All Materials.
|
||||
@pytest.mark.test_case_id("C34448117") # Closing all but Selected Material.
|
||||
@pytest.mark.test_case_id("C34448118") # Saving Material.
|
||||
@pytest.mark.test_case_id("C34448119") # Saving as a New Material.
|
||||
@pytest.mark.test_case_id("C34448120") # Saving as a Child Material.
|
||||
@pytest.mark.test_case_id("C34448121") # Saving all Open Materials.
|
||||
def test_MaterialEditorBasicTests(
|
||||
self, request, workspace, project, launcher_platform, generic_launcher, exe_file_name):
|
||||
|
||||
expected_lines = [
|
||||
"Material opened: True",
|
||||
"Test asset doesn't exist initially: True",
|
||||
"New asset created: True",
|
||||
"New Material opened: True",
|
||||
"Material closed: True",
|
||||
"All documents closed: True",
|
||||
"Close All Except Selected worked as expected: True",
|
||||
"Actual Document saved with changes: True",
|
||||
"Document saved as copy is saved with changes: True",
|
||||
"Document saved as child is saved with changes: True",
|
||||
"Save All worked as expected: True",
|
||||
]
|
||||
unexpected_lines = [
|
||||
"Traceback (most recent call last):"
|
||||
]
|
||||
|
||||
hydra.launch_and_validate_results(
|
||||
request,
|
||||
TEST_DIRECTORY,
|
||||
generic_launcher,
|
||||
"hydra_AtomMaterialEditor_BasicTests.py",
|
||||
run_python="--runpython",
|
||||
timeout=43,
|
||||
expected_lines=expected_lines,
|
||||
unexpected_lines=unexpected_lines,
|
||||
halt_on_unexpected=True,
|
||||
null_renderer=True,
|
||||
log_file_name="MaterialEditor.log",
|
||||
enable_prefab_system=False,
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
@@ -12,19 +12,13 @@ import pytest
|
||||
import ly_test_tools.environment.file_system as file_system
|
||||
import editor_python_test_tools.hydra_test_utils as hydra
|
||||
|
||||
from ly_test_tools.o3de.editor_test import EditorSharedTest, EditorTestSuite
|
||||
from Atom.atom_utils.atom_constants import LIGHT_TYPES
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
TEST_DIRECTORY = os.path.join(os.path.dirname(__file__), "tests")
|
||||
|
||||
|
||||
class TestAtomEditorComponentsSandbox(object):
|
||||
|
||||
# It requires at least one test
|
||||
def test_Dummy(self, request, editor, level, workspace, project, launcher_platform):
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.parametrize("project", ["AutomatedTesting"])
|
||||
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
|
||||
@pytest.mark.parametrize("level", ["auto_test"])
|
||||
@@ -90,72 +84,22 @@ class TestAtomEditorComponentsMain(object):
|
||||
|
||||
|
||||
@pytest.mark.parametrize("project", ["AutomatedTesting"])
|
||||
@pytest.mark.parametrize("launcher_platform", ['windows_generic'])
|
||||
@pytest.mark.system
|
||||
class TestMaterialEditorBasicTests(object):
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup_teardown(self, request, workspace, project):
|
||||
def delete_files():
|
||||
file_system.delete(
|
||||
[
|
||||
os.path.join(workspace.paths.project(), "Materials", "test_material.material"),
|
||||
os.path.join(workspace.paths.project(), "Materials", "test_material_1.material"),
|
||||
os.path.join(workspace.paths.project(), "Materials", "test_material_2.material"),
|
||||
],
|
||||
True,
|
||||
True,
|
||||
)
|
||||
# Cleanup our newly created materials
|
||||
delete_files()
|
||||
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
|
||||
class TestAutomation(EditorTestSuite):
|
||||
|
||||
def teardown():
|
||||
# Cleanup our newly created materials
|
||||
delete_files()
|
||||
enable_prefab_system = False
|
||||
|
||||
request.addfinalizer(teardown)
|
||||
#this test is intermittently timing out without ever having executed. sandboxing while we investigate cause.
|
||||
@pytest.mark.test_case_id("C36525660")
|
||||
class AtomEditorComponents_DisplayMapperAdded(EditorSharedTest):
|
||||
from Atom.tests import hydra_AtomEditorComponents_DisplayMapperAdded as test_module
|
||||
|
||||
@pytest.mark.parametrize("exe_file_name", ["MaterialEditor"])
|
||||
@pytest.mark.test_case_id("C34448113") # Creating a New Asset.
|
||||
@pytest.mark.test_case_id("C34448114") # Opening an Existing Asset.
|
||||
@pytest.mark.test_case_id("C34448115") # Closing Selected Material.
|
||||
@pytest.mark.test_case_id("C34448116") # Closing All Materials.
|
||||
@pytest.mark.test_case_id("C34448117") # Closing all but Selected Material.
|
||||
@pytest.mark.test_case_id("C34448118") # Saving Material.
|
||||
@pytest.mark.test_case_id("C34448119") # Saving as a New Material.
|
||||
@pytest.mark.test_case_id("C34448120") # Saving as a Child Material.
|
||||
@pytest.mark.test_case_id("C34448121") # Saving all Open Materials.
|
||||
def test_MaterialEditorBasicTests(
|
||||
self, request, workspace, project, launcher_platform, generic_launcher, exe_file_name):
|
||||
|
||||
expected_lines = [
|
||||
"Material opened: True",
|
||||
"Test asset doesn't exist initially: True",
|
||||
"New asset created: True",
|
||||
"New Material opened: True",
|
||||
"Material closed: True",
|
||||
"All documents closed: True",
|
||||
"Close All Except Selected worked as expected: True",
|
||||
"Actual Document saved with changes: True",
|
||||
"Document saved as copy is saved with changes: True",
|
||||
"Document saved as child is saved with changes: True",
|
||||
"Save All worked as expected: True",
|
||||
]
|
||||
unexpected_lines = [
|
||||
"Traceback (most recent call last):"
|
||||
]
|
||||
|
||||
hydra.launch_and_validate_results(
|
||||
request,
|
||||
TEST_DIRECTORY,
|
||||
generic_launcher,
|
||||
"hydra_AtomMaterialEditor_BasicTests.py",
|
||||
run_python="--runpython",
|
||||
timeout=120,
|
||||
expected_lines=expected_lines,
|
||||
unexpected_lines=unexpected_lines,
|
||||
halt_on_unexpected=True,
|
||||
null_renderer=True,
|
||||
log_file_name="MaterialEditor.log",
|
||||
enable_prefab_system=False,
|
||||
)
|
||||
# this test causes editor to crash when using slices. once automation transitions to prefabs it should pass
|
||||
@pytest.mark.test_case_id("C36529666")
|
||||
class AtomEditorComponentsLevel_DiffuseGlobalIlluminationAdded(EditorSharedTest):
|
||||
from Atom.tests import hydra_AtomEditorComponentsLevel_DiffuseGlobalIlluminationAdded as test_module
|
||||
|
||||
# this test causes editor to crash when using slices. once automation transitions to prefabs it should pass
|
||||
@pytest.mark.test_case_id("C36525660")
|
||||
class AtomEditorComponentsLevel_DisplayMapperAdded(EditorSharedTest):
|
||||
from Atom.tests import hydra_AtomEditorComponentsLevel_DisplayMapperAdded as test_module
|
||||
|
||||
@@ -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.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
|
||||
import azlmbr.math as math
|
||||
import azlmbr.object
|
||||
|
||||
import editor_python_test_tools.hydra_editor_utils as hydra
|
||||
from editor_python_test_tools.editor_test_helper import EditorTestHelper
|
||||
|
||||
helper = EditorTestHelper(log_prefix="Atom_EditorTestHelper")
|
||||
|
||||
# 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")
|
||||
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
|
||||
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)
|
||||
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.legacy.general as general
|
||||
|
||||
# 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]
|
||||
)
|
||||
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.paths
|
||||
|
||||
from editor_python_test_tools.asset_utils import Asset
|
||||
from editor_python_test_tools.editor_entity_utils import EditorEntity
|
||||
|
||||
from Atom.atom_utils.atom_constants import AtomComponentProperties
|
||||
|
||||
DEGREE_RADIAN_FACTOR = 0.0174533
|
||||
|
||||
# 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)
|
||||
|
||||
# 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)
|
||||
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)
|
||||
|
||||
# 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)
|
||||
# 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)
|
||||
|
||||
# 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]
|
||||
)
|
||||
# 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)
|
||||
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)
|
||||
|
||||
# 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)
|
||||
# 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)
|
||||
|
||||
@@ -19,6 +19,20 @@ LIGHT_TYPES = {
|
||||
}
|
||||
|
||||
|
||||
# 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,
|
||||
'Medium': 1,
|
||||
'High': 2,
|
||||
}
|
||||
|
||||
|
||||
class AtomComponentProperties:
|
||||
"""
|
||||
Holds Atom component related constants
|
||||
@@ -116,6 +130,21 @@ class AtomComponentProperties:
|
||||
}
|
||||
return properties[property]
|
||||
|
||||
@staticmethod
|
||||
def diffuse_global_illumination(property: str = 'name') -> str:
|
||||
"""
|
||||
Diffuse Global Illumination level component properties.
|
||||
Controls global settings for Diffuse Probe Grid components.
|
||||
- 'Quality Level' from atom_constants.py GLOBAL_ILLUMINATION_QUALITY
|
||||
: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': 'Diffuse Global Illumination',
|
||||
'Quality Level': 'Controller|Configuration|Quality Level'
|
||||
}
|
||||
return properties[property]
|
||||
|
||||
@staticmethod
|
||||
def diffuse_probe_grid(property: str = 'name') -> str:
|
||||
"""
|
||||
@@ -148,7 +177,8 @@ class AtomComponentProperties:
|
||||
@staticmethod
|
||||
def display_mapper(property: str = 'name') -> str:
|
||||
"""
|
||||
Display Mapper component properties.
|
||||
Display Mapper level component properties.
|
||||
- 'Enable LDR color grading LUT' toggles the use of LDR color grading LUT
|
||||
- 'LDR color Grading LUT' is the Low Definition Range (LDR) color grading for Look-up Textures (LUT) which is
|
||||
an Asset.id value corresponding to a lighting asset file.
|
||||
:param property: From the last element of the property tree path. Default 'name' for component name string.
|
||||
@@ -156,6 +186,7 @@ class AtomComponentProperties:
|
||||
"""
|
||||
properties = {
|
||||
'name': 'Display Mapper',
|
||||
'Enable LDR color grading LUT': 'Controller|Configuration|Enable LDR color grading LUT',
|
||||
'LDR color Grading LUT': 'Controller|Configuration|LDR color Grading LUT',
|
||||
}
|
||||
return properties[property]
|
||||
@@ -252,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]
|
||||
|
||||
|
||||
@@ -162,6 +162,13 @@ def select_model_config(configname):
|
||||
azlmbr.materialeditor.MaterialViewportRequestBus(azlmbr.bus.Broadcast, "SelectModelPresetByName", configname)
|
||||
|
||||
|
||||
def destroy_main_window():
|
||||
"""
|
||||
Closes the Material Editor window
|
||||
"""
|
||||
azlmbr.atomtools.AtomToolsMainWindowFactoryRequestBus(azlmbr.bus.Broadcast, "DestroyMainWindow")
|
||||
|
||||
|
||||
def wait_for_condition(function, timeout_in_seconds=1.0):
|
||||
# type: (function, float) -> bool
|
||||
"""
|
||||
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
"""
|
||||
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:
|
||||
creation_undo = (
|
||||
"UNDO Level component addition success",
|
||||
"UNDO Level component addition failed")
|
||||
creation_redo = (
|
||||
"REDO Level component addition success",
|
||||
"REDO Level component addition failed")
|
||||
diffuse_global_illumination_component = (
|
||||
"Level has a Diffuse Global Illumination component",
|
||||
"Level failed to find Diffuse Global Illumination component")
|
||||
diffuse_global_illumination_quality = (
|
||||
"Quality Level set",
|
||||
"Quality Level could not be set")
|
||||
enter_game_mode = (
|
||||
"Entered game mode",
|
||||
"Failed to enter game mode")
|
||||
exit_game_mode = (
|
||||
"Exited game mode",
|
||||
"Couldn't exit game mode")
|
||||
|
||||
|
||||
def AtomEditorComponentsLevel_DiffuseGlobalIllumination_AddedToEntity():
|
||||
"""
|
||||
Summary:
|
||||
Tests the Diffuse Global Illumination level component can be added to the level entity and is stable.
|
||||
|
||||
Test setup:
|
||||
- Wait for Editor idle loop.
|
||||
- Open the "Base" level.
|
||||
|
||||
Expected Behavior:
|
||||
The component can be added, used in game mode, and has accurate required components.
|
||||
Creation and deletion undo/redo should also work.
|
||||
|
||||
Test Steps:
|
||||
1) Add Diffuse Global Illumination level component to the level entity.
|
||||
2) UNDO the level component addition.
|
||||
3) REDO the level component addition.
|
||||
4) Set Quality Level property to Low
|
||||
5) Enter/Exit game mode.
|
||||
6) Look for errors and asserts.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
|
||||
import azlmbr.legacy.general as general
|
||||
|
||||
from editor_python_test_tools.editor_entity_utils import EditorLevelEntity
|
||||
from editor_python_test_tools.utils import Report, Tracer, TestHelper
|
||||
from Atom.atom_utils.atom_constants import AtomComponentProperties, GLOBAL_ILLUMINATION_QUALITY
|
||||
|
||||
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")
|
||||
|
||||
# Test steps begin.
|
||||
# 1. Add Diffuse Global Illumination level component to the level entity.
|
||||
diffuse_global_illumination_component = EditorLevelEntity.add_component(
|
||||
AtomComponentProperties.diffuse_global_illumination())
|
||||
Report.critical_result(
|
||||
Tests.diffuse_global_illumination_component,
|
||||
EditorLevelEntity.has_component(AtomComponentProperties.diffuse_global_illumination()))
|
||||
|
||||
# 2. UNDO the level component addition.
|
||||
# -> UNDO component addition.
|
||||
general.undo()
|
||||
general.idle_wait_frames(1)
|
||||
Report.result(Tests.creation_undo,
|
||||
not EditorLevelEntity.has_component(AtomComponentProperties.diffuse_global_illumination()))
|
||||
|
||||
# 3. REDO the level component addition.
|
||||
# -> REDO component addition.
|
||||
general.redo()
|
||||
general.idle_wait_frames(1)
|
||||
Report.result(Tests.creation_redo,
|
||||
EditorLevelEntity.has_component(AtomComponentProperties.diffuse_global_illumination()))
|
||||
|
||||
# 4. Set Quality Level property to Low
|
||||
diffuse_global_illumination_component.set_component_property_value(
|
||||
AtomComponentProperties.diffuse_global_illumination('Quality Level', GLOBAL_ILLUMINATION_QUALITY['Low']))
|
||||
quality = diffuse_global_illumination_component.get_component_property_value(
|
||||
AtomComponentProperties.diffuse_global_illumination('Quality Level'))
|
||||
Report.result(diffuse_global_illumination_quality, quality == GLOBAL_ILLUMINATION_QUALITY['Low'])
|
||||
|
||||
# 5. Enter/Exit game mode.
|
||||
TestHelper.enter_game_mode(Tests.enter_game_mode)
|
||||
general.idle_wait_frames(1)
|
||||
TestHelper.exit_game_mode(Tests.exit_game_mode)
|
||||
|
||||
# 6. Look for errors and asserts.
|
||||
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(AtomEditorComponentsLevel_DiffuseGlobalIllumination_AddedToEntity)
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
"""
|
||||
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:
|
||||
creation_undo = (
|
||||
"UNDO level component addition success",
|
||||
"UNDO level component addition failed")
|
||||
creation_redo = (
|
||||
"REDO Level component addition success",
|
||||
"REDO Level component addition failed")
|
||||
display_mapper_component = (
|
||||
"Level has a Display Mapper component",
|
||||
"Level failed to find Display Mapper component")
|
||||
ldr_color_grading_lut = (
|
||||
"LDR color Grading LUT asset set",
|
||||
"LDR color Grading LUT asset could not be set")
|
||||
enable_ldr_color_grading_lut = (
|
||||
"Enable LDR color grading LUT set",
|
||||
"Enable LDR color grading LUT could not be set")
|
||||
enter_game_mode = (
|
||||
"Entered game mode",
|
||||
"Failed to enter game mode")
|
||||
exit_game_mode = (
|
||||
"Exited game mode",
|
||||
"Couldn't exit game mode")
|
||||
|
||||
|
||||
def AtomEditorComponentsLevel_DisplayMapper_AddedToEntity():
|
||||
"""
|
||||
Summary:
|
||||
Tests the Display Mapper level component can be added to the level entity and has the expected functionality.
|
||||
|
||||
Test setup:
|
||||
- Wait for Editor idle loop.
|
||||
- Open the "Base" level.
|
||||
|
||||
Expected Behavior:
|
||||
The component can be added, used in game mode, and has accurate required components.
|
||||
Creation and deletion undo/redo should also work.
|
||||
|
||||
Test Steps:
|
||||
1) Add Display Mapper level component to the level entity.
|
||||
2) UNDO the level component addition.
|
||||
3) REDO the level component addition.
|
||||
4) Set LDR color Grading LUT asset.
|
||||
5) Set Enable LDR color grading LUT property True
|
||||
6) Enter/Exit game mode.
|
||||
7) Look for errors and asserts.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
import os
|
||||
|
||||
import azlmbr.legacy.general as general
|
||||
|
||||
from editor_python_test_tools.asset_utils import Asset
|
||||
from editor_python_test_tools.editor_entity_utils import EditorLevelEntity
|
||||
from editor_python_test_tools.utils import Report, Tracer, TestHelper
|
||||
from Atom.atom_utils.atom_constants import AtomComponentProperties
|
||||
|
||||
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")
|
||||
|
||||
# Test steps begin.
|
||||
# 1. Add Display Mapper level component to the level entity.
|
||||
display_mapper_component = EditorLevelEntity.add_component(AtomComponentProperties.display_mapper())
|
||||
Report.critical_result(
|
||||
Tests.display_mapper_component,
|
||||
EditorLevelEntity.has_component(AtomComponentProperties.display_mapper()))
|
||||
|
||||
# 2. UNDO the level component addition.
|
||||
# -> UNDO component addition.
|
||||
general.undo()
|
||||
general.idle_wait_frames(1)
|
||||
Report.result(Tests.creation_undo, not EditorLevelEntity.has_component(AtomComponentProperties.display_mapper()))
|
||||
|
||||
# 3. REDO the level component addition.
|
||||
# -> REDO component addition.
|
||||
general.redo()
|
||||
general.idle_wait_frames(1)
|
||||
Report.result(Tests.creation_redo, EditorLevelEntity.has_component(AtomComponentProperties.display_mapper()))
|
||||
|
||||
# 4. Set LDR color Grading LUT asset.
|
||||
display_mapper_asset_path = os.path.join("TestData", "test.lightingpreset.azasset")
|
||||
display_mapper_asset = Asset.find_asset_by_path(display_mapper_asset_path, False)
|
||||
display_mapper_component.set_component_property_value(
|
||||
AtomComponentProperties.display_mapper('LDR color Grading LUT'), display_mapper_asset.id)
|
||||
Report.result(
|
||||
Tests.ldr_color_grading_lut,
|
||||
display_mapper_component.get_component_property_value(
|
||||
AtomComponentProperties.display_mapper('LDR color Grading LUT')) == display_mapper_asset.id)
|
||||
|
||||
# 5. Set Enable LDR color grading LUT property True
|
||||
display_mapper_component.set_component_property_value(
|
||||
AtomComponentProperties.display_mapper('Enable LDR color grading LUT'), True)
|
||||
Report.result(
|
||||
Test.enable_ldr_color_grading_lut,
|
||||
display_mapper_component.get_component_property_value(
|
||||
AtomComponentProperties.display_mapper('Enable LDR color grading LUT')) is True)
|
||||
|
||||
# 6. Enter/Exit game mode.
|
||||
TestHelper.enter_game_mode(Tests.enter_game_mode)
|
||||
general.idle_wait_frames(1)
|
||||
TestHelper.exit_game_mode(Tests.exit_game_mode)
|
||||
|
||||
# 7. Look for errors and asserts.
|
||||
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(AtomEditorComponentsLevel_DisplayMapper_AddedToEntity)
|
||||
+39
-36
@@ -7,15 +7,6 @@ SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
|
||||
|
||||
class Tests:
|
||||
camera_creation = (
|
||||
"Camera Entity successfully created",
|
||||
"Camera Entity failed to be created")
|
||||
camera_component_added = (
|
||||
"Camera component was added to entity",
|
||||
"Camera component failed to be added to entity")
|
||||
camera_component_check = (
|
||||
"Entity has a Camera component",
|
||||
"Entity failed to find Camera component")
|
||||
creation_undo = (
|
||||
"UNDO Entity creation success",
|
||||
"UNDO Entity creation failed")
|
||||
@@ -43,6 +34,9 @@ class Tests:
|
||||
ldr_color_grading_lut = (
|
||||
"LDR color Grading LUT asset set",
|
||||
"LDR color Grading LUT asset could not be set")
|
||||
enable_ldr_color_grading_lut = (
|
||||
"Enable LDR color grading LUT set",
|
||||
"Enable LDR color grading LUT could not be set")
|
||||
entity_deleted = (
|
||||
"Entity deleted",
|
||||
"Entity was not deleted")
|
||||
@@ -72,14 +66,15 @@ def AtomEditorComponents_DisplayMapper_AddedToEntity():
|
||||
2) Add Display Mapper component to Display Mapper entity.
|
||||
3) UNDO the entity creation and component addition.
|
||||
4) REDO the entity creation and component addition.
|
||||
5) Enter/Exit game mode.
|
||||
6) Test IsHidden.
|
||||
7) Test IsVisible.
|
||||
8) Set LDR color Grading LUT asset.
|
||||
9) Delete Display Mapper entity.
|
||||
10) UNDO deletion.
|
||||
11) REDO deletion.
|
||||
12) Look for errors and asserts.
|
||||
5) Set LDR color Grading LUT asset.
|
||||
6) Set Enable LDR color grading LUT property True
|
||||
7) Enter/Exit game mode.
|
||||
8) Test IsHidden.
|
||||
9) Test IsVisible.
|
||||
10) Delete Display Mapper entity.
|
||||
11) UNDO deletion.
|
||||
12) REDO deletion.
|
||||
13) Look for errors and asserts.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
@@ -133,21 +128,7 @@ def AtomEditorComponents_DisplayMapper_AddedToEntity():
|
||||
general.idle_wait_frames(1)
|
||||
Report.result(Tests.creation_redo, display_mapper_entity.exists())
|
||||
|
||||
# 5. Enter/Exit game mode.
|
||||
TestHelper.enter_game_mode(Tests.enter_game_mode)
|
||||
general.idle_wait_frames(1)
|
||||
TestHelper.exit_game_mode(Tests.exit_game_mode)
|
||||
|
||||
# 6. Test IsHidden.
|
||||
display_mapper_entity.set_visibility_state(False)
|
||||
Report.result(Tests.is_hidden, display_mapper_entity.is_hidden() is True)
|
||||
|
||||
# 7. Test IsVisible.
|
||||
display_mapper_entity.set_visibility_state(True)
|
||||
general.idle_wait_frames(1)
|
||||
Report.result(Tests.is_visible, display_mapper_entity.is_visible() is True)
|
||||
|
||||
# 8. Set LDR color Grading LUT asset.
|
||||
# 5. Set LDR color Grading LUT asset.
|
||||
display_mapper_asset_path = os.path.join("TestData", "test.lightingpreset.azasset")
|
||||
display_mapper_asset = Asset.find_asset_by_path(display_mapper_asset_path, False)
|
||||
display_mapper_component.set_component_property_value(
|
||||
@@ -157,19 +138,41 @@ def AtomEditorComponents_DisplayMapper_AddedToEntity():
|
||||
display_mapper_component.get_component_property_value(
|
||||
AtomComponentProperties.display_mapper("LDR color Grading LUT")) == display_mapper_asset.id)
|
||||
|
||||
# 9. Delete Display Mapper entity.
|
||||
# 6. Set Enable LDR color grading LUT property True
|
||||
display_mapper_component.set_component_property_value(
|
||||
AtomComponentProperties.display_mapper('Enable LDR color grading LUT'), True)
|
||||
Report.result(
|
||||
Tests.enable_ldr_color_grading_lut,
|
||||
display_mapper_component.get_component_property_value(
|
||||
AtomComponentProperties.display_mapper('Enable LDR color grading LUT')) is True)
|
||||
|
||||
# 7. Enter/Exit game mode.
|
||||
TestHelper.enter_game_mode(Tests.enter_game_mode)
|
||||
general.idle_wait_frames(1)
|
||||
TestHelper.exit_game_mode(Tests.exit_game_mode)
|
||||
|
||||
# 8. Test IsHidden.
|
||||
display_mapper_entity.set_visibility_state(False)
|
||||
Report.result(Tests.is_hidden, display_mapper_entity.is_hidden() is True)
|
||||
|
||||
# 9. Test IsVisible.
|
||||
display_mapper_entity.set_visibility_state(True)
|
||||
general.idle_wait_frames(1)
|
||||
Report.result(Tests.is_visible, display_mapper_entity.is_visible() is True)
|
||||
|
||||
# 10. Delete Display Mapper entity.
|
||||
display_mapper_entity.delete()
|
||||
Report.result(Tests.entity_deleted, not display_mapper_entity.exists())
|
||||
|
||||
# 10. UNDO deletion.
|
||||
# 11. UNDO deletion.
|
||||
general.undo()
|
||||
Report.result(Tests.deletion_undo, display_mapper_entity.exists())
|
||||
|
||||
# 11. REDO deletion.
|
||||
# 12. REDO deletion.
|
||||
general.redo()
|
||||
Report.result(Tests.deletion_redo, not display_mapper_entity.exists())
|
||||
|
||||
# 12. Look for errors and asserts.
|
||||
# 13. Look for errors and asserts.
|
||||
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}")
|
||||
|
||||
@@ -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)
|
||||
@@ -186,6 +186,11 @@ def run():
|
||||
material_editor.set_property(document2_id, property2_name, initial_color)
|
||||
material_editor.save_all()
|
||||
material_editor.close_all_documents()
|
||||
material_editor.wait_for_condition(lambda:
|
||||
(not material_editor.is_open(document1_id)) and
|
||||
(not material_editor.is_open(document2_id)) and
|
||||
(not material_editor.is_open(document3_id)), 2.0)
|
||||
material_editor.destroy_main_window()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -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()
|
||||
+112
-8
@@ -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:
|
||||
"""
|
||||
@@ -107,7 +114,6 @@ class EditorComponent:
|
||||
return type_ids
|
||||
|
||||
|
||||
|
||||
def convert_to_azvector3(xyz) -> azlmbr.math.Vector3:
|
||||
"""
|
||||
Converts a vector3-like element into a azlmbr.math.Vector3
|
||||
@@ -120,6 +126,7 @@ def convert_to_azvector3(xyz) -> azlmbr.math.Vector3:
|
||||
else:
|
||||
raise ValueError("vector must be a 3 element list/tuple or azlmbr.math.Vector3")
|
||||
|
||||
|
||||
class EditorEntity:
|
||||
"""
|
||||
Entity class is used to create and interact with Editor Entities.
|
||||
@@ -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)
|
||||
@@ -470,3 +478,99 @@ class EditorEntity:
|
||||
assert self.id.isValid(), "A valid entity id is required to focus on its owning prefab."
|
||||
focus_prefab_result = azlmbr.prefab.PrefabFocusPublicRequestBus(bus.Broadcast, "FocusOnOwningPrefab", self.id)
|
||||
assert focus_prefab_result.IsSuccess(), f"Prefab operation 'FocusOnOwningPrefab' failed. Error: {focus_prefab_result.GetError()}"
|
||||
|
||||
|
||||
class EditorLevelEntity:
|
||||
"""
|
||||
EditorLevel class used to add and fetch level components.
|
||||
Level entity is a special entity that you do not create/destroy independently of larger systems of level creation.
|
||||
This collects a number of staticmethods that do not rely on entityId since Level entity is found internally by
|
||||
EditorLevelComponentAPIBus requests.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def get_type_ids(component_names: list) -> list:
|
||||
"""
|
||||
Used to get type ids of given components list for EntityType Level
|
||||
:param: component_names: List of components to get type ids
|
||||
:return: List of type ids of given components.
|
||||
"""
|
||||
type_ids = editor.EditorComponentAPIBus(
|
||||
bus.Broadcast, "FindComponentTypeIdsByEntityType", component_names, azlmbr.entity.EntityType().Level
|
||||
)
|
||||
return type_ids
|
||||
|
||||
@staticmethod
|
||||
def add_component(component_name: str) -> EditorComponent:
|
||||
"""
|
||||
Used to add new component to Level.
|
||||
:param component_name: String of component name to add.
|
||||
:return: Component object of newly added component.
|
||||
"""
|
||||
component = EditorLevelEntity.add_components([component_name])[0]
|
||||
return component
|
||||
|
||||
@staticmethod
|
||||
def add_components(component_names: list) -> List[EditorComponent]:
|
||||
"""
|
||||
Used to add multiple components
|
||||
:param: component_names: List of components to add to level
|
||||
:return: List of newly added components to the level
|
||||
"""
|
||||
components = []
|
||||
type_ids = EditorLevelEntity.get_type_ids(component_names)
|
||||
for type_id in type_ids:
|
||||
new_comp = EditorComponent()
|
||||
new_comp.type_id = type_id
|
||||
add_component_outcome = editor.EditorLevelComponentAPIBus(
|
||||
bus.Broadcast, "AddComponentsOfType", [type_id]
|
||||
)
|
||||
assert (
|
||||
add_component_outcome.IsSuccess()
|
||||
), f"Failure: Could not add component: '{new_comp.get_component_name()}' to level"
|
||||
new_comp.id = add_component_outcome.GetValue()[0]
|
||||
components.append(new_comp)
|
||||
return components
|
||||
|
||||
@staticmethod
|
||||
def get_components_of_type(component_names: list) -> List[EditorComponent]:
|
||||
"""
|
||||
Used to get components of type component_name that already exists on the level
|
||||
:param component_names: List of names of components to check
|
||||
:return: List of Level Component objects of given component name
|
||||
"""
|
||||
component_list = []
|
||||
type_ids = EditorLevelEntity.get_type_ids(component_names)
|
||||
for type_id in type_ids:
|
||||
component = EditorComponent()
|
||||
component.type_id = type_id
|
||||
get_component_of_type_outcome = editor.EditorLevelComponentAPIBus(
|
||||
bus.Broadcast, "GetComponentOfType", type_id
|
||||
)
|
||||
assert (
|
||||
get_component_of_type_outcome.IsSuccess()
|
||||
), f"Failure: Level does not have component:'{component.get_component_name()}'"
|
||||
component.id = get_component_of_type_outcome.GetValue()
|
||||
component_list.append(component)
|
||||
|
||||
return component_list
|
||||
|
||||
@staticmethod
|
||||
def has_component(component_name: str) -> bool:
|
||||
"""
|
||||
Used to verify if the level has the specified component
|
||||
:param component_name: Name of component to check for
|
||||
:return: True, if level has specified component. Else, False
|
||||
"""
|
||||
type_ids = EditorLevelEntity.get_type_ids([component_name])
|
||||
return editor.EditorLevelComponentAPIBus(bus.Broadcast, "HasComponentOfType", type_ids[0])
|
||||
|
||||
@staticmethod
|
||||
def count_components_of_type(component_name: str) -> int:
|
||||
"""
|
||||
Used to get a count of the specified level component attached to the level
|
||||
:param component_name: Name of component to check for
|
||||
:return: integer count of occurences of level component attached to level or zero if none are present
|
||||
"""
|
||||
type_ids = EditorLevelEntity.get_type_ids([component_name])
|
||||
return editor.EditorLevelComponentAPIBus(bus.Broadcast, "CountComponentsOfType", type_ids[0])
|
||||
|
||||
+36
-3
@@ -5,15 +5,22 @@ For complete copyright and license terms please see the LICENSE at the root of t
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
|
||||
from typing import List
|
||||
from math import isclose
|
||||
import collections.abc
|
||||
|
||||
import azlmbr.bus as bus
|
||||
import azlmbr.editor as editor
|
||||
import azlmbr.entity as entity
|
||||
import azlmbr.legacy.general as general
|
||||
import azlmbr.object
|
||||
|
||||
from typing import List
|
||||
from math import isclose
|
||||
import collections.abc
|
||||
from editor_python_test_tools.utils import TestHelper as helper
|
||||
|
||||
|
||||
def open_base_level():
|
||||
helper.init_idle()
|
||||
helper.open_level("Prefab", "Base")
|
||||
|
||||
|
||||
def find_entity_by_name(entity_name):
|
||||
@@ -46,6 +53,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 +164,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):
|
||||
"""
|
||||
|
||||
+4
-1
@@ -58,7 +58,10 @@ def launch_and_validate_results(request, test_directory, editor, editor_script,
|
||||
if null_renderer:
|
||||
editor.args.extend(["-rhi=Null"])
|
||||
if enable_prefab_system:
|
||||
editor.args.extend(["--regset=/Amazon/Preferences/EnablePrefabSystem=true"])
|
||||
from os import path
|
||||
editor.args.extend([
|
||||
"--regset=/Amazon/Preferences/EnablePrefabSystem=true",
|
||||
f"--regset-file={os.path.join(workspace.paths.engine_root(), 'Registry', 'prefab.test.setreg')}"])
|
||||
else:
|
||||
editor.args.extend(["--regset=/Amazon/Preferences/EnablePrefabSystem=false"])
|
||||
|
||||
|
||||
-1
@@ -39,7 +39,6 @@ def get_prefab_file_path(prefab_path):
|
||||
prefab_path = name + ".prefab"
|
||||
return prefab_path
|
||||
|
||||
|
||||
def get_all_entity_ids():
|
||||
return entity.SearchBus(bus.Broadcast, 'SearchEntities', entity.SearchFilter())
|
||||
|
||||
|
||||
+54
-26
@@ -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,31 +101,56 @@ 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 find_line(window, line, print_infos):
|
||||
"""
|
||||
Looks for an expected line in a list of tracer log lines
|
||||
:param window: The log's window name. For example, logs printed via script-canvas use the "Script" window.
|
||||
:param line: The log message to search for.
|
||||
:param print_infos: A list of PrintInfos collected by Tracer to search. Example options: your_tracer.warnings, your_tracer.errors, your_tracer.asserts, or your_tracer.prints
|
||||
|
||||
:return: True if the line is found, otherwise false.
|
||||
"""
|
||||
for printInfo in print_infos:
|
||||
if printInfo.window == window.strip() and printInfo.message.strip() == line:
|
||||
return True
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def succeed_if_log_line_found(window, line, print_infos, time_out):
|
||||
"""
|
||||
Looks for a line in a list of tracer log lines and reports success if found.
|
||||
:param window: The log's window name. For example, logs printed via script-canvas use the "Script" window.
|
||||
:param line: The log message we're hoping to find.
|
||||
:param print_infos: A list of PrintInfos collected by Tracer to search. Example options: your_tracer.warnings, your_tracer.errors, your_tracer.asserts, or your_tracer.prints
|
||||
:param time_out: The total amount of time to wait before giving up looking for the expected line.
|
||||
|
||||
:return: No return value, but if the message is found, a successful critical result is reported; otherwise failure.
|
||||
"""
|
||||
TestHelper.wait_for_condition(lambda : TestHelper.find_line(window, line, print_infos), time_out)
|
||||
Report.critical_result(("Found expected line: " + line, "Failed to find expected line: " + line), TestHelper.find_line(window, line, print_infos))
|
||||
|
||||
@staticmethod
|
||||
def fail_if_log_line_found(window, line, print_infos, time_out):
|
||||
"""
|
||||
Reports a failure if a log line in a list of tracer log lines is found.
|
||||
:param window: The log's window name. For example, logs printed via script-canvas use the "Script" window.
|
||||
:param line: The log message we're hoping to not find.
|
||||
:param print_infos: A list of PrintInfos collected by Tracer to search. Example options: your_tracer.warnings, your_tracer.errors, your_tracer.asserts, or your_tracer.prints
|
||||
:param time_out: The total amount of time to wait before giving up looking for the unexpected line. If time runs out and we don't see the unexpected line then report a success.
|
||||
|
||||
:return: No return value, but if the line is found, a failed critical result is reported; otherwise success.
|
||||
"""
|
||||
TestHelper.wait_for_condition(lambda : TestHelper.find_line(window, line, print_infos), time_out)
|
||||
Report.critical_result(("Unexpected line not found: " + line, "Unexpected line found: " + line), not TestHelper.find_line(window, line, print_infos))
|
||||
|
||||
@staticmethod
|
||||
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"
|
||||
|
||||
:return: None
|
||||
"""
|
||||
|
||||
# looks for an expected line in a list of tracers lines
|
||||
# lines: the tracer list of lines to search. options are section_tracer.warnings, section_tracer.errors, section_tracer.asserts, section_tracer.prints
|
||||
# return: true if the line is found, otherwise false
|
||||
def find_expected_line(expected_line, lines):
|
||||
found_lines = [printInfo.message.strip() for printInfo in lines]
|
||||
return expected_line in found_lines
|
||||
|
||||
def wait_for_critical_expected_line(expected_line, lines, time_out):
|
||||
TestHelper.wait_for_condition(lambda : find_expected_line(expected_line, lines), time_out)
|
||||
Report.critical_result(("Found expected line: " + expected_line, "Failed to find expected line: " + expected_line), find_expected_line(expected_line, lines))
|
||||
|
||||
def wait_for_critical_unexpected_line(unexpected_line, lines, time_out):
|
||||
TestHelper.wait_for_condition(lambda : find_expected_line(unexpected_line, lines), time_out)
|
||||
Report.critical_result(("Unexpected line not found: " + unexpected_line, "Unexpected line found: " + unexpected_line), not find_expected_line(unexpected_line, lines))
|
||||
|
||||
|
||||
Report.info("Entering game mode")
|
||||
if sv_default_player_spawn_asset :
|
||||
general.set_cvar("sv_defaultPlayerSpawnAsset", sv_default_player_spawn_asset)
|
||||
@@ -137,16 +161,20 @@ class TestHelper:
|
||||
multiplayer.PythonEditorFuncs_enter_game_mode()
|
||||
|
||||
# make sure the server launcher binary exists
|
||||
wait_for_critical_unexpected_line("LaunchEditorServer failed! The ServerLauncher binary is missing!", section_tracer.errors, 0.5)
|
||||
TestHelper.fail_if_log_line_found("MultiplayerEditor", "LaunchEditorServer failed! The ServerLauncher binary is missing!", section_tracer.errors, 0.5)
|
||||
|
||||
# make sure the server launcher is running
|
||||
waiter.wait_for(lambda: process_utils.process_exists("AutomatedTesting.ServerLauncher", ignore_extensions=True), timeout=5.0, exc=AssertionError("AutomatedTesting.ServerLauncher has NOT launched!"), interval=1.0)
|
||||
|
||||
# make sure the editor connects to the editor-server and sends the level data packet
|
||||
wait_for_critical_expected_line("Editor is sending the editor-server the level data packet.", section_tracer.prints, 5.0)
|
||||
TestHelper.succeed_if_log_line_found("EditorServer", "MultiplayerEditorConnection: Editor-server activation has found and connected to the editor.", section_tracer.prints, 15.0)
|
||||
|
||||
# make sure the editor finally connects to the editor-server network simulation
|
||||
wait_for_critical_expected_line("Editor-server ready. Editor has successfully connected to the editor-server's network simulation.", section_tracer.prints, 5.0)
|
||||
TestHelper.succeed_if_log_line_found("MultiplayerEditor", "Editor is sending the editor-server the level data packet.", section_tracer.prints, 5.0)
|
||||
|
||||
TestHelper.succeed_if_log_line_found("EditorServer", "Logger: Editor Server completed receiving the editor's level assets, responding to Editor...", section_tracer.prints, 5.0)
|
||||
|
||||
TestHelper.succeed_if_log_line_found("MultiplayerEditorConnection", "Editor-server ready. Editor has successfully connected to the editor-server's network simulation.", section_tracer.prints, 5.0)
|
||||
|
||||
TestHelper.fail_if_log_line_found("EditorServer", f"MultiplayerSystemComponent: SpawnDefaultPlayerPrefab failed. Missing sv_defaultPlayerSpawnAsset at path '{sv_default_player_spawn_asset.lower()}'.", section_tracer.prints, 0.5)
|
||||
|
||||
TestHelper.wait_for_condition(lambda : multiplayer.PythonEditorFuncs_is_in_game_mode(), 5.0)
|
||||
Report.critical_result(msgtuple_success_fail, multiplayer.PythonEditorFuncs_is_in_game_mode())
|
||||
|
||||
@@ -32,3 +32,6 @@ class TestAutomation(TestAutomationBase):
|
||||
from .tests import Multiplayer_AutoComponent_NetworkInput as test_module
|
||||
self._run_prefab_test(request, workspace, editor, test_module)
|
||||
|
||||
def test_Multiplayer_AutoComponent_RPC(self, request, workspace, editor, launcher_platform):
|
||||
from .tests import Multiplayer_AutoComponent_RPC as test_module
|
||||
self._run_prefab_test(request, workspace, editor, test_module)
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
"""
|
||||
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
|
||||
"""
|
||||
|
||||
|
||||
# Test Case Title : Check that the four network RPCs can be sent and received
|
||||
|
||||
|
||||
# fmt: off
|
||||
class TestSuccessFailTuples():
|
||||
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
|
||||
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
|
||||
find_network_player = ("Found network player", "Couldn't find network player")
|
||||
# fmt: on
|
||||
|
||||
|
||||
def Multiplayer_AutoComponent_RPC():
|
||||
r"""
|
||||
Summary:
|
||||
Runs a test to make sure that RPCs can be sent and received via script canvas
|
||||
|
||||
Level Description:
|
||||
- Dynamic
|
||||
1. Although the level is nearly empty, when the server and editor connect the server will spawn and replicate the player network prefab.
|
||||
a. The player network prefab has a NetworkTestPlayerComponent.AutoComponent and a script canvas attached which sends and receives various RPCs.
|
||||
Print logs occur upon sending and receiving the RPCs; we are testing to make sure the expected events and values are received.
|
||||
- Static
|
||||
1. NetLevelEntity. This is a networked entity which has a script attached. Used for cross-entity communication. The net-player prefab will send this level entity Server->Authority RPCs
|
||||
|
||||
|
||||
Expected Outcome:
|
||||
We should see editor logs stating that RPCs have been sent and received.
|
||||
However, if the script receives unexpected values for the Process event we will see print logs for bad data as well.
|
||||
|
||||
:return:
|
||||
"""
|
||||
import azlmbr.legacy.general as general
|
||||
from editor_python_test_tools.utils import Report
|
||||
from editor_python_test_tools.utils import Tracer
|
||||
|
||||
from editor_python_test_tools.utils import TestHelper as helper
|
||||
from ly_remote_console.remote_console_commands import RemoteConsole as RemoteConsole
|
||||
|
||||
level_name = "AutoComponent_RPC"
|
||||
player_prefab_name = "Player"
|
||||
player_prefab_path = f"levels/multiplayer/{level_name}/{player_prefab_name}.network.spawnable"
|
||||
|
||||
helper.init_idle()
|
||||
|
||||
# 1) Open Level
|
||||
helper.open_level("Multiplayer", level_name)
|
||||
|
||||
with Tracer() as section_tracer:
|
||||
# 2) Enter game mode
|
||||
helper.multiplayer_enter_game_mode(TestSuccessFailTuples.enter_game_mode, player_prefab_path.lower())
|
||||
|
||||
# 3) Make sure the network player was spawned
|
||||
player_id = general.find_game_entity(player_prefab_name)
|
||||
Report.critical_result(TestSuccessFailTuples.find_network_player, player_id.IsValid())
|
||||
|
||||
# 4) Check the editor logs for expected and unexpected log output
|
||||
PLAYERID_RPC_WAIT_TIME_SECONDS = 1.0 # The player id is sent from the server as soon as the player script is spawned. 1 second should be more than enough time to send/receive that RPC.
|
||||
helper.succeed_if_log_line_found('EditorServer', 'Script: AutoComponent_RPC: Sending client PlayerNumber 1', section_tracer.prints, PLAYERID_RPC_WAIT_TIME_SECONDS)
|
||||
helper.succeed_if_log_line_found('Script', "AutoComponent_RPC: I'm Player #1", section_tracer.prints, PLAYERID_RPC_WAIT_TIME_SECONDS)
|
||||
|
||||
# Uncomment once editor game-play mode supports level entities with net-binding
|
||||
#PLAYFX_RPC_WAIT_TIME_SECONDS = 1.1 # The server will send an RPC to play an fx on the client every second.
|
||||
#helper.succeed_if_log_line_found('EditorServer', "Script: AutoComponent_RPC_NetLevelEntity Activated on entity: NetLevelEntity", section_tracer.prints, PLAYFX_RPC_WAIT_TIME_SECONDS)
|
||||
#helper.succeed_if_log_line_found('EditorServer', "Script: AutoComponent_RPC_NetLevelEntity: Authority sending RPC to play some fx.", section_tracer.prints, PLAYFX_RPC_WAIT_TIME_SECONDS)
|
||||
#helper.succeed_if_log_line_found('Script', "AutoComponent_RPC_NetLevelEntity: I'm a client playing some superficial fx.", section_tracer.prints, PLAYFX_RPC_WAIT_TIME_SECONDS)
|
||||
|
||||
|
||||
# Exit game mode
|
||||
helper.exit_game_mode(TestSuccessFailTuples.exit_game_mode)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(Multiplayer_AutoComponent_RPC)
|
||||
@@ -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)
|
||||
|
||||
@@ -98,38 +98,32 @@ class FileManagement:
|
||||
"""
|
||||
file_map = FileManagement._load_file_map()
|
||||
backup_path = FileManagement.backup_folder_path
|
||||
backup_file_name = "{}.bak".format(file_name)
|
||||
backup_file = os.path.join(backup_path, backup_file_name)
|
||||
# If backup directory DNE, make one
|
||||
if not os.path.exists(backup_path):
|
||||
os.mkdir(backup_path)
|
||||
# If "traditional" backup file exists, delete it (myFile.txt.bak)
|
||||
if os.path.exists(backup_file):
|
||||
fs.delete([backup_file], True, False)
|
||||
# Find my next storage name (myFile_1.txt.bak)
|
||||
backup_storage_file_name = FileManagement._next_available_name(backup_file_name, file_map)
|
||||
if backup_storage_file_name is None:
|
||||
|
||||
# Find my next storage name (myFile_1.txt)
|
||||
backup_file_name = FileManagement._next_available_name(file_name, file_map)
|
||||
if backup_file_name is None:
|
||||
# If _next_available_name returns None, we have backed up MAX_BACKUPS of files name [file_name]
|
||||
raise Exception(
|
||||
"FileManagement class ran out of backups per name. Max: {}".format(FileManagement.MAX_BACKUPS)
|
||||
)
|
||||
backup_storage_file = os.path.join(backup_path, backup_storage_file_name)
|
||||
|
||||
# If this backup file already exists, delete it.
|
||||
backup_storage_file = "{}.bak".format(os.path.normpath(os.path.join(backup_path, backup_file_name)))
|
||||
if os.path.exists(backup_storage_file):
|
||||
# This file should not exists, but if it does it's about to get clobbered!
|
||||
fs.unlock_file(backup_storage_file)
|
||||
# Create "traditional" backup file (myFile.txt.bak)
|
||||
fs.create_backup(os.path.join(file_path, file_name), backup_path)
|
||||
# Copy "traditional" backup file into storage backup (myFile_1.txt.bak)
|
||||
FileManagement._copy_file(backup_file_name, backup_path, backup_storage_file_name, backup_path)
|
||||
fs.lock_file(backup_storage_file)
|
||||
# Delete "traditional" back up file
|
||||
fs.unlock_file(backup_file)
|
||||
fs.delete([backup_file], True, False)
|
||||
fs.delete([backup_storage_file], True, False)
|
||||
|
||||
# Create backup file (myFile_1.txt.bak)
|
||||
original_file = os.path.normpath(os.path.join(file_path, file_name))
|
||||
fs.create_backup(original_file, backup_path, backup_file_name)
|
||||
|
||||
# Update file map with new file
|
||||
file_map[os.path.join(file_path, file_name)] = backup_storage_file_name
|
||||
file_map[original_file] = backup_file_name
|
||||
FileManagement._save_file_map(file_map)
|
||||
# Unlock original file to get it ready to be edited by the test
|
||||
fs.unlock_file(os.path.join(file_path, file_name))
|
||||
fs.unlock_file(original_file)
|
||||
|
||||
@staticmethod
|
||||
def _restore_file(file_name, file_path):
|
||||
@@ -143,20 +137,15 @@ class FileManagement:
|
||||
"""
|
||||
file_map = FileManagement._load_file_map()
|
||||
backup_path = FileManagement.backup_folder_path
|
||||
src_file = os.path.join(file_path, file_name)
|
||||
src_file = os.path.normpath(os.path.join(file_path, file_name))
|
||||
if src_file in file_map:
|
||||
backup_file = os.path.join(backup_path, file_map[src_file])
|
||||
if os.path.exists(backup_file):
|
||||
fs.unlock_file(backup_file)
|
||||
fs.unlock_file(src_file)
|
||||
# Make temporary copy of backed up file to restore from
|
||||
temp_file = "{}.bak".format(file_name)
|
||||
FileManagement._copy_file(file_map[src_file], backup_path, temp_file, backup_path)
|
||||
fs.restore_backup(src_file, backup_path)
|
||||
fs.lock_file(src_file)
|
||||
# Delete backup file
|
||||
fs.delete([os.path.join(backup_path, temp_file)], True, False)
|
||||
backup_file_name = file_map[src_file]
|
||||
backup_file = "{}.bak".format(os.path.join(backup_path, backup_file_name))
|
||||
|
||||
fs.unlock_file(src_file)
|
||||
if fs.restore_backup(src_file, backup_path, backup_file_name):
|
||||
fs.delete([backup_file], True, False)
|
||||
|
||||
# Remove from file map
|
||||
del file_map[src_file]
|
||||
FileManagement._save_file_map(file_map)
|
||||
|
||||
@@ -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
|
||||
+1
@@ -24,6 +24,7 @@ def CreatePrefab_WithSingleEntity():
|
||||
# Creates a prefab from the new entity
|
||||
Prefab.create_prefab(car_prefab_entities, CAR_PREFAB_FILE_NAME)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(CreatePrefab_WithSingleEntity)
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
|
||||
@@ -12,12 +12,36 @@ import sys
|
||||
import os
|
||||
import pytest
|
||||
import logging
|
||||
import sqlite3
|
||||
pytest.importorskip('ly_test_tools')
|
||||
|
||||
import ly_test_tools.environment.file_system as file_system
|
||||
import ly_test_tools.log.log_monitor
|
||||
import ly_test_tools.environment.waiter as waiter
|
||||
|
||||
def detect_product(sql_connection, platform, target):
|
||||
cur = sql_connection.cursor()
|
||||
product_target = f'{platform}/{target}'
|
||||
print(f'Detecting {product_target} in assetdb.sqlite')
|
||||
hits = 0
|
||||
for row in cur.execute(f'select ProductID from Products where ProductName is "{product_target}"'):
|
||||
hits = hits + 1
|
||||
assert hits == 1
|
||||
|
||||
|
||||
def find_products(cache_folder, platform):
|
||||
con = sqlite3.connect(os.path.join(cache_folder, 'assetdb.sqlite'))
|
||||
detect_product(con, platform, 'gem/pythontests/pythonassetbuilder/test_asset.mock_asset')
|
||||
detect_product(con, platform, 'gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_z_positive_1.azmodel')
|
||||
detect_product(con, platform, 'gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_z_negative_1.azmodel')
|
||||
detect_product(con, platform, 'gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_y_positive_1.azmodel')
|
||||
detect_product(con, platform, 'gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_y_negative_1.azmodel')
|
||||
detect_product(con, platform, 'gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_x_positive_1.azmodel')
|
||||
detect_product(con, platform, 'gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_x_negative_1.azmodel')
|
||||
detect_product(con, platform, 'gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_center_1.azmodel')
|
||||
con.close()
|
||||
|
||||
|
||||
@pytest.mark.SUITE_periodic
|
||||
@pytest.mark.parametrize('launcher_platform', ['windows_editor'])
|
||||
@pytest.mark.parametrize('project', ['AutomatedTesting'])
|
||||
@@ -25,16 +49,7 @@ import ly_test_tools.environment.waiter as waiter
|
||||
class TestPythonAssetProcessing(object):
|
||||
def test_DetectPythonCreatedAsset(self, request, editor, level, launcher_platform):
|
||||
unexpected_lines = []
|
||||
expected_lines = [
|
||||
'Mock asset exists',
|
||||
'AssetId found for asset (gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_z_positive_1.azmodel) found',
|
||||
'AssetId found for asset (gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_z_negative_1.azmodel) found',
|
||||
'AssetId found for asset (gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_y_positive_1.azmodel) found',
|
||||
'AssetId found for asset (gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_y_negative_1.azmodel) found',
|
||||
'AssetId found for asset (gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_x_positive_1.azmodel) found',
|
||||
'AssetId found for asset (gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_x_negative_1.azmodel) found',
|
||||
'AssetId found for asset (gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_center_1.azmodel) found'
|
||||
]
|
||||
expected_lines = []
|
||||
timeout = 180
|
||||
halt_on_unexpected = False
|
||||
test_directory = os.path.join(os.path.dirname(__file__))
|
||||
@@ -50,3 +65,9 @@ class TestPythonAssetProcessing(object):
|
||||
exc=("Log file '{}' was never opened by another process.".format(editorlog_file)),
|
||||
interval=1)
|
||||
log_monitor.monitor_log_for_lines(expected_lines, unexpected_lines, halt_on_unexpected, timeout)
|
||||
|
||||
cache_folder = editor.workspace.paths.cache()
|
||||
platform = editor.workspace.asset_processor_platform
|
||||
if platform == 'windows':
|
||||
platform = 'pc'
|
||||
find_products(cache_folder, platform)
|
||||
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
"""
|
||||
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 HeightTests:
|
||||
single_gradient_height_correct = (
|
||||
"Successfully retrieved height for gradient1.",
|
||||
"Failed to retrieve height for gradient1."
|
||||
)
|
||||
double_gradient_height_correct = (
|
||||
"Successfully retrieved height when two gradients exist.",
|
||||
"Failed to retrieve height when two gradients exist."
|
||||
)
|
||||
triple_gradient_height_correct = (
|
||||
"Successfully retrieved height when three gradients exist.",
|
||||
"Failed to retrieve height when three gradients exist."
|
||||
)
|
||||
terrain_data_changed_call_count_correct = (
|
||||
"OnTerrainDataChanged called expected number of times.",
|
||||
"OnTerrainDataChanged call count incorrect."
|
||||
)
|
||||
|
||||
def TerrainHeightGradientList_AddRemoveGradientWorks():
|
||||
"""
|
||||
Summary:
|
||||
Test aspects of the TerrainHeightGradientList through the BehaviorContext and the Property Tree.
|
||||
:return: None
|
||||
"""
|
||||
|
||||
import os
|
||||
import math as sys_math
|
||||
|
||||
import azlmbr.legacy.general as general
|
||||
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
|
||||
|
||||
terrain_changed_call_count = 0
|
||||
expected_terrain_changed_calls = 0
|
||||
|
||||
aabb_component_name = "Axis Aligned Box Shape"
|
||||
gradientlist_component_name = "Terrain Height Gradient List"
|
||||
layerspawner_component_name = "Terrain Layer Spawner"
|
||||
|
||||
gradient_value_path = "Configuration|Value"
|
||||
|
||||
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 on_terrain_changed(args):
|
||||
nonlocal terrain_changed_call_count
|
||||
|
||||
terrain_changed_call_count += 1
|
||||
|
||||
def set_component_path_val(entity, component, path, value):
|
||||
entity.get_set_test(component, path, value)
|
||||
|
||||
def set_gradients_check_height(main_entity, gradient_list, expected_height, test_results):
|
||||
nonlocal expected_terrain_changed_calls
|
||||
|
||||
test_tolerance = 0.01
|
||||
gradient_list_path = "Configuration|Gradient Entities"
|
||||
|
||||
set_component_path_val(main_entity, 1, gradient_list_path, gradient_list)
|
||||
|
||||
expected_terrain_changed_calls += 1
|
||||
|
||||
# Wait until the terrain data has been updated.
|
||||
helper.wait_for_condition(lambda: terrain_changed_call_count == expected_terrain_changed_calls, 2.0)
|
||||
|
||||
# Get the height at the origin.
|
||||
height = terrain.TerrainDataRequestBus(bus.Broadcast, "GetHeightFromFloats", 0.0, 0.0, 0)
|
||||
|
||||
Report.result(test_results, sys_math.isclose(height, expected_height, abs_tol=test_tolerance))
|
||||
|
||||
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)
|
||||
|
||||
# Add a terrain world component
|
||||
world_component = hydra.add_level_component("Terrain World")
|
||||
|
||||
aabb_height = 1024.0
|
||||
box_dimensions = math.Vector3(1.0, 1.0, aabb_height);
|
||||
|
||||
# Create a main entity with a LayerSpawner, AAbb and HeightGradientList.
|
||||
main_entity = create_entity_at("entity2", [layerspawner_component_name, gradientlist_component_name, aabb_component_name], 0.0, 0.0, aabb_height/2.0)
|
||||
|
||||
# Create three gradient entities.
|
||||
gradient_entity1 = create_entity_at("Constant Gradient1", ["Constant Gradient"], 0.0, 0.0, 0.0);
|
||||
gradient_entity2 = create_entity_at("Constant Gradient2", ["Constant Gradient"], 0.0, 0.0, 0.0);
|
||||
gradient_entity3 = create_entity_at("Constant Gradient3", ["Constant Gradient"], 0.0, 0.0, 0.0);
|
||||
|
||||
# Give everything a chance to finish initializing.
|
||||
general.idle_wait_frames(1)
|
||||
|
||||
# Set the gradients to different values.
|
||||
gradient_values = [0.5, 0.8, 0.3]
|
||||
set_component_path_val(gradient_entity1, 0, gradient_value_path, gradient_values[0])
|
||||
set_component_path_val(gradient_entity2, 0, gradient_value_path, gradient_values[1])
|
||||
set_component_path_val(gradient_entity3, 0, gradient_value_path, gradient_values[2])
|
||||
|
||||
# Give the TerrainSystem time to tick.
|
||||
general.idle_wait_frames(1)
|
||||
|
||||
# Set the dimensions of the Aabb.
|
||||
set_component_path_val(main_entity, 2, "Axis Aligned Box Shape|Box Configuration|Dimensions", box_dimensions)
|
||||
|
||||
# Set up a handler to wait for notifications from the TerrainSystem.
|
||||
handler = azlmbr.terrain.TerrainDataNotificationBusHandler()
|
||||
handler.connect()
|
||||
handler.add_callback("OnTerrainDataChanged", on_terrain_changed)
|
||||
|
||||
# Add a gradient to GradientList, then check the height returned from the TerrainSystem is correct.
|
||||
set_gradients_check_height(main_entity, [gradient_entity1.id], aabb_height * gradient_values[0], HeightTests.single_gradient_height_correct)
|
||||
|
||||
# Add gradient2 and check height at the origin, this should have changed to match the second gradient value.
|
||||
set_gradients_check_height(main_entity, [gradient_entity1.id, gradient_entity2.id], aabb_height * gradient_values[1], HeightTests.double_gradient_height_correct)
|
||||
|
||||
# Add gradient3, the height should still be the second value, as that was the highest.
|
||||
set_gradients_check_height(main_entity, [gradient_entity1.id, gradient_entity2.id, gradient_entity3.id], aabb_height * gradient_values[1], HeightTests.triple_gradient_height_correct)
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(TerrainHeightGradientList_AddRemoveGradientWorks)
|
||||
+166
@@ -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)
|
||||
+214
@@ -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)
|
||||
+168
@@ -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,3 +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
|
||||
|
||||
@@ -3,8 +3,6 @@ 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
|
||||
|
||||
Fixture for clearing out 'MoveOutput' folders from \dev and \dev\PROJECT
|
||||
"""
|
||||
|
||||
# Import builtin libraries
|
||||
|
||||
+5
-5
@@ -108,7 +108,7 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
|
||||
"""
|
||||
helper = bundler_batch_helper
|
||||
seed_list = os.path.join(workspace.paths.engine_root(), "Assets", "Engine", "SeedAssetList.seed") # Engine seed list
|
||||
asset = r"levels\testdependencieslevel\level.pak"
|
||||
asset = r"levels\testdependencieslevel\testdependencieslevel.spawnable"
|
||||
|
||||
# Create Asset list
|
||||
helper.call_assetLists(
|
||||
@@ -191,7 +191,7 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
|
||||
"""
|
||||
helper = bundler_batch_helper
|
||||
seed_list = os.path.join(workspace.paths.engine_root(), "Assets", "Engine", "SeedAssetList.seed") # Engine seed list
|
||||
asset = r"levels\testdependencieslevel\level.pak"
|
||||
asset = r"levels\testdependencieslevel\testdependencieslevel.spawnable"
|
||||
|
||||
# Useful bundle locations / names (2 for comparing contents)
|
||||
# fmt:off
|
||||
@@ -924,7 +924,7 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
|
||||
# Create a seed file
|
||||
helper.call_seeds(
|
||||
seedListFile=helper["seed_list_file"],
|
||||
addSeed=r"levels\testdependencieslevel\level.pak",
|
||||
addSeed=r"levels\testdependencieslevel\testdependencieslevel.spawnable",
|
||||
platform="pc",
|
||||
)
|
||||
|
||||
@@ -947,9 +947,9 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
|
||||
# Specifying platform but not "add" or "remove" should fail
|
||||
result, _ = helper.call_assetLists(
|
||||
assetListFile=helper["asset_info_file_request"],
|
||||
allowOverwrites="",
|
||||
seedListFile=helper["seed_list_file"],
|
||||
platform="pc",
|
||||
allowOverwrites="",
|
||||
)
|
||||
assert result, "Overwriting with override threw an error"
|
||||
|
||||
@@ -982,7 +982,7 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
|
||||
request.addfinalizer(lambda: fs.delete([bundle_result_path], True, False))
|
||||
|
||||
bundles_folder = os.path.join(workspace.paths.project(), "Bundles")
|
||||
level_pak = r"levels\testdependencieslevel\level.pak"
|
||||
level_pak = r"levels\testdependencieslevel\testdependencieslevel.spawnable"
|
||||
bundle_request_path = os.path.join(bundles_folder, "bundle.pak")
|
||||
bundle_result_path = os.path.join(bundles_folder,
|
||||
helper.platform_file_name("bundle.pak", workspace.asset_processor_platform))
|
||||
|
||||
+10
@@ -8,6 +8,8 @@ General Asset Processor Batch Tests
|
||||
"""
|
||||
|
||||
# Import builtin libraries
|
||||
from os import listdir
|
||||
|
||||
import pytest
|
||||
import logging
|
||||
import os
|
||||
@@ -724,3 +726,11 @@ class TestsAssetProcessorBatch_AllPlatforms(object):
|
||||
|
||||
assert error_line_found, "The error could not be found in the newest run of the AP Batch log."
|
||||
|
||||
@pytest.mark.assetpipeline
|
||||
def test_AssetProcessor_Log_On_Failure(self, asset_processor, ap_setup_fixture, workspace):
|
||||
asset_processor.prepare_test_environment(ap_setup_fixture["tests_dir"], "test_AP_Logs")
|
||||
result, output = asset_processor.batch_process(expect_failure=True, capture_output=True)
|
||||
assert result == False, f'AssetProcessorBatch should have failed because there is a bad asset, output was {output}'
|
||||
|
||||
jobLogs = listdir(workspace.paths.ap_job_logs() + "/test_AP_Logs")
|
||||
assert not len(jobLogs) == 0, 'No job logs where output during failure.'
|
||||
|
||||
+6
-6
@@ -169,17 +169,17 @@ class TestsAssetRelocator_WindowsAndMac(object):
|
||||
|
||||
@pytest.mark.test_case_id("C21968388")
|
||||
@pytest.mark.assetpipeline
|
||||
def test_WindowsMacPlatforms_MoveCorruptedSliceFile_MoveSuccess(self, request, workspace, ap_setup_fixture,
|
||||
def test_WindowsMacPlatforms_MoveCorruptedPrefabFile_MoveSuccess(self, request, workspace, ap_setup_fixture,
|
||||
asset_processor):
|
||||
"""
|
||||
Asset with UUID/AssetId reference in non-standard format is
|
||||
successfully scanned and relocated to the MoveOutput folder.
|
||||
This test uses a pre-corrupted .slice file.
|
||||
This test uses a pre-corrupted .prefab file.
|
||||
|
||||
Test Steps:
|
||||
1. Create temporary testing environment with a corrupted slice
|
||||
2. Attempt to move the corrupted slice
|
||||
3. Verify that corrupted slice was moved successfully
|
||||
1. Create temporary testing environment with a corrupted prefab
|
||||
2. Attempt to move the corrupted prefab
|
||||
3. Verify that corrupted prefab was moved successfully
|
||||
"""
|
||||
|
||||
env = ap_setup_fixture
|
||||
@@ -187,7 +187,7 @@ class TestsAssetRelocator_WindowsAndMac(object):
|
||||
asset_folder = "C21968388"
|
||||
source_dir, _ = asset_processor.prepare_test_environment(env["tests_dir"], asset_folder)
|
||||
|
||||
filename = "DependencyScannerAsset.slice"
|
||||
filename = "DependencyScannerAsset.prefab"
|
||||
file_path = os.path.join(source_dir, filename)
|
||||
dst_rel_path = os.path.join("MoveOutput", filename)
|
||||
dst_full_path = os.path.join(source_dir, dst_rel_path)
|
||||
|
||||
+908
@@ -0,0 +1,908 @@
|
||||
{
|
||||
"ContainerEntity": {
|
||||
"Id": "ContainerEntity",
|
||||
"Name": "DependencyScannerAsset",
|
||||
"Components": {
|
||||
"Component_[10182366347512475253]": {
|
||||
"$type": "EditorPrefabComponent",
|
||||
"Id": 10182366347512475253
|
||||
},
|
||||
"Component_[12917798267488243668]": {
|
||||
"$type": "EditorPendingCompositionComponent",
|
||||
"Id": 12917798267488243668
|
||||
},
|
||||
"Component_[3261249813163778338]": {
|
||||
"$type": "EditorOnlyEntityComponent",
|
||||
"Id": 3261249813163778338
|
||||
},
|
||||
"Component_[3837204912784440039]": {
|
||||
"$type": "EditorDisabledCompositionComponent",
|
||||
"Id": 3837204912784440039
|
||||
},
|
||||
"Component_[4272963378099646759]": {
|
||||
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
|
||||
"Id": 4272963378099646759,
|
||||
"Parent Entity": ""
|
||||
},
|
||||
"Component_[4848458548047175816]": {
|
||||
"$type": "EditorVisibilityComponent",
|
||||
"Id": 4848458548047175816
|
||||
},
|
||||
"Component_[5787060997243919943]": {
|
||||
"$type": "EditorInspectorComponent",
|
||||
"Id": 5787060997243919943
|
||||
},
|
||||
"Component_[7804170251266531779]": {
|
||||
"$type": "EditorLockComponent",
|
||||
"Id": 7804170251266531779
|
||||
},
|
||||
"Component_[7874177159288365422]": {
|
||||
"$type": "EditorEntitySortComponent",
|
||||
"Id": 7874177159288365422
|
||||
},
|
||||
"Component_[8018146290632383969]": {
|
||||
"$type": "EditorEntityIconComponent",
|
||||
"Id": 8018146290632383969
|
||||
},
|
||||
"Component_[8452360690590857075]": {
|
||||
"$type": "SelectionComponent",
|
||||
"Id": 8452360690590857075
|
||||
}
|
||||
}
|
||||
},
|
||||
"Entities": {
|
||||
"Entity_[274004659287]": {
|
||||
"Id": "Entity_[274004659287]",
|
||||
"Name": "DependencyScannerAsset",
|
||||
"Components": {
|
||||
"Component_[10849460799799271301]": {
|
||||
"$type": "EditorPendingCompositionComponent",
|
||||
"Id": 10849460799799271301
|
||||
},
|
||||
"Component_[11098142762746045658]": {
|
||||
"$type": "SelectionComponent",
|
||||
"Id": 11098142762746045658
|
||||
},
|
||||
"Component_[11154538629717040387]": {
|
||||
"$type": "EditorEntitySortComponent",
|
||||
"Id": 11154538629717040387,
|
||||
"Child Entity Order": [
|
||||
"Entity_[305822185575]",
|
||||
"Entity_[278299626583]",
|
||||
"Entity_[282594593879]",
|
||||
"Entity_[286889561175]",
|
||||
"Entity_[291184528471]"
|
||||
]
|
||||
},
|
||||
"Component_[1365196255752273753]": {
|
||||
"$type": "EditorOnlyEntityComponent",
|
||||
"Id": 1365196255752273753
|
||||
},
|
||||
"Component_[280906579560376421]": {
|
||||
"$type": "EditorLockComponent",
|
||||
"Id": 280906579560376421
|
||||
},
|
||||
"Component_[4629965429001113748]": {
|
||||
"$type": "EditorDisabledCompositionComponent",
|
||||
"Id": 4629965429001113748
|
||||
},
|
||||
"Component_[4876910656129741263]": {
|
||||
"$type": "EditorEntityIconComponent",
|
||||
"Id": 4876910656129741263
|
||||
},
|
||||
"Component_[5763306492614623496]": {
|
||||
"$type": "EditorInspectorComponent",
|
||||
"Id": 5763306492614623496,
|
||||
"ComponentOrderEntryArray": [
|
||||
{
|
||||
"ComponentId": 7514977506847036117
|
||||
}
|
||||
]
|
||||
},
|
||||
"Component_[7327709568605458460]": {
|
||||
"$type": "EditorVisibilityComponent",
|
||||
"Id": 7327709568605458460
|
||||
},
|
||||
"Component_[7514977506847036117]": {
|
||||
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
|
||||
"Id": 7514977506847036117,
|
||||
"Parent Entity": "ContainerEntity"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Entity_[278299626583]": {
|
||||
"Id": "Entity_[278299626583]",
|
||||
"Name": "AssetIDMatch",
|
||||
"Components": {
|
||||
"Component_[10285740519857855186]": {
|
||||
"$type": "EditorDisabledCompositionComponent",
|
||||
"Id": 10285740519857855186
|
||||
},
|
||||
"Component_[11273731016303624898]": {
|
||||
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
|
||||
"Id": 11273731016303624898,
|
||||
"Parent Entity": "Entity_[274004659287]"
|
||||
},
|
||||
"Component_[1136790983026972010]": {
|
||||
"$type": "EditorVisibilityComponent",
|
||||
"Id": 1136790983026972010
|
||||
},
|
||||
"Component_[12777313618328131055]": {
|
||||
"$type": "EditorPendingCompositionComponent",
|
||||
"Id": 12777313618328131055
|
||||
},
|
||||
"Component_[13256044902558773795]": {
|
||||
"$type": "EditorLockComponent",
|
||||
"Id": 13256044902558773795
|
||||
},
|
||||
"Component_[15834551022302435776]": {
|
||||
"$type": "EditorInspectorComponent",
|
||||
"Id": 15834551022302435776,
|
||||
"ComponentOrderEntryArray": [
|
||||
{
|
||||
"ComponentId": 11273731016303624898
|
||||
},
|
||||
{
|
||||
"ComponentId": 9671522714018290727,
|
||||
"SortIndex": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"Component_[16345420368214930095]": {
|
||||
"$type": "SelectionComponent",
|
||||
"Id": 16345420368214930095
|
||||
},
|
||||
"Component_[5309075942188429052]": {
|
||||
"$type": "EditorEntitySortComponent",
|
||||
"Id": 5309075942188429052
|
||||
},
|
||||
"Component_[8639731896786645938]": {
|
||||
"$type": "EditorEntityIconComponent",
|
||||
"Id": 8639731896786645938
|
||||
},
|
||||
"Component_[9844585173698551415]": {
|
||||
"$type": "EditorOnlyEntityComponent",
|
||||
"Id": 9844585173698551415
|
||||
}
|
||||
}
|
||||
},
|
||||
"Entity_[282594593879]": {
|
||||
"Id": "Entity_[282594593879]",
|
||||
"Name": "UUIDMatch",
|
||||
"Components": {
|
||||
"Component_[10379494986254888760]": {
|
||||
"$type": "EditorDisabledCompositionComponent",
|
||||
"Id": 10379494986254888760
|
||||
},
|
||||
"Component_[10932830014545295552]": {
|
||||
"$type": "SelectionComponent",
|
||||
"Id": 10932830014545295552
|
||||
},
|
||||
"Component_[16077882919902242532]": {
|
||||
"$type": "EditorEntitySortComponent",
|
||||
"Id": 16077882919902242532
|
||||
},
|
||||
"Component_[2150375322459274584]": {
|
||||
"$type": "EditorPendingCompositionComponent",
|
||||
"Id": 2150375322459274584
|
||||
},
|
||||
"Component_[2645455411436465820]": {
|
||||
"$type": "EditorEntityIconComponent",
|
||||
"Id": 2645455411436465820
|
||||
},
|
||||
"Component_[5422214869037468733]": {
|
||||
"$type": "EditorOnlyEntityComponent",
|
||||
"Id": 5422214869037468733
|
||||
},
|
||||
"Component_[7238126895911071330]": {
|
||||
"$type": "EditorInspectorComponent",
|
||||
"Id": 7238126895911071330,
|
||||
"ComponentOrderEntryArray": [
|
||||
{
|
||||
"ComponentId": 8407607000804893064
|
||||
},
|
||||
{
|
||||
"ComponentId": 12952323341649885242,
|
||||
"SortIndex": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"Component_[7981670269715131988]": {
|
||||
"$type": "EditorVisibilityComponent",
|
||||
"Id": 7981670269715131988
|
||||
},
|
||||
"Component_[8407607000804893064]": {
|
||||
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
|
||||
"Id": 8407607000804893064,
|
||||
"Parent Entity": "Entity_[274004659287]"
|
||||
},
|
||||
"Component_[8567641786004090803]": {
|
||||
"$type": "EditorLockComponent",
|
||||
"Id": 8567641786004090803
|
||||
}
|
||||
}
|
||||
},
|
||||
"Entity_[286889561175]": {
|
||||
"Id": "Entity_[286889561175]",
|
||||
"Name": "RelativeProductMatch",
|
||||
"Components": {
|
||||
"Component_[10180645282669228972]": {
|
||||
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
|
||||
"Id": 10180645282669228972,
|
||||
"Parent Entity": "Entity_[274004659287]"
|
||||
},
|
||||
"Component_[10200807690182688147]": {
|
||||
"$type": "EditorDisabledCompositionComponent",
|
||||
"Id": 10200807690182688147
|
||||
},
|
||||
"Component_[11014661873645081316]": {
|
||||
"$type": "EditorInspectorComponent",
|
||||
"Id": 11014661873645081316,
|
||||
"ComponentOrderEntryArray": [
|
||||
{
|
||||
"ComponentId": 10180645282669228972
|
||||
},
|
||||
{
|
||||
"ComponentId": 12869852248016369650,
|
||||
"SortIndex": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"Component_[12869852248016369650]": {
|
||||
"$type": "{77CDE991-EC1A-B7C1-B112-7456ABAC81A1} EditorSpawnerComponent",
|
||||
"Id": 12869852248016369650,
|
||||
"Slice": {
|
||||
"assetId": {
|
||||
"guid": "{29F14025-3BD2-5CA9-A9DE-B8B349268C2F}",
|
||||
"subId": 2
|
||||
},
|
||||
"assetHint": "slices/bullet.dynamicslice"
|
||||
}
|
||||
},
|
||||
"Component_[15136448544716183259]": {
|
||||
"$type": "EditorOnlyEntityComponent",
|
||||
"Id": 15136448544716183259
|
||||
},
|
||||
"Component_[15966001894874626764]": {
|
||||
"$type": "SelectionComponent",
|
||||
"Id": 15966001894874626764
|
||||
},
|
||||
"Component_[16167982631516160155]": {
|
||||
"$type": "EditorEntityIconComponent",
|
||||
"Id": 16167982631516160155
|
||||
},
|
||||
"Component_[16672905198052847867]": {
|
||||
"$type": "EditorEntitySortComponent",
|
||||
"Id": 16672905198052847867
|
||||
},
|
||||
"Component_[4506946122562404190]": {
|
||||
"$type": "EditorPendingCompositionComponent",
|
||||
"Id": 4506946122562404190
|
||||
},
|
||||
"Component_[6836304267269231429]": {
|
||||
"$type": "EditorLockComponent",
|
||||
"Id": 6836304267269231429
|
||||
},
|
||||
"Component_[8756593519140349183]": {
|
||||
"$type": "EditorVisibilityComponent",
|
||||
"Id": 8756593519140349183
|
||||
}
|
||||
}
|
||||
},
|
||||
"Entity_[291184528471]": {
|
||||
"Id": "Entity_[291184528471]",
|
||||
"Name": "RelativeSourceMatch",
|
||||
"Components": {
|
||||
"Component_[11694027325905361034]": {
|
||||
"$type": "EditorEntitySortComponent",
|
||||
"Id": 11694027325905361034
|
||||
},
|
||||
"Component_[13891029613307790064]": {
|
||||
"$type": "EditorEntityIconComponent",
|
||||
"Id": 13891029613307790064
|
||||
},
|
||||
"Component_[15933511034411930900]": {
|
||||
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
|
||||
"Id": 15933511034411930900,
|
||||
"Parent Entity": "Entity_[274004659287]"
|
||||
},
|
||||
"Component_[17540827492846961803]": {
|
||||
"$type": "EditorLockComponent",
|
||||
"Id": 17540827492846961803
|
||||
},
|
||||
"Component_[2850297705939373458]": {
|
||||
"$type": "SelectionComponent",
|
||||
"Id": 2850297705939373458
|
||||
},
|
||||
"Component_[4809103331004345812]": {
|
||||
"$type": "EditorPendingCompositionComponent",
|
||||
"Id": 4809103331004345812
|
||||
},
|
||||
"Component_[5654779331777839943]": {
|
||||
"$type": "EditorInspectorComponent",
|
||||
"Id": 5654779331777839943,
|
||||
"ComponentOrderEntryArray": [
|
||||
{
|
||||
"ComponentId": 15933511034411930900
|
||||
},
|
||||
{
|
||||
"ComponentId": 10284025539900054207,
|
||||
"SortIndex": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"Component_[6097019179005900386]": {
|
||||
"$type": "EditorVisibilityComponent",
|
||||
"Id": 6097019179005900386
|
||||
},
|
||||
"Component_[7748387730313625157]": {
|
||||
"$type": "EditorDisabledCompositionComponent",
|
||||
"Id": 7748387730313625157
|
||||
},
|
||||
"Component_[9822265453841229082]": {
|
||||
"$type": "EditorOnlyEntityComponent",
|
||||
"Id": 9822265453841229082
|
||||
}
|
||||
}
|
||||
},
|
||||
"Entity_[297232250983]": {
|
||||
"Id": "Entity_[297232250983]",
|
||||
"Name": "1151F14D38A65579888ABE3139882E68:[0]",
|
||||
"Components": {
|
||||
"Component_[11936148741777754959]": {
|
||||
"$type": "EditorDisabledCompositionComponent",
|
||||
"Id": 11936148741777754959
|
||||
},
|
||||
"Component_[12610073699988743015]": {
|
||||
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
|
||||
"Id": 12610073699988743015,
|
||||
"Parent Entity": "Entity_[305822185575]"
|
||||
},
|
||||
"Component_[1603205169722765279]": {
|
||||
"$type": "EditorLockComponent",
|
||||
"Id": 1603205169722765279
|
||||
},
|
||||
"Component_[17691206348057560715]": {
|
||||
"$type": "EditorPendingCompositionComponent",
|
||||
"Id": 17691206348057560715
|
||||
},
|
||||
"Component_[2711821203680330048]": {
|
||||
"$type": "SelectionComponent",
|
||||
"Id": 2711821203680330048
|
||||
},
|
||||
"Component_[3887480309311474860]": {
|
||||
"$type": "EditorVisibilityComponent",
|
||||
"Id": 3887480309311474860
|
||||
},
|
||||
"Component_[5853968883842282450]": {
|
||||
"$type": "EditorEntityIconComponent",
|
||||
"Id": 5853968883842282450
|
||||
},
|
||||
"Component_[7679080692843343453]": {
|
||||
"$type": "EditorCommentComponent",
|
||||
"Id": 7679080692843343453,
|
||||
"Configuration": "Asset ID that matches an existing dependency of this asset (am_grass1.mtl)"
|
||||
},
|
||||
"Component_[8024752235278898687]": {
|
||||
"$type": "EditorOnlyEntityComponent",
|
||||
"Id": 8024752235278898687
|
||||
},
|
||||
"Component_[8373296084678231042]": {
|
||||
"$type": "EditorEntitySortComponent",
|
||||
"Id": 8373296084678231042
|
||||
},
|
||||
"Component_[9782967158965587831]": {
|
||||
"$type": "EditorInspectorComponent",
|
||||
"Id": 9782967158965587831,
|
||||
"ComponentOrderEntryArray": [
|
||||
{
|
||||
"ComponentId": 12610073699988743015
|
||||
},
|
||||
{
|
||||
"ComponentId": 7679080692843343453,
|
||||
"SortIndex": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Entity_[301527218279]": {
|
||||
"Id": "Entity_[301527218279]",
|
||||
"Name": "Slices/bullet.dynamicslice",
|
||||
"Components": {
|
||||
"Component_[15613078542630153866]": {
|
||||
"$type": "EditorInspectorComponent",
|
||||
"Id": 15613078542630153866,
|
||||
"ComponentOrderEntryArray": [
|
||||
{
|
||||
"ComponentId": 2483032678718995164
|
||||
},
|
||||
{
|
||||
"ComponentId": 9734604379060902193,
|
||||
"SortIndex": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"Component_[16098942854900817264]": {
|
||||
"$type": "SelectionComponent",
|
||||
"Id": 16098942854900817264
|
||||
},
|
||||
"Component_[16720139961856477500]": {
|
||||
"$type": "EditorEntitySortComponent",
|
||||
"Id": 16720139961856477500
|
||||
},
|
||||
"Component_[2483032678718995164]": {
|
||||
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
|
||||
"Id": 2483032678718995164,
|
||||
"Parent Entity": "Entity_[305822185575]"
|
||||
},
|
||||
"Component_[2502984783426127018]": {
|
||||
"$type": "EditorLockComponent",
|
||||
"Id": 2502984783426127018
|
||||
},
|
||||
"Component_[46714013890147210]": {
|
||||
"$type": "EditorOnlyEntityComponent",
|
||||
"Id": 46714013890147210
|
||||
},
|
||||
"Component_[4907474530744780429]": {
|
||||
"$type": "EditorDisabledCompositionComponent",
|
||||
"Id": 4907474530744780429
|
||||
},
|
||||
"Component_[5420332829198300813]": {
|
||||
"$type": "EditorPendingCompositionComponent",
|
||||
"Id": 5420332829198300813
|
||||
},
|
||||
"Component_[7683578164681693579]": {
|
||||
"$type": "EditorEntityIconComponent",
|
||||
"Id": 7683578164681693579
|
||||
},
|
||||
"Component_[8312115250363172310]": {
|
||||
"$type": "EditorVisibilityComponent",
|
||||
"Id": 8312115250363172310
|
||||
},
|
||||
"Component_[9734604379060902193]": {
|
||||
"$type": "EditorCommentComponent",
|
||||
"Id": 9734604379060902193,
|
||||
"Configuration": "Relative product path that matches an existing dependency of this asset"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Entity_[305822185575]": {
|
||||
"Id": "Entity_[305822185575]",
|
||||
"Name": "AssetReferences",
|
||||
"Components": {
|
||||
"Component_[13422135993444528172]": {
|
||||
"$type": "EditorPendingCompositionComponent",
|
||||
"Id": 13422135993444528172
|
||||
},
|
||||
"Component_[13988015667379021413]": {
|
||||
"$type": "EditorLockComponent",
|
||||
"Id": 13988015667379021413
|
||||
},
|
||||
"Component_[14885956487876614434]": {
|
||||
"$type": "EditorVisibilityComponent",
|
||||
"Id": 14885956487876614434
|
||||
},
|
||||
"Component_[15550715415947731915]": {
|
||||
"$type": "EditorDisabledCompositionComponent",
|
||||
"Id": 15550715415947731915
|
||||
},
|
||||
"Component_[2576266145980379805]": {
|
||||
"$type": "EditorInspectorComponent",
|
||||
"Id": 2576266145980379805,
|
||||
"ComponentOrderEntryArray": [
|
||||
{
|
||||
"ComponentId": 3176911836967955668
|
||||
},
|
||||
{
|
||||
"ComponentId": 836721549453007197,
|
||||
"SortIndex": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"Component_[3176911836967955668]": {
|
||||
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
|
||||
"Id": 3176911836967955668,
|
||||
"Parent Entity": "Entity_[274004659287]"
|
||||
},
|
||||
"Component_[5613459137294642234]": {
|
||||
"$type": "SelectionComponent",
|
||||
"Id": 5613459137294642234
|
||||
},
|
||||
"Component_[6400873582148097152]": {
|
||||
"$type": "EditorEntityIconComponent",
|
||||
"Id": 6400873582148097152
|
||||
},
|
||||
"Component_[684670817803453913]": {
|
||||
"$type": "EditorEntitySortComponent",
|
||||
"Id": 684670817803453913,
|
||||
"Child Entity Order": [
|
||||
"Entity_[323002054759]",
|
||||
"Entity_[327297022055]",
|
||||
"Entity_[301527218279]",
|
||||
"Entity_[318707087463]",
|
||||
"Entity_[297232250983]",
|
||||
"Entity_[314412120167]",
|
||||
"Entity_[331591989351]",
|
||||
"Entity_[310117152871]"
|
||||
]
|
||||
},
|
||||
"Component_[8118206464926826097]": {
|
||||
"$type": "EditorOnlyEntityComponent",
|
||||
"Id": 8118206464926826097
|
||||
},
|
||||
"Component_[836721549453007197]": {
|
||||
"$type": "EditorCommentComponent",
|
||||
"Id": 836721549453007197,
|
||||
"Configuration": "Entity names are used to trigger the missing dependency scanner. Comments are stripped from dynamic slices."
|
||||
}
|
||||
}
|
||||
},
|
||||
"Entity_[310117152871]": {
|
||||
"Id": "Entity_[310117152871]",
|
||||
"Name": "Materials/FakeMaterial.mtl",
|
||||
"Components": {
|
||||
"Component_[10593857511582714674]": {
|
||||
"$type": "EditorInspectorComponent",
|
||||
"Id": 10593857511582714674,
|
||||
"ComponentOrderEntryArray": [
|
||||
{
|
||||
"ComponentId": 11797216659359478300
|
||||
},
|
||||
{
|
||||
"ComponentId": 13816702107134233983,
|
||||
"SortIndex": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"Component_[11797216659359478300]": {
|
||||
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
|
||||
"Id": 11797216659359478300,
|
||||
"Parent Entity": "Entity_[305822185575]"
|
||||
},
|
||||
"Component_[13816702107134233983]": {
|
||||
"$type": "EditorCommentComponent",
|
||||
"Id": 13816702107134233983,
|
||||
"Configuration": "Invalid path that does not match an existing dependency of this asset"
|
||||
},
|
||||
"Component_[14868583012186337705]": {
|
||||
"$type": "EditorOnlyEntityComponent",
|
||||
"Id": 14868583012186337705
|
||||
},
|
||||
"Component_[14965348027145283648]": {
|
||||
"$type": "EditorEntitySortComponent",
|
||||
"Id": 14965348027145283648
|
||||
},
|
||||
"Component_[15075774238648121688]": {
|
||||
"$type": "EditorEntityIconComponent",
|
||||
"Id": 15075774238648121688
|
||||
},
|
||||
"Component_[16157883709857447266]": {
|
||||
"$type": "EditorLockComponent",
|
||||
"Id": 16157883709857447266
|
||||
},
|
||||
"Component_[17712080510249108208]": {
|
||||
"$type": "EditorVisibilityComponent",
|
||||
"Id": 17712080510249108208
|
||||
},
|
||||
"Component_[2247408514677946398]": {
|
||||
"$type": "SelectionComponent",
|
||||
"Id": 2247408514677946398
|
||||
},
|
||||
"Component_[5565369976544134481]": {
|
||||
"$type": "EditorPendingCompositionComponent",
|
||||
"Id": 5565369976544134481
|
||||
},
|
||||
"Component_[6044814215558788086]": {
|
||||
"$type": "EditorDisabledCompositionComponent",
|
||||
"Id": 6044814215558788086
|
||||
}
|
||||
}
|
||||
},
|
||||
"Entity_[314412120167]": {
|
||||
"Id": "Entity_[314412120167]",
|
||||
"Name": "88888888-4444-4444-4444-CCCCCCCCCCCC",
|
||||
"Components": {
|
||||
"Component_[10072177500579430176]": {
|
||||
"$type": "EditorLockComponent",
|
||||
"Id": 10072177500579430176
|
||||
},
|
||||
"Component_[10853215476279564671]": {
|
||||
"$type": "EditorCommentComponent",
|
||||
"Id": 10853215476279564671,
|
||||
"Configuration": "UUID that does not exist"
|
||||
},
|
||||
"Component_[13413154971272749631]": {
|
||||
"$type": "SelectionComponent",
|
||||
"Id": 13413154971272749631
|
||||
},
|
||||
"Component_[15316173756367163440]": {
|
||||
"$type": "EditorInspectorComponent",
|
||||
"Id": 15316173756367163440,
|
||||
"ComponentOrderEntryArray": [
|
||||
{
|
||||
"ComponentId": 3266728630359207653
|
||||
},
|
||||
{
|
||||
"ComponentId": 10853215476279564671,
|
||||
"SortIndex": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"Component_[15809307959802829291]": {
|
||||
"$type": "EditorEntitySortComponent",
|
||||
"Id": 15809307959802829291
|
||||
},
|
||||
"Component_[17649652752752487081]": {
|
||||
"$type": "EditorEntityIconComponent",
|
||||
"Id": 17649652752752487081
|
||||
},
|
||||
"Component_[2130036493438440377]": {
|
||||
"$type": "EditorPendingCompositionComponent",
|
||||
"Id": 2130036493438440377
|
||||
},
|
||||
"Component_[3266728630359207653]": {
|
||||
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
|
||||
"Id": 3266728630359207653,
|
||||
"Parent Entity": "Entity_[305822185575]"
|
||||
},
|
||||
"Component_[5892125564582966187]": {
|
||||
"$type": "EditorVisibilityComponent",
|
||||
"Id": 5892125564582966187
|
||||
},
|
||||
"Component_[597602776660257245]": {
|
||||
"$type": "EditorOnlyEntityComponent",
|
||||
"Id": 597602776660257245
|
||||
},
|
||||
"Component_[8238652007701465495]": {
|
||||
"$type": "EditorDisabledCompositionComponent",
|
||||
"Id": 8238652007701465495
|
||||
}
|
||||
}
|
||||
},
|
||||
"Entity_[318707087463]": {
|
||||
"Id": "Entity_[318707087463]",
|
||||
"Name": "BBA1A5494C73578894BF0692CDA5FC33",
|
||||
"Components": {
|
||||
"Component_[10222455787643359341]": {
|
||||
"$type": "EditorPendingCompositionComponent",
|
||||
"Id": 10222455787643359341
|
||||
},
|
||||
"Component_[11487845392038268864]": {
|
||||
"$type": "SelectionComponent",
|
||||
"Id": 11487845392038268864
|
||||
},
|
||||
"Component_[12135534290310046764]": {
|
||||
"$type": "EditorVisibilityComponent",
|
||||
"Id": 12135534290310046764
|
||||
},
|
||||
"Component_[14412623226519978498]": {
|
||||
"$type": "EditorOnlyEntityComponent",
|
||||
"Id": 14412623226519978498
|
||||
},
|
||||
"Component_[14516371382857751872]": {
|
||||
"$type": "EditorEntityIconComponent",
|
||||
"Id": 14516371382857751872
|
||||
},
|
||||
"Component_[16011611743122468576]": {
|
||||
"$type": "EditorInspectorComponent",
|
||||
"Id": 16011611743122468576,
|
||||
"ComponentOrderEntryArray": [
|
||||
{
|
||||
"ComponentId": 4157328932578509254
|
||||
},
|
||||
{
|
||||
"ComponentId": 8524796860605854850,
|
||||
"SortIndex": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"Component_[3813931698067937301]": {
|
||||
"$type": "EditorEntitySortComponent",
|
||||
"Id": 3813931698067937301
|
||||
},
|
||||
"Component_[4157328932578509254]": {
|
||||
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
|
||||
"Id": 4157328932578509254,
|
||||
"Parent Entity": "Entity_[305822185575]"
|
||||
},
|
||||
"Component_[8524796860605854850]": {
|
||||
"$type": "EditorCommentComponent",
|
||||
"Id": 8524796860605854850,
|
||||
"Configuration": "UUID that matches an existing dependency of this asset (lumbertank_body.cgf)"
|
||||
},
|
||||
"Component_[8660819596448699427]": {
|
||||
"$type": "EditorLockComponent",
|
||||
"Id": 8660819596448699427
|
||||
},
|
||||
"Component_[8768262795169819026]": {
|
||||
"$type": "EditorDisabledCompositionComponent",
|
||||
"Id": 8768262795169819026
|
||||
}
|
||||
}
|
||||
},
|
||||
"Entity_[323002054759]": {
|
||||
"Id": "Entity_[323002054759]",
|
||||
"Name": "Materials/am_rockground.mtl",
|
||||
"Components": {
|
||||
"Component_[13459503224133892836]": {
|
||||
"$type": "EditorEntityIconComponent",
|
||||
"Id": 13459503224133892836
|
||||
},
|
||||
"Component_[1346698328271204385]": {
|
||||
"$type": "EditorVisibilityComponent",
|
||||
"Id": 1346698328271204385
|
||||
},
|
||||
"Component_[13662830241397426219]": {
|
||||
"$type": "SelectionComponent",
|
||||
"Id": 13662830241397426219
|
||||
},
|
||||
"Component_[14169735046939083706]": {
|
||||
"$type": "EditorInspectorComponent",
|
||||
"Id": 14169735046939083706,
|
||||
"ComponentOrderEntryArray": [
|
||||
{
|
||||
"ComponentId": 833157791612452820
|
||||
},
|
||||
{
|
||||
"ComponentId": 3573928838741352115,
|
||||
"SortIndex": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"Component_[16049700338512950477]": {
|
||||
"$type": "EditorDisabledCompositionComponent",
|
||||
"Id": 16049700338512950477
|
||||
},
|
||||
"Component_[16191253524853449302]": {
|
||||
"$type": "EditorOnlyEntityComponent",
|
||||
"Id": 16191253524853449302
|
||||
},
|
||||
"Component_[1737139665005484521]": {
|
||||
"$type": "EditorPendingCompositionComponent",
|
||||
"Id": 1737139665005484521
|
||||
},
|
||||
"Component_[17562284119637289685]": {
|
||||
"$type": "EditorEntitySortComponent",
|
||||
"Id": 17562284119637289685
|
||||
},
|
||||
"Component_[3573928838741352115]": {
|
||||
"$type": "EditorCommentComponent",
|
||||
"Id": 3573928838741352115,
|
||||
"Configuration": "Relative source path that matches an existing dependency of this asset"
|
||||
},
|
||||
"Component_[485401015869338526]": {
|
||||
"$type": "EditorLockComponent",
|
||||
"Id": 485401015869338526
|
||||
},
|
||||
"Component_[833157791612452820]": {
|
||||
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
|
||||
"Id": 833157791612452820,
|
||||
"Parent Entity": "Entity_[305822185575]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Entity_[327297022055]": {
|
||||
"Id": "Entity_[327297022055]",
|
||||
"Name": "Config/Game.xml",
|
||||
"Components": {
|
||||
"Component_[11848260632907964142]": {
|
||||
"$type": "EditorInspectorComponent",
|
||||
"Id": 11848260632907964142,
|
||||
"ComponentOrderEntryArray": [
|
||||
{
|
||||
"ComponentId": 497869813123895830
|
||||
},
|
||||
{
|
||||
"ComponentId": 5248857300320701553,
|
||||
"SortIndex": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"Component_[12842864953492512672]": {
|
||||
"$type": "EditorEntitySortComponent",
|
||||
"Id": 12842864953492512672
|
||||
},
|
||||
"Component_[16656501539883791157]": {
|
||||
"$type": "EditorLockComponent",
|
||||
"Id": 16656501539883791157
|
||||
},
|
||||
"Component_[17365661125603122123]": {
|
||||
"$type": "EditorEntityIconComponent",
|
||||
"Id": 17365661125603122123
|
||||
},
|
||||
"Component_[2967487135389707052]": {
|
||||
"$type": "EditorDisabledCompositionComponent",
|
||||
"Id": 2967487135389707052
|
||||
},
|
||||
"Component_[3356294263684362888]": {
|
||||
"$type": "EditorOnlyEntityComponent",
|
||||
"Id": 3356294263684362888
|
||||
},
|
||||
"Component_[497869813123895830]": {
|
||||
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
|
||||
"Id": 497869813123895830,
|
||||
"Parent Entity": "Entity_[305822185575]"
|
||||
},
|
||||
"Component_[5248857300320701553]": {
|
||||
"$type": "EditorCommentComponent",
|
||||
"Id": 5248857300320701553,
|
||||
"Configuration": "Valid path that does not match an existing dependency of this asset. Should report as a missing dependency"
|
||||
},
|
||||
"Component_[746309483212393367]": {
|
||||
"$type": "SelectionComponent",
|
||||
"Id": 746309483212393367
|
||||
},
|
||||
"Component_[8319831469290771470]": {
|
||||
"$type": "EditorPendingCompositionComponent",
|
||||
"Id": 8319831469290771470
|
||||
},
|
||||
"Component_[9369067377618608622]": {
|
||||
"$type": "EditorVisibilityComponent",
|
||||
"Id": 9369067377618608622
|
||||
}
|
||||
}
|
||||
},
|
||||
"Entity_[331591989351]": {
|
||||
"Id": "Entity_[331591989351]",
|
||||
"Name": "1151F14D38A65579888ABE3139882E68:[333]",
|
||||
"Components": {
|
||||
"Component_[104857639379046106]": {
|
||||
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
|
||||
"Id": 104857639379046106,
|
||||
"Parent Entity": "Entity_[305822185575]"
|
||||
},
|
||||
"Component_[1061601983221247493]": {
|
||||
"$type": "EditorLockComponent",
|
||||
"Id": 1061601983221247493
|
||||
},
|
||||
"Component_[11028443253330664986]": {
|
||||
"$type": "EditorVisibilityComponent",
|
||||
"Id": 11028443253330664986
|
||||
},
|
||||
"Component_[13806275118632081006]": {
|
||||
"$type": "EditorEntitySortComponent",
|
||||
"Id": 13806275118632081006
|
||||
},
|
||||
"Component_[13922573109551604801]": {
|
||||
"$type": "EditorEntityIconComponent",
|
||||
"Id": 13922573109551604801
|
||||
},
|
||||
"Component_[17027032709917108335]": {
|
||||
"$type": "EditorDisabledCompositionComponent",
|
||||
"Id": 17027032709917108335
|
||||
},
|
||||
"Component_[17030988165269698825]": {
|
||||
"$type": "EditorOnlyEntityComponent",
|
||||
"Id": 17030988165269698825
|
||||
},
|
||||
"Component_[2294579021665535860]": {
|
||||
"$type": "EditorPendingCompositionComponent",
|
||||
"Id": 2294579021665535860
|
||||
},
|
||||
"Component_[5863078697041048226]": {
|
||||
"$type": "EditorInspectorComponent",
|
||||
"Id": 5863078697041048226,
|
||||
"ComponentOrderEntryArray": [
|
||||
{
|
||||
"ComponentId": 104857639379046106
|
||||
},
|
||||
{
|
||||
"ComponentId": 9466290982672370664,
|
||||
"SortIndex": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"Component_[7608263859116142496]": {
|
||||
"$type": "SelectionComponent",
|
||||
"Id": 7608263859116142496
|
||||
},
|
||||
"Component_[9466290982672370664]": {
|
||||
"$type": "EditorCommentComponent",
|
||||
"Id": 9466290982672370664,
|
||||
"Configuration": "Asset ID that does not exist (am_grass1.mtl UUID, no matching product ID)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7c6b33c6137d6bd8c696f180c30a23089c95c1af398a630b4b13e080bec3254d
|
||||
size 18220
|
||||
+2
-3
@@ -23,12 +23,11 @@ from ..ap_fixtures.timeout_option_fixture import timeout_option_fixture as timeo
|
||||
@pytest.mark.SUITE_periodic
|
||||
@pytest.mark.parametrize('launcher_platform', ['windows_editor'])
|
||||
@pytest.mark.parametrize('project', ['AutomatedTesting'])
|
||||
@pytest.mark.parametrize('level', ['auto_test'])
|
||||
@pytest.mark.parametrize('level', ['TestDependenciesLevel'])
|
||||
class TestBundleMode(object):
|
||||
def test_bundle_mode_with_levels_mounts_bundles_correctly(self, request, editor, level, launcher_platform,
|
||||
asset_processor, workspace, bundler_batch_helper):
|
||||
level_pak = os.path.join("levels", level, "level.pak")
|
||||
|
||||
level_pak = os.path.join("levels", level, "TestDependenciesLevel.spawnable")
|
||||
bundles_folder = os.path.join(workspace.paths.project(), "Bundles")
|
||||
bundle_request_path = os.path.join(bundles_folder, "bundle.pak")
|
||||
bundle_result_path = os.path.join(bundles_folder,
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ def output_test_data(scene):
|
||||
# Just write something to the file, but the filename is the main information
|
||||
# used for the test.
|
||||
f.write(f"scene.sourceFilename: {scene.sourceFilename}\n")
|
||||
return True
|
||||
return ''
|
||||
|
||||
mySceneJobHandler = None
|
||||
|
||||
|
||||
@@ -8,10 +8,12 @@ SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
|
||||
import os
|
||||
import logging
|
||||
import subprocess
|
||||
import sys
|
||||
import pytest
|
||||
import time
|
||||
|
||||
from os import path
|
||||
|
||||
import ly_test_tools.environment.file_system as file_system
|
||||
import ly_test_tools.environment.process_utils as process_utils
|
||||
import ly_test_tools.environment.waiter as waiter
|
||||
@@ -98,7 +100,9 @@ class TestAutomationBase:
|
||||
if autotest_mode:
|
||||
pycmd += ["-autotest_mode"]
|
||||
if enable_prefab_system:
|
||||
pycmd += ["--regset=/Amazon/Preferences/EnablePrefabSystem=true"]
|
||||
pycmd += [
|
||||
"--regset=/Amazon/Preferences/EnablePrefabSystem=true",
|
||||
f"--regset-file={path.join(workspace.paths.engine_root(), 'Registry', 'prefab.test.setreg')}"]
|
||||
else:
|
||||
pycmd += ["--regset=/Amazon/Preferences/EnablePrefabSystem=false"]
|
||||
|
||||
@@ -128,7 +132,8 @@ class TestAutomationBase:
|
||||
errors.append(TestRunError("FAILED TEST", error_str))
|
||||
if return_code and return_code != TestAutomationBase.TEST_FAIL_RETCODE: # Crashed
|
||||
crash_info = "-- No crash log available --"
|
||||
crash_log = os.path.join(workspace.paths.project_log(), 'error.log')
|
||||
crash_log = workspace.paths.crash_log()
|
||||
|
||||
try:
|
||||
waiter.wait_for(lambda: os.path.exists(crash_log), timeout=TestAutomationBase.WAIT_FOR_CRASH_LOG)
|
||||
except AssertionError:
|
||||
@@ -177,7 +182,7 @@ class TestAutomationBase:
|
||||
@staticmethod
|
||||
def _kill_ly_processes(include_asset_processor=True):
|
||||
LY_PROCESSES = [
|
||||
'Editor', 'Profiler', 'RemoteConsole',
|
||||
'Editor', 'Profiler', 'RemoteConsole', 'AutomatedTesting.ServerLauncher', 'o3de'
|
||||
]
|
||||
AP_PROCESSES = [
|
||||
'AssetProcessor', 'AssetProcessorBatch', 'AssetBuilder', 'CrySCompileServer',
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_FOUNDATION_TEST_SUPPORTED)
|
||||
|
||||
ly_add_pytest(
|
||||
NAME AutomatedTesting::EditorTests_Main_Optimized
|
||||
NAME AutomatedTesting::EditorTests_Main
|
||||
TEST_SUITE main
|
||||
TEST_SERIAL
|
||||
PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Main_Optimized.py
|
||||
PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Main.py
|
||||
PYTEST_MARKS "not REQUIRES_gpu"
|
||||
RUNTIME_DEPENDENCIES
|
||||
Legacy::Editor
|
||||
@@ -27,7 +27,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_
|
||||
TEST_SUITE main
|
||||
TEST_SERIAL
|
||||
TEST_REQUIRES gpu
|
||||
PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Main_Optimized.py
|
||||
PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Main.py
|
||||
PYTEST_MARKS "REQUIRES_gpu"
|
||||
RUNTIME_DEPENDENCIES
|
||||
Legacy::Editor
|
||||
@@ -37,17 +37,4 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_
|
||||
Editor
|
||||
)
|
||||
|
||||
ly_add_pytest(
|
||||
NAME AutomatedTesting::EditorTests_Sandbox_Optimized
|
||||
TEST_SUITE sandbox
|
||||
TEST_SERIAL
|
||||
PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Sandbox_Optimized.py
|
||||
RUNTIME_DEPENDENCIES
|
||||
Legacy::Editor
|
||||
AZ::AssetProcessor
|
||||
AutomatedTesting.Assets
|
||||
COMPONENT
|
||||
Editor
|
||||
)
|
||||
|
||||
endif()
|
||||
|
||||
+9
-4
@@ -59,8 +59,8 @@ def AssetBrowser_SearchFiltering():
|
||||
|
||||
import azlmbr.legacy.general as general
|
||||
|
||||
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 verify_files_appeared(model, allowed_asset_extensions, parent_index=QtCore.QModelIndex()):
|
||||
indexes = [parent_index]
|
||||
@@ -80,8 +80,7 @@ def AssetBrowser_SearchFiltering():
|
||||
return True
|
||||
|
||||
# 1) Open an existing simple level
|
||||
helper.init_idle()
|
||||
helper.open_level("Physics", "Base")
|
||||
hydra.open_base_level()
|
||||
|
||||
# 2) Open Asset Browser (if not opened already)
|
||||
editor_window = pyside_utils.get_editor_main_window()
|
||||
@@ -98,7 +97,13 @@ def AssetBrowser_SearchFiltering():
|
||||
# 3) Type the name of an asset in the search bar and make sure it is filtered to and selectable
|
||||
asset_browser = editor_window.findChild(QtWidgets.QDockWidget, "Asset Browser")
|
||||
search_bar = asset_browser.findChild(QtWidgets.QLineEdit, "textSearch")
|
||||
search_bar.setText("cedar.fbx")
|
||||
|
||||
# Add a small pause when typing in the search bar in order to check that the entries are updated properly
|
||||
search_bar.setText("Cedar.f")
|
||||
general.idle_wait(0.5)
|
||||
search_bar.setText("Cedar.fbx")
|
||||
general.idle_wait(0.5)
|
||||
|
||||
asset_browser_tree = asset_browser.findChild(QtWidgets.QTreeView, "m_assetBrowserTreeViewWidget")
|
||||
asset_browser_table = asset_browser.findChild(QtWidgets.QTreeView, "m_assetBrowserTableViewWidget")
|
||||
found = await pyside_utils.wait_for_condition(lambda: pyside_utils.find_child_by_pattern(asset_browser_table, "cedar.fbx"), 5.0)
|
||||
|
||||
+2
-2
@@ -53,6 +53,7 @@ def AssetBrowser_TreeNavigation():
|
||||
import azlmbr.legacy.general as general
|
||||
|
||||
import editor_python_test_tools.pyside_utils as pyside_utils
|
||||
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
|
||||
|
||||
@@ -69,8 +70,7 @@ def AssetBrowser_TreeNavigation():
|
||||
file_path = ("AutomatedTesting", "Assets", "ImageGradients", "image_grad_test_gsi.png")
|
||||
|
||||
# 1) Open an existing simple level
|
||||
helper.init_idle()
|
||||
helper.open_level("Physics", "Base")
|
||||
hydra.open_base_level()
|
||||
|
||||
# 2) Open Asset Browser (if not opened already)
|
||||
editor_window = pyside_utils.get_editor_main_window()
|
||||
|
||||
@@ -215,8 +215,7 @@ def AssetPicker_UI_UX():
|
||||
QtTest.QTest.keyClick(tree, Qt.Key_Enter, Qt.NoModifier)
|
||||
|
||||
# 1) Open an existing simple level
|
||||
helper.init_idle()
|
||||
helper.open_level("Physics", "Base")
|
||||
hydra.open_base_level()
|
||||
|
||||
# 2) Create entity and add Mesh component
|
||||
entity_position = math.Vector3(125.0, 136.0, 32.0)
|
||||
|
||||
+2
-4
@@ -56,19 +56,17 @@ def BasicEditorWorkflows_ExistingLevel_EntityComponentCRUD():
|
||||
06. delete parent entity
|
||||
"""
|
||||
|
||||
import editor_python_test_tools.hydra_editor_utils as hydra
|
||||
from editor_python_test_tools.utils import Report
|
||||
from editor_python_test_tools.editor_entity_utils import EditorEntity
|
||||
|
||||
import azlmbr.bus as bus
|
||||
import azlmbr.editor as editor
|
||||
import azlmbr.entity as entity
|
||||
import azlmbr.legacy.general as general
|
||||
import azlmbr.object
|
||||
|
||||
# 01. load an existing level
|
||||
test_level = 'Simple'
|
||||
general.open_level_no_prompt(test_level)
|
||||
Report.result(Tests.load_level, general.get_current_level_name() == test_level)
|
||||
hydra.open_base_level()
|
||||
|
||||
# 02. create parent entity and set name
|
||||
# Delete any exiting entity and Create a new Entity at the root level
|
||||
|
||||
+13
-17
@@ -31,7 +31,7 @@ class Tests:
|
||||
"Component removed from entity successfully",
|
||||
"Failed to remove component from entity"
|
||||
)
|
||||
level_saved_and_exported = (
|
||||
saved_and_exported = (
|
||||
"Level saved and exported successfully",
|
||||
"Failed to save/export level"
|
||||
)
|
||||
@@ -52,8 +52,7 @@ def BasicEditorWorkflows_LevelEntityComponentCRUD():
|
||||
- A new entity can be created
|
||||
- Entity hierarchy can be adjusted
|
||||
- Components can be added/removed/updated
|
||||
- Level can be saved
|
||||
- Level can be exported
|
||||
- Level can be saved/exported
|
||||
|
||||
Note:
|
||||
- This test file must be called from the O3DE Editor command terminal
|
||||
@@ -70,7 +69,7 @@ def BasicEditorWorkflows_LevelEntityComponentCRUD():
|
||||
import azlmbr.editor as editor
|
||||
import azlmbr.entity as entity
|
||||
import azlmbr.math as math
|
||||
import azlmbr.paths
|
||||
import azlmbr.paths as paths
|
||||
|
||||
import editor_python_test_tools.hydra_editor_utils as hydra
|
||||
from editor_python_test_tools.utils import Report
|
||||
@@ -84,7 +83,7 @@ def BasicEditorWorkflows_LevelEntityComponentCRUD():
|
||||
return None
|
||||
|
||||
# 1) Create a new level
|
||||
level = "tmp_level"
|
||||
lvl_name = "tmp_level"
|
||||
editor_window = pyside_utils.get_editor_main_window()
|
||||
new_level_action = pyside_utils.get_action_for_menu_path(editor_window, "File", "New Level")
|
||||
pyside_utils.trigger_action_async(new_level_action)
|
||||
@@ -95,23 +94,24 @@ def BasicEditorWorkflows_LevelEntityComponentCRUD():
|
||||
Report.info("New Level dialog opened")
|
||||
grp_box = new_level_dlg.findChild(QtWidgets.QGroupBox, "STATIC_GROUP1")
|
||||
level_name = grp_box.findChild(QtWidgets.QLineEdit, "LEVEL")
|
||||
level_name.setText(level)
|
||||
level_name.setText(lvl_name)
|
||||
button_box = new_level_dlg.findChild(QtWidgets.QDialogButtonBox, "buttonBox")
|
||||
button_box.button(QtWidgets.QDialogButtonBox.Ok).click()
|
||||
|
||||
# Verify new level was created successfully
|
||||
level_create_success = await pyside_utils.wait_for_condition(lambda: editor.EditorToolsApplicationRequestBus(
|
||||
bus.Broadcast, "GetCurrentLevelName") == level, 5.0)
|
||||
bus.Broadcast, "GetCurrentLevelName") == lvl_name, 5.0)
|
||||
Report.critical_result(Tests.level_created, level_create_success)
|
||||
|
||||
# 2) Delete existing entities, and create and manipulate new entities via Entity Inspector
|
||||
search_filter = azlmbr.entity.SearchFilter()
|
||||
all_entities = entity.SearchBus(azlmbr.bus.Broadcast, "SearchEntities", search_filter)
|
||||
editor.ToolsApplicationRequestBus(bus.Broadcast, "DeleteEntities", all_entities)
|
||||
entity_outliner_widget = editor_window.findChild(QtWidgets.QWidget, "OutlinerWidgetUI")
|
||||
entity_outliner_widget = editor_window.findChild(QtWidgets.QWidget, "EntityOutlinerWidgetUI")
|
||||
outliner_object_list = entity_outliner_widget.findChild(QtWidgets.QWidget, "m_objectList_Contents")
|
||||
outliner_tree = outliner_object_list.findChild(QtWidgets.QWidget, "m_objectTree")
|
||||
await pyside_utils.trigger_context_menu_entry(outliner_tree, "Create entity")
|
||||
outliner_viewport = outliner_tree.findChild(QtWidgets.QWidget, "qt_scrollarea_viewport")
|
||||
await pyside_utils.trigger_context_menu_entry(outliner_viewport, "Create entity")
|
||||
|
||||
# Find the new entity
|
||||
parent_entity_id = find_entity_by_name("Entity1")
|
||||
@@ -153,14 +153,10 @@ def BasicEditorWorkflows_LevelEntityComponentCRUD():
|
||||
save_level_action = pyside_utils.get_action_for_menu_path(editor_window, "File", "Save")
|
||||
pyside_utils.trigger_action_async(save_level_action)
|
||||
|
||||
# 5) Export the level
|
||||
export_action = pyside_utils.get_action_for_menu_path(editor_window, "Game", "Export to Engine")
|
||||
pyside_utils.trigger_action_async(export_action)
|
||||
level_pak_file = os.path.join(
|
||||
"AutomatedTesting", "Levels", level, "level.pak"
|
||||
)
|
||||
export_success = await pyside_utils.wait_for_condition(lambda: os.path.exists(level_pak_file), 5.0)
|
||||
Report.result(Tests.level_saved_and_exported, export_success)
|
||||
# 5) Verify the save/export of the level
|
||||
level_prefab_path = os.path.join(paths.products, "levels", lvl_name, f"{lvl_name}.spawnable")
|
||||
success = await pyside_utils.wait_for_condition(lambda: os.path.exists(level_prefab_path), 5.0)
|
||||
Report.result(Tests.saved_and_exported, success)
|
||||
|
||||
run_test()
|
||||
|
||||
|
||||
+2
-4
@@ -63,7 +63,7 @@ def ComponentCRUD_Add_Delete_Components():
|
||||
:return: None
|
||||
"""
|
||||
|
||||
from PySide2 import QtWidgets, QtTest, QtCore
|
||||
from PySide2 import QtWidgets, QtTest
|
||||
from PySide2.QtCore import Qt
|
||||
|
||||
import azlmbr.legacy.general as general
|
||||
@@ -74,7 +74,6 @@ def ComponentCRUD_Add_Delete_Components():
|
||||
|
||||
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
|
||||
|
||||
async def add_component(component_name):
|
||||
pyside_utils.click_button_async(add_comp_btn)
|
||||
@@ -88,8 +87,7 @@ def ComponentCRUD_Add_Delete_Components():
|
||||
QtTest.QTest.keyClick(tree, Qt.Key_Enter, Qt.NoModifier)
|
||||
|
||||
# 1) Open an existing simple level
|
||||
helper.init_idle()
|
||||
helper.open_level("Physics", "Base")
|
||||
hydra.open_base_level()
|
||||
|
||||
# 2) Create entity
|
||||
entity_position = math.Vector3(125.0, 136.0, 32.0)
|
||||
|
||||
@@ -62,12 +62,11 @@ def Docking_BasicDockedTools():
|
||||
import azlmbr.editor as editor
|
||||
import azlmbr.entity as entity
|
||||
|
||||
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
|
||||
|
||||
# Open an existing simple level
|
||||
helper.init_idle()
|
||||
helper.open_level("Physics", "Base")
|
||||
hydra.open_base_level()
|
||||
|
||||
# Make sure the Entity Outliner, Entity Inspector and Console tools are open
|
||||
general.open_pane("Entity Outliner (PREVIEW)")
|
||||
@@ -80,7 +79,7 @@ def Docking_BasicDockedTools():
|
||||
editor.EditorEntityAPIBus(bus.Event, 'SetName', entity_id, entity_original_name)
|
||||
|
||||
editor_window = pyside_utils.get_editor_main_window()
|
||||
entity_outliner = editor_window.findChild(QtWidgets.QDockWidget, "Entity Outliner (PREVIEW)")
|
||||
entity_outliner = editor_window.findChild(QtWidgets.QDockWidget, "Entity Outliner")
|
||||
|
||||
# 1) Open the tools and dock them together in a floating tabbed widget.
|
||||
# We drag/drop it over the viewport since it doesn't allow docking, so this will undock it
|
||||
@@ -88,15 +87,15 @@ def Docking_BasicDockedTools():
|
||||
pyside_utils.drag_and_drop(entity_outliner, render_overlay)
|
||||
|
||||
# We need to grab a new reference to the Entity Outliner QDockWidget because when it gets moved
|
||||
# to the floating window, its parent changes so the wrapped intance we had becomes invalid
|
||||
entity_outliner = editor_window.findChild(QtWidgets.QDockWidget, "Entity Outliner (PREVIEW)")
|
||||
# to the floating window, its parent changes so the wrapped instance we had becomes invalid
|
||||
entity_outliner = editor_window.findChild(QtWidgets.QDockWidget, "Entity Outliner")
|
||||
|
||||
# Dock the Entity Inspector tabbed with the floating Entity Outliner
|
||||
entity_inspector = editor_window.findChild(QtWidgets.QDockWidget, "Entity Inspector")
|
||||
pyside_utils.drag_and_drop(entity_inspector, entity_outliner)
|
||||
|
||||
# We need to grab a new reference to the Entity Inspector QDockWidget because when it gets moved
|
||||
# to the floating window, its parent changes so the wrapped intance we had becomes invalid
|
||||
# to the floating window, its parent changes so the wrapped instance we had becomes invalid
|
||||
entity_inspector = editor_window.findChild(QtWidgets.QDockWidget, "Entity Inspector")
|
||||
|
||||
# Dock the Console tabbed with the floating Entity Inspector
|
||||
@@ -106,7 +105,7 @@ def Docking_BasicDockedTools():
|
||||
# Check to ensure all the tools are parented to the same QStackedWidget
|
||||
def check_all_panes_tabbed():
|
||||
entity_inspector = editor_window.findChild(QtWidgets.QDockWidget, "Entity Inspector")
|
||||
entity_outliner = editor_window.findChild(QtWidgets.QDockWidget, "Entity Outliner (PREVIEW)")
|
||||
entity_outliner = editor_window.findChild(QtWidgets.QDockWidget, "Entity Outliner")
|
||||
console = editor_window.findChild(QtWidgets.QDockWidget, "Console")
|
||||
entity_inspector_parent = entity_inspector.parentWidget()
|
||||
entity_outliner_parent = entity_outliner.parentWidget()
|
||||
@@ -122,7 +121,7 @@ def Docking_BasicDockedTools():
|
||||
|
||||
# 2.1,2) Select an Entity in the Entity Outliner.
|
||||
entity_inspector = editor_window.findChild(QtWidgets.QDockWidget, "Entity Inspector")
|
||||
entity_outliner = editor_window.findChild(QtWidgets.QDockWidget, "Entity Outliner (PREVIEW)")
|
||||
entity_outliner = editor_window.findChild(QtWidgets.QDockWidget, "Entity Outliner")
|
||||
console = editor_window.findChild(QtWidgets.QDockWidget, "Console")
|
||||
object_tree = entity_outliner.findChild(QtWidgets.QTreeView, "m_objectTree")
|
||||
test_entity_index = pyside_utils.find_child_by_pattern(object_tree, entity_original_name)
|
||||
|
||||
+6
-5
@@ -30,11 +30,13 @@ def EntityOutliner_EntityOrdering():
|
||||
5) Add another new entity, ensure the rest of the order is unchanged
|
||||
"""
|
||||
|
||||
import editor_python_test_tools.pyside_utils as pyside_utils
|
||||
from PySide2 import QtCore
|
||||
|
||||
import azlmbr.legacy.general as general
|
||||
|
||||
import editor_python_test_tools.hydra_editor_utils as hydra
|
||||
import editor_python_test_tools.pyside_utils as pyside_utils
|
||||
from editor_python_test_tools.utils import Report
|
||||
from editor_python_test_tools.utils import TestHelper as helper
|
||||
from PySide2 import QtCore, QtWidgets, QtGui, QtTest
|
||||
|
||||
# Grab the Editor, Entity Outliner, and Outliner Model
|
||||
editor_window = pyside_utils.get_editor_main_window()
|
||||
@@ -110,8 +112,7 @@ def EntityOutliner_EntityOrdering():
|
||||
expected_order = []
|
||||
|
||||
# 1) Open the empty Prefab Base level
|
||||
helper.init_idle()
|
||||
helper.open_level("Prefab", "Base")
|
||||
hydra.open_base_level()
|
||||
|
||||
# 2) Add 5 entities to the outliner
|
||||
ENTITIES_TO_ADD = 5
|
||||
|
||||
+2
-3
@@ -69,8 +69,8 @@ def InputBindings_Add_Remove_Input_Events():
|
||||
|
||||
import azlmbr.legacy.general as general
|
||||
|
||||
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 open_asset_editor():
|
||||
general.open_pane("Asset Editor")
|
||||
@@ -81,8 +81,7 @@ def InputBindings_Add_Remove_Input_Events():
|
||||
return not general.is_pane_visible("Asset Editor")
|
||||
|
||||
# 1) Open an existing simple level
|
||||
helper.init_idle()
|
||||
helper.open_level("Physics", "Base")
|
||||
hydra.open_base_level()
|
||||
|
||||
# 2) Open Asset Editor
|
||||
Report.result(Tests.asset_editor_opened, open_asset_editor())
|
||||
|
||||
@@ -26,9 +26,9 @@ def Menus_EditMenuOptions_Work():
|
||||
:return: None
|
||||
"""
|
||||
|
||||
import editor_python_test_tools.hydra_editor_utils as hydra
|
||||
import editor_python_test_tools.pyside_utils as pyside_utils
|
||||
from editor_python_test_tools.utils import Report
|
||||
from editor_python_test_tools.utils import TestHelper as helper
|
||||
|
||||
edit_menu_options = [
|
||||
("Undo",),
|
||||
@@ -40,30 +40,32 @@ def Menus_EditMenuOptions_Work():
|
||||
("Toggle Pivot Location",),
|
||||
("Reset Entity Transform",),
|
||||
("Reset Manipulator",),
|
||||
("Reset Transform (Local)",),
|
||||
("Reset Transform (World)",),
|
||||
("Hide Selection",),
|
||||
("Show All",),
|
||||
("Modify", "Snap", "Snap angle"),
|
||||
("Lock Selection",),
|
||||
("Unlock All Entities",),
|
||||
("Modify", "Snap", "Angle snapping"),
|
||||
("Modify", "Snap", "Grid snapping"),
|
||||
("Modify", "Transform Mode", "Move"),
|
||||
("Modify", "Transform Mode", "Rotate"),
|
||||
("Modify", "Transform Mode", "Scale"),
|
||||
("Editor Settings", "Global Preferences"),
|
||||
("Editor Settings", "Editor Settings Manager"),
|
||||
("Editor Settings", "Keyboard Customization", "Customize Keyboard"),
|
||||
("Editor Settings", "Keyboard Customization", "Export Keyboard Settings"),
|
||||
("Editor Settings", "Keyboard Customization", "Import Keyboard Settings"),
|
||||
# The following menu options are temporarily disabled due to https://github.com/o3de/o3de/issues/6746
|
||||
#("Editor Settings", "Keyboard Customization", "Export Keyboard Settings"),
|
||||
#("Editor Settings", "Keyboard Customization", "Import Keyboard Settings"),
|
||||
]
|
||||
|
||||
# 1) Open an existing simple level
|
||||
helper.init_idle()
|
||||
helper.open_level("Physics", "Base")
|
||||
hydra.open_base_level()
|
||||
|
||||
# 2) Interact with Edit Menu options
|
||||
editor_window = pyside_utils.get_editor_main_window()
|
||||
for option in edit_menu_options:
|
||||
try:
|
||||
action = pyside_utils.get_action_for_menu_path(editor_window, "Edit", *option)
|
||||
Report.info(f"Triggering {action.iconText()}")
|
||||
action.trigger()
|
||||
action_triggered = True
|
||||
except Exception as e:
|
||||
|
||||
@@ -26,35 +26,34 @@ def Menus_FileMenuOptions_Work():
|
||||
:return: None
|
||||
"""
|
||||
|
||||
import editor_python_test_tools.hydra_editor_utils as hydra
|
||||
import editor_python_test_tools.pyside_utils as pyside_utils
|
||||
from editor_python_test_tools.utils import Report
|
||||
from editor_python_test_tools.utils import TestHelper as helper
|
||||
|
||||
file_menu_options = [
|
||||
("New Level",),
|
||||
("Open Level",),
|
||||
("Import",),
|
||||
#("Open Level",), Temporarily disabled due to https://github.com/o3de/o3de/issues/6605
|
||||
#("Import",), Temporarily disabled due to https://github.com/o3de/o3de/issues/6746
|
||||
("Save",),
|
||||
("Save As",),
|
||||
#("Save As",), Temporarily disabled due to https://github.com/o3de/o3de/issues/6605
|
||||
("Save Level Statistics",),
|
||||
("Edit Project Settings",),
|
||||
("Edit Platform Settings",),
|
||||
#("Edit Platform Settings",), Temporarily disabled due to https://github.com/o3de/o3de/issues/6604
|
||||
("New Project",),
|
||||
("Open Project",),
|
||||
("Show Log File",),
|
||||
("Resave All Slices",),
|
||||
("Exit",),
|
||||
]
|
||||
|
||||
# 1) Open an existing simple level
|
||||
helper.init_idle()
|
||||
helper.open_level("Physics", "Base")
|
||||
hydra.open_base_level()
|
||||
|
||||
# 2) Interact with File Menu options
|
||||
editor_window = pyside_utils.get_editor_main_window()
|
||||
for option in file_menu_options:
|
||||
try:
|
||||
action = pyside_utils.get_action_for_menu_path(editor_window, "File", *option)
|
||||
Report.info(f"Triggering {action.iconText()}")
|
||||
action.trigger()
|
||||
action_triggered = True
|
||||
except Exception as e:
|
||||
|
||||
@@ -26,32 +26,35 @@ def Menus_ViewMenuOptions_Work():
|
||||
:return: None
|
||||
"""
|
||||
|
||||
import editor_python_test_tools.hydra_editor_utils as hydra
|
||||
import editor_python_test_tools.pyside_utils as pyside_utils
|
||||
from editor_python_test_tools.utils import Report
|
||||
from editor_python_test_tools.utils import TestHelper as helper
|
||||
|
||||
view_menu_options = [
|
||||
("Center on Selection",),
|
||||
("Show Quick Access Bar",),
|
||||
("Layouts", "Component Entity Layout",),
|
||||
("Layouts", "Save Layout",),
|
||||
("Viewport", "Configure Layout"),
|
||||
("Viewport", "Go to Position"),
|
||||
("Viewport", "Center on Selection"),
|
||||
("Viewport", "Go to Location"),
|
||||
("Viewport", "Remember Location"),
|
||||
("Viewport", "Switch Camera"),
|
||||
("Viewport", "Show/Hide Helpers"),
|
||||
("Viewport", "Show Helpers"),
|
||||
("Viewport", "Show Icons"),
|
||||
("Refresh Style",),
|
||||
]
|
||||
|
||||
# 1) Open an existing simple level
|
||||
helper.init_idle()
|
||||
helper.open_level("Physics", "Base")
|
||||
hydra.open_base_level()
|
||||
|
||||
# 2) Interact with View Menu options
|
||||
editor_window = pyside_utils.get_editor_main_window()
|
||||
for option in view_menu_options:
|
||||
try:
|
||||
action = pyside_utils.get_action_for_menu_path(editor_window, "View", *option)
|
||||
Report.info(f"Triggering {action.iconText()}")
|
||||
action.trigger()
|
||||
action_triggered = True
|
||||
except Exception as e:
|
||||
|
||||
@@ -7,48 +7,77 @@ SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
|
||||
import os
|
||||
import pytest
|
||||
import sys
|
||||
|
||||
import ly_test_tools.environment.file_system as file_system
|
||||
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)) + '/../automatedtesting_shared')
|
||||
from base import TestAutomationBase
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def remove_test_level(request, workspace, project):
|
||||
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", "tmp_level")], True, True)
|
||||
|
||||
def teardown():
|
||||
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", "tmp_level")], True, True)
|
||||
|
||||
request.addfinalizer(teardown)
|
||||
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 TestAutomation(TestAutomationBase):
|
||||
class TestAutomationNoAutoTestMode(EditorTestSuite):
|
||||
|
||||
def test_BasicEditorWorkflows_LevelEntityComponentCRUD(self, request, workspace, editor, launcher_platform,
|
||||
remove_test_level):
|
||||
# Disable -autotest_mode and -BatchMode. Tests cannot run in -BatchMode due to UI interactions, and these tests
|
||||
# interact with modal dialogs
|
||||
global_extra_cmdline_args = []
|
||||
|
||||
class test_AssetPicker_UI_UX(EditorSharedTest):
|
||||
from .EditorScripts import AssetPicker_UI_UX as test_module
|
||||
|
||||
class test_BasicEditorWorkflows_ExistingLevel_EntityComponentCRUD(EditorSharedTest):
|
||||
from .EditorScripts import BasicEditorWorkflows_ExistingLevel_EntityComponentCRUD as test_module
|
||||
|
||||
class test_BasicEditorWorkflows_LevelEntityComponentCRUD(EditorSingleTest):
|
||||
# Custom teardown to remove level created during test
|
||||
def teardown(self, request, workspace, editor, editor_test_results, launcher_platform):
|
||||
file_system.delete([os.path.join(workspace.paths.engine_root(), "AutomatedTesting", "Levels", "tmp_level")],
|
||||
True, True)
|
||||
from .EditorScripts import BasicEditorWorkflows_LevelEntityComponentCRUD as test_module
|
||||
self._run_test(request, workspace, editor, test_module, batch_mode=False, autotest_mode=False, enable_prefab_system=False)
|
||||
|
||||
@pytest.mark.REQUIRES_gpu
|
||||
def test_BasicEditorWorkflows_GPU_LevelEntityComponentCRUD(self, request, workspace, editor, launcher_platform,
|
||||
remove_test_level):
|
||||
from .EditorScripts import BasicEditorWorkflows_LevelEntityComponentCRUD as test_module
|
||||
self._run_test(request, workspace, editor, test_module, batch_mode=False, autotest_mode=False,
|
||||
use_null_renderer=False, enable_prefab_system=False)
|
||||
class test_BasicEditorWorkflows_GPU_LevelEntityComponentCRUD(EditorSingleTest):
|
||||
# Disable null renderer
|
||||
use_null_renderer = False
|
||||
|
||||
def test_EntityOutlienr_EntityOrdering(self, request, workspace, editor, launcher_platform):
|
||||
# Custom teardown to remove level created during test
|
||||
def teardown(self, request, workspace, editor, editor_test_results, launcher_platform):
|
||||
file_system.delete([os.path.join(workspace.paths.engine_root(), "AutomatedTesting", "Levels", "tmp_level")],
|
||||
True, True)
|
||||
from .EditorScripts import BasicEditorWorkflows_LevelEntityComponentCRUD as test_module
|
||||
|
||||
class test_InputBindings_Add_Remove_Input_Events(EditorSharedTest):
|
||||
from .EditorScripts import InputBindings_Add_Remove_Input_Events as test_module
|
||||
|
||||
|
||||
@pytest.mark.SUITE_main
|
||||
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
|
||||
@pytest.mark.parametrize("project", ["AutomatedTesting"])
|
||||
class TestAutomationAutoTestMode(EditorTestSuite):
|
||||
|
||||
# Enable only -autotest_mode for these tests. Tests cannot run in -BatchMode due to UI interactions
|
||||
global_extra_cmdline_args = ["-autotest_mode"]
|
||||
|
||||
class test_AssetBrowser_SearchFiltering(EditorSharedTest):
|
||||
from .EditorScripts import AssetBrowser_SearchFiltering as test_module
|
||||
|
||||
class test_AssetBrowser_TreeNavigation(EditorSharedTest):
|
||||
from .EditorScripts import AssetBrowser_TreeNavigation as test_module
|
||||
|
||||
class test_ComponentCRUD_Add_Delete_Components(EditorSharedTest):
|
||||
from .EditorScripts import ComponentCRUD_Add_Delete_Components as test_module
|
||||
|
||||
@pytest.mark.skip("Passes locally/fails on Jenkins. https://github.com/o3de/o3de/issues/6747")
|
||||
class test_Docking_BasicDockedTools(EditorSharedTest):
|
||||
from .EditorScripts import Docking_BasicDockedTools as test_module
|
||||
|
||||
class test_EntityOutliner_EntityOrdering(EditorSharedTest):
|
||||
from .EditorScripts import EntityOutliner_EntityOrdering as test_module
|
||||
self._run_test(
|
||||
request,
|
||||
workspace,
|
||||
editor,
|
||||
test_module,
|
||||
batch_mode=False,
|
||||
autotest_mode=True,
|
||||
)
|
||||
|
||||
class test_Menus_EditMenuOptions_Work(EditorSharedTest):
|
||||
from .EditorScripts import Menus_EditMenuOptions as test_module
|
||||
|
||||
class test_Menus_FileMenuOptions_Work(EditorSharedTest):
|
||||
from .EditorScripts import Menus_FileMenuOptions as test_module
|
||||
|
||||
class test_Menus_ViewMenuOptions_Work(EditorSharedTest):
|
||||
from .EditorScripts import Menus_ViewMenuOptions as test_module
|
||||
|
||||
@@ -1,79 +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 EditorSingleTest, EditorSharedTest, EditorParallelTest, EditorTestSuite
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason="Optimized tests are experimental, we will enable xfail and monitor them temporarily.")
|
||||
@pytest.mark.SUITE_main
|
||||
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
|
||||
@pytest.mark.parametrize("project", ["AutomatedTesting"])
|
||||
class TestAutomationNoAutoTestMode(EditorTestSuite):
|
||||
|
||||
# Disable -autotest_mode and -BatchMode. Tests cannot run in -BatchMode due to UI interactions, and these tests
|
||||
# interact with modal dialogs
|
||||
global_extra_cmdline_args = []
|
||||
|
||||
enable_prefab_system = False
|
||||
|
||||
class test_BasicEditorWorkflows_LevelEntityComponentCRUD(EditorSingleTest):
|
||||
# Custom teardown to remove slice asset created during test
|
||||
def teardown(self, request, workspace, editor, editor_test_results, launcher_platform):
|
||||
file_system.delete([os.path.join(workspace.paths.engine_root(), "AutomatedTesting", "Levels", "tmp_level")],
|
||||
True, True)
|
||||
from .EditorScripts import BasicEditorWorkflows_LevelEntityComponentCRUD as test_module
|
||||
|
||||
@pytest.mark.REQUIRES_gpu
|
||||
class test_BasicEditorWorkflows_GPU_LevelEntityComponentCRUD(EditorSingleTest):
|
||||
# Disable null renderer
|
||||
use_null_renderer = False
|
||||
|
||||
# Custom teardown to remove slice asset created during test
|
||||
def teardown(self, request, workspace, editor, editor_test_results, launcher_platform):
|
||||
file_system.delete([os.path.join(workspace.paths.engine_root(), "AutomatedTesting", "Levels", "tmp_level")],
|
||||
True, True)
|
||||
from .EditorScripts import BasicEditorWorkflows_LevelEntityComponentCRUD as test_module
|
||||
|
||||
class test_InputBindings_Add_Remove_Input_Events(EditorSharedTest):
|
||||
from .EditorScripts import InputBindings_Add_Remove_Input_Events as test_module
|
||||
|
||||
class test_AssetPicker_UI_UX(EditorSharedTest):
|
||||
from .EditorScripts import AssetPicker_UI_UX as test_module
|
||||
|
||||
|
||||
@pytest.mark.SUITE_main
|
||||
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
|
||||
@pytest.mark.parametrize("project", ["AutomatedTesting"])
|
||||
class TestAutomationAutoTestMode(EditorTestSuite):
|
||||
|
||||
# Enable only -autotest_mode for these tests. Tests cannot run in -BatchMode due to UI interactions
|
||||
global_extra_cmdline_args = ["-autotest_mode"]
|
||||
|
||||
enable_prefab_system = False
|
||||
|
||||
class test_AssetBrowser_TreeNavigation(EditorSharedTest):
|
||||
from .EditorScripts import AssetBrowser_TreeNavigation as test_module
|
||||
|
||||
class test_AssetBrowser_SearchFiltering(EditorSharedTest):
|
||||
from .EditorScripts import AssetBrowser_SearchFiltering as test_module
|
||||
|
||||
class test_ComponentCRUD_Add_Delete_Components(EditorSharedTest):
|
||||
from .EditorScripts import ComponentCRUD_Add_Delete_Components as test_module
|
||||
|
||||
class test_Menus_ViewMenuOptions_Work(EditorSharedTest):
|
||||
from .EditorScripts import Menus_ViewMenuOptions as test_module
|
||||
|
||||
@pytest.mark.skip(reason="Times out due to dialogs failing to dismiss: LYN-4208")
|
||||
class test_Menus_FileMenuOptions_Work(EditorSharedTest):
|
||||
from .EditorScripts import Menus_FileMenuOptions as test_module
|
||||
|
||||
class test_BasicEditorWorkflows_ExistingLevel_EntityComponentCRUD(EditorSharedTest):
|
||||
from .EditorScripts import BasicEditorWorkflows_ExistingLevel_EntityComponentCRUD as test_module
|
||||
@@ -1,60 +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 sys
|
||||
|
||||
import ly_test_tools.environment.file_system as file_system
|
||||
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)) + '/../automatedtesting_shared')
|
||||
from base import TestAutomationBase
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def remove_test_level(request, workspace, project):
|
||||
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", "tmp_level")], True, True)
|
||||
|
||||
def teardown():
|
||||
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", "tmp_level")], True, True)
|
||||
|
||||
request.addfinalizer(teardown)
|
||||
|
||||
|
||||
@pytest.mark.SUITE_periodic
|
||||
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
|
||||
@pytest.mark.parametrize("project", ["AutomatedTesting"])
|
||||
class TestAutomation(TestAutomationBase):
|
||||
|
||||
def test_AssetBrowser_TreeNavigation(self, request, workspace, editor, launcher_platform):
|
||||
from .EditorScripts import AssetBrowser_TreeNavigation as test_module
|
||||
self._run_test(request, workspace, editor, test_module, batch_mode=False, enable_prefab_system=False)
|
||||
|
||||
def test_AssetBrowser_SearchFiltering(self, request, workspace, editor, launcher_platform):
|
||||
from .EditorScripts import AssetBrowser_SearchFiltering as test_module
|
||||
self._run_test(request, workspace, editor, test_module, batch_mode=False, enable_prefab_system=False)
|
||||
|
||||
def test_AssetPicker_UI_UX(self, request, workspace, editor, launcher_platform):
|
||||
from .EditorScripts import AssetPicker_UI_UX as test_module
|
||||
self._run_test(request, workspace, editor, test_module, autotest_mode=False, batch_mode=False, enable_prefab_system=False)
|
||||
|
||||
def test_ComponentCRUD_Add_Delete_Components(self, request, workspace, editor, launcher_platform):
|
||||
from .EditorScripts import ComponentCRUD_Add_Delete_Components as test_module
|
||||
self._run_test(request, workspace, editor, test_module, batch_mode=False, enable_prefab_system=False)
|
||||
|
||||
def test_InputBindings_Add_Remove_Input_Events(self, request, workspace, editor, launcher_platform):
|
||||
from .EditorScripts import InputBindings_Add_Remove_Input_Events as test_module
|
||||
self._run_test(request, workspace, editor, test_module, batch_mode=False, autotest_mode=False, enable_prefab_system=False)
|
||||
|
||||
def test_Menus_ViewMenuOptions_Work(self, request, workspace, editor, launcher_platform):
|
||||
from .EditorScripts import Menus_ViewMenuOptions as test_module
|
||||
self._run_test(request, workspace, editor, test_module, batch_mode=False, enable_prefab_system=False)
|
||||
|
||||
@pytest.mark.skip(reason="Times out due to dialogs failing to dismiss: LYN-4208")
|
||||
def test_Menus_FileMenuOptions_Work(self, request, workspace, editor, launcher_platform):
|
||||
from .EditorScripts import Menus_FileMenuOptions as test_module
|
||||
self._run_test(request, workspace, editor, test_module, batch_mode=False, enable_prefab_system=False)
|
||||
@@ -1,27 +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 sys
|
||||
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)) + '/../automatedtesting_shared')
|
||||
from base import TestAutomationBase
|
||||
|
||||
|
||||
@pytest.mark.SUITE_sandbox
|
||||
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
|
||||
@pytest.mark.parametrize("project", ["AutomatedTesting"])
|
||||
class TestAutomation(TestAutomationBase):
|
||||
|
||||
def test_Menus_EditMenuOptions_Work(self, request, workspace, editor, launcher_platform):
|
||||
from .EditorScripts import Menus_EditMenuOptions as test_module
|
||||
self._run_test(request, workspace, editor, test_module, batch_mode=False, enable_prefab_system=False)
|
||||
|
||||
def test_Docking_BasicDockedTools(self, request, workspace, editor, launcher_platform):
|
||||
from .EditorScripts import Docking_BasicDockedTools as test_module
|
||||
self._run_test(request, workspace, editor, test_module, batch_mode=False, enable_prefab_system=False)
|
||||
@@ -1,28 +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
|
||||
|
||||
from ly_test_tools.o3de.editor_test import EditorSingleTest, EditorSharedTest, EditorParallelTest, EditorTestSuite
|
||||
|
||||
|
||||
@pytest.mark.SUITE_sandbox
|
||||
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
|
||||
@pytest.mark.parametrize("project", ["AutomatedTesting"])
|
||||
class TestAutomationAutoTestMode(EditorTestSuite):
|
||||
|
||||
# Enable only -autotest_mode for these tests. Tests cannot run in -BatchMode due to UI interactions
|
||||
global_extra_cmdline_args = ["-autotest_mode"]
|
||||
|
||||
enable_prefab_system = False
|
||||
|
||||
class test_Docking_BasicDockedTools(EditorSharedTest):
|
||||
from .EditorScripts import Docking_BasicDockedTools as test_module
|
||||
|
||||
class test_Menus_EditMenuOptions_Work(EditorSharedTest):
|
||||
from .EditorScripts import Menus_EditMenuOptions as test_module
|
||||
+9
-3
@@ -51,19 +51,22 @@ def AltitudeFilter_ComponentAndOverrides_InstancesPlantAtSpecifiedAltitude():
|
||||
|
||||
import os
|
||||
|
||||
import azlmbr.asset as asset
|
||||
import azlmbr.editor as editor
|
||||
import azlmbr.legacy.general as general
|
||||
import azlmbr.bus as bus
|
||||
import azlmbr.math as math
|
||||
import azlmbr.prefab as prefab
|
||||
|
||||
import editor_python_test_tools.hydra_editor_utils as hydra
|
||||
from editor_python_test_tools.prefab_utils import Prefab
|
||||
from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
|
||||
from editor_python_test_tools.utils import Report
|
||||
from editor_python_test_tools.utils import TestHelper as helper
|
||||
|
||||
# 1) Open an existing simple level
|
||||
helper.init_idle()
|
||||
helper.open_level("Physics", "Base")
|
||||
helper.open_level("", "Base")
|
||||
|
||||
# Set view of planting area for visual debugging
|
||||
general.set_current_view_position(512.0, 500.0, 38.0)
|
||||
@@ -71,8 +74,11 @@ def AltitudeFilter_ComponentAndOverrides_InstancesPlantAtSpecifiedAltitude():
|
||||
|
||||
# 2) Create a new entity with required vegetation area components
|
||||
center_point = math.Vector3(512.0, 512.0, 32.0)
|
||||
asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
|
||||
spawner_entity = dynveg.create_vegetation_area("Instance Spawner", center_point, 32.0, 32.0, 32.0, asset_path)
|
||||
|
||||
flower_asset_path = os.path.join("assets", "objects", "foliage", "grass_flower_pink.azmodel")
|
||||
flower_prefab = dynveg.create_temp_mesh_prefab(flower_asset_path, "PinkFlower")[0]
|
||||
|
||||
spawner_entity = dynveg.create_prefab_vegetation_area("Instance Spawner", center_point, 32.0, 32.0, 32.0, flower_prefab)
|
||||
|
||||
# Add a Vegetation Altitude Filter
|
||||
spawner_entity.add_component("Vegetation Altitude Filter")
|
||||
|
||||
+8
-3
@@ -32,7 +32,9 @@ def AltitudeFilter_FilterStageToggle():
|
||||
import os
|
||||
|
||||
import azlmbr.legacy.general as general
|
||||
import azlmbr.bus as bus
|
||||
import azlmbr.math as math
|
||||
import azlmbr.prefab as prefab
|
||||
|
||||
import editor_python_test_tools.hydra_editor_utils as hydra
|
||||
from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
|
||||
@@ -44,13 +46,16 @@ def AltitudeFilter_FilterStageToggle():
|
||||
|
||||
# Open an existing simple level
|
||||
helper.init_idle()
|
||||
helper.open_level("Physics", "Base")
|
||||
helper.open_level("", "Base")
|
||||
general.set_current_view_position(512.0, 480.0, 38.0)
|
||||
|
||||
# Create basic vegetation entity
|
||||
position = math.Vector3(512.0, 512.0, 32.0)
|
||||
asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
|
||||
vegetation = dynveg.create_vegetation_area("vegetation", position, 16.0, 16.0, 16.0, asset_path)
|
||||
|
||||
flower_asset_path = os.path.join("assets", "objects", "foliage", "grass_flower_pink.azmodel")
|
||||
flower_prefab = dynveg.create_temp_mesh_prefab(flower_asset_path, "PinkFlower")[0]
|
||||
|
||||
vegetation = dynveg.create_prefab_vegetation_area("vegetation", position, 16.0, 16.0, 16.0, flower_prefab)
|
||||
|
||||
# Add a Vegetation Altitude Filter to the vegetation area entity
|
||||
vegetation.add_component("Vegetation Altitude Filter")
|
||||
|
||||
+6
-3
@@ -57,7 +57,7 @@ def AltitudeFilter_ShapeSample_InstancesPlantAtSpecifiedAltitude():
|
||||
|
||||
# 1) Open an existing simple level
|
||||
helper.init_idle()
|
||||
helper.open_level("Physics", "Base")
|
||||
helper.open_level("", "Base")
|
||||
|
||||
# Set view of planting area for visual debugging
|
||||
general.set_current_view_position(512.0, 500.0, 38.0)
|
||||
@@ -65,8 +65,11 @@ def AltitudeFilter_ShapeSample_InstancesPlantAtSpecifiedAltitude():
|
||||
|
||||
# 2) Create a new entity with required vegetation area components
|
||||
center_point = math.Vector3(512.0, 512.0, 32.0)
|
||||
asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
|
||||
spawner_entity = dynveg.create_vegetation_area("Instance Spawner", center_point, 16.0, 16.0, 16.0, asset_path)
|
||||
|
||||
flower_asset_path = os.path.join("assets", "objects", "foliage", "grass_flower_pink.azmodel")
|
||||
flower_prefab = dynveg.create_temp_mesh_prefab(flower_asset_path, "PinkFlower")[0]
|
||||
|
||||
spawner_entity = dynveg.create_prefab_vegetation_area("Instance Spawner", center_point, 16.0, 16.0, 16.0, flower_prefab)
|
||||
|
||||
# Add a Vegetation Altitude Filter
|
||||
spawner_entity.add_component("Vegetation Altitude Filter")
|
||||
|
||||
+1
-1
@@ -111,7 +111,7 @@ def AssetListCombiner_CombinedDescriptorsExpressInConfiguredArea():
|
||||
|
||||
# 4) Create a spawner using a Vegetation Asset List Combiner component and a Weight Selector, and disallow
|
||||
# spawning empty assets
|
||||
spawner_entity = dynveg.create_vegetation_area("Spawner Entity", center_point, 16.0, 16.0, 16.0, None)
|
||||
spawner_entity = dynveg.create_dynamic_slice_vegetation_area("Spawner Entity", center_point, 16.0, 16.0, 16.0, None)
|
||||
spawner_entity.remove_component("Vegetation Asset List")
|
||||
spawner_entity.add_component("Vegetation Asset List Combiner")
|
||||
spawner_entity.add_component("Vegetation Asset Weight Selector")
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user