16 Commits

Author SHA1 Message Date
Chris Galvan c1d7294c5b Merge pull request #7120 from aws-lumberyard-dev/cgalvan/gitflow_220124_o3de_main
Merged `stabilization/2111RTE` to `main`
2022-01-27 12:07:39 -06:00
Chris Galvan acc2346678 Merge remote-tracking branch 'upstream/stabilization/2111RTE' into cgalvan/gitflow_220124_o3de_main
Signed-off-by: Chris Galvan <chgalvan@amazon.com>
2022-01-24 13:13:43 -06:00
Chris Galvan 1da5ef16a8 Merge pull request #6046 from aws-lumberyard-dev/cgalvan/gitflow_211130_o3de_main
Merged `stabilization/2110` to `main`
2021-12-02 09:36:59 -06:00
Chris Galvan 57b201b0da Merged stabilization/2110 -> main
Signed-off-by: Chris Galvan <chgalvan@amazon.com>
2021-11-30 12:30:07 -06:00
Chris Galvan f8d39e2671 Merge pull request #3049 from aws-lumberyard-dev/cgalvan/gitflow_210811
Merged stabilization/2106 to main
2021-08-11 15:29:33 -05:00
Chris Galvan 575faa4443 Merge remote-tracking branch 'upstream/stabilization/2106' into cgalvan/gitflow_210811
Signed-off-by: Chris Galvan <chgalvan@amazon.com>
2021-08-11 14:13:27 -05:00
Terry Michaels 43603cad5e Merge pull request #2271 from aws-lumberyard-dev/Foundation/miterenc/ContributingUpdate
Updating CONTRIBUTING.md
2021-07-19 14:55:51 -05:00
Terry Michaels bb52475ce8 Updated text to be more descriptive
Signed-off-by: Terry Michaels <miterenc@amazon.com>
2021-07-19 14:55:14 -05:00
Terry Michaels 697dfad486 Fixed typo
Signed-off-by: Terry Michaels <miterenc@amazon.com>
2021-07-19 14:27:24 -05:00
Terry Michaels 650e1ab44d Updating CONTRIBUTING.md
Signed-off-by: Terry Michaels <miterenc@amazon.com>
2021-07-19 14:19:46 -05:00
Chris Galvan d9cce28a53 Merge pull request #2096 from aws-lumberyard-dev/cgalvan/gitflow_210712_main
Merged stabilization/2106 to main
2021-07-12 14:06:57 -05:00
Chris Galvan 486ba58628 Merged stabilization/2106 to development; Resolved merge conflicts
Signed-off-by: Chris Galvan <chgalvan@amazon.com>
2021-07-12 11:12:41 -05:00
Nicholas Lawson e7f787572e Updates licenses to APACHE-2.0 OR MIT (#1685)
Not to be committed before 7/6/2021

Signed-off-by: lawsonamzn <70027408+lawsonamzn@users.noreply.github.com>
2021-07-06 08:03:35 -07:00
Chris Galvan 837e1c7370 Merge pull request #1764 from aws-lumberyard-dev/cgalvan/gitflow_210702
Merged stabilization/2106 to main
2021-07-02 12:11:27 -05:00
Chris Galvan efcbe2c4a1 Merge remote-tracking branch 'upstream/stabilization/2106' into cgalvan/gitflow_210702
Signed-off-by: Chris Galvan <chgalvan@amazon.com>
2021-07-02 10:20:42 -05:00
Alex Peterson d30de01752 Updating LFS config to new endpoint (#1624)
Signed-off-by: AMZN-alexpete
2021-06-28 11:20:36 -07:00
6970 changed files with 307346 additions and 563640 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
---
name: Automated Review bug report
name: ar_bug_report.md
about: Create a bug for a an issue found in the Automated Review
title: 'AR Bug Report'
labels: 'needs-triage,kind/bug,kind/automation'
-1
View File
@@ -1 +0,0 @@
blank_issues_enabled: false
+8 -2
View File
@@ -2,14 +2,17 @@
.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
@@ -18,6 +21,9 @@ 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
@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
<path d="M3.145,8.433c0-1.47,1.196-2.666,2.666-2.666h9.544c-0.158-0.819-0.88-1.443-1.744-1.443H3.487
c-0.978,0-1.778,0.8-1.778,1.778v5.356c0,0.861,0.62,1.582,1.436,1.743V8.433z" fill="#FFFFFF"/>
<g>
<path d="M6.833,11.654c0-1.47,1.196-2.666,2.666-2.666h9.069c-0.158-0.819-0.88-1.443-1.744-1.443H6.7
c-0.978,0-1.778,0.8-1.778,1.778v5.356c0,0.978,0.8,1.778,1.778,1.778h0.133V11.654z" fill="#FFFFFF"/>
</g>
<path d="M20.513,10.765H10.388c-0.978,0-1.778,0.8-1.778,1.777v5.356c0,0.978,0.8,1.778,1.778,1.778h10.125
c0.978,0,1.778-0.8,1.778-1.778v-5.356C22.29,11.565,21.49,10.765,20.513,10.765z M19.332,15.967h-7.763
c-0.264,0-0.478-0.355-0.478-0.793c0-0.438,0.214-0.793,0.478-0.793h7.763c0.264,0,0.478,0.355,0.478,0.793
C19.81,15.612,19.597,15.967,19.332,15.967z" fill="#FFFFFF"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bfc2ef8c6dbf2fba078e27e4e94384099e090468e679327dd826a5cbf22b04ed
size 1019
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:708b12d41229afab78e0f7d59097ae3de855fea8525a920c5c214fc0ce79f1bd
size 1209
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:fab63af9b50790dca25330058e70517987ea8bf11c00f9353dd951ebdbd1dbe5
size 5008
+78
View File
@@ -0,0 +1,78 @@
#
# 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
#
#
"""
This script loads every level in a game project and exports them. You will be prompted with the standard
Check-Out/Overwrite/Cancel dialog for each level.
This is useful when there is a version update to a file that requires re-exporting.
"""
import sys, os
import azlmbr.legacy.general as general
import azlmbr.legacy.checkout_dialog as checkout_dialog
class CheckOutDialogEnableAll:
"""
Helper class to wrap enabling the "Apply to all" checkbox in the CheckOutDialog.
Guarantees that the old setting will be restored.
To use, do:
with CheckOutDialogEnableAll():
# your code here
"""
def __init__(self):
self.old_setting = False
def __enter__(self):
self.old_setting = checkout_dialog.enable_for_all(True)
print(self.old_setting)
def __exit__(self, type, value, traceback):
checkout_dialog.enable_for_all(self.old_setting)
level_list = []
def is_in_special_folder(file_full_path):
if file_full_path.find("_savebackup") >= 0:
return True
if file_full_path.find("_autobackup") >= 0:
return True
if file_full_path.find("_hold") >= 0:
return True
if file_full_path.find("_tmpresize") >= 0:
return True
return False
game_folder = general.get_game_folder()
# Recursively search every directory in the game project for files ending with .cry and .ly
for root, dirs, files in os.walk(game_folder):
for file in files:
if file.endswith(".cry") or file.endswith(".ly"):
# The engine expects the full path of the .cry file
file_full_path = os.path.abspath(os.path.join(root, file))
# Exclude files in special directories
if not is_in_special_folder(file_full_path):
level_list.append(file_full_path)
# make the checkout dialog enable the 'Apply to all' checkbox
with CheckOutDialogEnableAll():
# For each valid .cry file found, open it in the editor and export it
for level in level_list:
if not isinstance(level, str):
# general.open_level_no_prompt expects the file path in utf8 format
level = level.encode("utf-8")
general.open_level_no_prompt(level)
general.export_to_engine()
+40
View File
@@ -0,0 +1,40 @@
#
# 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
#
#
'''
Generates a 50% lod for the selected model
@argument name="Lod Percentage", type="string", default="50.0f"
'''
import sys
import time
percentage = float(sys.argv[1])
selectedcgf = lodtools.getselected()
selectedmaterial = lodtools.getselectedmaterial()
loadedmodel = lodtools.loadcgf(selectedcgf)
loadedmaterial = lodtools.loadmaterial(selectedmaterial)
if loadedmodel == True and loadedmaterial == True:
lodtools.generatelodchain()
finished = 0.0
while finished >= 0.0:
finished = lodtools.generatetick()
print 'Lod Chain Generation progress: ' + str(finished)
time.sleep(1)
if finished == 1.0:
break
lodtools.createlod(1,percentage)
lodtools.generatematerials(1,512,512)
lodtools.savetextures(1)
lodtools.savesettings()
lodtools.reloadmodel()
+14
View File
@@ -0,0 +1,14 @@
#
# 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
#
#
objects = general.get_all_objects("", "") # Get the name list of all objects in the level.
# If there is any object with the geometry file of "objects\\default\\primitive_box.cgf",
# change it to "objects\\default\\primitive_cube.cgf".
for obj in objects:
geometry_file = general.get_entity_geometry_file(obj)
if geometry_file == "objects\\default\\primitive_box.cgf":
general.set_entity_geometry_file(obj, "objects\\default\\primitive_cube.cgf")
+14
View File
@@ -0,0 +1,14 @@
#
# 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
#
#
objects = general.get_all_objects("AnimObject", "") # Get the name list of all anim objects in the level.
general.clear_selection()
# If there is any object with the geometry file whose name contains "\\story\\", select it
for obj in objects:
geometry_file = general.get_entity_geometry_file(obj)
if geometry_file.find("\\story\\") != -1:
general.select_object(obj)
+508
View File
@@ -0,0 +1,508 @@
#
# 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 __future__
import time, re, os, sys, itertools, ast
import azlmbr.legacy.general as general
global ctrlFile
ctrlFile = 'setState.txt'
global logfile
logfile = 'Editor.log'
global HIDDEN_MASK_PREFIX
HIDDEN_MASK_PREFIX= 'hidden_mask_for_'
global HIDDEN_MASK_PREFIX_LENGTH
HIDDEN_MASK_PREFIX_LENGTH = len(HIDDEN_MASK_PREFIX)
def getLogList(logfile):
logList = [x for x in open(logfile, 'r')]
return logList
def getCheckList(ctrlFile):
try:
with open(ctrlFile):
stateList = open(ctrlFile, 'r')
except:
open(ctrlFile, 'w').close()
stateList = open(ctrlFile, 'r')
checkList = [x for x in stateList]
return checkList
#Store/Restore CVars default values -----------------------------------------------------------------#
if 'CVARS' not in globals():
CVARS = {}
def saveDefaultValue(cVars, value):
if cVars not in CVARS:
CVARS[cVars] = value
def restoreDefaultValue(cVars):
if cVars not in CVARS:
return
defaultValue = CVARS[cVars]
del CVARS[cVars]
if cVars.startswith(HIDDEN_MASK_PREFIX):
type = cVars[HIDDEN_MASK_PREFIX_LENGTH:]
general.set_hidemask(type, defaultValue)
else:
general.set_cvar(cVars, defaultValue)
#toggle CVars--------------------------------------------------------------------#
def updateCvars(cVars, value):
saveDefaultValue(cVars, value)
general.set_cvar(cVars, value)
def toggleCvarsRestartCheck(log, state, mode, cVars, onValue, offValue, ctrlFile):
if log in state:
toggleCvarsV(mode, cVars, onValue, offValue, ctrlFile)
else:
stateList = open(ctrlFile, 'w')
stateList.write(log)
stateList = open(ctrlFile, 'r')
toggleCvarsV(mode, cVars, onValue, offValue, ctrlFile)
def toggleCvarsV(mode, cVars, onValue, offValue, ctrlFile):
stateList = open(ctrlFile, 'r')
setState = [x for x in enumerate(stateList)]
blankCheck = [x for x in setState]
getList = [x for x in stateList]
if blankCheck == []:
stateList = open(ctrlFile, 'w')
stateList.write(''.join(str("%s,{'%s': %s}" % (mode, cVars, offValue))+'\n'))
general.set_cvar(cVars, offValue)
else:
stateList = open(ctrlFile, 'r')
checkFor = str([x for x in stateList])
if mode not in str(checkFor):
stateList = open(ctrlFile, 'r')
getList = [x for x in stateList]
getList.insert(1, str("%s,{'%s': %s}\n" % (mode, cVars , offValue)))
print (str("{'%s': %s}\n" % (cVars , offValue)))
stateList = open(ctrlFile, 'w')
stateList.write(''.join(getList))
general.set_cvar(cVars, offValue)
else:
stateList = open(ctrlFile, 'r')
for d in enumerate(stateList):
values = d[1].split(',')
stateList = open(ctrlFile, 'r')
getList = [x for x in stateList]
if mode in values[0]:
getDict = ast.literal_eval(values[1])
getState = getDict.get(cVars)
if getState == offValue:
getDict[cVars] = onValue
joinStr = [mode,",",str(getDict), '\n']
newLine = ''.join(joinStr)
print (getDict)
getList[d[0]] = newLine
stateList = open(ctrlFile, 'w')
stateList.write(''.join(str(''.join(getList))))
general.set_cvar(cVars, onValue)
else:
getDict[cVars] = offValue
joinStr = [mode,",",str(getDict), '\n']
newLine = ''.join(joinStr)
print (getDict)
getList[d[0]] = newLine
stateList = open(ctrlFile, 'w')
stateList.write(''.join(str(''.join(getList))))
general.set_cvar(cVars, offValue)
def toggleCvarsValue(mode, cVars, onValue, offValue):
currentValue = general.get_cvar(cVars)
if type(onValue) is str:
saveDefaultValue(cVars, currentValue)
elif type(onValue) is int:
saveDefaultValue(cVars, int(currentValue))
elif type(onValue) is float:
saveDefaultValue(cVars, float(currentValue))
else:
general.log('Failed to store default value for {0}'.format(cVars))
if currentValue == str(onValue):
general.set_cvar(cVars, offValue)
else:
general.set_cvar(cVars, onValue)
#toggleConsol--------------------------------------------------------------------#
def toggleConsolRestartCheck(log,state, mode, onValue, offValue, ctrlFile):
if log in state:
toggleConsolV(mode, onValue, offValue, ctrlFile)
else:
stateList = open(ctrlFile, 'w')
stateList.write(log)
stateList = open(ctrlFile, 'r')
toggleConsolV(mode, onValue, offValue, ctrlFile)
def toggleConsolV(mode, onValue, offValue):
stateList = open(ctrlFile, 'r')
setState = [x for x in enumerate(stateList)]
blankCheck = [x for x in setState]
getList = [x for x in stateList]
onOffList = [onValue, offValue]
if blankCheck == []:
stateList = open(ctrlFile, 'w')
stateList.write(''.join(str("%s,'%s'" % (mode, offValue))+'\n'))
general.run_console(offValue)
else:
stateList = open(ctrlFile, 'r')
checkFor = str([x for x in stateList])
if mode not in str(checkFor):
stateList = open(ctrlFile, 'r')
getList = [x for x in stateList]
getList.insert(1, str("%s,'%s'\n" % (mode, offValue)))
print (str("{'%s': %s}\n" % (cVars , offValue)))
stateList = open(ctrlFile, 'w')
stateList.write(''.join(getList))
general.run_console(onValue)
else:
stateList = open(ctrlFile, 'r')
for d in enumerate(stateList):
values = d[1].split(',')
stateList = open(ctrlFile, 'r')
getList = [x for x in stateList]
if mode in values[0]:
getDict = values[1]
off = ["'",str(offValue),"'", '\n']
joinoff = ''.join(off)
if values[1] == joinoff:
getDict = onValue
joinStr = [mode,",","'",getDict,"'", '\n']
newLine = ''.join(joinStr)
print (getDict)
getList[d[0]] = str(newLine)
stateList = open(ctrlFile, 'w')
stateList.write(''.join(str(''.join(getList))))
general.run_console(onValue)
else:
getDict = offValue
joinStr = [mode,",","'",getDict,"'", '\n']
newLine = ''.join(joinStr)
print (getDict)
getList[d[0]] = str(newLine)
stateList = open(ctrlFile, 'w')
stateList.write(''.join(str(''.join(getList))))
general.run_console(offValue)
def toggleConsolValue(log, state, mode, onValue, offValue):
logList = getLogList(logfile)
checkList = getCheckList(ctrlFile)
toggleConsolRestartCheck(logList[1],checkList, mode, onValue, offValue, ctrlFile)
#cycleCvars----------------------------------------------------------------------#
def cycleCvarsRestartCheck(log, state, mode, cVars, cycleList, ctrlFile):
if log in state:
cycleCvarsV(mode, cVars, cycleList, ctrlFile)
else:
stateList = open(ctrlFile, 'w')
stateList.write(log)
stateList = open(ctrlFile, 'r')
cycleCvarsV(mode, cVars, cycleList, ctrlFile)
def cycleCvarsV(mode, cVars, cycleList, ctrlFile):
stateList = open(ctrlFile, 'r')
setState = [x for x in enumerate(stateList)]
blankCheck = [x for x in setState]
getList = [x for x in stateList]
if blankCheck == []:
stateList = open(ctrlFile, 'w')
stateList.write(''.join(str("%s,{'%s': %s}" % (mode, cVars, cycleList[1]))+'\n'))
general.set_cvar(cVars, cycleList[1])
else:
stateList = open(ctrlFile, 'r')
checkFor = str([x for x in stateList])
if mode not in str(checkFor):
stateList = open(ctrlFile, 'r')
getList = [x for x in stateList]
getList.insert(1, str("%s,{'%s': %s}\n" % (mode, cVars , cycleList[1])))
stateList = open(ctrlFile, 'w')
stateList.write(''.join(getList))
general.set_cvar(cVars, cycleList[1])
else:
stateList = open(ctrlFile, 'r')
for d in enumerate(stateList):
stateList = open(ctrlFile, 'r')
getList = [x for x in stateList]
values = d[1].split(',')
if mode in values[0]:
getDict = ast.literal_eval(values[1])
getState = getDict.get(cVars)
cycleL = [x for x in enumerate(cycleList)]
for x in cycleL:
if getState == x[1]:
number = [n[1] for n in cycleL]
getMax = max(number)
nextNum = x[0]+1
if nextNum > getMax:
getDict[cVars] = cycleList[0]
joinStr = [mode,",",str(getDict), '\n']
newLine = ''.join(joinStr)
getList[d[0]] = newLine
print (getDict)
stateList = open(ctrlFile, 'w')
stateList.write(''.join(str(''.join(getList))))
general.set_cvar(cVars, cycleList[0])
else:
getDict[cVars] = cycleList[x[0]+1]
joinStr = [mode,",",str(getDict), '\n']
newLine = ''.join(joinStr)
getList[d[0]] = newLine
print (getDict)
stateList = open(ctrlFile, 'w')
stateList.write(''.join(str(''.join(getList))))
general.set_cvar(cVars, cycleList[x[0]+1])
def cycleCvarsValue(log, state, mode, cVars, cycleList):
logList = getLogList(logfile)
checkList = getCheckList(ctrlFile)
cycleCvarsRestartCheck(logList[1],checkList, mode, cVars, cycleList, ctrlFile)
def cycleCvarsFloatValue(cVars, cycleList):
currentValueAsString = general.get_cvar(cVars)
try:
currentValue = float(currentValueAsString)
except:
currentValue = -1.0
saveDefaultValue(cVars, currentValue)
# make sure we sort the list in ascending fashion
cycleList = sorted(cycleList)
# find out what the next closest index is
newIndex = 0
for x in cycleList:
if (currentValue < x):
break
newIndex = newIndex + 1
# loop around, if we need to
if newIndex >= len(cycleList):
newIndex = 0
general.set_cvar(cVars, cycleList[newIndex])
def cycleCvarsIntValue(cVars, cycleList):
currentValueAsString = general.get_cvar(cVars)
try:
currentValue = int(currentValueAsString)
except:
currentValue = 0
saveDefaultValue(cVars, currentValue)
# find out what index we're on already
# default to -1 so that when we increment to the next, we'll be at 0
newIndex = -1
for x in cycleList:
if (currentValue == x):
break
newIndex = newIndex + 1
# move to the next item
newIndex = newIndex + 1
# validate
if newIndex >= len(cycleList):
newIndex = 0
general.set_cvar(cVars, cycleList[newIndex])
#cycleConsol----------------------------------------------------------------------#
def cycleConsolRestartCheck(log, state, mode, cycleList, ctrlFile):
if log in state:
cycleConsolV(mode, cycleList, ctrlFile)
else:
stateList = open(ctrlFile, 'w')
stateList.write(log)
stateList = open(ctrlFile, 'r')
cycleConsolV(mode, cycleList, ctrlFile)
def cycleConsolV(mode, cycleList, ctrlFile):
stateList = open(ctrlFile, 'r')
setState = [x for x in enumerate(stateList)]
blankCheck = [x for x in setState]
getList = [x for x in stateList]
if blankCheck == []:
stateList = open(ctrlFile, 'w')
stateList.write(''.join(str("%s,'%s'" % (mode, cycleList[0]))+'\n'))
general.run_console(cycleList[0])
else:
stateList = open(ctrlFile, 'r')
checkFor = str([x for x in stateList])
if mode not in str(checkFor):
stateList = open(ctrlFile, 'r')
getList = [x for x in stateList]
getList.insert(1, str("%s,'%s'\n" % (mode, cycleList[0])))
stateList = open(ctrlFile, 'w')
stateList.write(''.join(getList))
general.run_console(cycleList[0])
else:
stateList = open(ctrlFile, 'r')
for d in enumerate(stateList):
stateList = open(ctrlFile, 'r')
getList = [x for x in stateList]
values = d[1].split(',')
if mode in values[0]:
newValue = ''.join(values[1].split('\n'))
cycleL = [e for e in enumerate(cycleList)]
getDict = ''.join(values[1].split('\n'))
for x in cycleL:
if newValue in "'%s'" % x[1]:
number = [n[0] for n in cycleL]
getMax = max(number)
nextNum = x[0]+1
if nextNum > getMax:
getDict = '%s' % cycleList[0]
joinStr = [mode,",","'",getDict,"'", '\n']
newLine = ''.join(joinStr)
getList[d[0]] = newLine
print (getDict)
stateList = open(ctrlFile, 'w')
stateList.write(''.join(str(''.join(getList))))
general.run_console(getDict)
else:
getDict = '%s' % cycleList[x[0]+1]
joinStr = [mode,",","'",getDict,"'", '\n']
newLine = ''.join(joinStr)
getList[d[0]] = newLine
print (getDict)
stateList = open(ctrlFile, 'w')
stateList.write(''.join(str(''.join(getList))))
general.run_console(getDict)
def cycleConsolValue(mode, cycleList):
logList = getLogList(logfile)
checkList = getCheckList(ctrlFile)
cycleConsolRestartCheck(logList[1],checkList, mode, cycleList, ctrlFile)
def toggleHideMaskValues(type):
cVars = "%s%s" % (HIDDEN_MASK_PREFIX, type)
currentValue = general.get_hidemask(type)
saveDefaultValue(cVars, int(currentValue))
if (currentValue):
general.set_hidemask(type, 0)
else:
general.set_hidemask(type, 1)
#toggleHide------------------------------------------------------------------------#
def toggleHideRestartCheck(log, state, mode, type, onValue, offValue, ctrlFile):
if log in state:
toggleHideByT(mode, type, onValue, offValue, ctrlFile)
else:
stateList = open(ctrlFile, 'w')
stateList.write(log)
stateList = open(ctrlFile, 'r')
toggleHideByT(mode, type, onValue, offValue, ctrlFile)
def toggleHideByType(mode, type, onValue, offValue):
logList = getLogList(logfile)
checkList = getCheckList(ctrlFile)
toggleHideRestartCheck(logList[1],checkList, mode, type, onValue, offValue, ctrlFile)
def toggleHideByT(mode, type, onValue, offValue, ctrlFile):
stateList = open(ctrlFile, 'r')
setState = [x for x in enumerate(stateList)]
blankCheck = [x for x in setState]
getList = [x for x in stateList]
if blankCheck == []:
stateList = open(ctrlFile, 'w')
stateList.write(''.join(str("%s,{'%s': %s}" % (mode, type, offValue))+'\n'))
hideByType(type)
else:
stateList = open(ctrlFile, 'r')
checkFor = str([x for x in stateList])
if mode not in str(checkFor):
stateList = open(ctrlFile, 'r')
getList = [x for x in stateList]
getList.insert(1, str("%s,{'%s': %s}\n" % (mode, type , offValue)))
print (str("{'%s': %s}\n" % (type , offValue)))
stateList = open(ctrlFile, 'w')
stateList.write(''.join(getList))
hideByType(type)
else:
stateList = open(ctrlFile, 'r')
for d in enumerate(stateList):
values = d[1].split(',')
stateList = open(ctrlFile, 'r')
getList = [x for x in stateList]
if mode in values[0]:
getDict = ast.literal_eval(values[1])
getState = getDict.get(type)
if getState == offValue:
getDict[type] = onValue
joinStr = [mode,",",str(getDict), '\n']
newLine = ''.join(joinStr)
print (getDict)
getList[d[0]] = newLine
stateList = open(ctrlFile, 'w')
stateList.write(''.join(str(''.join(getList))))
unHideByType(type)
else:
getDict[type] = offValue
joinStr = [mode,",",str(getDict), '\n']
newLine = ''.join(joinStr)
print (getDict)
getList[d[0]] = newLine
stateList = open(ctrlFile, 'w')
stateList.write(''.join(str(''.join(getList))))
hideByType(type)
def hideByType(type):
typeList = general.get_all_objects(str(type), "")
for x in typeList:
general.hide_object(x)
def unHideByType(type):
typeList = general.get_all_objects(str(type), "")
for x in typeList:
general.unhide_object(x)
File diff suppressed because it is too large Load Diff
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7f94f2634eacb4d7bee20dacc45edef96e4d268f1adb7960b8aa8f3b6e2906ed
size 6867609
-129
View File
@@ -1,129 +0,0 @@
{
"ContainerEntity": {
"Id": "ContainerEntity",
"Name": "Bush",
"Components": {
"Component_[1140272189295067758]": {
"$type": "EditorInspectorComponent",
"Id": 1140272189295067758
},
"Component_[13437832196484687256]": {
"$type": "EditorOnlyEntityComponent",
"Id": 13437832196484687256
},
"Component_[1553903646452669645]": {
"$type": "EditorDisabledCompositionComponent",
"Id": 1553903646452669645
},
"Component_[15914009348632444632]": {
"$type": "EditorEntitySortComponent",
"Id": 15914009348632444632,
"Child Entity Order": [
"Entity_[7511491868318]"
]
},
"Component_[18046340308818780248]": {
"$type": "EditorPrefabComponent",
"Id": 18046340308818780248
},
"Component_[1948833233489872938]": {
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
"Id": 1948833233489872938,
"Parent Entity": ""
},
"Component_[2903632350157981339]": {
"$type": "SelectionComponent",
"Id": 2903632350157981339
},
"Component_[48827510535192710]": {
"$type": "EditorPendingCompositionComponent",
"Id": 48827510535192710
},
"Component_[5609536793322429681]": {
"$type": "EditorLockComponent",
"Id": 5609536793322429681
},
"Component_[5859168386298620990]": {
"$type": "EditorEntityIconComponent",
"Id": 5859168386298620990
},
"Component_[6604616929271524505]": {
"$type": "EditorVisibilityComponent",
"Id": 6604616929271524505
}
}
},
"Entities": {
"Entity_[7511491868318]": {
"Id": "Entity_[7511491868318]",
"Name": "Bush",
"Components": {
"Component_[10227459330338484901]": {
"$type": "EditorInspectorComponent",
"Id": 10227459330338484901,
"ComponentOrderEntryArray": [
{
"ComponentId": 4998941225335869157
},
{
"ComponentId": 9922994635792843826,
"SortIndex": 1
}
]
},
"Component_[10972351222359420947]": {
"$type": "EditorOnlyEntityComponent",
"Id": 10972351222359420947
},
"Component_[12101122374155214392]": {
"$type": "EditorPendingCompositionComponent",
"Id": 12101122374155214392
},
"Component_[1535264614652988260]": {
"$type": "SelectionComponent",
"Id": 1535264614652988260
},
"Component_[16367811417907891218]": {
"$type": "EditorVisibilityComponent",
"Id": 16367811417907891218
},
"Component_[17044216787716682880]": {
"$type": "EditorEntitySortComponent",
"Id": 17044216787716682880
},
"Component_[2129822594969629430]": {
"$type": "EditorEntityIconComponent",
"Id": 2129822594969629430
},
"Component_[2838015156782745450]": {
"$type": "EditorLockComponent",
"Id": 2838015156782745450
},
"Component_[4998941225335869157]": {
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
"Id": 4998941225335869157,
"Parent Entity": "ContainerEntity"
},
"Component_[8773358049076362578]": {
"$type": "EditorDisabledCompositionComponent",
"Id": 8773358049076362578
},
"Component_[9922994635792843826]": {
"$type": "AZ::Render::EditorMeshComponent",
"Id": 9922994635792843826,
"Controller": {
"Configuration": {
"ModelAsset": {
"assetId": {
"guid": "{1201406D-FB20-5B5F-B9B5-6A6E8DE00A14}",
"subId": 276506120
},
"assetHint": "assets/objects/foliage/bush_privet_01.azmodel"
}
}
}
}
}
}
}
}
File diff suppressed because it is too large Load Diff
@@ -1,131 +0,0 @@
{
"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
}
}
}
}
}
@@ -1,129 +0,0 @@
{
"ContainerEntity": {
"Id": "ContainerEntity",
"Name": "PurpleFlower",
"Components": {
"Component_[10519928302743666073]": {
"$type": "EditorPrefabComponent",
"Id": 10519928302743666073
},
"Component_[13894087802180240181]": {
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
"Id": 13894087802180240181,
"Parent Entity": ""
},
"Component_[15788541052719571801]": {
"$type": "EditorEntityIconComponent",
"Id": 15788541052719571801
},
"Component_[15842981265136092481]": {
"$type": "SelectionComponent",
"Id": 15842981265136092481
},
"Component_[16360384897559021149]": {
"$type": "EditorInspectorComponent",
"Id": 16360384897559021149
},
"Component_[16713545675046303279]": {
"$type": "EditorVisibilityComponent",
"Id": 16713545675046303279
},
"Component_[1806734194268113785]": {
"$type": "EditorPendingCompositionComponent",
"Id": 1806734194268113785
},
"Component_[5392020700593853313]": {
"$type": "EditorEntitySortComponent",
"Id": 5392020700593853313,
"Child Entity Order": [
"Entity_[14335611090324]"
]
},
"Component_[5995854518752659458]": {
"$type": "EditorLockComponent",
"Id": 5995854518752659458
},
"Component_[6963022284400845376]": {
"$type": "EditorDisabledCompositionComponent",
"Id": 6963022284400845376
},
"Component_[8055275578170091546]": {
"$type": "EditorOnlyEntityComponent",
"Id": 8055275578170091546
}
}
},
"Entities": {
"Entity_[14335611090324]": {
"Id": "Entity_[14335611090324]",
"Name": "PurpleFlower",
"Components": {
"Component_[10887353073528055802]": {
"$type": "EditorPendingCompositionComponent",
"Id": 10887353073528055802
},
"Component_[12641127425852859189]": {
"$type": "AZ::Render::EditorMeshComponent",
"Id": 12641127425852859189,
"Controller": {
"Configuration": {
"ModelAsset": {
"assetId": {
"guid": "{D493A670-6D82-5AE9-A2C8-A2EB02684F71}",
"subId": 284799939
},
"assetHint": "assets/objects/foliage/grass_flower_purple.azmodel"
}
}
}
},
"Component_[14406733303466080015]": {
"$type": "EditorInspectorComponent",
"Id": 14406733303466080015,
"ComponentOrderEntryArray": [
{
"ComponentId": 9231452352781000222
},
{
"ComponentId": 12641127425852859189,
"SortIndex": 1
}
]
},
"Component_[1452384341905923012]": {
"$type": "EditorLockComponent",
"Id": 1452384341905923012
},
"Component_[2215454016415585892]": {
"$type": "EditorDisabledCompositionComponent",
"Id": 2215454016415585892
},
"Component_[4104108067383423623]": {
"$type": "EditorVisibilityComponent",
"Id": 4104108067383423623
},
"Component_[4197335450471807917]": {
"$type": "SelectionComponent",
"Id": 4197335450471807917
},
"Component_[6877680739064997650]": {
"$type": "EditorOnlyEntityComponent",
"Id": 6877680739064997650
},
"Component_[7372550507186490390]": {
"$type": "EditorEntityIconComponent",
"Id": 7372550507186490390
},
"Component_[7673532337364366244]": {
"$type": "EditorEntitySortComponent",
"Id": 7673532337364366244
},
"Component_[9231452352781000222]": {
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
"Id": 9231452352781000222,
"Parent Entity": "ContainerEntity"
}
}
}
}
}
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4c1d04d687bdce69965965c290c907b3f9a730a7ea73874b734e1c7ff41426d9
size 3832768
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3789abdf439a6d70438fd4bb1e06881ae6686a4699209c6bc371d22d161e5347
size 26476
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c987d7d79685fda83efcffb7e1afbcd356c37fc68ec5c663a89b02d4df10caea
size 46412
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:27f87510ae07771dbad3e430e31502b1d1d13dee868f143b7f1de2a7febc8eb9
size 7046400
@@ -1,8 +0,0 @@
{
"values": [
{
"$type": "ScriptProcessorRule",
"scriptFilename": "Assets/TestAnim/scene_export_actor.py"
}
]
}
@@ -1,221 +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 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
@@ -1,66 +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 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
@@ -1,8 +0,0 @@
{
"values": [
{
"$type": "ScriptProcessorRule",
"scriptFilename": "Assets/TestAnim/scene_export_motion.py"
}
]
}
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:011454252e40c927343cce16296412f02f45d1f345c75c036651bdcca473bda5
size 2672
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7bafcc4aefab827e1414e64bcdde235500b51392e52c9ccd588b2d7a24b865a0
size 20214
@@ -2,11 +2,11 @@
<Class name="DescriptorListAsset" type="{60961B36-E3CA-4877-B197-1462C1363F6E}">
<Class name="AZStd::vector" field="Descriptors" type="{FC36C5E0-6152-5B06-AF30-3FC494B85FAB}">
<Class name="Descriptor" field="element" version="8" type="{A5A5E7F7-FC36-4BD1-8A93-21362574B9DA}">
<Class name="AZ::Uuid" field="SpawnerType" value="{74BEEDB5-81CF-409F-B375-0D93D81EF2E3}" type="{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}"/>
<Class name="AZ::Uuid" field="SpawnerType" value="{BBA5CC1E-B4CA-4792-89F7-93711E98FBD1}" type="{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}"/>
<Class name="AZStd::shared_ptr" field="InstanceSpawner" type="{7C7046DE-F8B1-529D-AD8C-829C6C0E2FCD}">
<Class name="PrefabInstanceSpawner" field="element" type="{74BEEDB5-81CF-409F-B375-0D93D81EF2E3}">
<Class name="DynamicSliceInstanceSpawner" field="element" type="{BBA5CC1E-B4CA-4792-89F7-93711E98FBD1}">
<Class name="InstanceSpawner" field="BaseClass1" type="{01AD0758-B04A-4B43-BC2B-BDCD77F4EF6A}"/>
<Class name="Asset" field="SpawnableAsset" value="id={80C0CF4E-9A5E-544B-B89E-BC980175A259}:e6f903d2,type={855E3021-D305-4845-B284-20C3F7FDF16B},hint={Assets/Prefabs/PinkFlower.prefab},loadBehavior=1" version="2" type="{77A19D40-8731-4D3C-9041-1B43047366A4}"/>
<Class name="Asset" field="SliceAsset" value="id={EBFE34EB-D0A8-5E51-8234-3BE38082B28C}:2,type={78802ABF-9595-463A-8D2B-D022F906F9B1},hint={slices/pinkflower.dynamicslice}" version="1" type="{77A19D40-8731-4D3C-9041-1B43047366A4}"/>
</Class>
</Class>
<Class name="float" field="Weight" value="1.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
+1
View File
@@ -27,4 +27,5 @@ else()
set_property(GLOBAL APPEND PROPERTY LY_PROJECTS_TARGET_NAME ${project_target_name})
add_subdirectory(Gem)
endif()
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ac841c02e81c9ae23476522b7e517889d746d4ff32b3251ac4360168f39b30a3
size 450708
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3653ca7fb42c7e5991815c17fc9ebeab14a1aa861bfb1d37e233ada66a835f00
size 7188
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a30b85428202c548e77cdcc0a595f9f26e82d29622be26891d569f4779a75830
size 1802388
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a3d76b7c93f8873ca7c4013dcc4eff887c25552c9c5847290481306656b22069
size 3668
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:219f57137c5bd44093762ea9d0fc24308679956b980f26bf194edd3a1798fcda
size 3668
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:96ed61c66d1d1f71e940ab75899ee253007e704004e1157c900c6308151a8bf1
size 1802388
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:70a7dc4e2455624067e842b059954f6c4bb9b0debc3cb1ffa783b14bffc61786
size 7188
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7f5a945c61f92f3da77dfd9fdd2c95aa257f4df6bcb82483c608ab660bb72e5f
size 450708
@@ -1,7 +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
#
#
@@ -1,30 +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 azlmbr.debug as debug
import pathlib
def test_profiler_system():
if not debug.g_ProfilerSystem.IsValid():
print('g_ProfilerSystem is INVALID')
return
state = 'ACTIVE' if debug.g_ProfilerSystem.IsActive() else 'INACTIVE'
print(f'Profiler system is currently {state}')
capture_location = pathlib.Path(debug.g_ProfilerSystem.GetCaptureLocation())
print(f'Capture location set to {capture_location}')
print('Capturing single frame...' )
capture_file = str(capture_location / 'script_capture_frame.json')
debug.g_ProfilerSystem.CaptureFrame(capture_file)
# Invoke main function
if __name__ == '__main__':
test_profiler_system()
-124
View File
@@ -1,124 +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, traceback, binascii, sys, json, pathlib, logging
import azlmbr.math
import azlmbr.bus
from scene_helpers import *
#
# SceneAPI Processor
#
def update_manifest(scene):
import uuid
import azlmbr.scene as sceneApi
import azlmbr.scene.graph
from scene_api import scene_data as sceneData
graph = sceneData.SceneGraph(scene.graph)
# Get a list of all the mesh nodes, as well as all the nodes
mesh_name_list, all_node_paths = get_mesh_node_names(graph)
mesh_name_list.sort(key=lambda node: str.casefold(node.get_path()))
scene_manifest = sceneData.SceneManifest()
clean_filename = scene.sourceFilename.replace('.', '_')
# Compute the filename of the scene file
source_basepath = scene.watchFolder
source_relative_path = os.path.dirname(os.path.relpath(clean_filename, source_basepath))
source_filename_only = os.path.basename(clean_filename)
created_entities = []
previous_entity_id = azlmbr.entity.InvalidEntityId
first_mesh = True
# Make a list of mesh node paths
mesh_path_list = list(map(lambda node: node.get_path(), mesh_name_list))
# Assume the first mesh is the main mesh
main_mesh = mesh_name_list[0]
mesh_path = main_mesh.get_path()
# Create a unique mesh group name using the filename + node name
mesh_group_name = '{}_{}'.format(source_filename_only, main_mesh.get_name())
# Remove forbidden filename characters from the name since this will become a file on disk later
mesh_group_name = "".join(char for char in mesh_group_name if char not in "|<>:\"/?*\\")
# Add the MeshGroup to the manifest and give it a unique ID
mesh_group = scene_manifest.add_mesh_group(mesh_group_name)
mesh_group['id'] = '{' + str(uuid.uuid5(uuid.NAMESPACE_DNS, source_filename_only + mesh_path)) + '}'
# Set our current node as the only node that is included in this MeshGroup
scene_manifest.mesh_group_select_node(mesh_group, mesh_path)
# Explicitly remove all other nodes to prevent implicit inclusions
for node in mesh_path_list:
if node != mesh_path:
scene_manifest.mesh_group_unselect_node(mesh_group, node)
# Create a LOD rule
lod_rule = scene_manifest.mesh_group_add_lod_rule(mesh_group)
# Loop all the mesh nodes after the first
for x in mesh_path_list[1:]:
# Add a new LOD level
lod = scene_manifest.lod_rule_add_lod(lod_rule)
# Select the current mesh for this LOD level
scene_manifest.lod_select_node(lod, x)
# Unselect every other mesh for this LOD level
for y in mesh_path_list:
if y != x:
scene_manifest.lod_unselect_node(lod, y)
# Create an editor entity
entity_id = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "CreateEditorReadyEntity", mesh_group_name)
# Add an EditorMeshComponent to the entity
editor_mesh_component = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "GetOrAddComponentByTypeName", entity_id, "AZ::Render::EditorMeshComponent")
# Set the ModelAsset assetHint to the relative path of the input asset + the name of the MeshGroup we just created + the azmodel extension
# The MeshGroup we created will be output as a product in the asset's path named mesh_group_name.azmodel
# The assetHint will be converted to an AssetId later during prefab loading
json_update = json.dumps({
"Controller": { "Configuration": { "ModelAsset": {
"assetHint": os.path.join(source_relative_path, mesh_group_name) + ".azmodel" }}}
});
# Apply the JSON above to the component we created
result = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "UpdateComponentForEntity", entity_id, editor_mesh_component, json_update)
if not result:
raise RuntimeError("UpdateComponentForEntity failed for Mesh component")
create_prefab(scene_manifest, source_filename_only, [entity_id])
# Convert the manifest to a JSON string and return it
new_manifest = scene_manifest.export()
return new_manifest
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 = None
# try to create SceneAPI handler for processing
try:
import azlmbr.scene as sceneApi
if (sceneJobHandler == None):
sceneJobHandler = sceneApi.ScriptBuildingNotificationBusHandler()
sceneJobHandler.connect()
sceneJobHandler.add_callback('OnUpdateManifest', on_update_manifest)
except:
sceneJobHandler = None
@@ -1,63 +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 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,109 +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 traceback, logging, json
from typing import Tuple, List
import azlmbr.bus
from scene_api import scene_data as sceneData
from scene_api.scene_data import SceneGraphName
def log_exception_traceback():
"""Outputs an exception stacktrace."""
data = traceback.format_exc()
logger = logging.getLogger('python')
logger.error(data)
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.
"""
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
Parameters
----------
scene_graph :
Scene graph to search
Returns
-------
Tuple[List[SceneGraphName], List[str]]
Tuple of [Mesh Nodes, All Node Paths]
"""
import azlmbr.scene as sceneApi
import azlmbr.scene.graph
mesh_data_list = []
node = scene_graph.get_root()
children = []
paths = []
while node.IsValid():
# store children to process after siblings
if scene_graph.has_node_child(node):
children.append(scene_graph.get_node_child(node))
node_name = sceneData.SceneGraphName(scene_graph.get_node_name(node))
paths.append(node_name.get_path())
# store any node that has mesh data content
node_content = scene_graph.get_node_content(node)
if node_content.CastWithTypeName('MeshData'):
if scene_graph.is_node_end_point(node) is False:
if len(node_name.get_path()):
mesh_data_list.append(sceneData.SceneGraphName(scene_graph.get_node_name(node)))
# advance to next node
if scene_graph.has_node_sibling(node):
node = scene_graph.get_node_sibling(node)
elif children:
node = children.pop()
else:
node = azlmbr.scene.graph.NodeIndex()
return mesh_data_list, paths
def create_prefab(scene_manifest: sceneData.SceneManifest, prefab_name: str, entities: list) -> None:
prefab_filename = prefab_name + ".prefab"
created_template_id = azlmbr.prefab.PrefabSystemScriptingBus(azlmbr.bus.Broadcast, "CreatePrefab", entities,
prefab_filename)
if created_template_id is None or created_template_id == azlmbr.prefab.InvalidTemplateId:
raise RuntimeError("CreatePrefab {} failed".format(prefab_filename))
# Convert the prefab to a JSON string
output = azlmbr.prefab.PrefabLoaderScriptingBus(azlmbr.bus.Broadcast, "SaveTemplateToString", created_template_id)
if output is not None and output.IsSuccess():
json_string = output.GetValue()
uuid = azlmbr.math.Uuid_CreateRandom().ToString()
json_result = json.loads(json_string)
# Add a PrefabGroup to the manifest and store the JSON on it
scene_manifest.add_prefab_group(prefab_name, uuid, json_result)
else:
raise RuntimeError(
"SaveTemplateToString failed for template id {}, prefab {}".format(created_template_id, prefab_filename))
@@ -5,17 +5,55 @@
# SPDX-License-Identifier: Apache-2.0 OR MIT
#
#
import azlmbr.bus
import os, traceback, binascii, sys, json, pathlib
import azlmbr.math
from scene_api.scene_data import PrimitiveShape, DecompositionMode, ColorChannel, TangentSpaceSource, TangentSpaceMethod
from scene_helpers import *
import azlmbr.bus
#
# SceneAPI Processor
#
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_mesh_node_names(sceneGraph):
import azlmbr.scene as sceneApi
import azlmbr.scene.graph
from scene_api import scene_data as sceneData
meshDataList = []
node = sceneGraph.get_root()
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 = sceneData.SceneGraphName(sceneGraph.get_node_name(node))
paths.append(nodeName.get_path())
# store any node that has mesh data content
nodeContent = sceneGraph.get_node_content(node)
if nodeContent.CastWithTypeName('MeshData'):
if sceneGraph.is_node_end_point(node) is False:
if (len(nodeName.get_path())):
meshDataList.append(sceneData.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 meshDataList, paths
def add_material_component(entity_id):
# Create an override AZ::Render::EditorMaterialComponent
editor_material_component = azlmbr.entity.EntityUtilityBus(
@@ -26,54 +64,24 @@ def add_material_component(entity_id):
# this fills out the material asset to a known product AZMaterial asset relative path
json_update = json.dumps({
"Controller": {"Configuration": {"materials": [
{
"Key": {},
"Value": {"MaterialAsset": {
"assetHint": "materials/basic_grey.azmaterial"
}}
}]
}}
})
result = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "UpdateComponentForEntity", entity_id,
editor_material_component, json_update)
"Controller": { "Configuration": { "materials": [
{
"Key": {},
"Value": { "MaterialAsset":{
"assetHint": "materials/basic_grey.azmaterial"
}}
}]
}}
});
result = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "UpdateComponentForEntity", entity_id, editor_material_component, json_update)
if not result:
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 json
import uuid, os
import azlmbr.scene as sceneApi
import azlmbr.scene.graph
from scene_api import scene_data as sceneData
@@ -81,9 +89,9 @@ def update_manifest(scene):
# Get a list of all the mesh nodes, as well as all the nodes
mesh_name_list, all_node_paths = get_mesh_node_names(graph)
scene_manifest = sceneData.SceneManifest()
clean_filename = scene.sourceFilename.replace('.', '_')
# Compute the filename of the scene file
source_basepath = scene.watchFolder
source_relative_path = os.path.dirname(os.path.relpath(clean_filename, source_basepath))
@@ -93,8 +101,6 @@ 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]
@@ -102,83 +108,52 @@ def update_manifest(scene):
# Create a unique mesh group name using the filename + node name
mesh_group_name = '{}_{}'.format(source_filename_only, mesh_name.get_name())
# Remove forbidden filename characters from the name since this will become a file on disk later
mesh_group_name = sanitize_name_for_disk(mesh_group_name)
mesh_group_name = "".join(char for char in mesh_group_name if char not in "|<>:\"/?*\\")
# Add the MeshGroup to the manifest and give it a unique ID
mesh_group = scene_manifest.add_mesh_group(mesh_group_name)
mesh_group['id'] = '{' + str(uuid.uuid5(uuid.NAMESPACE_DNS, source_filename_only + mesh_path)) + '}'
# Set our current node as the only node that is included in this MeshGroup
scene_manifest.mesh_group_select_node(mesh_group, mesh_path)
scene_manifest.mesh_group_add_comment(mesh_group, "Hello World")
# Explicitly remove all other nodes to prevent implicit inclusions
for node in all_node_paths:
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", 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, TangentSpaceSource.MIKKT_GENERATION, TangentSpaceMethod.TSPACE_BASIC)
# Create an editor entity
entity_id = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "CreateEditorReadyEntity", mesh_group_name)
# Add an EditorMeshComponent to the entity
editor_mesh_component = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "GetOrAddComponentByTypeName",
entity_id, "AZ::Render::EditorMeshComponent")
# Set the ModelAsset assetHint to the relative path of the input asset + the name of the MeshGroup we just
# created + the azmodel extension The MeshGroup we created will be output as a product in the asset's path
# named mesh_group_name.azmodel The assetHint will be converted to an AssetId later during prefab loading
editor_mesh_component = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "GetOrAddComponentByTypeName", entity_id, "AZ::Render::EditorMeshComponent")
# Set the ModelAsset assetHint to the relative path of the input asset + the name of the MeshGroup we just created + the azmodel extension
# The MeshGroup we created will be output as a product in the asset's path named mesh_group_name.azmodel
# The assetHint will be converted to an AssetId later during prefab loading
json_update = json.dumps({
"Controller": {"Configuration": {"ModelAsset": {
"assetHint": os.path.join(source_relative_path, mesh_group_name) + ".azmodel"}}}
})
"Controller": { "Configuration": { "ModelAsset": {
"assetHint": os.path.join(source_relative_path, mesh_group_name) + ".azmodel" }}}
});
# Apply the JSON above to the component we created
result = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "UpdateComponentForEntity", entity_id,
editor_mesh_component, json_update)
result = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "UpdateComponentForEntity", entity_id, editor_mesh_component, json_update)
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
add_material_component(entity_id)
# Get the transform component
transform_component = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "GetOrAddComponentByTypeName",
entity_id, "27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0")
transform_component = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "GetOrAddComponentByTypeName", entity_id, "27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0")
# Set this entity to be a child of the last entity we created
# This is just an example of how to do parenting and isn't necessarily useful to parent everything like this
if previous_entity_id is not None:
transform_json = json.dumps({
"Parent Entity": previous_entity_id.to_json()
})
"Parent Entity" : previous_entity_id.to_json()
});
# Apply the JSON update
result = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "UpdateComponentForEntity", entity_id,
transform_component, transform_json)
result = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "UpdateComponentForEntity", entity_id, transform_component, transform_json)
if not result:
raise RuntimeError("UpdateComponentForEntity failed for Transform component")
@@ -190,23 +165,37 @@ def update_manifest(scene):
created_entities.append(entity_id)
# Create a prefab with all our entities
create_prefab(scene_manifest, source_filename_only, created_entities)
prefab_filename = source_filename_only + ".prefab"
created_template_id = azlmbr.prefab.PrefabSystemScriptingBus(azlmbr.bus.Broadcast, "CreatePrefab", created_entities, prefab_filename)
if created_template_id == azlmbr.prefab.InvalidTemplateId:
raise RuntimeError("CreatePrefab {} failed".format(prefab_filename))
# Convert the prefab to a JSON string
output = azlmbr.prefab.PrefabLoaderScriptingBus(azlmbr.bus.Broadcast, "SaveTemplateToString", created_template_id)
if output.IsSuccess():
jsonString = output.GetValue()
uuid = azlmbr.math.Uuid_CreateRandom().ToString()
jsonResult = json.loads(jsonString)
# Add a PrefabGroup to the manifest and store the JSON on it
scene_manifest.add_prefab_group(source_filename_only, uuid, jsonResult)
else:
raise RuntimeError("SaveTemplateToString failed for template id {}, prefab {}".format(created_template_id, prefab_filename))
# Convert the manifest to a JSON string and return it
new_manifest = scene_manifest.export()
return new_manifest
sceneJobHandler = None
def on_update_manifest(args):
try:
scene = args[0]
return update_manifest(scene)
except RuntimeError as err:
print(f'ERROR - {err}')
print (f'ERROR - {err}')
log_exception_traceback()
except:
log_exception_traceback()
@@ -214,12 +203,10 @@ def on_update_manifest(args):
global sceneJobHandler
sceneJobHandler = None
# try to create SceneAPI handler for processing
try:
import azlmbr.scene as sceneApi
if sceneJobHandler is None:
if (sceneJobHandler == None):
sceneJobHandler = sceneApi.ScriptBuildingNotificationBusHandler()
sceneJobHandler.connect()
sceneJobHandler.add_callback('OnUpdateManifest', on_update_manifest)
+2 -5
View File
@@ -6,9 +6,6 @@
#
#
set(gem_path ${CMAKE_CURRENT_LIST_DIR})
set(gem_json ${gem_path}/gem.json)
o3de_restricted_path(${gem_json} gem_restricted_path gem_parent_relative_path)
add_subdirectory(Code)
add_subdirectory(PythonTests)
add_subdirectory(PythonTests)
add_subdirectory(PythonCoverage)
+1 -11
View File
@@ -6,7 +6,7 @@
#
#
o3de_pal_dir(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME} ${gem_restricted_path} ${gem_path} ${gem_parent_relative_path})
ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME})
ly_add_target(
NAME AutomatedTesting ${PAL_TRAIT_MONOLITHIC_DRIVEN_MODULE_TYPE}
@@ -14,25 +14,15 @@ ly_add_target(
FILES_CMAKE
automatedtesting_files.cmake
${pal_dir}/platform_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake
automatedtesting_autogen_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
Source
PUBLIC
Include
BUILD_DEPENDENCIES
PUBLIC
AZ::AzNetworking
Gem::Multiplayer
PRIVATE
AZ::AzCore
Gem::Atom_AtomBridge.Static
Gem::Multiplayer.Static
AUTOGEN_RULES
*.AutoComponent.xml,AutoComponent_Header.jinja,$path/$fileprefix.AutoComponent.h
*.AutoComponent.xml,AutoComponent_Source.jinja,$path/$fileprefix.AutoComponent.cpp
*.AutoComponent.xml,AutoComponentTypes_Header.jinja,$path/AutoComponentTypes.h
*.AutoComponent.xml,AutoComponentTypes_Source.jinja,$path/AutoComponentTypes.cpp
)
# if enabled, AutomatedTesting is used by all kinds of applications
@@ -1,15 +0,0 @@
<?xml version="1.0"?>
<Component
Name="NetworkTestLevelEntityComponent"
Namespace="AutomatedTesting"
OverrideComponent="false"
OverrideController="false"
OverrideInclude=""
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ComponentRelation Constraint="Required" HasController="true" Name="NetworkTransformComponent" Namespace="Multiplayer" Include="Multiplayer/Components/NetworkTransformComponent.h" />
<RemoteProcedure Name="AuthorityToClientNoParams_PlayFx" InvokeFrom="Authority" HandleOn="Client" IsPublic="false" IsReliable="true" GenerateEventBindings="true" Description="" />
</Component>
@@ -1,39 +0,0 @@
<?xml version="1.0"?>
<Component
Name="NetworkTestPlayerComponent"
Namespace="AutomatedTesting"
OverrideComponent="false"
OverrideController="false"
OverrideInclude=""
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ComponentRelation Constraint="Required" HasController="true" Name="NetworkTransformComponent" Namespace="Multiplayer" Include="Multiplayer/Components/NetworkTransformComponent.h" />
<NetworkInput Type="float" Name="FwdBack" Init="0.0f" ExposeToScript="true"/>
<NetworkInput Type="float" Name="LeftRight" Init="0.0f" ExposeToScript="true"/>
<RemoteProcedure Name="AutonomousToAuthority" InvokeFrom="Autonomous" HandleOn="Authority" IsPublic="false" IsReliable="false" GenerateEventBindings="true" Description="" >
<Param Type="float" Name="SomeFloat" />
</RemoteProcedure>
<RemoteProcedure Name="AutonomousToAuthorityNoParams" InvokeFrom="Autonomous" HandleOn="Authority" IsPublic="false" IsReliable="false" GenerateEventBindings="true" Description="" />
<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="" />
<RemoteProcedure Name="AuthorityToClient" InvokeFrom="Authority" HandleOn="Client" IsPublic="false" IsReliable="false" GenerateEventBindings="true" Description="" >
<Param Type="float" Name="SomeFloat" />
</RemoteProcedure>
<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="" />
</Component>
@@ -8,7 +8,6 @@
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Module/Module.h>
#include <Source/AutoGen/AutoComponentTypes.h>
#include <AutomatedTestingSystemComponent.h>
@@ -28,8 +27,6 @@ namespace AutomatedTesting
m_descriptors.insert(m_descriptors.end(), {
AutomatedTestingSystemComponent::CreateDescriptor(),
});
CreateComponentDescriptors(m_descriptors); //< Register multiplayer components
}
/**
@@ -9,7 +9,6 @@
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/EditContextConstants.inl>
#include <Source/AutoGen/AutoComponentTypes.h>
#include <AutomatedTestingSystemComponent.h>
@@ -46,7 +45,7 @@ namespace AutomatedTesting
void AutomatedTestingSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
required.push_back(AZ_CRC_CE("MultiplayerService"));
AZ_UNUSED(required);
}
void AutomatedTestingSystemComponent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent)
@@ -61,7 +60,6 @@ namespace AutomatedTesting
void AutomatedTestingSystemComponent::Activate()
{
AutomatedTestingRequestBus::Handler::BusConnect();
RegisterMultiplayerComponents(); //< Register AutomatedTesting's multiplayer components to assign NetComponentIds
}
void AutomatedTestingSystemComponent::Deactivate()
@@ -1,14 +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
#
#
set(FILES
${LY_ROOT_FOLDER}/Gems/Multiplayer/Code/Include/Multiplayer/AutoGen/AutoComponent_Common.jinja
${LY_ROOT_FOLDER}/Gems/Multiplayer/Code/Include/Multiplayer/AutoGen/AutoComponent_Header.jinja
${LY_ROOT_FOLDER}/Gems/Multiplayer/Code/Include/Multiplayer/AutoGen/AutoComponent_Source.jinja
${LY_ROOT_FOLDER}/Gems/Multiplayer/Code/Include/Multiplayer/AutoGen/AutoComponentTypes_Header.jinja
${LY_ROOT_FOLDER}/Gems/Multiplayer/Code/Include/Multiplayer/AutoGen/AutoComponentTypes_Source.jinja
)
@@ -11,6 +11,4 @@ set(FILES
Source/AutomatedTestingModule.cpp
Source/AutomatedTestingSystemComponent.cpp
Source/AutomatedTestingSystemComponent.h
Source/AutoGen/NetworkTestPlayerComponent.AutoComponent.xml
Source/AutoGen/NetworkTestLevelEntityComponent.AutoComponent.xml
)
@@ -56,5 +56,4 @@ set(ENABLED_GEMS
AudioSystem
Terrain
Profiler
Multiplayer
)
@@ -6,8 +6,4 @@
#
#
set(gem_path ${CMAKE_CURRENT_LIST_DIR})
set(gem_json ${gem_path}/gem.json)
o3de_restricted_path(${gem_json} gem_restricted_path gem_parent_relative_path)
add_subdirectory(Code)
@@ -6,7 +6,7 @@
#
#
o3de_pal_dir(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME} ${gem_restricted_path} ${gem_path} ${gem_parent_relative_path})
ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME} ${o3de_gem_restricted_path} ${o3de_gem_path} ${o3de_gem_name})
include(${pal_dir}/PAL_${PAL_PLATFORM_NAME_LOWERCASE}.cmake)
if(PAL_TRAIT_PYTHONCOVERAGE_SUPPORTED)
+1 -5
View File
@@ -4,7 +4,6 @@
"license": "Apache-2.0 Or MIT",
"license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt",
"origin": "Open 3D Engine - o3de.org",
"origin_url": "https://github.com/o3de/o3de",
"type": "Tool",
"summary": "A tool for generating gem coverage for Python tests.",
"canonical_tags": [
@@ -14,8 +13,5 @@
"PythonCoverage"
],
"icon_path": "preview.png",
"requirements": "",
"documentation_url": "",
"dependencies": [
]
"requirements": ""
}
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7
size 1232
oid sha256:6d6204c6730e5675791765ca194e9b1cbec282208e280507de830afc2805e5fa
size 41127
@@ -12,12 +12,18 @@
################################################################################
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")
return()
endif()
ly_add_pytest(
NAME AutomatedTesting::AWSTests
TEST_SUITE awsi
TEST_SERIAL
PATH ${CMAKE_CURRENT_LIST_DIR}/
PATH ${CMAKE_CURRENT_LIST_DIR}/${PAL_PLATFORM_NAME}/
RUNTIME_DEPENDENCIES
Legacy::Editor
AZ::AssetProcessor
AutomatedTesting.GameLauncher
AutomatedTesting.Assets
+16 -47
View File
@@ -2,61 +2,30 @@
## Prerequisites
1. Build the O3DE Editor and AutomatedTesting.GameLauncher in Profile.
2. Install the latest version of NodeJs.
3. AWS CLI is installed and AWS crendentials are configured via [environment variables](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html) or [default profile](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.
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.
## 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 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.
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.
## Run Automation Tests
### CLI
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}
```
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}
### 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 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.
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.
@@ -0,0 +1,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
"""
@@ -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', ['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'))
"""
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'))
@@ -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)
@@ -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
@@ -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'
)
@@ -0,0 +1,7 @@
"""
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
"""
@@ -40,7 +40,7 @@ class TestAWSClientAuthWindows(object):
Test class to verify AWS Client Auth gem features on Windows.
"""
@pytest.mark.parametrize('level', ['levels/aws/clientauth/clientauth.spawnable'])
@pytest.mark.parametrize('level', ['AWS/ClientAuth'])
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', ['levels/aws/clientauth/clientauth.spawnable'])
@pytest.mark.parametrize('level', ['AWS/ClientAuth'])
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', 'levels/aws/clientauthpasswordsignup/clientauthpasswordsignup.spawnable']
launcher.args = ['+LoadLevel', 'AWS/ClientAuthPasswordSignUp']
launcher.args.extend(['-rhi=null'])
with launcher.start(launch_ap=False):
@@ -158,7 +158,7 @@ class TestAWSClientAuthWindows(object):
Username='test1'
)
launcher.args = ['+LoadLevel', 'levels/aws/clientauthpasswordsignin/clientauthpasswordsignin.spawnable']
launcher.args = ['+LoadLevel', 'AWS/ClientAuthPasswordSignIn']
launcher.args.extend(['-rhi=null'])
with launcher.start(launch_ap=False):
@@ -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', ['levels/aws/core/core.spawnable'])
@pytest.mark.parametrize('level', ['AWS/Core'])
@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}',
@@ -68,10 +68,6 @@ class AwsCredentials:
if (len(self._credentials.sections()) == 0) and (not self._credentials_file_exists):
os.remove(self._credentials_path)
return
credentials_file_dir = os.path.dirname(self._credentials_path)
if not os.path.isdir(credentials_file_dir):
os.makedirs(credentials_file_dir)
with open(self._credentials_path, 'w+') as credential_file:
self._credentials.write(credential_file)
@@ -16,7 +16,7 @@ logging.getLogger('nose').setLevel(logging.WARNING)
class AwsUtils:
def __init__(self, arn: str, session_name: str, region_name: str):
local_session = boto3.Session()
local_session = boto3.Session(profile_name='default')
local_sts_client = local_session.client('sts')
self._local_account_id = local_sts_client.get_caller_identity()["Account"]
logger.info(f'Local Account Id: {self._local_account_id}')
@@ -6,13 +6,11 @@ SPDX-License-Identifier: Apache-2.0 OR MIT
"""
import os
import platform
# ARN of the IAM role to assume for retrieving temporary AWS credentials
ASSUME_ROLE_ARN = os.environ.get('ASSUME_ROLE_ARN', 'arn:aws:iam::645075835648:role/o3de-automation-tests')
# Name of the AWS project deployed by the CDK applications
AWS_PROJECT_NAME = os.environ.get('O3DE_AWS_PROJECT_NAME').upper() if os.environ.get('O3DE_AWS_PROJECT_NAME') else \
(os.environ.get('BRANCH_NAME', '') + '-' + os.environ.get('PIPELINE_NAME', '') + '-' + platform.system()).upper()
AWS_PROJECT_NAME = os.environ.get('O3DE_AWS_PROJECT_NAME', 'AWSAUTO')
# Region for the existing CloudFormation stacks used by the automation tests
AWS_REGION = os.environ.get('O3DE_AWS_DEPLOY_REGION', 'us-east-1')
# Name of the default resource mapping config file used by the automation tests
@@ -1,6 +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
"""
@@ -20,6 +20,19 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_BUILD_TESTS_SUPPORTED)
COMPONENT
Atom
)
ly_add_pytest(
NAME AutomatedTesting::Atom_TestSuite_Main_Optimized
TEST_SUITE main
PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Main_Optimized.py
TEST_SERIAL
TIMEOUT 600
RUNTIME_DEPENDENCIES
AssetProcessor
AutomatedTesting.Assets
Editor
COMPONENT
Atom
)
ly_add_pytest(
NAME AutomatedTesting::Atom_TestSuite_Sandbox
TEST_SUITE sandbox
@@ -48,12 +61,12 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_BUILD_TESTS_SUPPORTED)
Atom
)
ly_add_pytest(
NAME AutomatedTesting::Atom_TestSuite_Benchmark_GPU
NAME AutomatedTesting::Atom_TestSuite_Main_GPU_Optimized
TEST_SUITE main
TEST_REQUIRES gpu
TEST_SERIAL
TIMEOUT 700
PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Benchmark_GPU.py
TIMEOUT 1200
PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Main_GPU_Optimized.py
RUNTIME_DEPENDENCIES
AssetProcessor
AutomatedTesting.Assets
@@ -1,59 +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 logging
import os
import pytest
import editor_python_test_tools.hydra_test_utils as hydra
from ly_test_tools.benchmark.data_aggregator import BenchmarkDataAggregator
logger = logging.getLogger(__name__)
@pytest.mark.parametrize('rhi', ['dx12', 'vulkan'])
@pytest.mark.parametrize("project", ["AutomatedTesting"])
@pytest.mark.parametrize("launcher_platform", ["windows_editor"])
@pytest.mark.parametrize("level", ["AtomFeatureIntegrationBenchmark"])
class TestPerformanceBenchmarkSuite(object):
def test_AtomFeatureIntegrationBenchmarkTest_UploadMetrics(
self, request, editor, workspace, rhi, project, launcher_platform, level):
"""
Please review the hydra script run by this test for more specific test info.
Tests the performance of the Simple level.
"""
expected_lines = [
"Benchmark metadata captured.",
"Pass timestamps captured.",
"CPU frame time captured.",
"Captured data successfully.",
"Exited game mode"
]
unexpected_lines = [
"Failed to capture data.",
"Failed to capture pass timestamps.",
"Failed to capture CPU frame time.",
"Failed to capture benchmark metadata."
]
hydra.launch_and_validate_results(
request,
os.path.join(os.path.dirname(__file__), "tests"),
editor,
"hydra_GPUTest_AtomFeatureIntegrationBenchmark.py",
timeout=600,
expected_lines=expected_lines,
unexpected_lines=unexpected_lines,
halt_on_unexpected=True,
cfg_args=[level],
null_renderer=False,
enable_prefab_system=False,
)
aggregator = BenchmarkDataAggregator(workspace, logger, 'periodic')
aggregator.upload_metrics(rhi)
@@ -6,12 +6,12 @@ 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
from Atom.atom_utils.atom_constants import LIGHT_TYPES
logger = logging.getLogger(__name__)
TEST_DIRECTORY = os.path.join(os.path.dirname(__file__), "tests")
@@ -19,190 +19,237 @@ TEST_DIRECTORY = os.path.join(os.path.dirname(__file__), "tests")
@pytest.mark.parametrize("project", ["AutomatedTesting"])
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
class TestAutomation(EditorTestSuite):
@pytest.mark.parametrize("level", ["auto_test"])
class TestAtomEditorComponentsMain(object):
"""Holds tests for Atom components."""
enable_prefab_system = True
@pytest.mark.test_case_id("C36529679")
class AtomLevelLoadTest_Editor(EditorSharedTest):
from Atom.tests import hydra_Atom_LevelLoadTest as test_module
@pytest.mark.test_case_id("C36525657")
class AtomEditorComponents_BloomAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_BloomAdded as test_module
@pytest.mark.test_case_id("C32078118")
class AtomEditorComponents_DecalAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_DecalAdded as test_module
@pytest.mark.test_case_id("C36525658")
class AtomEditorComponents_DeferredFogAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_DeferredFogAdded as test_module
@pytest.mark.test_case_id("C32078119")
class AtomEditorComponents_DepthOfFieldAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_DepthOfFieldAdded as test_module
@pytest.mark.test_case_id("C36525659")
class AtomEditorComponents_DiffuseProbeGridAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_DiffuseProbeGridAdded as test_module
@pytest.mark.test_case_id("C32078120")
class AtomEditorComponents_DirectionalLightAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_DirectionalLightAdded as test_module
@pytest.mark.test_case_id("C36525661")
class AtomEditorComponents_EntityReferenceAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_EntityReferenceAdded as test_module
@pytest.mark.test_case_id("C32078121")
class AtomEditorComponents_ExposureControlAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_ExposureControlAdded as test_module
@pytest.mark.test_case_id("C32078115")
class AtomEditorComponents_GlobalSkylightIBLAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_GlobalSkylightIBLAdded as test_module
@pytest.mark.test_case_id("C32078122")
class AtomEditorComponents_GridAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_GridAdded as test_module
@pytest.mark.test_case_id("C36525671")
class AtomEditorComponents_HDRColorGradingAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_HDRColorGradingAdded as test_module
@pytest.mark.test_case_id("C32078116")
class AtomEditorComponents_HDRiSkyboxAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_HDRiSkyboxAdded as test_module
@pytest.mark.test_case_id("C32078117")
class AtomEditorComponents_LightAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_LightAdded as test_module
@pytest.mark.test_case_id("C36525662")
class AtomEditorComponents_LookModificationAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_LookModificationAdded as test_module
@pytest.mark.test_case_id("C32078123")
class AtomEditorComponents_MaterialAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_MaterialAdded as test_module
@pytest.mark.test_case_id("C32078124")
class AtomEditorComponents_MeshAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_MeshAdded as test_module
@pytest.mark.test_case_id("C36525663")
class AtomEditorComponents_OcclusionCullingPlaneAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_OcclusionCullingPlaneAdded as test_module
@pytest.mark.test_case_id("C32078125")
class AtomEditorComponents_PhysicalSkyAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_PhysicalSkyAdded as test_module
@pytest.mark.test_case_id("C36525664")
class AtomEditorComponents_PostFXGradientWeightModifierAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_PostFXGradientWeightModifierAdded as test_module
@pytest.mark.test_case_id("C32078127")
class AtomEditorComponents_PostFXLayerAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_PostFXLayerAdded as test_module
@pytest.mark.test_case_id("C32078131")
class AtomEditorComponents_PostFXRadiusWeightModifierAdded(EditorSharedTest):
from Atom.tests import (
hydra_AtomEditorComponents_PostFXRadiusWeightModifierAdded as test_module)
@pytest.mark.test_case_id("C36525665")
class AtomEditorComponents_PostFXShapeWeightModifierAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_PostFxShapeWeightModifierAdded as test_module
@pytest.mark.test_case_id("C32078128")
class AtomEditorComponents_ReflectionProbeAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_ReflectionProbeAdded as test_module
@pytest.mark.test_case_id("C36525666")
class AtomEditorComponents_SSAOAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_SSAOAdded as test_module
@pytest.mark.test_case_id("C36529666")
class AtomEditorComponentsLevel_DiffuseGlobalIlluminationAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponentsLevel_DiffuseGlobalIlluminationAdded as test_module
@pytest.mark.test_case_id("C36525660")
class AtomEditorComponentsLevel_DisplayMapperAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponentsLevel_DisplayMapperAdded as test_module
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):
@pytest.mark.test_case_id("C32078118") # Decal
@pytest.mark.test_case_id("C32078119") # DepthOfField
@pytest.mark.test_case_id("C32078120") # Directional Light
@pytest.mark.test_case_id("C32078121") # Exposure Control
@pytest.mark.test_case_id("C32078115") # Global Skylight (IBL)
@pytest.mark.test_case_id("C32078125") # Physical Sky
@pytest.mark.test_case_id("C32078127") # PostFX Layer
@pytest.mark.test_case_id("C32078131") # PostFX Radius Weight Modifier
@pytest.mark.test_case_id("C32078117") # Light
@pytest.mark.test_case_id("C36525660") # Display Mapper
def test_AtomEditorComponents_AddedToEntity(self, request, editor, level, workspace, project, launcher_platform):
"""
Please review the hydra script run by this test for more specific test info.
Tests the Atom components & verifies all "expected_lines" appear in Editor.log
"""
cfg_args = [level]
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",
"P1: Asset Browser visibility working as expected: True",
"P1: Inspector visibility working as expected: True",
# Decal Component
"Decal Entity successfully created",
"Decal_test: Component added to the entity: True",
"Decal_test: Component removed after UNDO: True",
"Decal_test: Component added after REDO: True",
"Decal_test: Entered game mode: True",
"Decal_test: Exit game mode: True",
"Decal Controller|Configuration|Material: SUCCESS",
"Decal_test: Entity is hidden: True",
"Decal_test: Entity is shown: True",
"Decal_test: Entity deleted: True",
"Decal_test: UNDO entity deletion works: True",
"Decal_test: REDO entity deletion works: True",
# DepthOfField Component
"DepthOfField Entity successfully created",
"DepthOfField_test: Component added to the entity: True",
"DepthOfField_test: Component removed after UNDO: True",
"DepthOfField_test: Component added after REDO: True",
"DepthOfField_test: Entered game mode: True",
"DepthOfField_test: Exit game mode: True",
"DepthOfField_test: Entity disabled initially: True",
"DepthOfField_test: Entity enabled after adding required components: True",
"DepthOfField Controller|Configuration|Camera Entity: SUCCESS",
"DepthOfField_test: Entity is hidden: True",
"DepthOfField_test: Entity is shown: True",
"DepthOfField_test: Entity deleted: True",
"DepthOfField_test: UNDO entity deletion works: True",
"DepthOfField_test: REDO entity deletion works: True",
# Directional Light Component
"Directional Light Entity successfully created",
"Directional Light_test: Component added to the entity: True",
"Directional Light_test: Component removed after UNDO: True",
"Directional Light_test: Component added after REDO: True",
"Directional Light_test: Entered game mode: True",
"Directional Light_test: Exit game mode: True",
"Directional Light_test: Entity is hidden: True",
"Directional Light_test: Entity is shown: True",
"Directional Light_test: Entity deleted: True",
"Directional Light_test: UNDO entity deletion works: True",
"Directional Light_test: REDO entity deletion works: True",
# Exposure Control Component
"Exposure Control Entity successfully created",
"Exposure Control_test: Component added to the entity: True",
"Exposure Control_test: Component removed after UNDO: True",
"Exposure Control_test: Component added after REDO: True",
"Exposure Control_test: Entered game mode: True",
"Exposure Control_test: Exit game mode: True",
"Exposure Control_test: Entity disabled initially: True",
"Exposure Control_test: Entity enabled after adding required components: True",
"Exposure Control_test: Entity is hidden: True",
"Exposure Control_test: Entity is shown: True",
"Exposure Control_test: Entity deleted: True",
"Exposure Control_test: UNDO entity deletion works: True",
"Exposure Control_test: REDO entity deletion works: True",
# Global Skylight (IBL) Component
"Global Skylight (IBL) Entity successfully created",
"Global Skylight (IBL)_test: Component added to the entity: True",
"Global Skylight (IBL)_test: Component removed after UNDO: True",
"Global Skylight (IBL)_test: Component added after REDO: True",
"Global Skylight (IBL)_test: Entered game mode: True",
"Global Skylight (IBL)_test: Exit game mode: True",
"Global Skylight (IBL) Controller|Configuration|Diffuse Image: SUCCESS",
"Global Skylight (IBL) Controller|Configuration|Specular Image: SUCCESS",
"Global Skylight (IBL)_test: Entity is hidden: True",
"Global Skylight (IBL)_test: Entity is shown: True",
"Global Skylight (IBL)_test: Entity deleted: True",
"Global Skylight (IBL)_test: UNDO entity deletion works: True",
"Global Skylight (IBL)_test: REDO entity deletion works: True",
# Physical Sky Component
"Physical Sky Entity successfully created",
"Physical Sky component was added to entity",
"Entity has a Physical Sky component",
"Physical Sky_test: Component added to the entity: True",
"Physical Sky_test: Component removed after UNDO: True",
"Physical Sky_test: Component added after REDO: True",
"Physical Sky_test: Entered game mode: True",
"Physical Sky_test: Exit game mode: True",
"Physical Sky_test: Entity is hidden: True",
"Physical Sky_test: Entity is shown: True",
"Physical Sky_test: Entity deleted: True",
"Physical Sky_test: UNDO entity deletion works: True",
"Physical Sky_test: REDO entity deletion works: True",
# PostFX Layer Component
"PostFX Layer Entity successfully created",
"PostFX Layer_test: Component added to the entity: True",
"PostFX Layer_test: Component removed after UNDO: True",
"PostFX Layer_test: Component added after REDO: True",
"PostFX Layer_test: Entered game mode: True",
"PostFX Layer_test: Exit game mode: True",
"PostFX Layer_test: Entity is hidden: True",
"PostFX Layer_test: Entity is shown: True",
"PostFX Layer_test: Entity deleted: True",
"PostFX Layer_test: UNDO entity deletion works: True",
"PostFX Layer_test: REDO entity deletion works: True",
# PostFX Radius Weight Modifier Component
"PostFX Radius Weight Modifier Entity successfully created",
"PostFX Radius Weight Modifier_test: Component added to the entity: True",
"PostFX Radius Weight Modifier_test: Component removed after UNDO: True",
"PostFX Radius Weight Modifier_test: Component added after REDO: True",
"PostFX Radius Weight Modifier_test: Entered game mode: True",
"PostFX Radius Weight Modifier_test: Exit game mode: True",
"PostFX Radius Weight Modifier_test: Entity is hidden: True",
"PostFX Radius Weight Modifier_test: Entity is shown: True",
"PostFX Radius Weight Modifier_test: Entity deleted: True",
"PostFX Radius Weight Modifier_test: UNDO entity deletion works: True",
"PostFX Radius Weight Modifier_test: REDO entity deletion works: True",
# Light Component
"Light Entity successfully created",
"Light_test: Component added to the entity: True",
"Light_test: Component removed after UNDO: True",
"Light_test: Component added after REDO: True",
"Light_test: Entered game mode: True",
"Light_test: Exit game mode: True",
"Light_test: Entity is hidden: True",
"Light_test: Entity is shown: True",
"Light_test: Entity deleted: True",
"Light_test: UNDO entity deletion works: True",
"Light_test: REDO entity deletion works: True",
# Display Mapper Component
"Display Mapper Entity successfully created",
"Display Mapper_test: Component added to the entity: True",
"Display Mapper_test: Component removed after UNDO: True",
"Display Mapper_test: Component added after REDO: True",
"Display Mapper_test: Entered game mode: True",
"Display Mapper_test: Exit game mode: True",
"Display Mapper_test: Entity is hidden: True",
"Display Mapper_test: Entity is shown: True",
"Display Mapper_test: Entity deleted: True",
"Display Mapper_test: UNDO entity deletion works: True",
"Display Mapper_test: REDO entity deletion works: True",
]
unexpected_lines = [
# Including any lines in unexpected_lines will cause the test to run for the duration of the timeout
"Trace::Assert",
"Trace::Error",
"Traceback (most recent call last):",
]
hydra.launch_and_validate_results(
request,
TEST_DIRECTORY,
generic_launcher,
"hydra_AtomMaterialEditor_BasicTests.py",
run_python="--runpython",
timeout=43,
editor,
"hydra_AtomEditorComponents_AddedToEntity.py",
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,
cfg_args=cfg_args,
)
@pytest.mark.test_case_id("C34525095")
def test_AtomEditorComponents_LightComponent(
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 has the expected property options available to it.
"""
cfg_args = [level]
expected_lines = [
"light_entity Entity successfully created",
"Entity has a Light component",
"light_entity_test: Component added to the entity: True",
f"light_entity_test: Property value is {LIGHT_TYPES['sphere']} which matches {LIGHT_TYPES['sphere']}",
"Controller|Configuration|Shadows|Enable shadow set to True",
"light_entity Controller|Configuration|Shadows|Shadowmap size: SUCCESS",
"Controller|Configuration|Shadows|Shadow filter method set to 1", # PCF
"Controller|Configuration|Shadows|Filtering sample count set to 4",
"Controller|Configuration|Shadows|Filtering sample count set to 64",
"Controller|Configuration|Shadows|Shadow filter method set to 2", # ESM
"Controller|Configuration|Shadows|ESM exponent set to 50.0",
"Controller|Configuration|Shadows|ESM exponent set to 5000.0",
"Controller|Configuration|Shadows|Shadow filter method set to 3", # ESM+PCF
f"light_entity_test: Property value is {LIGHT_TYPES['spot_disk']} which matches {LIGHT_TYPES['spot_disk']}",
f"light_entity_test: Property value is {LIGHT_TYPES['capsule']} which matches {LIGHT_TYPES['capsule']}",
f"light_entity_test: Property value is {LIGHT_TYPES['quad']} which matches {LIGHT_TYPES['quad']}",
"light_entity Controller|Configuration|Fast approximation: SUCCESS",
"light_entity Controller|Configuration|Both directions: SUCCESS",
f"light_entity_test: Property value is {LIGHT_TYPES['polygon']} which matches {LIGHT_TYPES['polygon']}",
f"light_entity_test: Property value is {LIGHT_TYPES['simple_point']} "
f"which matches {LIGHT_TYPES['simple_point']}",
"Controller|Configuration|Attenuation radius|Mode set to 0",
"Controller|Configuration|Attenuation radius|Radius set to 100.0",
f"light_entity_test: Property value is {LIGHT_TYPES['simple_spot']} "
f"which matches {LIGHT_TYPES['simple_spot']}",
"Controller|Configuration|Shutters|Outer angle set to 45.0",
"Controller|Configuration|Shutters|Outer angle set to 90.0",
"light_entity_test: Component added to the entity: True",
"Light component test (non-GPU) completed.",
]
unexpected_lines = [
"Trace::Assert",
"Trace::Error",
"Traceback (most recent call last):",
]
hydra.launch_and_validate_results(
request,
TEST_DIRECTORY,
editor,
"hydra_AtomEditorComponents_LightComponent.py",
timeout=120,
expected_lines=expected_lines,
unexpected_lines=unexpected_lines,
halt_on_unexpected=True,
null_renderer=True,
cfg_args=cfg_args,
)
@@ -4,159 +4,222 @@ 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.o3de.editor_test import EditorSingleTest, EditorTestSuite
from Atom.atom_utils.atom_component_helper import compare_screenshot_to_golden_image, golden_images_directory
from ly_test_tools.image.screenshot_compare_qssim import qssim as compare_screenshots
from ly_test_tools.benchmark.data_aggregator import BenchmarkDataAggregator
import editor_python_test_tools.hydra_test_utils as hydra
DEFAULT_SUBFOLDER_PATH = 'user/PythonTests/Automated/Screenshots'
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)
@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 = ["-autotest_mode"] # Default is ["-BatchMode", "-autotest_mode"]
use_null_renderer = False # Default is True
enable_prefab_system = False
@pytest.mark.parametrize("launcher_platform", ["windows_editor"])
@pytest.mark.parametrize("level", ["auto_test"])
class TestAllComponentsIndepthTests(object):
@staticmethod
def screenshot_setup(screenshot_directory, screenshot_names):
@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):
"""
:param screenshot_names: list of screenshot file names with extensions
:return: tuple test_screenshots, golden_images each a list of full file paths
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.).
"""
# 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)
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 = [
"Trace::Assert",
"Trace::Error",
"Traceback (most recent call last):",
"Screenshot failed"
]
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,
)
for test_screenshot, golden_screenshot in zip(test_screenshots, golden_images):
compare_screenshots(test_screenshot, golden_screenshot)
create_screenshots_archive(screenshot_directory)
@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.
"""
screenshot_names = [
"AreaLight_1.ppm",
"AreaLight_2.ppm",
"AreaLight_3.ppm",
"AreaLight_4.ppm",
"AreaLight_5.ppm",
"SpotLight_1.ppm",
"SpotLight_2.ppm",
"SpotLight_3.ppm",
"SpotLight_4.ppm",
"SpotLight_5.ppm",
"SpotLight_6.ppm",
]
screenshot_directory = os.path.join(workspace.paths.project(), DEFAULT_SUBFOLDER_PATH)
test_screenshots = []
golden_images = []
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)
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)
return test_screenshots, golden_images
expected_lines = ["spot_light Controller|Configuration|Shadows|Shadowmap size: SUCCESS"]
unexpected_lines = [
"Trace::Assert",
"Trace::Error",
"Traceback (most recent call last):",
"Screenshot failed",
]
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,
)
for test_screenshot, golden_screenshot in zip(test_screenshots, golden_images):
compare_screenshots(test_screenshot, golden_screenshot)
create_screenshots_archive(screenshot_directory)
@pytest.mark.test_case_id("C34525095")
class AtomGPU_LightComponent_AreaLightScreenshotsMatchGoldenImages_DX12(EditorSingleTest):
from Atom.tests import hydra_AtomGPU_AreaLightScreenshotTest as test_module
@pytest.mark.parametrize('rhi', ['dx12', 'vulkan'])
@pytest.mark.parametrize("project", ["AutomatedTesting"])
@pytest.mark.parametrize("launcher_platform", ["windows_editor"])
@pytest.mark.parametrize("level", ["AtomFeatureIntegrationBenchmark"])
class TestPerformanceBenchmarkSuite(object):
def test_AtomFeatureIntegrationBenchmark(
self, request, editor, workspace, rhi, project, launcher_platform, level):
"""
Please review the hydra script run by this test for more specific test info.
Tests the performance of the Simple level.
"""
expected_lines = [
"Benchmark metadata captured.",
"Pass timestamps captured.",
"CPU frame time captured.",
"Captured data successfully.",
"Exited game mode"
]
extra_cmdline_args = ["-rhi=dx12"]
unexpected_lines = [
"Failed to capture data.",
"Failed to capture pass timestamps.",
"Failed to capture CPU frame time.",
"Failed to capture benchmark metadata."
]
# Custom setup/teardown to remove old screenshots and establish paths to golden images
def setup(self, request, workspace, editor, editor_test_results, launcher_platform):
self.screenshot_directory = os.path.join(workspace.paths.project(), DEFAULT_SUBFOLDER_PATH)
self.screenshot_names = [
"AreaLight_1.ppm",
"AreaLight_2.ppm",
"AreaLight_3.ppm",
"AreaLight_4.ppm",
"AreaLight_5.ppm",
]
self.test_screenshots, self.golden_images = TestAutomation.screenshot_setup(
screenshot_directory=self.screenshot_directory,
screenshot_names=self.screenshot_names)
hydra.launch_and_validate_results(
request,
TEST_DIRECTORY,
editor,
"hydra_GPUTest_AtomFeatureIntegrationBenchmark.py",
timeout=600,
expected_lines=expected_lines,
unexpected_lines=unexpected_lines,
halt_on_unexpected=True,
cfg_args=[level],
null_renderer=False,
)
def wrap_run(self, request, workspace, editor, editor_test_results, launcher_platform):
yield
assert compare_screenshot_to_golden_image(self.screenshot_directory,
self.test_screenshots,
self.golden_images,
similarity_threshold=0.96) is True
@pytest.mark.test_case_id("C34525095")
class AtomGPU_LightComponent_AreaLightScreenshotsMatchGoldenImages_Vulkan(EditorSingleTest):
from Atom.tests import hydra_AtomGPU_AreaLightScreenshotTest as test_module
extra_cmdline_args = ["-rhi=vulkan"]
# Custom setup/teardown to remove old screenshots and establish paths to golden images
def setup(self, request, workspace, editor, editor_test_results, launcher_platform):
self.screenshot_directory = os.path.join(workspace.paths.project(), DEFAULT_SUBFOLDER_PATH)
self.screenshot_names = [
"AreaLight_1.ppm",
"AreaLight_2.ppm",
"AreaLight_3.ppm",
"AreaLight_4.ppm",
"AreaLight_5.ppm",
]
self.test_screenshots, self.golden_images = TestAutomation.screenshot_setup(
screenshot_directory=self.screenshot_directory,
screenshot_names=self.screenshot_names)
def wrap_run(self, request, workspace, editor, editor_test_results, launcher_platform):
yield
assert compare_screenshot_to_golden_image(self.screenshot_directory,
self.test_screenshots,
self.golden_images,
similarity_threshold=0.96) is True
@pytest.mark.test_case_id("C34525110")
class AtomGPU_LightComponent_SpotLightScreenshotsMatchGoldenImages_DX12(EditorSingleTest):
from Atom.tests import hydra_AtomGPU_SpotLightScreenshotTest as test_module
extra_cmdline_args = ["-rhi=dx12"]
# Custom setup/teardown to remove old screenshots and establish paths to golden images
def setup(self, request, workspace, editor, editor_test_results, launcher_platform):
self.screenshot_directory = os.path.join(workspace.paths.project(), DEFAULT_SUBFOLDER_PATH)
self.screenshot_names = [
"SpotLight_1.ppm",
"SpotLight_2.ppm",
"SpotLight_3.ppm",
"SpotLight_4.ppm",
"SpotLight_5.ppm",
"SpotLight_6.ppm",
]
self.test_screenshots, self.golden_images = TestAutomation.screenshot_setup(
screenshot_directory=self.screenshot_directory,
screenshot_names=self.screenshot_names)
def wrap_run(self, request, workspace, editor, editor_test_results, launcher_platform):
yield
assert compare_screenshot_to_golden_image(self.screenshot_directory,
self.test_screenshots,
self.golden_images,
similarity_threshold=0.96) is True
@pytest.mark.test_case_id("C34525110")
class AtomGPU_LightComponent_SpotLightScreenshotsMatchGoldenImages_Vulkan(EditorSingleTest):
from Atom.tests import hydra_AtomGPU_SpotLightScreenshotTest as test_module
extra_cmdline_args = ["-rhi=vulkan"]
# Custom setup/teardown to remove old screenshots and establish paths to golden images
def setup(self, request, workspace, editor, editor_test_results, launcher_platform):
self.screenshot_directory = os.path.join(workspace.paths.project(), DEFAULT_SUBFOLDER_PATH)
self.screenshot_names = [
"SpotLight_1.ppm",
"SpotLight_2.ppm",
"SpotLight_3.ppm",
"SpotLight_4.ppm",
"SpotLight_5.ppm",
"SpotLight_6.ppm",
]
self.test_screenshots, self.golden_images = TestAutomation.screenshot_setup(
screenshot_directory=self.screenshot_directory,
screenshot_names=self.screenshot_names)
def wrap_run(self, request, workspace, editor, editor_test_results, launcher_platform):
yield
assert compare_screenshot_to_golden_image(self.screenshot_directory,
self.test_screenshots,
self.golden_images,
similarity_threshold=0.96) is True
aggregator = BenchmarkDataAggregator(workspace, logger, 'periodic')
aggregator.upload_metrics(rhi)
@pytest.mark.parametrize("project", ["AutomatedTesting"])
@pytest.mark.parametrize("launcher_platform", ['windows_generic'])
@pytest.mark.system
class TestMaterialEditor(object):
@pytest.mark.parametrize("cfg_args,expected_lines", [
@@ -176,7 +239,7 @@ class TestMaterialEditor(object):
hydra.launch_and_validate_results(
request,
os.path.join(os.path.dirname(__file__), "tests"),
TEST_DIRECTORY,
generic_launcher,
editor_script="",
run_python="--runpython",
@@ -186,6 +249,5 @@ class TestMaterialEditor(object):
halt_on_unexpected=False,
null_renderer=False,
cfg_args=[cfg_args],
log_file_name="MaterialEditor.log",
enable_prefab_system=False,
log_file_name="MaterialEditor.log"
)
@@ -0,0 +1,44 @@
"""
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"]
@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)
@@ -0,0 +1,79 @@
"""
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 EditorSharedTest, EditorTestSuite
@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):
@pytest.mark.test_case_id("C32078118")
class AtomEditorComponents_DecalAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_DecalAdded as test_module
@pytest.mark.test_case_id("C32078119")
class AtomEditorComponents_DepthOfFieldAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_DepthOfFieldAdded as test_module
@pytest.mark.test_case_id("C32078120")
class AtomEditorComponents_DirectionalLightAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_DirectionalLightAdded as test_module
@pytest.mark.test_case_id("C32078121")
class AtomEditorComponents_ExposureControlAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_ExposureControlAdded as test_module
@pytest.mark.test_case_id("C32078115")
class AtomEditorComponents_GlobalSkylightIBLAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_GlobalSkylightIBLAdded as test_module
@pytest.mark.test_case_id("C32078125")
class AtomEditorComponents_PhysicalSkyAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_PhysicalSkyAdded as test_module
@pytest.mark.test_case_id("C32078131")
class AtomEditorComponents_PostFXRadiusWeightModifierAdded(EditorSharedTest):
from Atom.tests import (
hydra_AtomEditorComponents_PostFXRadiusWeightModifierAdded as test_module)
@pytest.mark.test_case_id("C32078117")
class AtomEditorComponents_LightAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_LightAdded 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("C32078128")
class AtomEditorComponents_ReflectionProbeAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_ReflectionProbeAdded as test_module
@pytest.mark.test_case_id("C32078124")
class AtomEditorComponents_MeshAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_MeshAdded as test_module
@pytest.mark.test_case_id("C32078123")
class AtomEditorComponents_MaterialAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_MaterialAdded as test_module
@pytest.mark.test_case_id("C32078127")
class AtomEditorComponents_PostFXLayerAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_PostFXLayerAdded as test_module
@pytest.mark.test_case_id("C36525665")
class AtomEditorComponents_PostFXShapeWeightModifierAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_PostFxShapeWeightModifierAdded as test_module
@pytest.mark.test_case_id("C36525664")
class AtomEditorComponents_PostFXGradientWeightModifierAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_PostFXGradientWeightModifierAdded as test_module
class ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges(EditorSharedTest):
from Atom.tests import hydra_ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges as test_module
@@ -9,25 +9,132 @@ import os
import pytest
from ly_test_tools.o3de.editor_test import EditorSharedTest, EditorTestSuite
import editor_python_test_tools.hydra_test_utils as hydra
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"])
class TestAtomEditorComponentsMain(object):
"""Holds tests for Atom components."""
@pytest.mark.test_case_id("C32078128")
def test_AtomEditorComponents_ReflectionProbeAddedToEntity(
self, request, editor, level, workspace, project, launcher_platform):
"""
Please review the hydra script run by this test for more specific test info.
Tests the following Atom components and verifies all "expected_lines" appear in Editor.log:
1. Reflection Probe
"""
cfg_args = [level]
expected_lines = [
# Reflection Probe Component
"Reflection Probe Entity successfully created",
"Reflection Probe_test: Component added to the entity: True",
"Reflection Probe_test: Component removed after UNDO: True",
"Reflection Probe_test: Component added after REDO: True",
"Reflection Probe_test: Entered game mode: True",
"Reflection Probe_test: Exit game mode: True",
"Reflection Probe_test: Entity disabled initially: True",
"Reflection Probe_test: Entity enabled after adding required components: True",
"Reflection Probe_test: Cubemap is generated: True",
"Reflection Probe_test: Entity is hidden: True",
"Reflection Probe_test: Entity is shown: True",
"Reflection Probe_test: Entity deleted: True",
"Reflection Probe_test: UNDO entity deletion works: True",
"Reflection Probe_test: REDO entity deletion works: True",
]
hydra.launch_and_validate_results(
request,
TEST_DIRECTORY,
editor,
"hydra_AtomEditorComponents_AddedToEntity.py",
timeout=120,
expected_lines=expected_lines,
unexpected_lines=[],
halt_on_unexpected=True,
null_renderer=True,
cfg_args=cfg_args,
)
@pytest.mark.parametrize("project", ["AutomatedTesting"])
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
class TestAutomation(EditorTestSuite):
@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()
enable_prefab_system = True
def teardown():
# Cleanup our newly created materials
delete_files()
# 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
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 = [
# "Trace::Assert",
# "Trace::Error",
"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",
)
# The "Sponza" level is failing with a hard lock 4-12% of the time, needs root causing and fixing.
@pytest.mark.test_case_id("C36529679")
class AtomLevelLoadTest_Editor_Sandbox(EditorSharedTest):
from Atom.tests import hydra_Atom_LevelLoadTest_Sandbox as test_module
@@ -1,6 +1,5 @@
"""
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.
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
@@ -9,19 +8,12 @@ import datetime
import os
import zipfile
from ly_test_tools.image.screenshot_compare_qssim import qssim as compare_screenshots
class ImageComparisonTestFailure(Exception):
"""Custom test failure for failed image comparisons."""
pass
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: path to the created .zip file archive.
:return: None, but creates a new zip file archive inside path containing all of the files inside archive_path.
"""
files_to_archive = []
@@ -35,16 +27,14 @@ def create_screenshots_archive(screenshot_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_zip_file = os.path.join(screenshot_path, f'screenshots_{formatted_timestamp}.zip')
screenshots_file = os.path.join(screenshot_path, f'screenshots_{formatted_timestamp}.zip')
# Write all of the valid .png and .ppm files to the archive file.
with zipfile.ZipFile(screenshots_zip_file, 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=True) as zip_archive:
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)
return screenshots_zip_file
def golden_images_directory():
"""
@@ -63,201 +53,176 @@ def golden_images_directory():
return golden_images_dir
def compare_screenshot_similarity(
test_screenshot, golden_image, similarity_threshold, create_zip_archive=False, screenshot_directory=""):
def create_basic_atom_level(level_name):
"""
Compares the similarity between a test screenshot and a golden image.
It returns a "Screenshots match" string if the comparison mean value is higher than the similarity threshold.
Otherwise, it returns an error string.
:param test_screenshot: path to the test screenshot to compare.
:param golden_image: path to the golden image to compare.
:param similarity_threshold: value for the comparison mean value to be asserted against.
:param create_zip_archive: toggle to create a zip archive containing the screenshots if the assert check fails.
:param screenshot_directory: directory containing screenshots to create zip archive from.
:return: Error string if compared mean value < similarity threshold or screenshot_directory is missing for .zip,
otherwise it returns a "Screenshots match" string.
"""
result = "Screenshots match"
if create_zip_archive and not screenshot_directory:
result = 'You must specify a screenshot_directory in order to create a zip archive.\n'
mean_similarity = compare_screenshots(test_screenshot, golden_image)
if not mean_similarity > similarity_threshold:
if create_zip_archive:
create_screenshots_archive(screenshot_directory)
result = (
f"When comparing the test_screenshot: '{test_screenshot}' to golden_image: '{golden_image}'.\n"
f"The mean similarity ({mean_similarity}) was lower than the similarity threshold ({similarity_threshold})")
return result
def compare_screenshot_to_golden_image(
screenshot_directory, test_screenshots, golden_images, similarity_threshold=0.99):
"""
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.
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.
: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
general.set_viewport_size(screen_width, screen_height)
general.idle_wait_frames(1)
general.update_viewport()
general.idle_wait_frames(1)
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
from editor_python_test_tools.utils import TestHelper, Report
from Atom.atom_utils.screenshot_utils import ScreenshotHelper
screenshot_helper = ScreenshotHelper(general.idle_wait_frames)
TestHelper.enter_game_mode(enter_game_tuple)
TestHelper.wait_for_condition(function=lambda: general.is_in_game_mode(), timeout_in_seconds=timeout_in_seconds)
screenshot_helper.prepare_viewport_for_screenshot(1920, 1080)
success_screenshot = TestHelper.wait_for_condition(
function=lambda: screenshot_helper.capture_screenshot_blocking(screenshot_name),
timeout_in_seconds=timeout_in_seconds)
Report.result(("Screenshot taken", "Screenshot failed to be taken"), success_screenshot)
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
import azlmbr.object
from editor_python_test_tools.asset_utils import Asset
from editor_python_test_tools.editor_entity_utils import EditorEntity
import editor_python_test_tools.hydra_editor_utils as hydra
from editor_python_test_tools.editor_test_helper import EditorTestHelper
from Atom.atom_utils.atom_constants import AtomComponentProperties
helper = EditorTestHelper(log_prefix="Atom_EditorTestHelper")
DEGREE_RADIAN_FACTOR = 0.0174533
# Create a new level.
new_level_name = level_name
heightmap_resolution = 512
heightmap_meters_per_pixel = 1
terrain_texture_resolution = 412
use_terrain = False
# Setup: Deletes all existing entities before creating the scene.
# Return codes are ECreateLevelResult defined in CryEdit.h
return_code = general.create_level_no_prompt(
new_level_name, heightmap_resolution, heightmap_meters_per_pixel, terrain_texture_resolution, use_terrain)
if return_code == 1:
general.log(f"{new_level_name} level already exists")
elif return_code == 2:
general.log("Failed to create directory")
elif return_code == 3:
general.log("Directory length is too long")
elif return_code != 0:
general.log("Unknown error, failed to create level")
else:
general.log(f"{new_level_name} level created successfully")
# Enable idle and update viewport.
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)
# Delete all existing entities & create default_level entity
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)
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)
# 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)
# 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")
# 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)
# 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)
# 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)
# 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)
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)
# 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 = 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 = 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)
# Work around to add the correct Atom Mesh component
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("Models", "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)
# 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 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)
# 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 = 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())
# 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)
sphere_material_asset_path = os.path.join("Materials", "Presets", "PBR", "metal_brass_polished.azmaterial")
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)
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)
# 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)
# Work around to add the correct Atom Mesh component
sphere_entity.components.append(
editor.EditorComponentAPIBus(
bus.Broadcast, "AddComponentsOfType", sphere_entity.id, [mesh_type_id]
).GetValue()[0]
)
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)
# 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)
@@ -19,24 +19,6 @@ 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,
}
# Level list used in Editor Level Load Test
# WARNING: "Sponza" level is sandboxed due to an intermittent failure.
LEVEL_LIST = ["hermanubis", "hermanubis_high", "macbeth_shaderballs", "PbrMaterialChart", "ShadowTest"]
class AtomComponentProperties:
"""
Holds Atom component related constants
@@ -60,14 +42,12 @@ class AtomComponentProperties:
Bloom component properties. Requires PostFX Layer component.
- 'requires' a list of component names as strings required by this component.
Use editor_entity_utils EditorEntity.add_components(list) to add this list of requirements.\n
- 'Enable Bloom' Toggle active state of the component True/False
: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': 'Bloom',
'requires': [AtomComponentProperties.postfx_layer()],
'Enable Bloom': 'Controller|Configuration|Enable Bloom',
}
return properties[property]
@@ -89,18 +69,12 @@ class AtomComponentProperties:
def decal(property: str = 'name') -> str:
"""
Decal component properties.
- 'Attenuation Angle' controls how much the angle between geometry and decal impacts opacity. 0-1 Radians
- 'Opacity' where one is opaque and zero is transparent
- 'Sort Key' 0-255 stacking z-sort like key to define which decal is on top of another
- 'Material' the material Asset.id of the decal.
: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': 'Decal',
'Attenuation Angle': 'Controller|Configuration|Attenuation Angle',
'Opacity': 'Controller|Configuration|Opacity',
'Sort Key': 'Controller|Configuration|Sort Key',
'Material': 'Controller|Configuration|Material',
}
return properties[property]
@@ -111,14 +85,12 @@ class AtomComponentProperties:
Deferred Fog component properties. Requires PostFX Layer component.
- 'requires' a list of component names as strings required by this component.
Use editor_entity_utils EditorEntity.add_components(list) to add this list of requirements.\n
- 'Enable Deferred Fog' Toggle active state of the component True/False
: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': 'Deferred Fog',
'requires': [AtomComponentProperties.postfx_layer()],
'Enable Deferred Fog': 'Controller|Configuration|Enable Deferred Fog',
}
return properties[property]
@@ -141,22 +113,7 @@ 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:
def diffuse_probe(property: str = 'name') -> str:
"""
Diffuse Probe Grid component properties. Requires one of 'shapes'.
- 'shapes' a list of supported shapes as component names.
@@ -187,17 +144,12 @@ class AtomComponentProperties:
@staticmethod
def display_mapper(property: str = 'name') -> str:
"""
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.
Display Mapper component properties.
: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': '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]
@@ -205,13 +157,11 @@ class AtomComponentProperties:
def entity_reference(property: str = 'name') -> str:
"""
Entity Reference component properties.
- 'EntityIdReferences' component container of entityId references. Initially empty.
: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': 'Entity Reference',
'EntityIdReferences': 'Controller|Configuration|EntityIdReferences',
}
return properties[property]
@@ -250,14 +200,12 @@ class AtomComponentProperties:
def grid(property: str = 'name') -> str:
"""
Grid component properties.
- 'Grid Size': The size of the grid, default value is 32
- 'Secondary Grid Spacing': The spacing value for the secondary grid, i.e. 1.0
: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': 'Grid',
'Grid Size': 'Controller|Configuration|Grid Size',
'Secondary Grid Spacing': 'Controller|Configuration|Secondary Grid Spacing',
}
return properties[property]
@@ -268,14 +216,12 @@ class AtomComponentProperties:
HDR Color Grading component properties. Requires PostFX Layer component.
- 'requires' a list of component names as strings required by this component.
Use editor_entity_utils EditorEntity.add_components(list) to add this list of requirements.\n
- 'Enable HDR color grading' Toggle active state of the component True/False
: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': 'HDR Color Grading',
'requires': [AtomComponentProperties.postfx_layer()],
'Enable HDR color grading': 'Controller|Configuration|Enable HDR color grading',
}
return properties[property]
@@ -297,27 +243,13 @@ 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]
@@ -327,16 +259,12 @@ class AtomComponentProperties:
Look Modification component properties. Requires PostFX Layer component.
- 'requires' a list of component names as strings required by this component.
Use editor_entity_utils EditorEntity.add_components(list) to add this list of requirements.\n
- 'Enable look modification' Toggle active state of the component True/False
- 'Color Grading LUT' Asset.id for the LUT used for affecting level look.
: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': 'Look Modification',
'requires': [AtomComponentProperties.postfx_layer()],
'Enable look modification': 'Controller|Configuration|Enable look modification',
'Color Grading LUT': 'Controller|Configuration|Color Grading LUT',
}
return properties[property]
@@ -388,13 +316,11 @@ class AtomComponentProperties:
def physical_sky(property: str = 'name') -> str:
"""
Physical Sky component properties.
- 'Sky Intensity' float that determines sky intensity value, default value is 4.
: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': 'Physical Sky',
'Sky Intensity': 'Controller|Configuration|Sky Intensity',
}
return properties[property]
@@ -454,7 +380,7 @@ class AtomComponentProperties:
'name': 'PostFX Shape Weight Modifier',
'requires': [AtomComponentProperties.postfx_layer()],
'shapes': ['Axis Aligned Box Shape', 'Box Shape', 'Capsule Shape', 'Compound Shape', 'Cylinder Shape',
'Disk Shape', 'Polygon Prism Shape', 'Quad Shape', 'Sphere Shape', 'Shape Reference'],
'Disk Shape', 'Polygon Prism Shape', 'Quad Shape', 'Sphere Shape', 'Vegetation Reference Shape'],
}
return properties[property]
@@ -114,22 +114,22 @@ def get_property(document_id, property_name):
"""
:return: property value or invalid value if the document is not open or the property_name can't be found
"""
return azlmbr.materialeditor.MaterialDocumentRequestBus(bus.Event, "GetPropertyValue", document_id, property_name)
return azlmbr.atomtools.AtomToolsDocumentRequestBus(bus.Event, "GetPropertyValue", document_id, property_name)
def set_property(document_id, property_name, value):
azlmbr.materialeditor.MaterialDocumentRequestBus(bus.Event, "SetPropertyValue", document_id, property_name, value)
azlmbr.atomtools.AtomToolsDocumentRequestBus(bus.Event, "SetPropertyValue", document_id, property_name, value)
def is_pane_visible(pane_name):
"""
:return: bool
"""
return atomtools.AtomToolsMainWindowRequestBus(bus.Broadcast, "IsDockWidgetVisible", pane_name)
return atomtools.AtomToolsWindowRequestBus(bus.Broadcast, "IsDockWidgetVisible", pane_name)
def set_pane_visibility(pane_name, value):
atomtools.AtomToolsMainWindowRequestBus(bus.Broadcast, "SetDockWidgetVisible", pane_name, value)
atomtools.AtomToolsWindowRequestBus(bus.Broadcast, "SetDockWidgetVisible", pane_name, value)
def select_lighting_config(config_name):
@@ -162,13 +162,6 @@ def select_model_config(configname):
azlmbr.materialeditor.MaterialViewportRequestBus(azlmbr.bus.Broadcast, "SelectModelPresetByName", configname)
def exit():
"""
Closes the Material Editor
"""
azlmbr.atomtools.general.exit()
def wait_for_condition(function, timeout_in_seconds=1.0):
# type: (function, float) -> bool
"""
@@ -1,109 +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
"""
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("Graphics", "base_empty")
# 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(Tests.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)
@@ -1,124 +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
"""
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("Graphics", "base_empty")
# 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(
Tests.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)
@@ -0,0 +1,238 @@
"""
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.math as math
import azlmbr.bus as bus
import azlmbr.paths
import azlmbr.asset as asset
import azlmbr.entity as entity
import azlmbr.legacy.general as general
import azlmbr.editor as editor
import azlmbr.render as render
sys.path.append(os.path.join(azlmbr.paths.projectroot, "Gem", "PythonTests"))
import editor_python_test_tools.hydra_editor_utils as hydra
from editor_python_test_tools.utils import TestHelper
def run():
"""
Summary:
The below common tests are done for each of the components.
1) Addition of component to the entity
2) UNDO/REDO of addition of component
3) Enter/Exit game mode
4) Hide/Show entity containing component
5) Deletion of component
6) UNDO/REDO of deletion of component
Some additional tests for specific components include
1) Assigning value to some properties of each component
2) Verifying if the component is activated only when the required components are added
Expected Result:
1) Component can be added to an entity.
2) The addition of component can be undone and redone.
3) Game mode can be entered/exited without issue.
4) Entity with component can be hidden/shown.
5) Component can be deleted.
6) The deletion of component can be undone and redone.
7) Component is activated only when the required components are added
8) Values can be assigned to the properties of the component
:return: None
"""
def create_entity_undo_redo_component_addition(component_name):
new_entity = hydra.Entity(f"{component_name}")
new_entity.create_entity(math.Vector3(512.0, 512.0, 34.0), [component_name])
general.log(f"{component_name}_test: Component added to the entity: "
f"{hydra.has_components(new_entity.id, [component_name])}")
# undo component addition
general.undo()
TestHelper.wait_for_condition(lambda: not hydra.has_components(new_entity.id, [component_name]), 2.0)
general.log(f"{component_name}_test: Component removed after UNDO: "
f"{not hydra.has_components(new_entity.id, [component_name])}")
# redo component addition
general.redo()
TestHelper.wait_for_condition(lambda: hydra.has_components(new_entity.id, [component_name]), 2.0)
general.log(f"{component_name}_test: Component added after REDO: "
f"{hydra.has_components(new_entity.id, [component_name])}")
return new_entity
def verify_enter_exit_game_mode(component_name):
general.enter_game_mode()
TestHelper.wait_for_condition(lambda: general.is_in_game_mode(), 2.0)
general.log(f"{component_name}_test: Entered game mode: {general.is_in_game_mode()}")
general.exit_game_mode()
TestHelper.wait_for_condition(lambda: not general.is_in_game_mode(), 2.0)
general.log(f"{component_name}_test: Exit game mode: {not general.is_in_game_mode()}")
def verify_hide_unhide_entity(component_name, entity_obj):
def is_entity_hidden(entity_id):
return editor.EditorEntityInfoRequestBus(bus.Event, "IsHidden", entity_id)
editor.EditorEntityAPIBus(bus.Event, "SetVisibilityState", entity_obj.id, False)
general.idle_wait_frames(1)
general.log(f"{component_name}_test: Entity is hidden: {is_entity_hidden(entity_obj.id)}")
editor.EditorEntityAPIBus(bus.Event, "SetVisibilityState", entity_obj.id, True)
general.idle_wait_frames(1)
general.log(f"{component_name}_test: Entity is shown: {not is_entity_hidden(entity_obj.id)}")
def verify_deletion_undo_redo(component_name, entity_obj):
editor.ToolsApplicationRequestBus(bus.Broadcast, "DeleteEntityById", entity_obj.id)
TestHelper.wait_for_condition(lambda: not hydra.find_entity_by_name(entity_obj.name), 2.0)
general.log(f"{component_name}_test: Entity deleted: {not hydra.find_entity_by_name(entity_obj.name)}")
general.undo()
TestHelper.wait_for_condition(lambda: hydra.find_entity_by_name(entity_obj.name) is not None, 2.0)
general.log(f"{component_name}_test: UNDO entity deletion works: "
f"{hydra.find_entity_by_name(entity_obj.name) is not None}")
general.redo()
TestHelper.wait_for_condition(lambda: not hydra.find_entity_by_name(entity_obj.name), 2.0)
general.log(f"{component_name}_test: REDO entity deletion works: "
f"{not hydra.find_entity_by_name(entity_obj.name)}")
def verify_required_component_addition(entity_obj, components_to_add, component_name):
def is_component_enabled(entity_componentid_pair):
return editor.EditorComponentAPIBus(bus.Broadcast, "IsComponentEnabled", entity_componentid_pair)
general.log(
f"{component_name}_test: Entity disabled initially: "
f"{not is_component_enabled(entity_obj.components[0])}")
for component in components_to_add:
entity_obj.add_component(component)
TestHelper.wait_for_condition(lambda: is_component_enabled(entity_obj.components[0]), 2.0)
general.log(
f"{component_name}_test: Entity enabled after adding "
f"required components: {is_component_enabled(entity_obj.components[0])}"
)
def verify_set_property(entity_obj, path, value):
entity_obj.get_set_test(0, path, value)
# Verify cubemap generation
def verify_cubemap_generation(component_name, entity_obj):
# Initially Check if the component has Reflection Probe component
if not hydra.has_components(entity_obj.id, ["Reflection Probe"]):
raise ValueError(f"Given entity {entity_obj.name} has no Reflection Probe component")
render.EditorReflectionProbeBus(azlmbr.bus.Event, "BakeReflectionProbe", entity_obj.id)
def get_value():
hydra.get_component_property_value(entity_obj.components[0], "Cubemap|Baked Cubemap Path")
TestHelper.wait_for_condition(lambda: get_value() != "", 20.0)
general.log(f"{component_name}_test: Cubemap is generated: {get_value() != ''}")
# Wait for Editor idle loop before executing Python hydra scripts.
TestHelper.init_idle()
# Delete all existing entities initially
search_filter = azlmbr.entity.SearchFilter()
all_entities = entity.SearchBus(azlmbr.bus.Broadcast, "SearchEntities", search_filter)
editor.ToolsApplicationRequestBus(bus.Broadcast, "DeleteEntities", all_entities)
class ComponentTests:
"""Test launcher for each component."""
def __init__(self, component_name, *additional_tests):
self.component_name = component_name
self.additional_tests = additional_tests
self.run_component_tests()
def run_component_tests(self):
# Run common and additional tests
entity_obj = create_entity_undo_redo_component_addition(self.component_name)
# Enter/Exit game mode test
verify_enter_exit_game_mode(self.component_name)
# Any additional tests are executed here
for test in self.additional_tests:
test(entity_obj)
# Hide/Unhide entity test
verify_hide_unhide_entity(self.component_name, entity_obj)
# Deletion/Undo/Redo test
verify_deletion_undo_redo(self.component_name, entity_obj)
# DepthOfField Component
camera_entity = hydra.Entity("camera_entity")
camera_entity.create_entity(math.Vector3(512.0, 512.0, 34.0), ["Camera"])
depth_of_field = "DepthOfField"
ComponentTests(
depth_of_field,
lambda entity_obj: verify_required_component_addition(entity_obj, ["PostFX Layer"], depth_of_field),
lambda entity_obj: verify_set_property(
entity_obj, "Controller|Configuration|Camera Entity", camera_entity.id))
# Decal Component
material_asset_path = os.path.join("AutomatedTesting", "Materials", "basic_grey.material")
material_asset = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", material_asset_path, math.Uuid(), False)
ComponentTests(
"Decal", lambda entity_obj: verify_set_property(
entity_obj, "Controller|Configuration|Material", material_asset))
# Directional Light Component
ComponentTests(
"Directional Light",
lambda entity_obj: verify_set_property(
entity_obj, "Controller|Configuration|Shadow|Camera", camera_entity.id))
# Exposure Control Component
ComponentTests(
"Exposure Control", lambda entity_obj: verify_required_component_addition(
entity_obj, ["PostFX Layer"], "Exposure Control"))
# Global Skylight (IBL) Component
diffuse_image_path = os.path.join("LightingPresets", "greenwich_park_02_4k_iblskyboxcm.exr.streamingimage")
diffuse_image_asset = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", diffuse_image_path, math.Uuid(), False)
specular_image_path = os.path.join("LightingPresets", "greenwich_park_02_4k_iblskyboxcm.exr.streamingimage")
specular_image_asset = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", specular_image_path, math.Uuid(), False)
ComponentTests(
"Global Skylight (IBL)",
lambda entity_obj: verify_set_property(
entity_obj, "Controller|Configuration|Diffuse Image", diffuse_image_asset),
lambda entity_obj: verify_set_property(
entity_obj, "Controller|Configuration|Specular Image", specular_image_asset))
# Physical Sky Component
ComponentTests("Physical Sky")
# PostFX Layer Component
ComponentTests("PostFX Layer")
# PostFX Radius Weight Modifier Component
ComponentTests("PostFX Radius Weight Modifier")
# Light Component
ComponentTests("Light")
# Display Mapper Component
ComponentTests("Display Mapper")
# Reflection Probe Component
reflection_probe = "Reflection Probe"
ComponentTests(
reflection_probe,
lambda entity_obj: verify_required_component_addition(entity_obj, ["Box Shape"], reflection_probe),
lambda entity_obj: verify_cubemap_generation(reflection_probe, entity_obj),)
if __name__ == "__main__":
run()
@@ -1,191 +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
"""
class Tests:
creation_undo = (
"UNDO Entity creation success",
"UNDO Entity creation failed")
creation_redo = (
"REDO Entity creation success",
"REDO Entity creation failed")
bloom_creation = (
"Bloom Entity successfully created",
"Bloom Entity failed to be created")
bloom_component = (
"Entity has a Bloom component",
"Entity failed to find Bloom component")
bloom_disabled = (
"Bloom component disabled",
"Bloom component was not disabled")
postfx_layer_component = (
"Entity has a PostFX Layer component",
"Entity did not have an PostFX Layer component")
bloom_enabled = (
"Bloom component enabled",
"Bloom component was not enabled")
enable_bloom_parameter_enabled = (
"Enable Bloom parameter enabled",
"Enable Bloom parameter was not enabled")
enter_game_mode = (
"Entered game mode",
"Failed to enter game mode")
exit_game_mode = (
"Exited game mode",
"Couldn't exit game mode")
is_visible = (
"Entity is visible",
"Entity was not visible")
is_hidden = (
"Entity is hidden",
"Entity was not hidden")
entity_deleted = (
"Entity deleted",
"Entity was not deleted")
deletion_undo = (
"UNDO deletion success",
"UNDO deletion failed")
deletion_redo = (
"REDO deletion success",
"REDO deletion failed")
def AtomEditorComponents_Bloom_AddedToEntity():
"""
Summary:
Tests the Bloom component can be added to an 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, hidden/shown, deleted, and has accurate required components.
Creation and deletion undo/redo should also work.
Test Steps:
1) Create an Bloom entity with no components.
2) Add Bloom component to Bloom entity.
3) UNDO the entity creation and component addition.
4) REDO the entity creation and component addition.
5) Verify Bloom component not enabled.
6) Add PostFX Layer component since it is required by the Bloom component.
7) Verify Bloom component is enabled.
8) Enable the "Enable Bloom" parameter.
9) Enter/Exit game mode.
10) Test IsHidden.
11) Test IsVisible.
12) Delete Bloom entity.
13) UNDO deletion.
14) REDO deletion.
15) Look for errors.
:return: None
"""
import azlmbr.legacy.general as general
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
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("Graphics", "base_empty")
# Test steps begin.
# 1. Create an Bloom entity with no components.
bloom_entity = EditorEntity.create_editor_entity(AtomComponentProperties.bloom())
Report.critical_result(Tests.bloom_creation, bloom_entity.exists())
# 2. Add Bloom component to Bloom entity.
bloom_component = bloom_entity.add_component(AtomComponentProperties.bloom())
Report.critical_result(Tests.bloom_component, bloom_entity.has_component(AtomComponentProperties.bloom()))
# 3. UNDO the entity creation and component addition.
# -> UNDO component addition.
general.undo()
# -> UNDO naming entity.
general.undo()
# -> UNDO selecting entity.
general.undo()
# -> UNDO entity creation.
general.undo()
general.idle_wait_frames(1)
Report.result(Tests.creation_undo, not bloom_entity.exists())
# 4. REDO the entity creation and component addition.
# -> REDO entity creation.
general.redo()
# -> REDO selecting entity.
general.redo()
# -> REDO naming entity.
general.redo()
# -> REDO component addition.
general.redo()
general.idle_wait_frames(1)
Report.result(Tests.creation_redo, bloom_entity.exists())
# 5. Verify Bloom component not enabled.
Report.result(Tests.bloom_disabled, not bloom_component.is_enabled())
# 6. Add PostFX Layer component since it is required by the Bloom component.
bloom_entity.add_component(AtomComponentProperties.postfx_layer())
Report.result(
Tests.postfx_layer_component,
bloom_entity.has_component(AtomComponentProperties.postfx_layer()))
# 7. Verify Bloom component is enabled.
Report.result(Tests.bloom_enabled, bloom_component.is_enabled())
# 8. Enable the "Enable Bloom" parameter.
bloom_component.set_component_property_value(AtomComponentProperties.bloom('Enable Bloom'), True)
Report.result(
Tests.enable_bloom_parameter_enabled,
bloom_component.get_component_property_value(AtomComponentProperties.bloom('Enable Bloom')) is True)
# 9. 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)
# 10. Test IsHidden.
bloom_entity.set_visibility_state(False)
Report.result(Tests.is_hidden, bloom_entity.is_hidden() is True)
# 11. Test IsVisible.
bloom_entity.set_visibility_state(True)
general.idle_wait_frames(1)
Report.result(Tests.is_visible, bloom_entity.is_visible() is True)
# 12. Delete Bloom entity.
bloom_entity.delete()
Report.result(Tests.entity_deleted, not bloom_entity.exists())
# 13. UNDO deletion.
general.undo()
general.idle_wait_frames(1)
Report.result(Tests.deletion_undo, bloom_entity.exists())
# 14. REDO deletion.
general.redo()
general.idle_wait_frames(1)
Report.result(Tests.deletion_redo, not bloom_entity.exists())
# 15. 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(AtomEditorComponents_Bloom_AddedToEntity)
@@ -5,64 +5,25 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
# fmt: off
class Tests:
camera_creation = (
"Camera Entity successfully created",
"P0: Camera Entity failed to be created")
camera_component_added = (
"Camera component was added to entity",
"P0: Camera component failed to be added to entity")
camera_component_check = (
"Entity has a Camera component",
"P0: Entity failed to find Camera component")
creation_undo = (
"UNDO Entity creation success",
"P0: UNDO Entity creation failed")
creation_redo = (
"REDO Entity creation success",
"P0: REDO Entity creation failed")
decal_creation = (
"Decal Entity successfully created",
"P0: Decal Entity failed to be created")
decal_component = (
"Entity has a Decal component",
"P0: Entity failed to find Decal component")
decal_component_removed = (
"Decal component removed",
"P0: Decal component failed to be removed")
material_property_set = (
"Material property set on Decal component",
"P0: Couldn't set Material property on Decal component")
attenuation_property_set = (
"Attenuation Angle property set on Decal component",
"P1: Couldn't set Attenuation Angle property on Decal component")
opacity_property_set = (
"Opacity property set on Decal component",
"P1: Coudn't set Opacity property on Decal component")
sort_key_property_set = (
"Sort Key property set on Decal component",
"P1: Couldn't set Sort Key property on Decal component")
enter_game_mode = (
"Entered game mode",
"P0: Failed to enter game mode")
exit_game_mode = (
"Exited game mode",
"P0: Couldn't exit game mode")
is_visible = (
"Entity is visible",
"P0: Entity was not visible")
is_hidden = (
"Entity is hidden",
"P0: Entity was not hidden")
entity_deleted = (
"Entity deleted",
"P0: Entity was not deleted")
deletion_undo = (
"UNDO deletion success",
"P0: UNDO deletion failed")
deletion_redo = (
"REDO deletion success",
"P0: REDO deletion failed")
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")
creation_redo = ("REDO Entity creation success", "REDO Entity creation failed")
decal_creation = ("Decal Entity successfully created", "Decal Entity failed to be created")
decal_component = ("Entity has a Decal component", "Entity failed to find Decal component")
material_property_set = ("Material property set on Decal component", "Couldn't set Material property on Decal component")
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
is_visible = ("Entity is visible", "Entity was not visible")
is_hidden = ("Entity is hidden", "Entity was not hidden")
entity_deleted = ("Entity deleted", "Entity was not deleted")
deletion_undo = ("UNDO deletion success", "UNDO deletion failed")
deletion_redo = ("REDO deletion success", "REDO deletion failed")
no_error_occurred = ("No errors detected", "Errors were detected")
# fmt: on
def AtomEditorComponents_Decal_AddedToEntity():
@@ -83,44 +44,42 @@ def AtomEditorComponents_Decal_AddedToEntity():
2) Add Decal component to Decal entity.
3) UNDO the entity creation and component addition.
4) REDO the entity creation and component addition.
5) Set Material property on Decal component.
6) Set Attenuation Angle property on Decal component.
7) Set Opacity property on Decal component
8) Set Sort Key property on Decal Component
9) Remove Decal component then UNDO the remove
10) Enter/Exit game mode.
11) Test IsHidden.
12) Test IsVisible.
13) Delete Decal entity.
14) UNDO deletion.
15) REDO deletion.
16) Look for errors and asserts.
5) Enter/Exit game mode.
6) Test IsHidden.
7) Test IsVisible.
8) Set Material property on Decal component.
9) Delete Decal entity.
10) UNDO deletion.
11) REDO deletion.
12) Look for errors.
:return: None
"""
import os
import azlmbr.asset as asset
import azlmbr.bus as bus
import azlmbr.legacy.general as general
import azlmbr.math as math
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
from editor_python_test_tools.utils import Report, Tracer, TestHelper as helper
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("Graphics", "base_empty")
helper.init_idle()
helper.open_level("", "Base")
# Test steps begin.
# 1. Create a Decal entity with no components.
decal_entity = EditorEntity.create_editor_entity(AtomComponentProperties.decal())
decal_name = "Decal"
decal_entity = EditorEntity.create_editor_entity_at(math.Vector3(512.0, 512.0, 34.0), decal_name)
Report.critical_result(Tests.decal_creation, decal_entity.exists())
# 2. Add Decal component to Decal entity.
decal_component = decal_entity.add_component(AtomComponentProperties.decal())
Report.critical_result(Tests.decal_component, decal_entity.has_component(AtomComponentProperties.decal()))
decal_component = decal_entity.add_component(decal_name)
Report.critical_result(Tests.decal_component, decal_entity.has_component(decal_name))
# 3. UNDO the entity creation and component addition.
# -> UNDO component addition.
@@ -146,74 +105,45 @@ def AtomEditorComponents_Decal_AddedToEntity():
general.idle_wait_frames(1)
Report.result(Tests.creation_redo, decal_entity.exists())
# 5. Set Material property on Decal component.
decal_material_asset_path = os.path.join("materials", "decal", "airship_symbol_decal.azmaterial")
decal_material_asset = Asset.find_asset_by_path(decal_material_asset_path, False)
decal_component.set_component_property_value(AtomComponentProperties.decal('Material'), decal_material_asset.id)
get_material_property = decal_component.get_component_property_value(AtomComponentProperties.decal('Material'))
Report.result(Tests.material_property_set, get_material_property == decal_material_asset.id)
# 6. Set Attenuation Angle property on Decal component
decal_component.set_component_property_value(AtomComponentProperties.decal('Attenuation Angle'), value=0.75)
get_attenuation_property = decal_component.get_component_property_value(
AtomComponentProperties.decal('Attenuation Angle'))
Report.result(Tests.attenuation_property_set, get_attenuation_property == 0.75)
# 7. Set Opacity property on Decal component
decal_component.set_component_property_value(AtomComponentProperties.decal('Opacity'), value=0.5)
get_opacity_property = decal_component.get_component_property_value(AtomComponentProperties.decal('Opacity'))
Report.result(Tests.opacity_property_set, get_opacity_property == 0.5)
# 8. Set Sort Key property on Decal component
decal_component.set_component_property_value(AtomComponentProperties.decal('Sort Key'), value=255.0)
get_sort_key_property = decal_component.get_component_property_value(AtomComponentProperties.decal('Sort Key'))
Report.result(Tests.sort_key_property_set, get_sort_key_property == 255.0)
decal_component.set_component_property_value(AtomComponentProperties.decal('Sort Key'), value=0)
get_sort_key_property = decal_component.get_component_property_value(AtomComponentProperties.decal('Sort Key'))
Report.result(Tests.sort_key_property_set, get_sort_key_property == 0)
# 9. Remove Decal component then UNDO the remove
decal_component.remove()
# 5. Enter/Exit game mode.
helper.enter_game_mode(Tests.enter_game_mode)
general.idle_wait_frames(1)
Report.result(Tests.decal_component_removed, not decal_entity.has_component(AtomComponentProperties.decal()))
general.undo()
general.idle_wait_frames(1)
Report.result(Tests.decal_component, decal_entity.has_component(AtomComponentProperties.decal()))
helper.exit_game_mode(Tests.exit_game_mode)
# 10. 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)
# 11. Test IsHidden.
# 6. Test IsHidden.
decal_entity.set_visibility_state(False)
Report.result(Tests.is_hidden, decal_entity.is_hidden() is True)
# 12. Test IsVisible.
# 7. Test IsVisible.
decal_entity.set_visibility_state(True)
general.idle_wait_frames(1)
Report.result(Tests.is_visible, decal_entity.is_visible() is True)
# 13. Delete Decal entity.
# 8. Set Material property on Decal component.
decal_material_property_path = "Controller|Configuration|Material"
decal_material_asset_path = os.path.join("AutomatedTesting", "Materials", "basic_grey.material")
decal_material_asset = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", decal_material_asset_path, math.Uuid(), False)
decal_component.set_component_property_value(decal_material_property_path, decal_material_asset)
get_material_property = decal_component.get_component_property_value(decal_material_property_path)
Report.result(Tests.material_property_set, get_material_property == decal_material_asset)
# 9. Delete Decal entity.
decal_entity.delete()
Report.result(Tests.entity_deleted, not decal_entity.exists())
# 14. UNDO deletion.
# 10. UNDO deletion.
general.undo()
general.idle_wait_frames(1)
Report.result(Tests.deletion_undo, decal_entity.exists())
# 15. REDO deletion.
# 11. REDO deletion.
general.redo()
general.idle_wait_frames(1)
Report.result(Tests.deletion_redo, not decal_entity.exists())
# 16. 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}")
# 12. Look for errors.
helper.wait_for_condition(lambda: error_tracer.has_errors, 1.0)
Report.result(Tests.no_error_occurred, not error_tracer.has_errors)
if __name__ == "__main__":
@@ -1,195 +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
"""
class Tests:
creation_undo = (
"UNDO Entity creation success",
"UNDO Entity creation failed")
creation_redo = (
"REDO Entity creation success",
"REDO Entity creation failed")
deferred_fog_creation = (
"Deferred Fog Entity successfully created",
"Deferred Fog Entity failed to be created")
deferred_fog_component = (
"Entity has a Deferred Fog component",
"Entity failed to find Deferred Fog component")
deferred_fog_disabled = (
"Deferred Fog component disabled",
"Deferred Fog component was not disabled")
postfx_layer_component = (
"Entity has a PostFX Layer component",
"Entity did not have an PostFX Layer component")
deferred_fog_enabled = (
"Deferred Fog component enabled",
"Deferred Fog component was not enabled")
enable_deferred_fog_parameter_enabled = (
"Enable Deferred Fog parameter enabled",
"Enable Deferred Fog parameter was not enabled")
enter_game_mode = (
"Entered game mode",
"Failed to enter game mode")
exit_game_mode = (
"Exited game mode",
"Couldn't exit game mode")
is_visible = (
"Entity is visible",
"Entity was not visible")
is_hidden = (
"Entity is hidden",
"Entity was not hidden")
entity_deleted = (
"Entity deleted",
"Entity was not deleted")
deletion_undo = (
"UNDO deletion success",
"UNDO deletion failed")
deletion_redo = (
"REDO deletion success",
"REDO deletion failed")
def AtomEditorComponents_DeferredFog_AddedToEntity():
"""
Summary:
Tests the Deferred Fog component can be added to an 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, hidden/shown, deleted, and has accurate required components.
Creation and deletion undo/redo should also work.
Test Steps:
1) Create an Deferred Fog entity with no components.
2) Add Deferred Fog component to Deferred Fog entity.
3) UNDO the entity creation and component addition.
4) REDO the entity creation and component addition.
5) Verify Deferred Fog component not enabled.
6) Add PostFX Layer component since it is required by the Deferred Fog component.
7) Verify Deferred Fog component is enabled.
8) Enable the "Enable Deferred Fog" parameter.
9) Enter/Exit game mode.
10) Test IsHidden.
11) Test IsVisible.
12) Delete Deferred Fog entity.
13) UNDO deletion.
14) REDO deletion.
15) Look for errors.
:return: None
"""
import azlmbr.legacy.general as general
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
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("Graphics", "base_empty")
# Test steps begin.
# 1. Create an Deferred Fog entity with no components.
deferred_fog_entity = EditorEntity.create_editor_entity(AtomComponentProperties.deferred_fog())
Report.critical_result(Tests.deferred_fog_creation, deferred_fog_entity.exists())
# 2. Add Deferred Fog component to Deferred Fog entity.
deferred_fog_component = deferred_fog_entity.add_component(
AtomComponentProperties.deferred_fog())
Report.critical_result(
Tests.deferred_fog_component,
deferred_fog_entity.has_component(AtomComponentProperties.deferred_fog()))
# 3. UNDO the entity creation and component addition.
# -> UNDO component addition.
general.undo()
# -> UNDO naming entity.
general.undo()
# -> UNDO selecting entity.
general.undo()
# -> UNDO entity creation.
general.undo()
general.idle_wait_frames(1)
Report.result(Tests.creation_undo, not deferred_fog_entity.exists())
# 4. REDO the entity creation and component addition.
# -> REDO entity creation.
general.redo()
# -> REDO selecting entity.
general.redo()
# -> REDO naming entity.
general.redo()
# -> REDO component addition.
general.redo()
general.idle_wait_frames(1)
Report.result(Tests.creation_redo, deferred_fog_entity.exists())
# 5. Verify Deferred Fog component not enabled.
Report.result(Tests.deferred_fog_disabled, not deferred_fog_component.is_enabled())
# 6. Add PostFX Layer component since it is required by the Deferred Fog component.
deferred_fog_entity.add_component(AtomComponentProperties.postfx_layer())
Report.result(
Tests.postfx_layer_component,
deferred_fog_entity.has_component(AtomComponentProperties.postfx_layer()))
# 7. Verify Deferred Fog component is enabled.
Report.result(Tests.deferred_fog_enabled, deferred_fog_component.is_enabled())
# 8. Enable the "Enable Deferred Fog" parameter.
deferred_fog_component.set_component_property_value(
AtomComponentProperties.deferred_fog('Enable Deferred Fog'), True)
Report.result(Tests.enable_deferred_fog_parameter_enabled,
deferred_fog_component.get_component_property_value(
AtomComponentProperties.deferred_fog('Enable Deferred Fog')) is True)
# 9. 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)
# 10. Test IsHidden.
deferred_fog_entity.set_visibility_state(False)
Report.result(Tests.is_hidden, deferred_fog_entity.is_hidden() is True)
# 11. Test IsVisible.
deferred_fog_entity.set_visibility_state(True)
general.idle_wait_frames(1)
Report.result(Tests.is_visible, deferred_fog_entity.is_visible() is True)
# 12. Delete Deferred Fog entity.
deferred_fog_entity.delete()
Report.result(Tests.entity_deleted, not deferred_fog_entity.exists())
# 13. UNDO deletion.
general.undo()
general.idle_wait_frames(1)
Report.result(Tests.deletion_undo, deferred_fog_entity.exists())
# 14. REDO deletion.
general.redo()
general.idle_wait_frames(1)
Report.result(Tests.deletion_redo, not deferred_fog_entity.exists())
# 15. 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(AtomEditorComponents_DeferredFog_AddedToEntity)
@@ -5,61 +5,28 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
# fmt: off
class Tests:
camera_creation = (
"Camera Entity successfully created",
"Camera Entity failed to be created")
camera_component_added = (
"Camera component was added to Camera entity",
"Camera component failed to be added to entity")
camera_component_check = (
"Entity has a Camera component",
"Entity failed to find Camera component")
camera_property_set = (
"DepthOfField Entity set Camera Entity",
"DepthOfField Entity could not set Camera Entity")
creation_undo = (
"UNDO Entity creation success",
"UNDO Entity creation failed")
creation_redo = (
"REDO Entity creation success",
"REDO Entity creation failed")
depth_of_field_creation = (
"DepthOfField Entity successfully created",
"DepthOfField Entity failed to be created")
depth_of_field_component = (
"Entity has a DepthOfField component",
"Entity failed to find DepthOfField component")
depth_of_field_disabled = (
"DepthOfField component disabled",
"DepthOfField component was not disabled.")
post_fx_component = (
"Entity has a Post FX Layer component",
"Entity did not have a Post FX Layer component")
depth_of_field_enabled = (
"DepthOfField component enabled",
"DepthOfField component was not enabled.")
enter_game_mode = (
"Entered game mode",
"Failed to enter game mode")
exit_game_mode = (
"Exited game mode",
"Couldn't exit game mode")
is_visible = (
"Entity is visible",
"Entity was not visible")
is_hidden = (
"Entity is hidden",
"Entity was not hidden")
entity_deleted = (
"Entity deleted",
"Entity was not deleted")
deletion_undo = (
"UNDO deletion success",
"UNDO deletion failed")
deletion_redo = (
"REDO deletion success",
"REDO deletion failed")
camera_creation = ("Camera Entity successfully created", "Camera Entity failed to be created")
camera_component_added = ("Camera component was added to Camera entity", "Camera component failed to be added to entity")
camera_component_check = ("Entity has a Camera component", "Entity failed to find Camera component")
camera_property_set = ("DepthOfField Entity set Camera Entity", "DepthOfField Entity could not set Camera Entity")
creation_undo = ("UNDO Entity creation success", "UNDO Entity creation failed")
creation_redo = ("REDO Entity creation success", "REDO Entity creation failed")
depth_of_field_creation = ("DepthOfField Entity successfully created", "DepthOfField Entity failed to be created")
depth_of_field_component = ("Entity has a DepthOfField component", "Entity failed to find DepthOfField component")
depth_of_field_disabled = ("DepthOfField component disabled", "DepthOfField component was not disabled.")
post_fx_component = ("Entity has a Post FX Layer component", "Entity did not have a Post FX Layer component")
depth_of_field_enabled = ("DepthOfField component enabled", "DepthOfField component was not enabled.")
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
is_visible = ("Entity is visible", "Entity was not visible")
is_hidden = ("Entity is hidden", "Entity was not hidden")
entity_deleted = ("Entity deleted", "Entity was not deleted")
deletion_undo = ("UNDO deletion success", "UNDO deletion failed")
deletion_redo = ("REDO deletion success", "REDO deletion failed")
no_error_occurred = ("No errors detected", "Errors were detected")
# fmt: on
def AtomEditorComponents_DepthOfField_AddedToEntity():
@@ -92,32 +59,33 @@ def AtomEditorComponents_DepthOfField_AddedToEntity():
14) Delete DepthOfField entity.
15) UNDO deletion.
16) REDO deletion.
17) Look for errors and asserts.
17) Look for errors.
:return: None
"""
import azlmbr.legacy.general as general
import azlmbr.math as math
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
from editor_python_test_tools.utils import Report, Tracer, TestHelper as helper
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("Graphics", "base_empty")
helper.init_idle()
helper.open_level("", "Base")
# Test steps begin.
# 1. Create a DepthOfField entity with no components.
depth_of_field_entity = EditorEntity.create_editor_entity(AtomComponentProperties.depth_of_field())
depth_of_field_name = "DepthOfField"
depth_of_field_entity = EditorEntity.create_editor_entity_at(
math.Vector3(512.0, 512.0, 34.0), depth_of_field_name)
Report.critical_result(Tests.depth_of_field_creation, depth_of_field_entity.exists())
# 2. Add a DepthOfField component to DepthOfField entity.
depth_of_field_component = depth_of_field_entity.add_component(AtomComponentProperties.depth_of_field())
Report.critical_result(Tests.depth_of_field_component,
depth_of_field_entity.has_component(AtomComponentProperties.depth_of_field()))
depth_of_field_component = depth_of_field_entity.add_component(depth_of_field_name)
Report.critical_result(Tests.depth_of_field_component, depth_of_field_entity.has_component(depth_of_field_name))
# 3. UNDO the entity creation and component addition.
# -> UNDO component addition.
@@ -147,16 +115,17 @@ def AtomEditorComponents_DepthOfField_AddedToEntity():
Report.result(Tests.depth_of_field_disabled, not depth_of_field_component.is_enabled())
# 6. Add Post FX Layer component since it is required by the DepthOfField component.
depth_of_field_entity.add_component(AtomComponentProperties.postfx_layer())
Report.result(Tests.post_fx_component, depth_of_field_entity.has_component(AtomComponentProperties.postfx_layer()))
post_fx_layer = "PostFX Layer"
depth_of_field_entity.add_component(post_fx_layer)
Report.result(Tests.post_fx_component, depth_of_field_entity.has_component(post_fx_layer))
# 7. Verify DepthOfField component is enabled.
Report.result(Tests.depth_of_field_enabled, depth_of_field_component.is_enabled())
# 8. Enter/Exit game mode.
TestHelper.enter_game_mode(Tests.enter_game_mode)
helper.enter_game_mode(Tests.enter_game_mode)
general.idle_wait_frames(1)
TestHelper.exit_game_mode(Tests.exit_game_mode)
helper.exit_game_mode(Tests.exit_game_mode)
# 9. Test IsHidden.
depth_of_field_entity.set_visibility_state(False)
@@ -168,20 +137,19 @@ def AtomEditorComponents_DepthOfField_AddedToEntity():
Report.result(Tests.is_visible, depth_of_field_entity.is_visible() is True)
# 11. Add Camera entity.
camera_entity = EditorEntity.create_editor_entity(AtomComponentProperties.camera())
camera_name = "Camera"
camera_entity = EditorEntity.create_editor_entity_at(math.Vector3(512.0, 512.0, 34.0), camera_name)
Report.result(Tests.camera_creation, camera_entity.exists())
# 12. Add Camera component to Camera entity.
camera_entity.add_component(AtomComponentProperties.camera())
Report.result(Tests.camera_component_added, camera_entity.has_component(AtomComponentProperties.camera()))
camera_entity.add_component(camera_name)
Report.result(Tests.camera_component_added, camera_entity.has_component(camera_name))
# 13. Set the DepthOfField components's Camera Entity to the newly created Camera entity.
depth_of_field_component.set_component_property_value(
AtomComponentProperties.depth_of_field('Camera Entity'), camera_entity.id)
Report.result(
Tests.camera_property_set,
camera_entity.id == depth_of_field_component.get_component_property_value(
AtomComponentProperties.depth_of_field('Camera Entity')))
depth_of_field_camera_property_path = "Controller|Configuration|Camera Entity"
depth_of_field_component.set_component_property_value(depth_of_field_camera_property_path, camera_entity.id)
camera_entity_set = depth_of_field_component.get_component_property_value(depth_of_field_camera_property_path)
Report.result(Tests.camera_property_set, camera_entity.id == camera_entity_set)
# 14. Delete DepthOfField entity.
depth_of_field_entity.delete()
@@ -189,20 +157,15 @@ def AtomEditorComponents_DepthOfField_AddedToEntity():
# 15. UNDO deletion.
general.undo()
general.idle_wait_frames(1)
Report.result(Tests.deletion_undo, depth_of_field_entity.exists())
# 16. REDO deletion.
general.redo()
general.idle_wait_frames(1)
Report.result(Tests.deletion_redo, not depth_of_field_entity.exists())
# 17. 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}")
# 17. Look for errors.
helper.wait_for_condition(lambda: error_tracer.has_errors, 1.0)
Report.result(Tests.no_error_occurred, not error_tracer.has_errors)
if __name__ == "__main__":
@@ -1,189 +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
"""
class Tests:
creation_undo = (
"UNDO Entity creation success",
"UNDO Entity creation failed")
creation_redo = (
"REDO Entity creation success",
"REDO Entity creation failed")
diffuse_probe_grid_creation = (
"Diffuse Probe Grid Entity successfully created",
"Diffuse Probe Grid Entity failed to be created")
diffuse_probe_grid_component = (
"Entity has a Diffuse Probe Grid component",
"Entity failed to find Diffuse Probe Grid component")
diffuse_probe_grid_disabled = (
"Diffuse Probe Grid component disabled",
"Diffuse Probe Grid component was not disabled")
diffuse_probe_grid_enabled = (
"Diffuse Probe Grid component enabled",
"Diffuse Probe Grid component was not enabled")
enter_game_mode = (
"Entered game mode",
"Failed to enter game mode")
exit_game_mode = (
"Exited game mode",
"Couldn't exit game mode")
is_visible = (
"Entity is visible",
"Entity was not visible")
is_hidden = (
"Entity is hidden",
"Entity was not hidden")
entity_deleted = (
"Entity deleted",
"Entity was not deleted")
deletion_undo = (
"UNDO deletion success",
"UNDO deletion failed")
deletion_redo = (
"REDO deletion success",
"REDO deletion failed")
def AtomEditorComponents_DiffuseProbeGrid_AddedToEntity():
"""
Summary:
Tests the Diffuse Probe Grid component can be added to an 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, hidden/shown, deleted, and has accurate required components.
Creation and deletion undo/redo should also work.
Test Steps:
1) Create a Diffuse Probe Grid entity with no components.
2) Add a Diffuse Probe Grid component to Diffuse Probe Grid entity.
3) UNDO the entity creation and component addition.
4) REDO the entity creation and component addition.
5) Verify Diffuse Probe Grid component not enabled.
6) Add Shape component since it is required by the Diffuse Probe Grid component.
7) Verify Diffuse Probe Grid component is enabled.
8) Enter/Exit game mode.
9) Test IsHidden.
10) Test IsVisible.
11) Delete Diffuse Probe Grid entity.
12) UNDO deletion.
13) REDO deletion.
14) Look for errors.
:return: None
"""
import azlmbr.legacy.general as general
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
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("Graphics", "base_empty")
# Test steps begin.
# 1. Create a Diffuse Probe Grid entity with no components.
diffuse_probe_grid_entity = EditorEntity.create_editor_entity(AtomComponentProperties.diffuse_probe_grid())
Report.critical_result(Tests.diffuse_probe_grid_creation, diffuse_probe_grid_entity.exists())
# 2. Add a Diffuse Probe Grid component to Diffuse Probe Grid entity.
diffuse_probe_grid_component = diffuse_probe_grid_entity.add_component(
AtomComponentProperties.diffuse_probe_grid())
Report.critical_result(
Tests.diffuse_probe_grid_component,
diffuse_probe_grid_entity.has_component(AtomComponentProperties.diffuse_probe_grid()))
# 3. UNDO the entity creation and component addition.
# -> UNDO component addition.
general.undo()
# -> UNDO naming entity.
general.undo()
# -> UNDO selecting entity.
general.undo()
# -> UNDO entity creation.
general.undo()
general.idle_wait_frames(1)
Report.result(Tests.creation_undo, not diffuse_probe_grid_entity.exists())
# 4. REDO the entity creation and component addition.
# -> REDO entity creation.
general.redo()
# -> REDO selecting entity.
general.redo()
# -> REDO naming entity.
general.redo()
# -> REDO component addition.
general.redo()
general.idle_wait_frames(1)
Report.result(Tests.creation_redo, diffuse_probe_grid_entity.exists())
# 5. Verify Diffuse Probe Grid component not enabled.
Report.result(Tests.diffuse_probe_grid_disabled, not diffuse_probe_grid_component.is_enabled())
# 6. Add Shape component since it is required by the Diffuse Probe Grid component.
for shape in AtomComponentProperties.diffuse_probe_grid('shapes'):
diffuse_probe_grid_entity.add_component(shape)
test_shape = (
f"Entity has a {shape} component",
f"Entity did not have a {shape} component")
Report.result(test_shape, diffuse_probe_grid_entity.has_component(shape))
# 7. Check if required shape allows Diffuse Probe Grid to be enabled
Report.result(Tests.diffuse_probe_grid_enabled, diffuse_probe_grid_component.is_enabled())
# Undo to remove each added shape except the last one and verify Diffuse Probe Grid is not enabled.
if not (shape == AtomComponentProperties.diffuse_probe_grid('shapes')[-1]):
general.undo()
TestHelper.wait_for_condition(lambda: not diffuse_probe_grid_entity.has_component(shape), 1.0)
Report.result(Tests.diffuse_probe_grid_disabled, not diffuse_probe_grid_component.is_enabled())
# 8. 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)
# 9. Test IsHidden.
diffuse_probe_grid_entity.set_visibility_state(False)
Report.result(Tests.is_hidden, diffuse_probe_grid_entity.is_hidden() is True)
# 10. Test IsVisible.
diffuse_probe_grid_entity.set_visibility_state(True)
general.idle_wait_frames(1)
Report.result(Tests.is_visible, diffuse_probe_grid_entity.is_visible() is True)
# 11. Delete Diffuse Probe Grid entity.
diffuse_probe_grid_entity.delete()
Report.result(Tests.entity_deleted, not diffuse_probe_grid_entity.exists())
# 12. UNDO deletion.
general.undo()
general.idle_wait_frames(1)
Report.result(Tests.deletion_undo, diffuse_probe_grid_entity.exists())
# 13. REDO deletion.
general.redo()
general.idle_wait_frames(1)
Report.result(Tests.deletion_redo, not diffuse_probe_grid_entity.exists())
# 14. Look for errors or 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(AtomEditorComponents_DiffuseProbeGrid_AddedToEntity)
@@ -5,52 +5,25 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
# fmt: off
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")
creation_redo = (
"REDO Entity creation success",
"REDO Entity creation failed")
directional_light_creation = (
"Directional Light Entity successfully created",
"Directional Light Entity failed to be created")
directional_light_component = (
"Entity has a Directional Light component",
"Entity failed to find Directional Light component")
shadow_camera_check = (
"Directional Light component Shadow camera set",
"Directional Light component Shadow camera was not set")
enter_game_mode = (
"Entered game mode",
"Failed to enter game mode")
exit_game_mode = (
"Exited game mode",
"Couldn't exit game mode")
is_visible = (
"Entity is visible",
"Entity was not visible")
is_hidden = (
"Entity is hidden",
"Entity was not hidden")
entity_deleted = (
"Entity deleted",
"Entity was not deleted")
deletion_undo = (
"UNDO deletion success",
"UNDO deletion failed")
deletion_redo = (
"REDO deletion success",
"REDO deletion failed")
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")
creation_redo = ("REDO Entity creation success", "REDO Entity creation failed")
directional_light_creation = ("Directional Light Entity successfully created", "Directional Light Entity failed to be created")
directional_light_component = ("Entity has a Directional Light component", "Entity failed to find Directional Light component")
shadow_camera_check = ("Directional Light component Shadow camera set", "Directional Light component Shadow camera was not set")
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
is_visible = ("Entity is visible", "Entity was not visible")
is_hidden = ("Entity is hidden", "Entity was not hidden")
entity_deleted = ("Entity deleted", "Entity was not deleted")
deletion_undo = ("UNDO deletion success", "UNDO deletion failed")
deletion_redo = ("REDO deletion success", "REDO deletion failed")
no_error_occurred = ("No errors detected", "Errors were detected")
# fmt: on
def AtomEditorComponents_DirectionalLight_AddedToEntity():
@@ -80,33 +53,34 @@ def AtomEditorComponents_DirectionalLight_AddedToEntity():
11) Delete Directional Light entity.
12) UNDO deletion.
13) REDO deletion.
14) Look for errors and asserts.
14) Look for errors.
:return: None
"""
import azlmbr.legacy.general as general
import azlmbr.math as math
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
from editor_python_test_tools.utils import Report, Tracer, TestHelper as helper
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("Graphics", "base_empty")
helper.init_idle()
helper.open_level("", "Base")
# Test steps begin.
# 1. Create a Directional Light entity with no components.
directional_light_entity = EditorEntity.create_editor_entity(AtomComponentProperties.directional_light())
directional_light_name = "Directional Light"
directional_light_entity = EditorEntity.create_editor_entity_at(
math.Vector3(512.0, 512.0, 34.0), directional_light_name)
Report.critical_result(Tests.directional_light_creation, directional_light_entity.exists())
# 2. Add Directional Light component to Directional Light entity.
directional_light_component = directional_light_entity.add_component(AtomComponentProperties.directional_light())
directional_light_component = directional_light_entity.add_component(directional_light_name)
Report.critical_result(
Tests.directional_light_component,
directional_light_entity.has_component(AtomComponentProperties.directional_light()))
Tests.directional_light_component, directional_light_entity.has_component(directional_light_name))
# 3. UNDO the entity creation and component addition.
# -> UNDO component addition.
@@ -133,9 +107,9 @@ def AtomEditorComponents_DirectionalLight_AddedToEntity():
Report.result(Tests.creation_redo, directional_light_entity.exists())
# 5. Enter/Exit game mode.
TestHelper.enter_game_mode(Tests.enter_game_mode)
helper.enter_game_mode(Tests.enter_game_mode)
general.idle_wait_frames(1)
TestHelper.exit_game_mode(Tests.exit_game_mode)
helper.exit_game_mode(Tests.exit_game_mode)
# 6. Test IsHidden.
directional_light_entity.set_visibility_state(False)
@@ -147,20 +121,19 @@ def AtomEditorComponents_DirectionalLight_AddedToEntity():
Report.result(Tests.is_visible, directional_light_entity.is_visible() is True)
# 8. Add Camera entity.
camera_entity = EditorEntity.create_editor_entity(AtomComponentProperties.camera())
camera_name = "Camera"
camera_entity = EditorEntity.create_editor_entity_at(math.Vector3(512.0, 512.0, 34.0), camera_name)
Report.result(Tests.camera_creation, camera_entity.exists())
# 9. Add Camera component to Camera entity.
camera_entity.add_component(AtomComponentProperties.camera())
Report.result(Tests.camera_component_added, camera_entity.has_component(AtomComponentProperties.camera()))
camera_entity.add_component(camera_name)
Report.result(Tests.camera_component_added, camera_entity.has_component(camera_name))
# 10. Set the Directional Light component property Shadow|Camera to the Camera entity.
directional_light_component.set_component_property_value(
AtomComponentProperties.directional_light('Camera'), camera_entity.id)
Report.result(
Tests.shadow_camera_check,
camera_entity.id == directional_light_component.get_component_property_value(
AtomComponentProperties.directional_light('Camera')))
shadow_camera_property_path = "Controller|Configuration|Shadow|Camera"
directional_light_component.set_component_property_value(shadow_camera_property_path, camera_entity.id)
shadow_camera_set = directional_light_component.get_component_property_value(shadow_camera_property_path)
Report.result(Tests.shadow_camera_check, camera_entity.id == shadow_camera_set)
# 11. Delete DirectionalLight entity.
directional_light_entity.delete()
@@ -168,20 +141,15 @@ def AtomEditorComponents_DirectionalLight_AddedToEntity():
# 12. UNDO deletion.
general.undo()
general.idle_wait_frames(1)
Report.result(Tests.deletion_undo, directional_light_entity.exists())
# 13. REDO deletion.
general.redo()
general.idle_wait_frames(1)
Report.result(Tests.deletion_redo, not directional_light_entity.exists())
# 14. 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}")
# 14. Look for errors.
helper.wait_for_condition(lambda: error_tracer.has_errors, 1.0)
Report.result(Tests.no_error_occurred, not error_tracer.has_errors)
if __name__ == "__main__":
@@ -5,47 +5,24 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
# fmt: off
class Tests:
creation_undo = (
"UNDO Entity creation success",
"UNDO Entity creation failed")
creation_redo = (
"REDO Entity creation success",
"REDO Entity creation failed")
display_mapper_creation = (
"Display Mapper Entity successfully created",
"Display Mapper Entity failed to be created")
display_mapper_component = (
"Entity has a Display Mapper component",
"Entity failed to find Display Mapper component")
enter_game_mode = (
"Entered game mode",
"Failed to enter game mode")
exit_game_mode = (
"Exited game mode",
"Couldn't exit game mode")
is_visible = (
"Entity is visible",
"Entity was not visible")
is_hidden = (
"Entity is hidden",
"Entity was not hidden")
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")
deletion_undo = (
"UNDO deletion success",
"UNDO deletion failed")
deletion_redo = (
"REDO deletion success",
"REDO deletion failed")
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")
creation_redo = ("REDO Entity creation success", "REDO Entity creation failed")
display_mapper_creation = ("Display Mapper Entity successfully created", "Display Mapper Entity failed to be created")
display_mapper_component = ("Entity has a Display Mapper component", "Entity failed to find Display Mapper component")
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
is_visible = ("Entity is visible", "Entity was not visible")
is_hidden = ("Entity is hidden", "Entity was not hidden")
entity_deleted = ("Entity deleted", "Entity was not deleted")
deletion_undo = ("UNDO deletion success", "UNDO deletion failed")
deletion_redo = ("REDO deletion success", "REDO deletion failed")
no_error_occurred = ("No errors detected", "Errors were detected")
# fmt: on
def AtomEditorComponents_DisplayMapper_AddedToEntity():
@@ -66,43 +43,39 @@ 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) 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.
5) Enter/Exit game mode.
6) Test IsHidden.
7) Test IsVisible.
8) Delete Display Mapper entity.
9) UNDO deletion.
10) REDO deletion.
11) Look for errors.
:return: None
"""
import os
import azlmbr.legacy.general as general
import azlmbr.math as math
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
from editor_python_test_tools.utils import Report, Tracer, TestHelper as helper
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("Graphics", "base_empty")
helper.init_idle()
helper.open_level("", "Base")
# Test steps begin.
# 1. Create a Display Mapper entity with no components.
display_mapper_entity = EditorEntity.create_editor_entity(AtomComponentProperties.display_mapper())
display_mapper = "Display Mapper"
display_mapper_entity = EditorEntity.create_editor_entity_at(
math.Vector3(512.0, 512.0, 34.0), f"{display_mapper}")
Report.critical_result(Tests.display_mapper_creation, display_mapper_entity.exists())
# 2. Add Display Mapper component to Display Mapper entity.
display_mapper_component = display_mapper_entity.add_component(AtomComponentProperties.display_mapper())
Report.critical_result(
Tests.display_mapper_component,
display_mapper_entity.has_component(AtomComponentProperties.display_mapper()))
display_mapper_entity.add_component(display_mapper)
Report.critical_result(Tests.display_mapper_component, display_mapper_entity.has_component(display_mapper))
# 3. UNDO the entity creation and component addition.
# -> UNDO component addition.
@@ -128,58 +101,35 @@ def AtomEditorComponents_DisplayMapper_AddedToEntity():
general.idle_wait_frames(1)
Report.result(Tests.creation_redo, display_mapper_entity.exists())
# 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(
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)
# 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)
# 5. Enter/Exit game mode.
helper.enter_game_mode(Tests.enter_game_mode)
general.idle_wait_frames(1)
TestHelper.exit_game_mode(Tests.exit_game_mode)
helper.exit_game_mode(Tests.exit_game_mode)
# 8. Test IsHidden.
# 6. Test IsHidden.
display_mapper_entity.set_visibility_state(False)
Report.result(Tests.is_hidden, display_mapper_entity.is_hidden() is True)
# 9. Test IsVisible.
# 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)
# 10. Delete Display Mapper entity.
# 8. Delete Display Mapper entity.
display_mapper_entity.delete()
Report.result(Tests.entity_deleted, not display_mapper_entity.exists())
# 11. UNDO deletion.
# 9. UNDO deletion.
general.undo()
general.idle_wait_frames(1)
Report.result(Tests.deletion_undo, display_mapper_entity.exists())
# 12. REDO deletion.
# 10. REDO deletion.
general.redo()
general.idle_wait_frames(1)
Report.result(Tests.deletion_redo, not display_mapper_entity.exists())
# 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}")
for assert_info in error_tracer.asserts:
Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}")
# 11. Look for errors.
helper.wait_for_condition(lambda: error_tracer.has_errors, 1.0)
Report.result(Tests.no_error_occurred, not error_tracer.has_errors)
if __name__ == "__main__":
@@ -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
"""
class Tests:
creation_undo = (
"UNDO Entity creation success",
"P0: UNDO Entity creation failed")
creation_redo = (
"REDO Entity creation success",
"P0: REDO Entity creation failed")
entity_reference_creation = (
"Entity Reference Entity successfully created",
"P0: Entity Reference Entity failed to be created")
entity_reference_component = (
"Entity has an Entity Reference component",
"P0: Entity failed to find Entity Reference component")
enter_game_mode = (
"Entered game mode",
"P0: Failed to enter game mode")
exit_game_mode = (
"Exited game mode",
"P0: Couldn't exit game mode")
is_visible = (
"Entity is visible",
"P0: Entity was not visible")
is_hidden = (
"Entity is hidden",
"P0: Entity was not hidden")
entity_deleted = (
"Entity deleted",
"P0: Entity was not deleted")
deletion_undo = (
"UNDO deletion success",
"P0: UNDO deletion failed")
deletion_redo = (
"REDO deletion success",
"P0: REDO deletion failed")
entity_id_references_is_container = (
"EntityIdReferences is a container property",
"P1: EntityIdReferences is NOT a container property")
container_append = (
"EntityIdReferences append succeeded",
"P1: EntityIdReferences append did not succeed")
container_add = (
"EntityIdReferences add succeeded",
"P1: EntityIdReferences add did not succeed")
container_update = (
"EntityIdReferences update succeeded",
"P1: EntityIdReferences update did not succeed")
container_remove = (
"EntityIdReferences remove succeeded",
"P1: EntityIdReferences remove did not succeed")
container_reset = (
"EntityIdReferences reset succeeded",
"P1: EntityIdReferences reset did not succeed")
entity_reference_component_removed = (
"Entity Reference component removed from entity",
"P1: Entity Reference component NOT removed from entity")
def AtomEditorComponents_EntityReference_AddedToEntity():
"""
Summary:
Tests the Entity Reference component can be added to an 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, hidden/shown, deleted, and has accurate required components.
Creation and deletion undo/redo should also work.
Test Steps:
1) Create an Entity Reference entity with no components.
2) Add Entity Reference component to Entity Reference entity.
3) UNDO the entity creation and component addition.
4) REDO the entity creation and component addition.
5) 'EntityIdReferences' is a container property
6) Append item to 'EntityIdReferences'
7) Add item to 'EntityIdReferences'
8) Update item in 'EntityIdReferences'
9) Remove item from 'EntityIdReferences'
10) Reset the container property then put one entity reference back for further tests
11) Remove component
12) UNDO component remove
13) Enter/Exit game mode.
14) Test IsHidden.
15) Test IsVisible.
16) Delete Entity Reference entity.
17) UNDO deletion.
18) REDO deletion.
19) Look for errors.
:return: None
"""
import azlmbr.legacy.general as general
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
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("Graphics", "base_empty")
# Test steps begin.
# 1. Create an Entity Reference entity with no components.
entity_reference_entity = EditorEntity.create_editor_entity(AtomComponentProperties.entity_reference())
Report.critical_result(Tests.entity_reference_creation, entity_reference_entity.exists())
# 2. Add Entity Reference component to Entity Reference entity.
entity_reference_component = entity_reference_entity.add_component(
AtomComponentProperties.entity_reference())
Report.critical_result(
Tests.entity_reference_component,
entity_reference_entity.has_component(AtomComponentProperties.entity_reference()))
# 3. UNDO the entity creation and component addition.
# -> UNDO component addition.
general.undo()
# -> UNDO naming entity.
general.undo()
# -> UNDO selecting entity.
general.undo()
# -> UNDO entity creation.
general.undo()
general.idle_wait_frames(1)
Report.result(Tests.creation_undo, not entity_reference_entity.exists())
# 4. REDO the entity creation and component addition.
# -> REDO entity creation.
general.redo()
# -> REDO selecting entity.
general.redo()
# -> REDO naming entity.
general.redo()
# -> REDO component addition.
general.redo()
general.idle_wait_frames(1)
Report.result(Tests.creation_redo, entity_reference_entity.exists())
# Entities for EntityIdReferences tests
test_1 = EditorEntity.create_editor_entity('test_1')
test_2 = EditorEntity.create_editor_entity('test_2')
test_3 = EditorEntity.create_editor_entity('test_3')
# 5. 'EntityIdReferences' is a container property
Report.result(
Tests.entity_id_references_is_container,
entity_reference_component.is_property_container(
AtomComponentProperties.entity_reference('EntityIdReferences')))
# 6. Append item to 'EntityIdReferences'
entity_reference_component.append_container_item(
AtomComponentProperties.entity_reference('EntityIdReferences'), test_1.id)
Report.result(
Tests.container_append,
entity_reference_component.get_container_item(
AtomComponentProperties.entity_reference('EntityIdReferences'), 0) == test_1.id)
# 7. Add item to 'EntityIdReferences'
entity_reference_component.add_container_item(
AtomComponentProperties.entity_reference('EntityIdReferences'), 1, test_1.id)
Report.result(
Tests.container_add,
entity_reference_component.get_container_count(
AtomComponentProperties.entity_reference('EntityIdReferences')) == 2)
# 8. Update item in 'EntityIdReferences'
entity_reference_component.update_container_item(
AtomComponentProperties.entity_reference('EntityIdReferences'), 1, test_2.id)
Report.result(
Tests.container_update,
entity_reference_component.get_container_item(
AtomComponentProperties.entity_reference('EntityIdReferences'), 1) == test_2.id)
# 9. Remove item from 'EntityIdReferences'
entity_reference_component.append_container_item(
AtomComponentProperties.entity_reference('EntityIdReferences'), test_3.id)
count_before = entity_reference_component.get_container_count(
AtomComponentProperties.entity_reference('EntityIdReferences'))
entity_reference_component.remove_container_item(
AtomComponentProperties.entity_reference('EntityIdReferences'), 1)
count_after = entity_reference_component.get_container_count(
AtomComponentProperties.entity_reference('EntityIdReferences'))
Report.result(
Tests.container_remove,
((count_before == 3) and (count_after == 2) and
(entity_reference_component.get_container_item(
AtomComponentProperties.entity_reference('EntityIdReferences'), 1) == test_3.id)))
# 10. Reset the container property then put one entity reference back for further tests
entity_reference_component.reset_container(AtomComponentProperties.entity_reference('EntityIdReferences'))
general.idle_wait_frames(1)
Report.result(
Tests.container_reset,
entity_reference_component.get_container_count(
AtomComponentProperties.entity_reference('EntityIdReferences')) == 0)
entity_reference_component.append_container_item(
AtomComponentProperties.entity_reference('EntityIdReferences'), test_1.id)
# 11. Remove component
entity_reference_entity.remove_component(AtomComponentProperties.entity_reference())
general.idle_wait_frames(1)
Report.result(Tests.entity_reference_component_removed, not entity_reference_entity.has_component(
AtomComponentProperties.entity_reference()))
# 12. UNDO component remove
general.undo()
general.idle_wait_frames(1)
Report.result(Tests.entity_reference_component, entity_reference_entity.has_component(
AtomComponentProperties.entity_reference()))
# 13. 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)
# 14. Test IsHidden.
entity_reference_entity.set_visibility_state(False)
Report.result(Tests.is_hidden, entity_reference_entity.is_hidden() is True)
# 15. Test IsVisible.
entity_reference_entity.set_visibility_state(True)
general.idle_wait_frames(1)
Report.result(Tests.is_visible, entity_reference_entity.is_visible() is True)
# 16. Delete Entity Reference entity.
entity_reference_entity.delete()
Report.result(Tests.entity_deleted, not entity_reference_entity.exists())
# 17. UNDO deletion.
general.undo()
general.idle_wait_frames(1)
Report.result(Tests.deletion_undo, entity_reference_entity.exists())
# 18. REDO deletion.
general.redo()
general.idle_wait_frames(1)
Report.result(Tests.deletion_redo, not entity_reference_entity.exists())
# 19. 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(AtomEditorComponents_EntityReference_AddedToEntity)
@@ -5,58 +5,25 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
# fmt: off
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")
creation_redo = (
"REDO Entity creation success",
"REDO Entity creation failed")
exposure_control_creation = (
"ExposureControl Entity successfully created",
"ExposureControl Entity failed to be created")
exposure_control_component = (
"Entity has a Exposure Control component",
"Entity failed to find Exposure Control component")
exposure_control_disabled = (
"DepthOfField component disabled",
"DepthOfField component was not disabled.")
post_fx_component = (
"Entity has a Post FX Layer component",
"Entity did not have a Post FX Layer component")
exposure_control_enabled = (
"DepthOfField component enabled",
"DepthOfField component was not enabled.")
enter_game_mode = (
"Entered game mode",
"Failed to enter game mode")
exit_game_mode = (
"Exited game mode",
"Couldn't exit game mode")
is_visible = (
"Entity is visible",
"Entity was not visible")
is_hidden = (
"Entity is hidden",
"Entity was not hidden")
entity_deleted = (
"Entity deleted",
"Entity was not deleted")
deletion_undo = (
"UNDO deletion success",
"UNDO deletion failed")
deletion_redo = (
"REDO deletion success",
"REDO deletion failed")
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")
creation_redo = ("REDO Entity creation success", "REDO Entity creation failed")
exposure_control_creation = ("ExposureControl Entity successfully created", "ExposureControl Entity failed to be created")
exposure_control_component = ("Entity has a Exposure Control component", "Entity failed to find Exposure Control component")
post_fx_component = ("Entity has a Post FX Layer component", "Entity did not have a Post FX Layer component")
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
is_visible = ("Entity is visible", "Entity was not visible")
is_hidden = ("Entity is hidden", "Entity was not hidden")
entity_deleted = ("Entity deleted", "Entity was not deleted")
deletion_undo = ("UNDO deletion success", "UNDO deletion failed")
deletion_redo = ("REDO deletion success", "REDO deletion failed")
no_error_occurred = ("No errors detected", "Errors were detected")
# fmt: on
def AtomEditorComponents_ExposureControl_AddedToEntity():
@@ -77,42 +44,41 @@ def AtomEditorComponents_ExposureControl_AddedToEntity():
2) Add Exposure Control component to Exposure Control entity.
3) UNDO the entity creation and component addition.
4) REDO the entity creation and component addition.
5) Verify Exposure Control component not enabled.
6) Add Post FX Layer component since it is required by the Exposure Control component.
7) Verify Exposure Control component is enabled.
8) Enter/Exit game mode.
9) Test IsHidden.
10) Test IsVisible.
11) Delete Exposure Control entity.
12) UNDO deletion.
13) REDO deletion.
14) Look for errors and asserts.
5) Enter/Exit game mode.
6) Test IsHidden.
7) Test IsVisible.
8) Add Post FX Layer component.
9) Delete Exposure Control entity.
10) UNDO deletion.
11) REDO deletion.
12) Look for errors.
:return: None
"""
import azlmbr.legacy.general as general
import azlmbr.math as math
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
from editor_python_test_tools.utils import Report, Tracer, TestHelper as helper
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("Graphics", "base_empty")
helper.init_idle()
helper.open_level("", "Base")
# Test steps begin.
# 1. Creation of Exposure Control entity with no components.
exposure_control_entity = EditorEntity.create_editor_entity(AtomComponentProperties.exposure_control())
exposure_control_name = "Exposure Control"
exposure_control_entity = EditorEntity.create_editor_entity_at(
math.Vector3(512.0, 512.0, 34.0), f"{exposure_control_name}")
Report.critical_result(Tests.exposure_control_creation, exposure_control_entity.exists())
# 2. Add Exposure Control component to Exposure Control entity.
exposure_control_component = exposure_control_entity.add_component(AtomComponentProperties.exposure_control())
exposure_control_entity.add_component(exposure_control_name)
Report.critical_result(
Tests.exposure_control_component,
exposure_control_entity.has_component(AtomComponentProperties.exposure_control()))
Tests.exposure_control_component, exposure_control_entity.has_component(exposure_control_name))
# 3. UNDO the entity creation and component addition.
# -> UNDO component addition.
@@ -138,51 +104,40 @@ def AtomEditorComponents_ExposureControl_AddedToEntity():
general.idle_wait_frames(1)
Report.result(Tests.creation_redo, exposure_control_entity.exists())
# 5. Verify Exposure Control component not enabled.
Report.result(Tests.exposure_control_disabled, not exposure_control_component.is_enabled())
# 6. Add Post FX Layer component since it is required by the Exposure Control component.
exposure_control_entity.add_component(AtomComponentProperties.postfx_layer())
Report.result(Tests.post_fx_component,
exposure_control_entity.has_component(AtomComponentProperties.postfx_layer()))
# 7. Verify Exposure Control component is enabled.
Report.result(Tests.exposure_control_enabled, exposure_control_component.is_enabled())
# 8. Enter/Exit game mode.
TestHelper.enter_game_mode(Tests.enter_game_mode)
# 5. Enter/Exit game mode.
helper.enter_game_mode(Tests.enter_game_mode)
general.idle_wait_frames(1)
TestHelper.exit_game_mode(Tests.exit_game_mode)
helper.exit_game_mode(Tests.exit_game_mode)
# 9. Test IsHidden.
# 6. Test IsHidden.
exposure_control_entity.set_visibility_state(False)
Report.result(Tests.is_hidden, exposure_control_entity.is_hidden() is True)
# 10. Test IsVisible.
# 7. Test IsVisible.
exposure_control_entity.set_visibility_state(True)
general.idle_wait_frames(1)
Report.result(Tests.is_visible, exposure_control_entity.is_visible() is True)
# 11. Delete ExposureControl entity.
# 8. Add Post FX Layer component.
post_fx_layer_name = "PostFX Layer"
exposure_control_entity.add_component(post_fx_layer_name)
Report.result(Tests.post_fx_component, exposure_control_entity.has_component(post_fx_layer_name))
# 9. Delete ExposureControl entity.
exposure_control_entity.delete()
Report.result(Tests.entity_deleted, not exposure_control_entity.exists())
# 12. UNDO deletion.
# 10. UNDO deletion.
general.undo()
general.idle_wait_frames(1)
Report.result(Tests.deletion_undo, exposure_control_entity.exists())
# 13. REDO deletion.
# 11. REDO deletion.
general.redo()
general.idle_wait_frames(1)
Report.result(Tests.deletion_redo, not exposure_control_entity.exists())
# 14. 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}")
# 12. Look for errors.
helper.wait_for_condition(lambda: error_tracer.has_errors, 1.0)
Report.result(Tests.no_error_occurred, not error_tracer.has_errors)
if __name__ == "__main__":
@@ -5,55 +5,26 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
# fmt: off
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")
creation_redo = (
"REDO Entity creation success",
"REDO Entity creation failed")
global_skylight_creation = (
"Global Skylight (IBL) Entity successfully created",
"Global Skylight (IBL) Entity failed to be created")
global_skylight_component = (
"Entity has a Global Skylight (IBL) component",
"Entity failed to find Global Skylight (IBL) component")
diffuse_image_set = (
"Entity has the Diffuse Image set",
"Entity did not the Diffuse Image set")
specular_image_set = (
"Entity has the Specular Image set",
"Entity did not the Specular Image set")
enter_game_mode = (
"Entered game mode",
"Failed to enter game mode")
exit_game_mode = (
"Exited game mode",
"Couldn't exit game mode")
is_visible = (
"Entity is visible",
"Entity was not visible")
is_hidden = (
"Entity is hidden",
"Entity was not hidden")
entity_deleted = (
"Entity deleted",
"Entity was not deleted")
deletion_undo = (
"UNDO deletion success",
"UNDO deletion failed")
deletion_redo = (
"REDO deletion success",
"REDO deletion failed")
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")
creation_redo = ("REDO Entity creation success", "REDO Entity creation failed")
global_skylight_creation = ("Global Skylight (IBL) Entity successfully created", "Global Skylight (IBL) Entity failed to be created")
global_skylight_component = ("Entity has a Global Skylight (IBL) component", "Entity failed to find Global Skylight (IBL) component")
diffuse_image_set = ("Entity has the Diffuse Image set", "Entity did not the Diffuse Image set")
specular_image_set = ("Entity has the Specular Image set", "Entity did not the Specular Image set")
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
is_visible = ("Entity is visible", "Entity was not visible")
is_hidden = ("Entity is hidden", "Entity was not hidden")
entity_deleted = ("Entity deleted", "Entity was not deleted")
deletion_undo = ("UNDO deletion success", "UNDO deletion failed")
deletion_redo = ("REDO deletion success", "REDO deletion failed")
no_error_occurred = ("No errors detected", "Errors were detected")
# fmt: on
def AtomEditorComponents_GlobalSkylightIBL_AddedToEntity():
@@ -89,28 +60,29 @@ def AtomEditorComponents_GlobalSkylightIBL_AddedToEntity():
import os
import azlmbr.legacy.general as general
import azlmbr.math as math
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
from editor_python_test_tools.utils import Report, Tracer, TestHelper as helper
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("Graphics", "base_empty")
helper.init_idle()
helper.open_level("", "Base")
# Test steps begin.
# 1. Create a Global Skylight (IBL) entity with no components.
global_skylight_entity = EditorEntity.create_editor_entity(AtomComponentProperties.global_skylight())
global_skylight_name = "Global Skylight (IBL)"
global_skylight_entity = EditorEntity.create_editor_entity_at(
math.Vector3(512.0, 512.0, 34.0), global_skylight_name)
Report.critical_result(Tests.global_skylight_creation, global_skylight_entity.exists())
# 2. Add Global Skylight (IBL) component to Global Skylight (IBL) entity.
global_skylight_component = global_skylight_entity.add_component(AtomComponentProperties.global_skylight())
global_skylight_component = global_skylight_entity.add_component(global_skylight_name)
Report.critical_result(
Tests.global_skylight_component,
global_skylight_entity.has_component(AtomComponentProperties.global_skylight()))
Tests.global_skylight_component, global_skylight_entity.has_component(global_skylight_name))
# 3. UNDO the entity creation and component addition.
# -> UNDO component addition.
@@ -137,9 +109,9 @@ def AtomEditorComponents_GlobalSkylightIBL_AddedToEntity():
Report.result(Tests.creation_redo, global_skylight_entity.exists())
# 5. Enter/Exit game mode.
TestHelper.enter_game_mode(Tests.enter_game_mode)
helper.enter_game_mode(Tests.enter_game_mode)
general.idle_wait_frames(1)
TestHelper.exit_game_mode(Tests.exit_game_mode)
helper.exit_game_mode(Tests.exit_game_mode)
# 6. Test IsHidden.
global_skylight_entity.set_visibility_state(False)
@@ -154,21 +126,19 @@ def AtomEditorComponents_GlobalSkylightIBL_AddedToEntity():
diffuse_image_path = os.path.join("LightingPresets", "default_iblskyboxcm.exr.streamingimage")
diffuse_image_asset = Asset.find_asset_by_path(diffuse_image_path, False)
global_skylight_component.set_component_property_value(
AtomComponentProperties.global_skylight('Diffuse Image'), diffuse_image_asset.id)
Report.result(
Tests.diffuse_image_set,
diffuse_image_asset.id == global_skylight_component.get_component_property_value(
AtomComponentProperties.global_skylight('Diffuse Image')))
global_skylight_diffuse_image_property, diffuse_image_asset.id)
diffuse_image_set = global_skylight_component.get_component_property_value(
global_skylight_diffuse_image_property)
Report.result(Tests.diffuse_image_set, diffuse_image_set == diffuse_image_asset.id)
# 9. Set the Specular Image asset on the Global Light (IBL) entity.
specular_image_path = os.path.join("LightingPresets", "default_iblskyboxcm.exr.streamingimage")
specular_image_asset = Asset.find_asset_by_path(specular_image_path, False)
global_skylight_component.set_component_property_value(
AtomComponentProperties.global_skylight('Specular Image'), specular_image_asset.id)
Report.result(
Tests.specular_image_set,
specular_image_asset.id == global_skylight_component.get_component_property_value(
AtomComponentProperties.global_skylight('Specular Image')))
global_skylight_specular_image_property, specular_image_asset.id)
specular_image_added = global_skylight_component.get_component_property_value(
global_skylight_specular_image_property)
Report.result(Tests.specular_image_set, specular_image_added == specular_image_asset.id)
# 10. Delete Global Skylight (IBL) entity.
global_skylight_entity.delete()
@@ -176,20 +146,15 @@ def AtomEditorComponents_GlobalSkylightIBL_AddedToEntity():
# 11. UNDO deletion.
general.undo()
general.idle_wait_frames(1)
Report.result(Tests.deletion_undo, global_skylight_entity.exists())
# 12. REDO deletion.
general.redo()
general.idle_wait_frames(1)
Report.result(Tests.deletion_redo, not global_skylight_entity.exists())
# 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}")
for assert_info in error_tracer.asserts:
Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}")
# 13. Look for errors.
helper.wait_for_condition(lambda: error_tracer.has_errors, 1.0)
Report.result(Tests.no_error_occurred, not error_tracer.has_errors)
if __name__ == "__main__":
@@ -1,171 +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
"""
class Tests:
creation_undo = (
"UNDO Entity creation success",
"UNDO Entity creation failed")
creation_redo = (
"REDO Entity creation success",
"REDO Entity creation failed")
grid_entity_creation = (
"Grid Entity successfully created",
"Grid Entity failed to be created")
grid_component_added = (
"Entity has a Grid component",
"Entity failed to find Grid component")
grid_size = (
"Grid Size value set successfully",
"Grid Size value 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")
is_visible = (
"Entity is visible",
"Entity was not visible")
is_hidden = (
"Entity is hidden",
"Entity was not hidden")
entity_deleted = (
"Entity deleted",
"Entity was not deleted")
deletion_undo = (
"UNDO deletion success",
"UNDO deletion failed")
deletion_redo = (
"REDO deletion success",
"REDO deletion failed")
def AtomEditorComponents_Grid_AddedToEntity():
"""
Summary:
Tests the Grid component can be added to an 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, hidden/shown, deleted, and has accurate required components.
Creation and deletion undo/redo should also work.
Test Steps:
1) Create a Grid entity with no components.
2) Add a Grid component to Grid entity.
3) UNDO the entity creation and component addition.
4) REDO the entity creation and component addition.
5) Grid Size changed.
6) Enter/Exit game mode.
7) Test IsHidden.
8) Test IsVisible.
9) Delete Grid entity.
10) UNDO deletion.
11) REDO deletion.
12) Look for errors.
:return: None
"""
import os
import azlmbr.legacy.general as general
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
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("Graphics", "base_empty")
# Test steps begin.
# 1. Create a Grid entity with no components.
grid_entity = EditorEntity.create_editor_entity(AtomComponentProperties.grid())
Report.critical_result(Tests.grid_entity_creation, grid_entity.exists())
# 2. Add a Grid component to Grid entity.
grid_component = grid_entity.add_component(AtomComponentProperties.grid())
Report.critical_result(
Tests.grid_component_added,
grid_entity.has_component(AtomComponentProperties.grid()))
# 3. UNDO the entity creation and component addition.
# -> UNDO component addition.
general.undo()
# -> UNDO naming entity.
general.undo()
# -> UNDO selecting entity.
general.undo()
# -> UNDO entity creation.
general.undo()
general.idle_wait_frames(1)
Report.result(Tests.creation_undo, not grid_entity.exists())
# 4. REDO the entity creation and component addition.
# -> REDO entity creation.
general.redo()
# -> REDO selecting entity.
general.redo()
# -> REDO naming entity.
general.redo()
# -> REDO component addition.
general.redo()
general.idle_wait_frames(1)
Report.result(Tests.creation_redo, grid_entity.exists())
# 5. Grid Size changed
grid_component.set_component_property_value(
AtomComponentProperties.grid('Grid Size'), value=64)
current_grid_size = grid_component.get_component_property_value(
AtomComponentProperties.grid('Grid Size'))
Report.result(Tests.grid_size, current_grid_size == 64)
# 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. Test IsHidden.
grid_entity.set_visibility_state(False)
Report.result(Tests.is_hidden, grid_entity.is_hidden() is True)
# 8. Test IsVisible.
grid_entity.set_visibility_state(True)
general.idle_wait_frames(1)
Report.result(Tests.is_visible, grid_entity.is_visible() is True)
# 9. Delete Grid entity.
grid_entity.delete()
Report.result(Tests.entity_deleted, not grid_entity.exists())
# 10. UNDO deletion.
general.undo()
general.idle_wait_frames(1)
Report.result(Tests.deletion_undo, grid_entity.exists())
# 11. REDO deletion.
general.redo()
general.idle_wait_frames(1)
Report.result(Tests.deletion_redo, not grid_entity.exists())
# 12. Look for errors or 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(AtomEditorComponents_Grid_AddedToEntity)
@@ -1,194 +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
"""
class Tests:
creation_undo = (
"UNDO Entity creation success",
"UNDO Entity creation failed")
creation_redo = (
"REDO Entity creation success",
"REDO Entity creation failed")
hdr_color_grading_creation = (
"HDR Color Grading Entity successfully created",
"HDR Color Grading Entity failed to be created")
hdr_color_grading_component = (
"Entity has an HDR Color Grading component",
"Entity failed to find HDR Color Grading component")
hdr_color_grading_disabled = (
"HDR Color Grading component disabled",
"HDR Color Grading component was not disabled")
postfx_layer_component = (
"Entity has a PostFX Layer component",
"Entity did not have an PostFX Layer component")
hdr_color_grading_enabled = (
"HDR Color Grading component enabled",
"HDR Color Grading component was not enabled")
enable_hdr_color_grading_parameter_enabled = (
"Enable HDR Color Grading parameter enabled",
"Enable HDR Color Grading parameter was not enabled")
enter_game_mode = (
"Entered game mode",
"Failed to enter game mode")
exit_game_mode = (
"Exited game mode",
"Couldn't exit game mode")
is_visible = (
"Entity is visible",
"Entity was not visible")
is_hidden = (
"Entity is hidden",
"Entity was not hidden")
entity_deleted = (
"Entity deleted",
"Entity was not deleted")
deletion_undo = (
"UNDO deletion success",
"UNDO deletion failed")
deletion_redo = (
"REDO deletion success",
"REDO deletion failed")
def AtomEditorComponents_HDRColorGrading_AddedToEntity():
"""
Summary:
Tests the HDR Color Grading component can be added to an 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, hidden/shown, deleted, and has accurate required components.
Creation and deletion undo/redo should also work.
Test Steps:
1) Create an HDR Color Grading entity with no components.
2) Add HDR Color Grading component to HDR Color Grading entity.
3) UNDO the entity creation and component addition.
4) REDO the entity creation and component addition.
5) Verify HDR Color Grading component not enabled.
6) Add PostFX Layer component since it is required by the HDR Color Grading component.
7) Verify HDR Color Grading component is enabled.
8) Enable the "Enable HDR Color Grading" parameter.
9) Enter/Exit game mode.
10) Test IsHidden.
11) Test IsVisible.
12) Delete HDR Color Grading entity.
13) UNDO deletion.
14) REDO deletion.
15) Look for errors.
:return: None
"""
import azlmbr.legacy.general as general
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
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("Graphics", "base_empty")
# Test steps begin.
# 1. Create an HDR Color Grading entity with no components.
hdr_color_grading_entity = EditorEntity.create_editor_entity(AtomComponentProperties.hdr_color_grading())
Report.critical_result(Tests.hdr_color_grading_creation, hdr_color_grading_entity.exists())
# 2. Add HDR Color Grading component to HDR Color Grading entity.
hdr_color_grading_component = hdr_color_grading_entity.add_component(
AtomComponentProperties.hdr_color_grading())
Report.critical_result(
Tests.hdr_color_grading_component,
hdr_color_grading_entity.has_component(AtomComponentProperties.hdr_color_grading()))
# 3. UNDO the entity creation and component addition.
# -> UNDO component addition.
general.undo()
# -> UNDO naming entity.
general.undo()
# -> UNDO selecting entity.
general.undo()
# -> UNDO entity creation.
general.undo()
general.idle_wait_frames(1)
Report.result(Tests.creation_undo, not hdr_color_grading_entity.exists())
# 4. REDO the entity creation and component addition.
# -> REDO entity creation.
general.redo()
# -> REDO selecting entity.
general.redo()
# -> REDO naming entity.
general.redo()
# -> REDO component addition.
general.redo()
general.idle_wait_frames(1)
Report.result(Tests.creation_redo, hdr_color_grading_entity.exists())
# 5. Verify HDR Color Grading component not enabled.
Report.result(Tests.hdr_color_grading_disabled, not hdr_color_grading_component.is_enabled())
# 6. Add PostFX Layer component since it is required by the HDR Color Grading component.
hdr_color_grading_entity.add_component(AtomComponentProperties.postfx_layer())
Report.result(
Tests.postfx_layer_component,
hdr_color_grading_entity.has_component(AtomComponentProperties.postfx_layer()))
# 7. Verify HDR Color Grading component is enabled.
Report.result(Tests.hdr_color_grading_enabled, hdr_color_grading_component.is_enabled())
# 8. Enable the "Enable HDR Color Grading" parameter.
hdr_color_grading_component.set_component_property_value(
AtomComponentProperties.hdr_color_grading('Enable HDR color grading'), True)
Report.result(Tests.enable_hdr_color_grading_parameter_enabled,
hdr_color_grading_component.get_component_property_value(
AtomComponentProperties.hdr_color_grading('Enable HDR color grading')) is True)
# 9. 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)
# 10. Test IsHidden.
hdr_color_grading_entity.set_visibility_state(False)
Report.result(Tests.is_hidden, hdr_color_grading_entity.is_hidden() is True)
# 11. Test IsVisible.
hdr_color_grading_entity.set_visibility_state(True)
general.idle_wait_frames(1)
Report.result(Tests.is_visible, hdr_color_grading_entity.is_visible() is True)
# 12. Delete HDR Color Grading entity.
hdr_color_grading_entity.delete()
Report.result(Tests.entity_deleted, not hdr_color_grading_entity.exists())
# 13. UNDO deletion.
general.undo()
general.idle_wait_frames(1)
Report.result(Tests.deletion_undo, hdr_color_grading_entity.exists())
# 14. REDO deletion.
general.redo()
general.idle_wait_frames(1)
Report.result(Tests.deletion_redo, not hdr_color_grading_entity.exists())
# 15. 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(AtomEditorComponents_HDRColorGrading_AddedToEntity)
@@ -1,179 +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
"""
class Tests:
creation_undo = (
"UNDO Entity creation success",
"UNDO Entity creation failed")
creation_redo = (
"REDO Entity creation success",
"REDO Entity creation failed")
hdri_skybox_entity_creation = (
"HDRi Skybox successfully created",
"HDRi Skybox failed to be created")
hdri_skybox_component = (
"Entity has an HDRi Skybox component",
"Entity failed to find HDRi Skybox component")
cubemap_property_set = (
"Cubemap property set on HDRi Skybox component",
"Couldn't set Cubemap property on HDRi Skybox component")
enter_game_mode = (
"Entered game mode",
"Failed to enter game mode")
exit_game_mode = (
"Exited game mode",
"Couldn't exit game mode")
is_visible = (
"Entity is visible",
"Entity was not visible")
is_hidden = (
"Entity is hidden",
"Entity was not hidden")
entity_deleted = (
"Entity deleted",
"Entity was not deleted")
deletion_undo = (
"UNDO deletion success",
"UNDO deletion failed")
deletion_redo = (
"REDO deletion success",
"REDO deletion failed")
def AtomEditorComponents_HDRiSkybox_AddedToEntity():
"""
Summary:
Tests the HDRi Skybox component can be added to an 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, hidden/shown, deleted, and has accurate required components.
Creation and deletion undo/redo should also work.
Test Steps:
1) Create an HDRi Skybox with no components.
2) Add an HDRi Skybox component to HDRi Skybox.
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) Delete HDRi Skybox.
9) UNDO deletion.
10) REDO deletion.
11) Look for errors.
: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 EditorEntity
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("Graphics", "base_empty")
# Test steps begin.
# 1. Create an HDRi Skybox with no components.
hdri_skybox_entity = EditorEntity.create_editor_entity(
AtomComponentProperties.hdri_skybox())
Report.critical_result(Tests.hdri_skybox_entity_creation,
hdri_skybox_entity.exists())
# 2. Add an HDRi Skybox component to HDRi Skybox.
hdri_skybox_component = hdri_skybox_entity.add_component(
AtomComponentProperties.hdri_skybox())
Report.critical_result(
Tests.hdri_skybox_component,
hdri_skybox_entity.has_component(AtomComponentProperties.hdri_skybox()))
# 3. UNDO the entity creation and component addition.
# -> UNDO component addition.
general.undo()
# -> UNDO naming entity.
general.undo()
# -> UNDO selecting entity.
general.undo()
# -> UNDO entity creation.
general.undo()
general.idle_wait_frames(1)
Report.result(Tests.creation_undo, not hdri_skybox_entity.exists())
# 4. REDO the entity creation and component addition.
# -> REDO entity creation.
general.redo()
# -> REDO selecting entity.
general.redo()
# -> REDO naming entity.
general.redo()
# -> REDO component addition.
general.redo()
general.idle_wait_frames(1)
Report.result(Tests.creation_redo, hdri_skybox_entity.exists())
# 5. Set Cubemap Texture on HDRi Skybox component.
skybox_cubemap_asset_path = os.path.join("LightingPresets", "default_iblskyboxcm.exr.streamingimage")
skybox_cubemap_material_asset = Asset.find_asset_by_path(skybox_cubemap_asset_path, False)
hdri_skybox_component.set_component_property_value(
AtomComponentProperties.hdri_skybox('Cubemap Texture'), skybox_cubemap_material_asset.id)
get_cubemap_property = hdri_skybox_component.get_component_property_value(
AtomComponentProperties.hdri_skybox('Cubemap Texture'))
Report.result(Tests.cubemap_property_set, get_cubemap_property == skybox_cubemap_material_asset.id)
# 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. Test IsHidden.
hdri_skybox_entity.set_visibility_state(False)
Report.result(Tests.is_hidden, hdri_skybox_entity.is_hidden() is True)
# 8. Test IsVisible.
hdri_skybox_entity.set_visibility_state(True)
general.idle_wait_frames(1)
Report.result(Tests.is_visible, hdri_skybox_entity.is_visible() is True)
# 9. Delete hdri_skybox entity.
hdri_skybox_entity.delete()
Report.result(Tests.entity_deleted, not hdri_skybox_entity.exists())
# 10. UNDO deletion.
general.undo()
general.idle_wait_frames(1)
Report.result(Tests.deletion_undo, hdri_skybox_entity.exists())
# 11. REDO deletion.
general.redo()
general.idle_wait_frames(1)
Report.result(Tests.deletion_redo, not hdri_skybox_entity.exists())
# 12. Look for errors or 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(AtomEditorComponents_HDRiSkybox_AddedToEntity)

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