Merge branch 'development' of https://github.com/o3de/o3de into carlitosan/development

monroegm-disable-blank-issue-2
chcurran 4 years ago
commit 782c6748ae

1
.gitignore vendored

@ -5,6 +5,7 @@ __pycache__
AssetProcessorTemp/** AssetProcessorTemp/**
[Bb]uild/** [Bb]uild/**
[Oo]ut/** [Oo]ut/**
CMakeUserPresets.json
[Cc]ache/ [Cc]ache/
/install/ /install/
Editor/EditorEventLog.xml Editor/EditorEventLog.xml

@ -6,4 +6,4 @@
# #
# #
set(SQUISH-CCR_LIBS ${BASE_PATH}/lib/Mac/Release/libsquish-ccr.a) ly_install_directory(DIRECTORIES .)

@ -5,12 +5,14 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT SPDX-License-Identifier: Apache-2.0 OR MIT
""" """
import os
# ARN of the IAM role to assume for retrieving temporary AWS credentials # ARN of the IAM role to assume for retrieving temporary AWS credentials
ASSUME_ROLE_ARN = 'arn:aws:iam::645075835648:role/o3de-automation-tests' 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 # Name of the AWS project deployed by the CDK applications
AWS_PROJECT_NAME = 'AWSAUTO' AWS_PROJECT_NAME = os.environ.get('O3DE_AWS_PROJECT_NAME', 'AWSAUTO')
# Region for the existing CloudFormation stacks used by the automation tests # Region for the existing CloudFormation stacks used by the automation tests
AWS_REGION = 'us-east-1' 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 # Name of the default resource mapping config file used by the automation tests
AWS_RESOURCE_MAPPING_FILE_NAME = 'default_aws_resource_mappings.json' AWS_RESOURCE_MAPPING_FILE_NAME = 'default_aws_resource_mappings.json'
# Name of the game launcher log # Name of the game launcher log

@ -181,3 +181,39 @@ class TestPerformanceBenchmarkSuite(object):
aggregator = BenchmarkDataAggregator(workspace, logger, 'periodic') aggregator = BenchmarkDataAggregator(workspace, logger, 'periodic')
aggregator.upload_metrics(rhi) 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", ["-rhi=dx12", "-rhi=Vulkan"])
@pytest.mark.parametrize("exe_file_name", ["MaterialEditor"])
def test_MaterialEditorLaunch_AllRHIOptionsSucceed(
self, request, workspace, project, launcher_platform, generic_launcher, exe_file_name, cfg_args):
"""
Tests each valid RHI option (Null RHI excluded) can be launched with the MaterialEditor.
Checks for the "Finished loading viewport configurtions." success message post lounch.
"""
expected_lines = ["Finished loading viewport configurtions."]
unexpected_lines = [
# "Trace::Assert",
# "Trace::Error",
"Traceback (most recent call last):",
]
hydra.launch_and_validate_results(
request,
TEST_DIRECTORY,
generic_launcher,
editor_script="",
run_python="--runpython",
timeout=30,
expected_lines=expected_lines,
unexpected_lines=unexpected_lines,
halt_on_unexpected=False,
null_renderer=False,
cfg_args=[cfg_args],
log_file_name="MaterialEditor.log"
)

@ -303,5 +303,6 @@ class TestMaterialEditorBasicTests(object):
expected_lines=expected_lines, expected_lines=expected_lines,
unexpected_lines=unexpected_lines, unexpected_lines=unexpected_lines,
halt_on_unexpected=True, halt_on_unexpected=True,
null_renderer=True,
log_file_name="MaterialEditor.log", log_file_name="MaterialEditor.log",
) )

@ -51,8 +51,8 @@ class TestAutomationBase:
cls.asset_processor.teardown() cls.asset_processor.teardown()
cls._kill_ly_processes() cls._kill_ly_processes()
def _run_test(self, request, workspace, editor, testcase_module, extra_cmdline_args=[], batch_mode=True,
def _run_test(self, request, workspace, editor, testcase_module, extra_cmdline_args=[], use_null_renderer=True): autotest_mode=True, use_null_renderer=True):
test_starttime = time.time() test_starttime = time.time()
self.logger = logging.getLogger(__name__) self.logger = logging.getLogger(__name__)
errors = [] errors = []
@ -90,9 +90,13 @@ class TestAutomationBase:
editor_starttime = time.time() editor_starttime = time.time()
self.logger.debug("Running automated test") self.logger.debug("Running automated test")
testcase_module_filepath = self._get_testcase_module_filepath(testcase_module) testcase_module_filepath = self._get_testcase_module_filepath(testcase_module)
pycmd = ["--runpythontest", testcase_module_filepath, "-BatchMode", "-autotest_mode", f"-pythontestcase={request.node.originalname}"] pycmd = ["--runpythontest", testcase_module_filepath, f"-pythontestcase={request.node.originalname}"]
if use_null_renderer: if use_null_renderer:
pycmd += ["-rhi=null"] pycmd += ["-rhi=null"]
if batch_mode:
pycmd += ["-BatchMode"]
if autotest_mode:
pycmd += ["-autotest_mode"]
pycmd += extra_cmdline_args pycmd += extra_cmdline_args
editor.args.extend(pycmd) # args are added to the WinLauncher start command editor.args.extend(pycmd) # args are added to the WinLauncher start command
editor.start(backupFiles = False, launch_ap = False) editor.start(backupFiles = False, launch_ap = False)

@ -11,8 +11,23 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_
NAME AutomatedTesting::EditorTests_Main NAME AutomatedTesting::EditorTests_Main
TEST_SUITE main TEST_SUITE main
TEST_SERIAL TEST_SERIAL
PATH ${CMAKE_CURRENT_LIST_DIR} PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Main.py
PYTEST_MARKS "SUITE_main and not REQUIRES_gpu" PYTEST_MARKS "not REQUIRES_gpu"
RUNTIME_DEPENDENCIES
Legacy::Editor
AZ::AssetProcessor
AutomatedTesting.Assets
COMPONENT
Editor
)
ly_add_pytest(
NAME AutomatedTesting::EditorTests_Main_GPU
TEST_SUITE main
TEST_SERIAL
TEST_REQUIRES gpu
PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Main.py
PYTEST_MARKS "REQUIRES_gpu"
RUNTIME_DEPENDENCIES RUNTIME_DEPENDENCIES
Legacy::Editor Legacy::Editor
AZ::AssetProcessor AZ::AssetProcessor
@ -25,8 +40,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_
NAME AutomatedTesting::EditorTests_Periodic NAME AutomatedTesting::EditorTests_Periodic
TEST_SUITE periodic TEST_SUITE periodic
TEST_SERIAL TEST_SERIAL
PATH ${CMAKE_CURRENT_LIST_DIR} PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Periodic.py
PYTEST_MARKS "SUITE_periodic and not REQUIRES_gpu"
RUNTIME_DEPENDENCIES RUNTIME_DEPENDENCIES
Legacy::Editor Legacy::Editor
AZ::AssetProcessor AZ::AssetProcessor
@ -36,12 +50,39 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_
) )
ly_add_pytest( ly_add_pytest(
NAME AutomatedTesting::EditorTests_Main_GPU NAME AutomatedTesting::EditorTests_Sandbox
TEST_SUITE sandbox
TEST_SERIAL
PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Sandbox.py
RUNTIME_DEPENDENCIES
Legacy::Editor
AZ::AssetProcessor
AutomatedTesting.Assets
COMPONENT
Editor
)
ly_add_pytest(
NAME AutomatedTesting::EditorTests_Main_Optimized
TEST_SUITE main
TEST_SERIAL
PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Main_Optimized.py
PYTEST_MARKS "not REQUIRES_gpu"
RUNTIME_DEPENDENCIES
Legacy::Editor
AZ::AssetProcessor
AutomatedTesting.Assets
COMPONENT
Editor
)
ly_add_pytest(
NAME AutomatedTesting::EditorTests_Main_GPU_Optimized
TEST_SUITE main TEST_SUITE main
TEST_SERIAL TEST_SERIAL
TEST_REQUIRES gpu TEST_REQUIRES gpu
PATH ${CMAKE_CURRENT_LIST_DIR} PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Main_Optimized.py
PYTEST_MARKS "SUITE_main and REQUIRES_gpu" PYTEST_MARKS "REQUIRES_gpu"
RUNTIME_DEPENDENCIES RUNTIME_DEPENDENCIES
Legacy::Editor Legacy::Editor
AZ::AssetProcessor AZ::AssetProcessor
@ -51,11 +92,10 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_
) )
ly_add_pytest( ly_add_pytest(
NAME AutomatedTesting::EditorTests_Sandbox NAME AutomatedTesting::EditorTests_Sandbox_Optimized
TEST_SUITE sandbox TEST_SUITE sandbox
TEST_SERIAL TEST_SERIAL
PATH ${CMAKE_CURRENT_LIST_DIR} PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Sandbox_Optimized.py
PYTEST_MARKS "SUITE_sandbox"
RUNTIME_DEPENDENCIES RUNTIME_DEPENDENCIES
Legacy::Editor Legacy::Editor
AZ::AssetProcessor AZ::AssetProcessor
@ -63,4 +103,5 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_
COMPONENT COMPONENT
Editor Editor
) )
endif() endif()

@ -5,30 +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 SPDX-License-Identifier: Apache-2.0 OR MIT
""" """
"""
C13660194 : Asset Browser - Filtering
"""
import os
import sys
from PySide2 import QtWidgets, QtTest, QtCore
from PySide2.QtCore import Qt
import azlmbr.legacy.general as general class Tests:
import azlmbr.paths asset_filtered = (
"Asset was filtered to in the Asset Browser",
"Failed to filter to the expected asset"
)
asset_type_filtered = (
"Expected asset type was filtered to in the Asset Browser",
"Failed to filter to the expected asset type"
)
sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests'))
import editor_python_test_tools.hydra_editor_utils as hydra
import editor_python_test_tools.pyside_utils as pyside_utils
from editor_python_test_tools.editor_test_helper import EditorTestHelper
def AssetBrowser_SearchFiltering():
class AssetBrowserSearchFilteringTest(EditorTestHelper): import editor_python_test_tools.pyside_utils as pyside_utils
def __init__(self):
EditorTestHelper.__init__(self, log_prefix="AssetBrowser_SearchFiltering", args=["level"])
@pyside_utils.wrap_async @pyside_utils.wrap_async
async def run_test(self): async def run_test():
""" """
Summary: Summary:
Asset Browser - Filtering Asset Browser - Filtering
@ -60,7 +54,13 @@ class AssetBrowserSearchFilteringTest(EditorTestHelper):
:return: None :return: None
""" """
self.incorrect_file_found = False from PySide2 import QtWidgets, QtTest, QtCore
from PySide2.QtCore import Qt
import azlmbr.legacy.general as general
from editor_python_test_tools.utils import Report
from editor_python_test_tools.utils import TestHelper as helper
def verify_files_appeared(model, allowed_asset_extentions, parent_index=QtCore.QModelIndex()): def verify_files_appeared(model, allowed_asset_extentions, parent_index=QtCore.QModelIndex()):
indexes = [parent_index] indexes = [parent_index]
@ -74,25 +74,24 @@ class AssetBrowserSearchFilteringTest(EditorTestHelper):
and (cur_data.lower().split(".")[-1] not in allowed_asset_extentions) and (cur_data.lower().split(".")[-1] not in allowed_asset_extentions)
and not cur_data[-1] == ")" and not cur_data[-1] == ")"
): ):
print(f"Incorrect file found: {cur_data}") Report.info(f"Incorrect file found: {cur_data}")
self.incorrect_file_found = True return False
indexes = list()
break
indexes.append(cur_index) indexes.append(cur_index)
return True
# 1) Open an existing simple level
helper.init_idle()
helper.open_level("Physics", "Base")
# 1) Open level # 2) Open Asset Browser (if not opened already)
self.test_success = self.create_level( editor_window = pyside_utils.get_editor_main_window()
self.args["level"], asset_browser_open = general.is_pane_visible("Asset Browser")
heightmap_resolution=1024, if not asset_browser_open:
heightmap_meters_per_pixel=1, Report.info("Opening Asset Browser")
terrain_texture_resolution=4096, action = pyside_utils.get_action_for_menu_path(editor_window, "Tools", "Asset Browser")
use_terrain=False, action.trigger()
) else:
Report.info("Asset Browser is already open")
# 2) Open Asset Browser
general.close_pane("Asset Browser")
general.open_pane("Asset Browser")
editor_window = pyside_utils.get_editor_main_window() editor_window = pyside_utils.get_editor_main_window()
app = QtWidgets.QApplication.instance() app = QtWidgets.QApplication.instance()
@ -103,10 +102,9 @@ class AssetBrowserSearchFilteringTest(EditorTestHelper):
asset_browser_tree = asset_browser.findChild(QtWidgets.QTreeView, "m_assetBrowserTreeViewWidget") asset_browser_tree = asset_browser.findChild(QtWidgets.QTreeView, "m_assetBrowserTreeViewWidget")
model_index = pyside_utils.find_child_by_pattern(asset_browser_tree, "cedar.fbx") model_index = pyside_utils.find_child_by_pattern(asset_browser_tree, "cedar.fbx")
pyside_utils.item_view_index_mouse_click(asset_browser_tree, model_index) pyside_utils.item_view_index_mouse_click(asset_browser_tree, model_index)
is_filtered = pyside_utils.wait_for_condition( is_filtered = await pyside_utils.wait_for_condition(
lambda: asset_browser_tree.indexBelow(asset_browser_tree.currentIndex()) == QtCore.QModelIndex(), 5.0) lambda: asset_browser_tree.indexBelow(asset_browser_tree.currentIndex()) == QtCore.QModelIndex(), 5.0)
if is_filtered: Report.result(Tests.asset_filtered, is_filtered)
print("cedar.fbx asset is filtered in Asset Browser")
# 4) Click the "X" in the search bar. # 4) Click the "X" in the search bar.
clear_search = asset_browser.findChild(QtWidgets.QToolButton, "ClearToolButton") clear_search = asset_browser.findChild(QtWidgets.QToolButton, "ClearToolButton")
@ -122,40 +120,47 @@ class AssetBrowserSearchFilteringTest(EditorTestHelper):
tree.model().setData(animation_model_index, 2, Qt.CheckStateRole) tree.model().setData(animation_model_index, 2, Qt.CheckStateRole)
general.idle_wait(1.0) general.idle_wait(1.0)
# check asset types after clicking on Animation filter # check asset types after clicking on Animation filter
verify_files_appeared(asset_browser_tree.model(), ["i_caf", "fbx", "xml", "animgraph", "motionset"]) asset_type_filter = verify_files_appeared(asset_browser_tree.model(), ["i_caf", "fbx", "xml", "animgraph", "motionset"])
print(f"Animation file type(s) is present in the file tree: {not self.incorrect_file_found}") Report.result(Tests.asset_type_filtered, asset_type_filter)
# 6) Add additional filter(FileTag) from the filter menu # 6) Add additional filter(FileTag) from the filter menu
self.incorrect_file_found = False
line_edit.setText("FileTag") line_edit.setText("FileTag")
filetag_model_index = await pyside_utils.wait_for_child_by_pattern(tree, "FileTag") filetag_model_index = await pyside_utils.wait_for_child_by_pattern(tree, "FileTag")
tree.model().setData(filetag_model_index, 2, Qt.CheckStateRole) tree.model().setData(filetag_model_index, 2, Qt.CheckStateRole)
general.idle_wait(1.0) general.idle_wait(1.0)
# check asset types after clicking on FileTag filter # check asset types after clicking on FileTag filter
verify_files_appeared( more_types_filtered = verify_files_appeared(
asset_browser_tree.model(), ["i_caf", "fbx", "xml", "animgraph", "motionset", "filetag"] asset_browser_tree.model(), ["i_caf", "fbx", "xml", "animgraph", "motionset", "filetag"]
) )
print(f"FileTag file type(s) and Animation file type(s) is present in the file tree: {not self.incorrect_file_found}") Report.result(Tests.asset_type_filtered, more_types_filtered)
# 7) Remove one of the filtered asset types from the list of applied filters # 7) Remove one of the filtered asset types from the list of applied filters
self.incorrect_file_found = False
filter_layout = asset_browser.findChild(QtWidgets.QFrame, "filteredLayout") filter_layout = asset_browser.findChild(QtWidgets.QFrame, "filteredLayout")
animation_close_button = filter_layout.children()[1] animation_close_button = filter_layout.children()[1]
first_close_button = animation_close_button.findChild(QtWidgets.QPushButton, "closeTag") first_close_button = animation_close_button.findChild(QtWidgets.QPushButton, "closeTag")
first_close_button.click() first_close_button.click()
general.idle_wait(1.0) general.idle_wait(1.0)
# check asset types after removing Animation filter # check asset types after removing Animation filter
verify_files_appeared(asset_browser_tree.model(), ["filetag"]) remove_filtered = verify_files_appeared(asset_browser_tree.model(), ["filetag"])
print(f"FileTag file type(s) is present in the file tree after removing Animation filter: {not self.incorrect_file_found}") Report.result(Tests.asset_type_filtered, remove_filtered)
# 8) Remove all of the filter asset types from the list of filters # 8) Remove all of the filter asset types from the list of filters
filetag_close_button = filter_layout.children()[1] filetag_close_button = filter_layout.children()[1]
second_close_button = filetag_close_button.findChild(QtWidgets.QPushButton, "closeTag") second_close_button = filetag_close_button.findChild(QtWidgets.QPushButton, "closeTag")
second_close_button.click() second_close_button.click()
# 9) Close the asset browser # Click off of the Asset Browser filter window to close it
asset_browser.close() QtTest.QTest.mouseClick(tree, Qt.LeftButton, Qt.NoModifier)
# 9) Restore Asset Browser tool state and
if not asset_browser_open:
Report.info("Closing Asset Browser")
general.close_pane("Asset Browser")
run_test()
if __name__ == "__main__":
test = AssetBrowserSearchFilteringTest() from editor_python_test_tools.utils import Report
test.run() Report.start_test(AssetBrowser_SearchFiltering)

@ -5,124 +5,119 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT SPDX-License-Identifier: Apache-2.0 OR MIT
""" """
"""
C13660195: Asset Browser - File Tree Navigation
"""
import os class Tests:
import sys collapse_expand = (
from PySide2 import QtWidgets, QtTest, QtCore "Asset Browser hierarchy successfully collapsed/expanded",
"Failed to collapse/expand Asset Browser hierarchy"
import azlmbr.legacy.general as general )
import azlmbr.paths asset_visible = (
"Expected asset is visible in the Asset Browser hierarchy",
sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests')) "Failed to find expected asset in the Asset Browser hierarchy"
from editor_python_test_tools.editor_test_helper import EditorTestHelper )
import editor_python_test_tools.pyside_utils as pyside_utils scrollbar_visible = (
"Scrollbar is visible",
"Scrollbar was not found"
class AssetBrowserTreeNavigationTest(EditorTestHelper): )
def __init__(self):
EditorTestHelper.__init__(self, log_prefix="AssetBrowser_TreeNavigation", args=["level"])
def AssetBrowser_TreeNavigation():
def run_test(self): """
""" Summary:
Summary: Verify if we are able to expand a file hierarchy in the Asset Browser and ScrollBar appears
Verify if we are able to expand a file hierarchy in the Asset Browser and ScrollBar appears appropriately.
appropriately.
Expected Behavior:
Expected Behavior: The folder list is expanded to display the children of the selected folder.
The folder list is expanded to display the children of the selected folder. A scroll bar appears to allow scrolling up and down through the asset browser.
A scroll bar appears to allow scrolling up and down through the asset browser. Assets are present in the Asset Browser.
Assets are present in the Asset Browser.
Test Steps:
Test Steps: 1) Open a simple level
1) Open a new level 2) Open Asset Browser
2) Open Asset Browser 3) Collapse all files initially
3) Collapse all files initially 4) Get all Model Indexes
4) Get all Model Indexes 5) Expand each of the folder and verify if it is opened
5) Expand each of the folder and verify if it is opened 6) Verify if the ScrollBar appears after expanding the tree
6) Verify if the ScrollBar appears after expanding the tree
Note:
Note: - This test file must be called from the Open 3D Engine Editor command terminal
- This test file must be called from the Open 3D Engine Editor command terminal - Any passed and failed tests are written to the Editor.log file.
- Any passed and failed tests are written to the Editor.log file. Parsing the file or running a log_monitor are required to observe the test results.
Parsing the file or running a log_monitor are required to observe the test results.
:return: None
:return: None """
"""
from PySide2 import QtWidgets, QtTest, QtCore
def collapse_expand_and_verify(model_index, hierarchy_level):
tree.collapse(model_index) import azlmbr.legacy.general as general
collapse_success = not tree.isExpanded(model_index)
self.log(f"Level {hierarchy_level} collapsed: {collapse_success}") import editor_python_test_tools.pyside_utils as pyside_utils
tree.expand(model_index) from editor_python_test_tools.utils import Report
expand_success = tree.isExpanded(model_index) from editor_python_test_tools.utils import TestHelper as helper
self.log(f"Level {hierarchy_level} expanded: {expand_success}")
return collapse_success and expand_success def collapse_expand_and_verify(model_index, hierarchy_level):
tree.collapse(model_index)
# This is the hierarchy we are expanding (4 steps inside) collapse_success = not tree.isExpanded(model_index)
self.file_path = ("AutomatedTesting", "Assets", "ImageGradients", "image_grad_test_gsi.png") Report.info(f"Level {hierarchy_level} collapsed: {collapse_success}")
tree.expand(model_index)
# 1) Open a new level expand_success = tree.isExpanded(model_index)
self.test_success = self.create_level( Report.info(f"Level {hierarchy_level} expanded: {expand_success}")
self.args["level"], return collapse_success and expand_success
heightmap_resolution=1024,
heightmap_meters_per_pixel=1, # This is the hierarchy we are expanding (4 steps inside)
terrain_texture_resolution=4096, file_path = ("AutomatedTesting", "Assets", "ImageGradients", "image_grad_test_gsi.png")
use_terrain=False,
) # 1) Open an existing simple level
helper.init_idle()
# 2) Open Asset Browser (if not opened already) helper.open_level("Physics", "Base")
editor_window = pyside_utils.get_editor_main_window()
asset_browser_open = general.is_pane_visible("Asset Browser") # 2) Open Asset Browser (if not opened already)
if not asset_browser_open: editor_window = pyside_utils.get_editor_main_window()
self.log("Opening Asset Browser") asset_browser_open = general.is_pane_visible("Asset Browser")
action = pyside_utils.get_action_for_menu_path(editor_window, "Tools", "Asset Browser") if not asset_browser_open:
action.trigger() Report.info("Opening Asset Browser")
else: action = pyside_utils.get_action_for_menu_path(editor_window, "Tools", "Asset Browser")
self.log("Asset Browser is already open") action.trigger()
else:
# 3) Collapse all files initially Report.info("Asset Browser is already open")
main_window = editor_window.findChild(QtWidgets.QMainWindow)
asset_browser = pyside_utils.find_child_by_hierarchy(main_window, ..., "Asset Browser") # 3) Collapse all files initially
tree = pyside_utils.find_child_by_hierarchy(asset_browser, ..., "m_assetBrowserTreeViewWidget") main_window = editor_window.findChild(QtWidgets.QMainWindow)
scroll_area = tree.findChild(QtWidgets.QWidget, "qt_scrollarea_vcontainer") asset_browser = pyside_utils.find_child_by_hierarchy(main_window, ..., "Asset Browser")
scroll_bar = scroll_area.findChild(QtWidgets.QScrollBar) tree = pyside_utils.find_child_by_hierarchy(asset_browser, ..., "m_assetBrowserTreeViewWidget")
tree.collapseAll() scroll_area = tree.findChild(QtWidgets.QWidget, "qt_scrollarea_vcontainer")
scroll_bar = scroll_area.findChild(QtWidgets.QScrollBar)
# 4) Get all Model Indexes tree.collapseAll()
model_index_1 = pyside_utils.find_child_by_hierarchy(tree, self.file_path[0])
model_index_2 = pyside_utils.find_child_by_hierarchy(model_index_1, self.file_path[1]) # 4) Get all Model Indexes
model_index_3 = pyside_utils.find_child_by_hierarchy(model_index_2, self.file_path[2]) model_index_1 = pyside_utils.find_child_by_hierarchy(tree, file_path[0])
model_index_4 = pyside_utils.find_child_by_hierarchy(model_index_3, self.file_path[3]) model_index_2 = pyside_utils.find_child_by_hierarchy(model_index_1, file_path[1])
model_index_3 = pyside_utils.find_child_by_hierarchy(model_index_2, file_path[2])
# 5) Verify each level of the hierarchy to the file can be collapsed/expanded model_index_4 = pyside_utils.find_child_by_hierarchy(model_index_3, file_path[3])
self.test_success = collapse_expand_and_verify(model_index_1, 1) and self.test_success
self.test_success = collapse_expand_and_verify(model_index_2, 2) and self.test_success # 5) Verify each level of the hierarchy to the file can be collapsed/expanded
self.test_success = collapse_expand_and_verify(model_index_3, 3) and self.test_success Report.result(Tests.collapse_expand, collapse_expand_and_verify(model_index_1, 1) and
self.log(f"Collapse/Expand tests: {self.test_success}") collapse_expand_and_verify(model_index_2, 2) and collapse_expand_and_verify(model_index_3, 3))
# Select the asset # Select the asset
tree.scrollTo(model_index_4) tree.scrollTo(model_index_4)
pyside_utils.item_view_index_mouse_click(tree, model_index_4) pyside_utils.item_view_index_mouse_click(tree, model_index_4)
# Verify if the currently selected item model index is same as the Asset Model index # Verify if the currently selected item model index is same as the Asset Model index
# to prove that it is visible # to prove that it is visible
asset_visible = tree.currentIndex() == model_index_4 Report.result(Tests.asset_visible, tree.currentIndex() == model_index_4)
self.test_success = asset_visible and self.test_success
self.log(f"Asset visibility test: {asset_visible}") # 6) Verify if the ScrollBar appears after expanding the tree
Report.result(Tests.scrollbar_visible, scroll_bar.isVisible())
# 6) Verify if the ScrollBar appears after expanding the tree
scrollbar_visible = scroll_bar.isVisible() # 7) Restore Asset Browser tool state
self.test_success = scrollbar_visible and self.test_success if not asset_browser_open:
self.log(f"Scrollbar visibility test: {scrollbar_visible}") Report.info("Closing Asset Browser")
general.close_pane("Asset Browser")
# 7) Restore Asset Browser tool state
if not asset_browser_open:
self.log("Closing Asset Browser") if __name__ == "__main__":
general.close_pane("Asset Browser")
from editor_python_test_tools.utils import Report
Report.start_test(AssetBrowser_TreeNavigation)
test = AssetBrowserTreeNavigationTest()
test.run()

@ -5,33 +5,13 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT SPDX-License-Identifier: Apache-2.0 OR MIT
""" """
"""
C13751579: Asset Picker UI/UX
"""
import os
import sys
from PySide2 import QtWidgets, QtTest, QtCore
from PySide2.QtCore import Qt
import azlmbr.asset as asset def AssetPicker_UI_UX():
import azlmbr.bus as bus
import azlmbr.legacy.general as general
import azlmbr.paths
import azlmbr.math as math
sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests')) import editor_python_test_tools.pyside_utils as pyside_utils
import editor_python_test_tools.hydra_editor_utils as hydra
import editor_python_test_tools.pyside_utils as pyside_utils
from editor_python_test_tools.editor_test_helper import EditorTestHelper
class AssetPickerUIUXTest(EditorTestHelper):
def __init__(self):
EditorTestHelper.__init__(self, log_prefix="AssetPicker_UI_UX", args=["level"])
@pyside_utils.wrap_async @pyside_utils.wrap_async
async def run_test(self): async def run_test():
""" """
Summary: Summary:
Verify the functionality of Asset Picker and UI/UX properties Verify the functionality of Asset Picker and UI/UX properties
@ -45,7 +25,7 @@ class AssetPickerUIUXTest(EditorTestHelper):
The asset picker is closed and the selected asset is assigned to the mesh component. The asset picker is closed and the selected asset is assigned to the mesh component.
Test Steps: Test Steps:
1) Open a new level 1) Open a simple level
2) Create entity and add Mesh component 2) Create entity and add Mesh component
3) Access Entity Inspector 3) Access Entity Inspector
4) Click Asset Picker (Mesh Asset) 4) Click Asset Picker (Mesh Asset)
@ -68,10 +48,20 @@ class AssetPickerUIUXTest(EditorTestHelper):
:return: None :return: None
""" """
self.file_path = ["AutomatedTesting", "Assets", "Objects", "Foliage"] import os
self.incorrect_file_found = False from PySide2 import QtWidgets, QtTest, QtCore
self.mesh_asset = "cedar.azmodel" from PySide2.QtCore import Qt
self.prefix = ""
import azlmbr.asset as asset
import azlmbr.bus as bus
import azlmbr.legacy.general as general
import azlmbr.math as math
import editor_python_test_tools.hydra_editor_utils as hydra
from editor_python_test_tools.utils import Report
from editor_python_test_tools.utils import TestHelper as helper
file_path = ["AutomatedTesting", "Assets", "Objects", "Foliage"]
def is_asset_assigned(component, interaction_option): def is_asset_assigned(component, interaction_option):
path = os.path.join("assets", "objects", "foliage", "cedar.azmodel") path = os.path.join("assets", "objects", "foliage", "cedar.azmodel")
@ -80,7 +70,7 @@ class AssetPickerUIUXTest(EditorTestHelper):
result = hydra.get_component_property_value(component, "Controller|Configuration|Mesh Asset") result = hydra.get_component_property_value(component, "Controller|Configuration|Mesh Asset")
expected_asset_str = expected_asset_id.invoke("ToString") expected_asset_str = expected_asset_id.invoke("ToString")
result_str = result.invoke("ToString") result_str = result.invoke("ToString")
print(f"Asset assigned for {interaction_option} option: {expected_asset_str == result_str}") Report.info(f"Asset assigned for {interaction_option} option: {expected_asset_str == result_str}")
return expected_asset_str == result_str return expected_asset_str == result_str
def move_and_resize_widget(widget): def move_and_resize_widget(widget):
@ -89,9 +79,11 @@ class AssetPickerUIUXTest(EditorTestHelper):
x, y = initial_position.x() + 5, initial_position.y() + 5 x, y = initial_position.x() + 5, initial_position.y() + 5
widget.move(x, y) widget.move(x, y)
curr_position = widget.pos() curr_position = widget.pos()
move_success = curr_position.x() == x and curr_position.y() == y asset_picker_moved = (
self.test_success = move_success and self.test_success "Asset Picker widget moved successfully",
self.log(f"Widget Move Test: {move_success}") "Failed to move Asset Picker widget"
)
Report.result(asset_picker_moved, curr_position.x() == x and curr_position.y() == y)
# Resize the widget and verify size # Resize the widget and verify size
width, height = ( width, height = (
@ -99,9 +91,36 @@ class AssetPickerUIUXTest(EditorTestHelper):
widget.geometry().height() + 10, widget.geometry().height() + 10,
) )
widget.resize(width, height) widget.resize(width, height)
resize_success = widget.geometry().width() == width and widget.geometry().height() == height asset_picker_resized = (
self.test_success = resize_success and self.test_success "Resized Asset Picker widget successfully",
self.log(f"Widget Resize Test: {resize_success}") "Failed to resize Asset Picker widget"
)
Report.result(asset_picker_resized, widget.geometry().width() == width and widget.geometry().height() ==
height)
def verify_expand(model_index, tree):
initially_collapsed = (
"Folder initially collapsed",
"Folder unexpectedly expanded"
)
expanded = (
"Folder expanded successfully",
"Failed to expand folder"
)
# Check initial collapse
Report.result(initially_collapsed, not tree.isExpanded(model_index))
# Expand at the specified index
tree.expand(model_index)
# Verify expansion
Report.result(expanded, tree.isExpanded(model_index))
def verify_collapse(model_index, tree):
collapsed = (
"Folder hierarchy collapsed successfully",
"Failed to collapse folder hierarchy"
)
tree.collapse(model_index)
Report.result(collapsed, not tree.isExpanded(model_index))
def verify_files_appeared(model, allowed_asset_extensions, parent_index=QtCore.QModelIndex()): def verify_files_appeared(model, allowed_asset_extensions, parent_index=QtCore.QModelIndex()):
indices = [parent_index] indices = [parent_index]
@ -115,22 +134,20 @@ class AssetPickerUIUXTest(EditorTestHelper):
and (cur_data.lower().split(".")[-1] not in allowed_asset_extensions) and (cur_data.lower().split(".")[-1] not in allowed_asset_extensions)
and not cur_data[-1] == ")" and not cur_data[-1] == ")"
): ):
print(f"Incorrect file found: {cur_data}") Report.info(f"Incorrect file found: {cur_data}")
self.incorrect_file_found = True return False
indices = list()
break
indices.append(cur_index) indices.append(cur_index)
self.test_success = not self.incorrect_file_found and self.test_success return True
def print_message_prefix(message):
print(f"{self.prefix}: {message}")
async def asset_picker(prefix, allowed_asset_extensions, asset, interaction_option): async def asset_picker(allowed_asset_extensions, asset, interaction_option):
active_modal_widget = await pyside_utils.wait_for_modal_widget() active_modal_widget = await pyside_utils.wait_for_modal_widget()
if active_modal_widget and self.prefix == "": if active_modal_widget:
self.prefix = prefix
dialog = active_modal_widget.findChildren(QtWidgets.QDialog, "AssetPickerDialogClass")[0] dialog = active_modal_widget.findChildren(QtWidgets.QDialog, "AssetPickerDialogClass")[0]
print_message_prefix(f"Asset Picker title for Mesh: {dialog.windowTitle()}") asset_picker_title = (
"Asset Picker window is titled as expected",
"Asset Picker window has an unexpected title"
)
Report.result(asset_picker_title, dialog.windowTitle() == "Pick ModelAsset")
tree = dialog.findChildren(QtWidgets.QTreeView, "m_assetBrowserTreeViewWidget")[0] tree = dialog.findChildren(QtWidgets.QTreeView, "m_assetBrowserTreeViewWidget")[0]
scroll_area = tree.findChild(QtWidgets.QWidget, "qt_scrollarea_vcontainer") scroll_area = tree.findChild(QtWidgets.QWidget, "qt_scrollarea_vcontainer")
scroll_bar = scroll_area.findChild(QtWidgets.QScrollBar) scroll_bar = scroll_area.findChild(QtWidgets.QScrollBar)
@ -138,39 +155,42 @@ class AssetPickerUIUXTest(EditorTestHelper):
# a) Collapse all the files initially and verify if scroll bar is not visible # a) Collapse all the files initially and verify if scroll bar is not visible
tree.collapseAll() tree.collapseAll()
await pyside_utils.wait_for_condition(lambda: not scroll_bar.isVisible(), 0.5) await pyside_utils.wait_for_condition(lambda: not scroll_bar.isVisible(), 0.5)
print_message_prefix( scroll_bar_hidden = (
f"Scroll Bar is not visible before expanding the tree: {not scroll_bar.isVisible()}" "Scroll Bar is not visible before tree expansion",
"Scroll Bar is visible before tree expansion"
) )
Report.result(scroll_bar_hidden, not scroll_bar.isVisible())
# Get Model Index of the file paths # Get Model Index of the file paths
model_index_1 = pyside_utils.find_child_by_pattern(tree, self.file_path[0]) model_index_1 = pyside_utils.find_child_by_pattern(tree, file_path[0])
print(model_index_1.model()) model_index_2 = pyside_utils.find_child_by_pattern(model_index_1, file_path[1])
model_index_2 = pyside_utils.find_child_by_pattern(model_index_1, self.file_path[1])
# b) Expand/Verify Top folder of file path # b) Expand/Verify Top folder of file path
print_message_prefix(f"Top level folder initially collapsed: {not tree.isExpanded(model_index_1)}") verify_expand(model_index_1, tree)
tree.expand(model_index_1)
print_message_prefix(f"Top level folder expanded: {tree.isExpanded(model_index_1)}")
# c) Expand/Verify Nested folder of file path # c) Expand/Verify Nested folder of file path
print_message_prefix(f"Nested folder initially collapsed: {not tree.isExpanded(model_index_2)}") verify_expand(model_index_2, tree)
tree.expand(model_index_2)
print_message_prefix(f"Nested folder expanded: {tree.isExpanded(model_index_2)}")
# d) Verify if the ScrollBar appears after expanding folders # d) Verify if the ScrollBar appears after expanding folders
tree.expandAll() tree.expandAll()
await pyside_utils.wait_for_condition(lambda: scroll_bar.isVisible(), 0.5) await pyside_utils.wait_for_condition(lambda: scroll_bar.isVisible(), 0.5)
print_message_prefix(f"Scroll Bar appeared after expanding tree: {scroll_bar.isVisible()}") scroll_bar_visible = (
"Scroll Bar is visible after tree expansion",
"Scroll Bar is not visible after tree expansion"
)
Report.result(scroll_bar_visible, scroll_bar.isVisible())
# e) Collapse Nested and Top Level folders and verify if collapsed # e) Collapse Nested and Top Level folders and verify if collapsed
tree.collapse(model_index_2) verify_collapse(model_index_2, tree)
print_message_prefix(f"Nested folder collapsed: {not tree.isExpanded(model_index_2)}") verify_collapse(model_index_1, tree)
tree.collapse(model_index_1)
print_message_prefix(f"Top level folder collapsed: {not tree.isExpanded(model_index_1)}")
# f) Verify if the correct files are appearing in the Asset Picker # f) Verify if the correct files are appearing in the Asset Picker
verify_files_appeared(tree.model(), allowed_asset_extensions) asset_picker_correct_files_appear = (
print_message_prefix(f"Expected Assets populated in the file picker: {not self.incorrect_file_found}") "Expected assets populated in the file picker",
"Found unexpected assets in the file picker"
)
Report.result(asset_picker_correct_files_appear, verify_files_appeared(tree.model(),
allowed_asset_extensions))
# While we are here we can also check if we can resize and move the widget # While we are here we can also check if we can resize and move the widget
move_and_resize_widget(active_modal_widget) move_and_resize_widget(active_modal_widget)
@ -193,16 +213,10 @@ class AssetPickerUIUXTest(EditorTestHelper):
await pyside_utils.click_button_async(ok_button) await pyside_utils.click_button_async(ok_button)
elif interaction_option == "enter": elif interaction_option == "enter":
QtTest.QTest.keyClick(tree, Qt.Key_Enter, Qt.NoModifier) QtTest.QTest.keyClick(tree, Qt.Key_Enter, Qt.NoModifier)
self.prefix = ""
# 1) Open an existing simple level
# 1) Open a new level helper.init_idle()
self.test_success = self.create_level( helper.open_level("Physics", "Base")
self.args["level"],
heightmap_resolution=1024,
heightmap_meters_per_pixel=1,
terrain_texture_resolution=4096,
use_terrain=False,
)
# 2) Create entity and add Mesh component # 2) Create entity and add Mesh component
entity_position = math.Vector3(125.0, 136.0, 32.0) entity_position = math.Vector3(125.0, 136.0, 32.0)
@ -222,7 +236,7 @@ class AssetPickerUIUXTest(EditorTestHelper):
# Assign Mesh Asset via OK button # Assign Mesh Asset via OK button
pyside_utils.click_button_async(attached_button) pyside_utils.click_button_async(attached_button)
await asset_picker("Mesh Asset", ["azmodel", "fbx"], "cedar (ModelAsset)", "ok") await asset_picker(["azmodel", "fbx"], "cedar (ModelAsset)", "ok")
# 5) Verify if Mesh Asset is assigned # 5) Verify if Mesh Asset is assigned
try: try:
@ -231,7 +245,11 @@ class AssetPickerUIUXTest(EditorTestHelper):
except pyside_utils.EventLoopTimeoutException as err: except pyside_utils.EventLoopTimeoutException as err:
print(err) print(err)
mesh_success = False mesh_success = False
self.test_success = mesh_success and self.test_success mesh_asset_assigned_ok = (
"Successfully assigned Mesh asset via OK button",
"Failed to assign Mesh asset via OK button"
)
Report.result(mesh_asset_assigned_ok, mesh_success)
# Clear Mesh Asset # Clear Mesh Asset
hydra.get_set_test(entity, 0, "Controller|Configuration|Mesh Asset", None) hydra.get_set_test(entity, 0, "Controller|Configuration|Mesh Asset", None)
@ -242,7 +260,7 @@ class AssetPickerUIUXTest(EditorTestHelper):
# Assign Mesh Asset via Enter # Assign Mesh Asset via Enter
pyside_utils.click_button_async(attached_button) pyside_utils.click_button_async(attached_button)
await asset_picker("Mesh Asset", ["azmodel", "fbx"], "cedar (ModelAsset)", "enter") await asset_picker(["azmodel", "fbx"], "cedar (ModelAsset)", "enter")
# 5) Verify if Mesh Asset is assigned # 5) Verify if Mesh Asset is assigned
try: try:
@ -251,8 +269,16 @@ class AssetPickerUIUXTest(EditorTestHelper):
except pyside_utils.EventLoopTimeoutException as err: except pyside_utils.EventLoopTimeoutException as err:
print(err) print(err)
mesh_success = False mesh_success = False
self.test_success = mesh_success and self.test_success mesh_asset_assigned_enter = (
"Successfully assigned Mesh asset via Enter button",
"Failed to assign Mesh asset via Enter button"
)
Report.result(mesh_asset_assigned_enter, mesh_success)
run_test()
if __name__ == "__main__":
test = AssetPickerUIUXTest() from editor_python_test_tools.utils import Report
test.run() Report.start_test(AssetPicker_UI_UX)

@ -5,36 +5,44 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT SPDX-License-Identifier: Apache-2.0 OR MIT
""" """
"""
C6351273: Create a new level
C6384955: Basic Workflow: Entity Manipulation in the Outliner
C16929880: Add Delete Components
C15167490: Save a level
C15167491: Export a level
"""
import os
import sys
from PySide2 import QtWidgets
import azlmbr.bus as bus
import azlmbr.editor as editor
import azlmbr.entity as entity
import azlmbr.math as math
import azlmbr.paths
sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests'))
from editor_python_test_tools.editor_test_helper import EditorTestHelper
import editor_python_test_tools.pyside_utils as pyside_utils
import editor_python_test_tools.hydra_editor_utils as hydra
class TestBasicEditorWorkflows(EditorTestHelper): class Tests:
def __init__(self): level_created = (
EditorTestHelper.__init__(self, log_prefix="BasicEditorWorkflows_LevelEntityComponent", args=["level"]) "New level created successfully",
"Failed to create new level"
)
new_entity_created = (
"New entity created successfully",
"Failed to create a new entity"
)
child_entity_created = (
"New child entity created successfully",
"Failed to create new child entity"
)
component_added = (
"Component added to entity successfully",
"Failed to add component to entity"
)
component_updated = (
"Component property updated successfully",
"Failed to update component property"
)
component_removed = (
"Component removed from entity successfully",
"Failed to remove component from entity"
)
level_saved_and_exported = (
"Level saved and exported successfully",
"Failed to save/export level"
)
def BasicEditorWorkflows_LevelEntityComponentCRUD():
import editor_python_test_tools.pyside_utils as pyside_utils
@pyside_utils.wrap_async @pyside_utils.wrap_async
async def run_test(self): async def run_test():
""" """
Summary: Summary:
Open O3DE editor and check if basic Editor workflows are completable. Open O3DE editor and check if basic Editor workflows are completable.
@ -55,6 +63,18 @@ class TestBasicEditorWorkflows(EditorTestHelper):
:return: None :return: None
""" """
import os
from PySide2 import QtWidgets
import azlmbr.bus as bus
import azlmbr.editor as editor
import azlmbr.entity as entity
import azlmbr.math as math
import azlmbr.paths
import editor_python_test_tools.hydra_editor_utils as hydra
from editor_python_test_tools.utils import Report
def find_entity_by_name(entity_name): def find_entity_by_name(entity_name):
search_filter = entity.SearchFilter() search_filter = entity.SearchFilter()
search_filter.names = [entity_name] search_filter.names = [entity_name]
@ -64,6 +84,7 @@ class TestBasicEditorWorkflows(EditorTestHelper):
return None return None
# 1) Create a new level # 1) Create a new level
level = "tmp_level"
editor_window = pyside_utils.get_editor_main_window() editor_window = pyside_utils.get_editor_main_window()
new_level_action = pyside_utils.get_action_for_menu_path(editor_window, "File", "New Level") new_level_action = pyside_utils.get_action_for_menu_path(editor_window, "File", "New Level")
pyside_utils.trigger_action_async(new_level_action) pyside_utils.trigger_action_async(new_level_action)
@ -71,21 +92,17 @@ class TestBasicEditorWorkflows(EditorTestHelper):
new_level_dlg = active_modal_widget.findChild(QtWidgets.QWidget, "CNewLevelDialog") new_level_dlg = active_modal_widget.findChild(QtWidgets.QWidget, "CNewLevelDialog")
if new_level_dlg: if new_level_dlg:
if new_level_dlg.windowTitle() == "New Level": if new_level_dlg.windowTitle() == "New Level":
self.log("New Level dialog opened") Report.info("New Level dialog opened")
grp_box = new_level_dlg.findChild(QtWidgets.QGroupBox, "STATIC_GROUP1") grp_box = new_level_dlg.findChild(QtWidgets.QGroupBox, "STATIC_GROUP1")
level_name = grp_box.findChild(QtWidgets.QLineEdit, "LEVEL") level_name = grp_box.findChild(QtWidgets.QLineEdit, "LEVEL")
level_name.setText(self.args["level"]) level_name.setText(level)
button_box = new_level_dlg.findChild(QtWidgets.QDialogButtonBox, "buttonBox") button_box = new_level_dlg.findChild(QtWidgets.QDialogButtonBox, "buttonBox")
button_box.button(QtWidgets.QDialogButtonBox.Ok).click() button_box.button(QtWidgets.QDialogButtonBox.Ok).click()
# Verify new level was created successfully # Verify new level was created successfully
level_create_success = await pyside_utils.wait_for_condition(lambda: editor.EditorToolsApplicationRequestBus( level_create_success = await pyside_utils.wait_for_condition(lambda: editor.EditorToolsApplicationRequestBus(
bus.Broadcast, "GetCurrentLevelName") == self.args["level"], 5.0) bus.Broadcast, "GetCurrentLevelName") == level, 5.0)
self.test_success = level_create_success Report.critical_result(Tests.level_created, level_create_success)
self.log(f"Create and load new level: {level_create_success}")
# Execute EditorTestHelper setup since level was created outside of EditorTestHelper's methods
self.test_success = self.test_success and self.after_level_load()
# 2) Delete existing entities, and create and manipulate new entities via Entity Inspector # 2) Delete existing entities, and create and manipulate new entities via Entity Inspector
search_filter = azlmbr.entity.SearchFilter() search_filter = azlmbr.entity.SearchFilter()
@ -99,8 +116,7 @@ class TestBasicEditorWorkflows(EditorTestHelper):
# Find the new entity # Find the new entity
parent_entity_id = find_entity_by_name("Entity1") parent_entity_id = find_entity_by_name("Entity1")
parent_entity_success = await pyside_utils.wait_for_condition(lambda: parent_entity_id is not None, 5.0) parent_entity_success = await pyside_utils.wait_for_condition(lambda: parent_entity_id is not None, 5.0)
self.test_success = self.test_success and parent_entity_success Report.critical_result(Tests.new_entity_created, parent_entity_success)
self.log(f"New entity creation: {parent_entity_success}")
# TODO: Replace Hydra call to creates child entity and add components with context menu triggering - LYN-3951 # TODO: Replace Hydra call to creates child entity and add components with context menu triggering - LYN-3951
# Create a new child entity # Create a new child entity
@ -111,29 +127,27 @@ class TestBasicEditorWorkflows(EditorTestHelper):
# Verify entity hierarchy # Verify entity hierarchy
child_entity.get_parent_info() child_entity.get_parent_info()
self.test_success = self.test_success and child_entity.parent_id == parent_entity_id Report.result(Tests.child_entity_created, child_entity.parent_id == parent_entity_id)
self.log(f"Create entity hierarchy: {child_entity.parent_id == parent_entity_id}")
# 3) Add/configure a component on an entity # 3) Add/configure a component on an entity
# Add component and verify success # Add component and verify success
child_entity.add_component("Box Shape") child_entity.add_component("Box Shape")
component_add_success = self.wait_for_condition(lambda: hydra.has_components(child_entity.id, ["Box Shape"]), 5.0) component_add_success = await pyside_utils.wait_for_condition(lambda: hydra.has_components(child_entity.id,
self.test_success = self.test_success and component_add_success ["Box Shape"]), 5.0)
self.log(f"Add component: {component_add_success}") Report.result(Tests.component_added, component_add_success)
# Update the component # Update the component
dimensions_to_set = math.Vector3(16.0, 16.0, 16.0) dimensions_to_set = math.Vector3(16.0, 16.0, 16.0)
child_entity.get_set_test(0, "Box Shape|Box Configuration|Dimensions", dimensions_to_set) child_entity.get_set_test(0, "Box Shape|Box Configuration|Dimensions", dimensions_to_set)
box_shape_dimensions = hydra.get_component_property_value(child_entity.components[0], "Box Shape|Box Configuration|Dimensions") box_shape_dimensions = hydra.get_component_property_value(child_entity.components[0],
self.test_success = self.test_success and box_shape_dimensions == dimensions_to_set "Box Shape|Box Configuration|Dimensions")
self.log(f"Component update: {box_shape_dimensions == dimensions_to_set}") Report.result(Tests.component_updated, box_shape_dimensions == dimensions_to_set)
# Remove the component # Remove the component
child_entity.remove_component("Box Shape") child_entity.remove_component("Box Shape")
component_rem_success = self.wait_for_condition(lambda: not hydra.has_components(child_entity.id, ["Box Shape"]), component_rem_success = await pyside_utils.wait_for_condition(lambda: not hydra.has_components(child_entity.id,
5.0) ["Box Shape"]), 5.0)
self.test_success = self.test_success and component_rem_success Report.result(Tests.component_removed, component_rem_success)
self.log(f"Remove component: {component_rem_success}")
# 4) Save the level # 4) Save the level
save_level_action = pyside_utils.get_action_for_menu_path(editor_window, "File", "Save") save_level_action = pyside_utils.get_action_for_menu_path(editor_window, "File", "Save")
@ -143,12 +157,15 @@ class TestBasicEditorWorkflows(EditorTestHelper):
export_action = pyside_utils.get_action_for_menu_path(editor_window, "Game", "Export to Engine") export_action = pyside_utils.get_action_for_menu_path(editor_window, "Game", "Export to Engine")
pyside_utils.trigger_action_async(export_action) pyside_utils.trigger_action_async(export_action)
level_pak_file = os.path.join( level_pak_file = os.path.join(
"AutomatedTesting", "Levels", self.args["level"], "level.pak" "AutomatedTesting", "Levels", level, "level.pak"
) )
export_success = self.wait_for_condition(lambda: os.path.exists(level_pak_file), 5.0) export_success = await pyside_utils.wait_for_condition(lambda: os.path.exists(level_pak_file), 5.0)
self.test_success = self.test_success and export_success Report.result(Tests.level_saved_and_exported, export_success)
self.log(f"Save and Export: {export_success}")
run_test()
if __name__ == "__main__":
test = TestBasicEditorWorkflows() from editor_python_test_tools.utils import Report
test.run() Report.start_test(BasicEditorWorkflows_LevelEntityComponentCRUD)

@ -5,37 +5,39 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT SPDX-License-Identifier: Apache-2.0 OR MIT
""" """
"""
C16929880: Add Delete Components
"""
import os
import sys
from PySide2 import QtWidgets, QtTest, QtCore
from PySide2.QtCore import Qt
import azlmbr.legacy.general as general
import azlmbr.bus as bus
import azlmbr.editor as editor
import azlmbr.entity as entity
import azlmbr.math as math
import azlmbr.paths
sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests'))
import editor_python_test_tools.hydra_editor_utils as hydra
import editor_python_test_tools.pyside_utils as pyside_utils
from editor_python_test_tools.editor_test_helper import EditorTestHelper
class AddDeleteComponentsTest(EditorTestHelper): class Tests:
def __init__(self): entity_created = (
EditorTestHelper.__init__(self, log_prefix="ComponentCRUD_Add_Delete_Components", args=["level"]) "Entity created successfully",
"Failed to create entity"
)
box_component_added = (
"Box Shape component added to entity",
"Failed to add Box Shape component to entity"
)
mesh_component_added = (
"Mesh component added to entity",
"Failed to add Mesh component to entity"
)
mesh_component_deleted = (
"Mesh component removed from entity",
"Failed to remove Mesh component from entity"
)
mesh_component_delete_undo = (
"Mesh component removal was successfully undone",
"Failed to undo Mesh component removal"
)
def ComponentCRUD_Add_Delete_Components():
import editor_python_test_tools.pyside_utils as pyside_utils
@pyside_utils.wrap_async @pyside_utils.wrap_async
async def run_test(self): async def run_test():
""" """
Summary: Summary:
Add/Delete Components to an entity. Add/Delete Components to/from an entity.
Expected Behavior: Expected Behavior:
1) Components can be added to an entity. 1) Components can be added to an entity.
@ -61,36 +63,43 @@ class AddDeleteComponentsTest(EditorTestHelper):
:return: None :return: None
""" """
from PySide2 import QtWidgets, QtTest, QtCore
from PySide2.QtCore import Qt
import azlmbr.legacy.general as general
import azlmbr.bus as bus
import azlmbr.editor as editor
import azlmbr.entity as entity
import azlmbr.math as math
import editor_python_test_tools.hydra_editor_utils as hydra
from editor_python_test_tools.utils import Report
from editor_python_test_tools.utils import TestHelper as helper
async def add_component(component_name): async def add_component(component_name):
pyside_utils.click_button_async(add_comp_btn) pyside_utils.click_button_async(add_comp_btn)
popup = await pyside_utils.wait_for_popup_widget() popup = await pyside_utils.wait_for_popup_widget()
tree = popup.findChild(QtWidgets.QTreeView, "Tree") tree = popup.findChild(QtWidgets.QTreeView, "Tree")
component_index = pyside_utils.find_child_by_pattern(tree, component_name) component_index = pyside_utils.find_child_by_pattern(tree, component_name)
if component_index.isValid(): if component_index.isValid():
print(f"{component_name} found") Report.info(f"{component_name} found")
tree.expand(component_index) tree.expand(component_index)
tree.setCurrentIndex(component_index) tree.setCurrentIndex(component_index)
QtTest.QTest.keyClick(tree, Qt.Key_Enter, Qt.NoModifier) QtTest.QTest.keyClick(tree, Qt.Key_Enter, Qt.NoModifier)
# 1) Open level # 1) Open an existing simple level
self.test_success = self.create_level( helper.init_idle()
self.args["level"], helper.open_level("Physics", "Base")
heightmap_resolution=1024,
heightmap_meters_per_pixel=1,
terrain_texture_resolution=4096,
use_terrain=False,
)
# 2) Create entity # 2) Create entity
entity_position = math.Vector3(125.0, 136.0, 32.0) entity_position = math.Vector3(125.0, 136.0, 32.0)
entity_id = editor.ToolsApplicationRequestBus( entity_id = editor.ToolsApplicationRequestBus(
bus.Broadcast, "CreateNewEntityAtPosition", entity_position, entity.EntityId() bus.Broadcast, "CreateNewEntityAtPosition", entity_position, entity.EntityId()
) )
if entity_id.IsValid(): Report.critical_result(Tests.entity_created, entity_id.IsValid())
print("Entity Created")
# 3) Select the newly created entity # 3) Select the newly created entity
general.select_object("Entity2") general.select_object("Entity1")
# Give the Entity Inspector time to fully create its contents # Give the Entity Inspector time to fully create its contents
general.idle_wait(0.5) general.idle_wait(0.5)
@ -100,11 +109,11 @@ class AddDeleteComponentsTest(EditorTestHelper):
entity_inspector = editor_window.findChild(QtWidgets.QDockWidget, "Entity Inspector") entity_inspector = editor_window.findChild(QtWidgets.QDockWidget, "Entity Inspector")
add_comp_btn = entity_inspector.findChild(QtWidgets.QPushButton, "m_addComponentButton") add_comp_btn = entity_inspector.findChild(QtWidgets.QPushButton, "m_addComponentButton")
await add_component("Box Shape") await add_component("Box Shape")
print(f"Box Shape Component added: {hydra.has_components(entity_id, ['Box Shape'])}") Report.result(Tests.box_component_added, hydra.has_components(entity_id, ['Box Shape']))
# 5) Add/verify Mesh component # 5) Add/verify Mesh component
await add_component("Mesh") await add_component("Mesh")
print(f"Mesh Component added: {hydra.has_components(entity_id, ['Mesh'])}") Report.result(Tests.mesh_component_added, hydra.has_components(entity_id, ['Mesh']))
# 6) Delete Mesh Component # 6) Delete Mesh Component
general.idle_wait(0.5) general.idle_wait(0.5)
@ -116,15 +125,17 @@ class AddDeleteComponentsTest(EditorTestHelper):
QtTest.QTest.mouseClick(mesh_frame, Qt.LeftButton, Qt.NoModifier) QtTest.QTest.mouseClick(mesh_frame, Qt.LeftButton, Qt.NoModifier)
QtTest.QTest.keyClick(mesh_frame, Qt.Key_Delete, Qt.NoModifier) QtTest.QTest.keyClick(mesh_frame, Qt.Key_Delete, Qt.NoModifier)
success = await pyside_utils.wait_for_condition(lambda: not hydra.has_components(entity_id, ['Mesh']), 5.0) success = await pyside_utils.wait_for_condition(lambda: not hydra.has_components(entity_id, ['Mesh']), 5.0)
if success: Report.result(Tests.mesh_component_deleted, success)
print(f"Mesh Component deleted: {not hydra.has_components(entity_id, ['Mesh'])}")
# 7) Undo deletion of component # 7) Undo deletion of component
QtTest.QTest.keyPress(entity_inspector, Qt.Key_Z, Qt.ControlModifier) QtTest.QTest.keyPress(entity_inspector, Qt.Key_Z, Qt.ControlModifier)
success = await pyside_utils.wait_for_condition(lambda: hydra.has_components(entity_id, ['Mesh']), 5.0) success = await pyside_utils.wait_for_condition(lambda: hydra.has_components(entity_id, ['Mesh']), 5.0)
if success: Report.result(Tests.mesh_component_delete_undo, success)
print(f"Mesh Component deletion undone: {hydra.has_components(entity_id, ['Mesh'])}")
run_test()
if __name__ == "__main__":
test = AddDeleteComponentsTest() from editor_python_test_tools.utils import Report
test.run() Report.start_test(ComponentCRUD_Add_Delete_Components)

@ -7,27 +7,32 @@ SPDX-License-Identifier: Apache-2.0 OR MIT
C6376081: Basic Function: Docked/Undocked Tools C6376081: Basic Function: Docked/Undocked Tools
""" """
import os
import sys
from PySide2 import QtWidgets, QtTest, QtCore
import azlmbr.legacy.general as general class Tests:
import azlmbr.bus as bus all_tools_docked = (
import azlmbr.editor as editor "The tools are all docked together in a tabbed widget",
import azlmbr.entity as entity "Failed to dock all tools together"
import azlmbr.paths )
docked_outliner_works = (
sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests')) "Entity Outliner works when docked, can select an Entity",
from editor_python_test_tools.editor_test_helper import EditorTestHelper "Failed to select an Entity in the Outliner while docked"
import editor_python_test_tools.pyside_utils as pyside_utils )
docked_inspector_works = (
"Entity Inspector works when docked, Entity name changed",
class TestDockingBasicDockedTools(EditorTestHelper): "Failed to change Entity name in the Inspector while docked"
def __init__(self): )
EditorTestHelper.__init__(self, log_prefix="Docking_BasicDockedTools", args=["level"]) docked_console_works = (
"Console works when docked, sent a Console Command",
"Failed to send Console Command in the Console while docked"
)
def Docking_BasicDockedTools():
import editor_python_test_tools.pyside_utils as pyside_utils
@pyside_utils.wrap_async @pyside_utils.wrap_async
async def run_test(self): async def run_test():
""" """
Summary: Summary:
Test that tools still work as expected when docked together. Test that tools still work as expected when docked together.
@ -50,14 +55,19 @@ class TestDockingBasicDockedTools(EditorTestHelper):
:return: None :return: None
""" """
# Create a level since we are going to be dealing with an Entity. from PySide2 import QtWidgets, QtTest, QtCore
self.create_level(
self.args["level"], import azlmbr.legacy.general as general
heightmap_resolution=1024, import azlmbr.bus as bus
heightmap_meters_per_pixel=1, import azlmbr.editor as editor
terrain_texture_resolution=4096, import azlmbr.entity as entity
use_terrain=False,
) from editor_python_test_tools.utils import Report
from editor_python_test_tools.utils import TestHelper as helper
# Open an existing simple level
helper.init_idle()
helper.open_level("Physics", "Base")
# Make sure the Entity Outliner, Entity Inspector and Console tools are open # Make sure the Entity Outliner, Entity Inspector and Console tools are open
general.open_pane("Entity Outliner (PREVIEW)") general.open_pane("Entity Outliner (PREVIEW)")
@ -101,12 +111,14 @@ class TestDockingBasicDockedTools(EditorTestHelper):
entity_inspector_parent = entity_inspector.parentWidget() entity_inspector_parent = entity_inspector.parentWidget()
entity_outliner_parent = entity_outliner.parentWidget() entity_outliner_parent = entity_outliner.parentWidget()
console_parent = console.parentWidget() console_parent = console.parentWidget()
print(f"Entity Inspector parent = {entity_inspector_parent}, Entity Outliner parent = {entity_outliner_parent}, Console parent = {console_parent}") Report.info(f"Entity Inspector parent = {entity_inspector_parent}, Entity Outliner parent = "
return isinstance(entity_inspector_parent, QtWidgets.QStackedWidget) and (entity_inspector_parent == entity_outliner_parent) and (entity_outliner_parent == console_parent) f"{entity_outliner_parent}, Console parent = {console_parent}")
return isinstance(entity_inspector_parent, QtWidgets.QStackedWidget) and \
(entity_inspector_parent == entity_outliner_parent) and \
(entity_outliner_parent == console_parent)
success = await pyside_utils.wait_for(check_all_panes_tabbed, timeout=3.0) success = await pyside_utils.wait_for(check_all_panes_tabbed, timeout=3.0)
if success: Report.result(Tests.all_tools_docked, success)
print("The tools are all docked together in a tabbed widget")
# 2.1,2) Select an Entity in the Entity Outliner. # 2.1,2) Select an Entity in the Entity Outliner.
entity_inspector = editor_window.findChild(QtWidgets.QDockWidget, "Entity Inspector") entity_inspector = editor_window.findChild(QtWidgets.QDockWidget, "Entity Inspector")
@ -116,8 +128,7 @@ class TestDockingBasicDockedTools(EditorTestHelper):
test_entity_index = pyside_utils.find_child_by_pattern(object_tree, entity_original_name) test_entity_index = pyside_utils.find_child_by_pattern(object_tree, entity_original_name)
object_tree.clearSelection() object_tree.clearSelection()
object_tree.setCurrentIndex(test_entity_index) object_tree.setCurrentIndex(test_entity_index)
if object_tree.currentIndex(): Report.result(Tests.docked_outliner_works, object_tree.currentIndex() == test_entity_index)
print("Entity Outliner works when docked, can select an Entity")
# 2.3,4) Change the name of the selected Entity via the Entity Inspector. # 2.3,4) Change the name of the selected Entity via the Entity Inspector.
entity_inspector_name_field = entity_inspector.findChild(QtWidgets.QLineEdit, "m_entityNameEditor") entity_inspector_name_field = entity_inspector.findChild(QtWidgets.QLineEdit, "m_entityNameEditor")
@ -125,14 +136,23 @@ class TestDockingBasicDockedTools(EditorTestHelper):
entity_inspector_name_field.setText(expected_new_name) entity_inspector_name_field.setText(expected_new_name)
QtTest.QTest.keyClick(entity_inspector_name_field, QtCore.Qt.Key_Enter) QtTest.QTest.keyClick(entity_inspector_name_field, QtCore.Qt.Key_Enter)
entity_new_name = editor.EditorEntityInfoRequestBus(bus.Event, "GetName", entity_id) entity_new_name = editor.EditorEntityInfoRequestBus(bus.Event, "GetName", entity_id)
if entity_new_name == expected_new_name: Report.result(Tests.docked_inspector_works, entity_new_name == expected_new_name)
print(f"Entity Inspector works when docked, Entity name changed to {entity_new_name}")
# 2.5,6) Send a console command. # 2.5,6) Send a console command.
console_line_edit = console.findChild(QtWidgets.QLineEdit, "lineEdit") console_line_edit = console.findChild(QtWidgets.QLineEdit, "lineEdit")
console_line_edit.setText("Hello, world!") console_line_edit.setText("t_Scale 2")
QtTest.QTest.keyClick(console_line_edit, QtCore.Qt.Key_Enter)
general.get_cvar("t_Scale")
Report.result(Tests.docked_console_works, general.get_cvar("t_Scale") == "2")
# Reset the altered cvar
console_line_edit.setText("t_Scale 1")
QtTest.QTest.keyClick(console_line_edit, QtCore.Qt.Key_Enter) QtTest.QTest.keyClick(console_line_edit, QtCore.Qt.Key_Enter)
run_test()
if __name__ == "__main__":
test = TestDockingBasicDockedTools() from editor_python_test_tools.utils import Report
test.run() Report.start_test(Docking_BasicDockedTools)

@ -5,32 +5,36 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT SPDX-License-Identifier: Apache-2.0 OR MIT
""" """
"""
C1506881: Adding/Removing Event Groups
"""
import os
import sys
from PySide2 import QtWidgets
import azlmbr.legacy.general as general
import azlmbr.bus as bus
import azlmbr.editor as editor
import azlmbr.entity as entity
import azlmbr.math as math
import azlmbr.paths
sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests'))
import editor_python_test_tools.hydra_editor_utils as hydra
import editor_python_test_tools.pyside_utils as pyside_utils
from editor_python_test_tools.editor_test_helper import EditorTestHelper
class AddRemoveInputEventsTest(EditorTestHelper): class Tests:
def __init__(self): asset_editor_opened = (
EditorTestHelper.__init__(self, log_prefix="InputBindings_Add_Remove_Input_Events", args=["level"]) "Successfully opened the Asset Editor",
"Failed to open the Asset Editor"
)
event_groups_added = (
"Successfully added event groups via +",
"Failed to add event groups"
)
single_event_group_deleted = (
"Successfully deleted an event group",
"Failed to delete event group"
)
all_event_groups_deleted = (
"Successfully deleted all event groups",
"Failed to delete all event groups"
)
asset_editor_closed = (
"Successfully closed the Asset Editor",
"Failed to close the Asset Editor"
)
def InputBindings_Add_Remove_Input_Events():
import editor_python_test_tools.pyside_utils as pyside_utils
@pyside_utils.wrap_async @pyside_utils.wrap_async
async def run_test(self): async def run_test():
""" """
Summary: Summary:
Verify if we are able add/remove input events in inputbindings file. Verify if we are able add/remove input events in inputbindings file.
@ -42,7 +46,7 @@ class AddRemoveInputEventsTest(EditorTestHelper):
Test Steps: Test Steps:
1) Open a new level 1) Open an existing level
2) Open Asset Editor 2) Open Asset Editor
3) Access Asset Editor 3) Access Asset Editor
4) Create a new .inputbindings file and add event groups 4) Create a new .inputbindings file and add event groups
@ -61,6 +65,13 @@ class AddRemoveInputEventsTest(EditorTestHelper):
:return: None :return: None
""" """
from PySide2 import QtWidgets
import azlmbr.legacy.general as general
from editor_python_test_tools.utils import Report
from editor_python_test_tools.utils import TestHelper as helper
def open_asset_editor(): def open_asset_editor():
general.open_pane("Asset Editor") general.open_pane("Asset Editor")
return general.is_pane_visible("Asset Editor") return general.is_pane_visible("Asset Editor")
@ -69,17 +80,12 @@ class AddRemoveInputEventsTest(EditorTestHelper):
general.close_pane("Asset Editor") general.close_pane("Asset Editor")
return not general.is_pane_visible("Asset Editor") return not general.is_pane_visible("Asset Editor")
# 1) Open a new level # 1) Open an existing simple level
self.test_success = self.create_level( helper.init_idle()
self.args["level"], helper.open_level("Physics", "Base")
heightmap_resolution=1024,
heightmap_meters_per_pixel=1,
terrain_texture_resolution=4096,
use_terrain=False,
)
# 2) Open Asset Editor # 2) Open Asset Editor
print(f"Asset Editor opened: {open_asset_editor()}") Report.result(Tests.asset_editor_opened, open_asset_editor())
# 3) Access Asset Editor # 3) Access Asset Editor
editor_window = pyside_utils.get_editor_main_window() editor_window = pyside_utils.get_editor_main_window()
@ -103,8 +109,7 @@ class AddRemoveInputEventsTest(EditorTestHelper):
# 5) Verify if there are 3 elements in the Input Event Groups label # 5) Verify if there are 3 elements in the Input Event Groups label
no_of_elements_label = input_event_groups.findChild(QtWidgets.QLabel, "DefaultLabel") no_of_elements_label = input_event_groups.findChild(QtWidgets.QLabel, "DefaultLabel")
success = await pyside_utils.wait_for_condition(lambda: "3 elements" in no_of_elements_label.text(), 2.0) success = await pyside_utils.wait_for_condition(lambda: "3 elements" in no_of_elements_label.text(), 2.0)
if success: Report.result(Tests.event_groups_added, success)
print("New Event Groups added when + is clicked")
# 6) Delete one event group # 6) Delete one event group
event = asset_editor_widget.findChildren(QtWidgets.QFrame, "<Unspecified Event>")[0] event = asset_editor_widget.findChildren(QtWidgets.QFrame, "<Unspecified Event>")[0]
@ -121,11 +126,11 @@ class AddRemoveInputEventsTest(EditorTestHelper):
input_event_group = input_event_groups[1] input_event_group = input_event_groups[1]
no_of_elements_label = input_event_group.findChild(QtWidgets.QLabel, "DefaultLabel") no_of_elements_label = input_event_group.findChild(QtWidgets.QLabel, "DefaultLabel")
return no_of_elements_label.text() return no_of_elements_label.text()
return ""
return ""; success = await pyside_utils.wait_for_condition(lambda: "2 elements" in
success = await pyside_utils.wait_for_condition(lambda: "2 elements" in get_elements_label_text(asset_editor_widget), 2.0) get_elements_label_text(asset_editor_widget), 2.0)
if success: Report.result(Tests.single_event_group_deleted, success)
print("Event Group deleted when the Delete button is clicked on an Event Group")
# 8) Click on Delete button to delete all the Event Groups # 8) Click on Delete button to delete all the Event Groups
# First QToolButton child of active input_event_groups is +, Second QToolButton is Delete # First QToolButton child of active input_event_groups is +, Second QToolButton is Delete
@ -141,13 +146,17 @@ class AddRemoveInputEventsTest(EditorTestHelper):
yes_button.click() yes_button.click()
# 9) Verify if all the elements are deleted # 9) Verify if all the elements are deleted
success = await pyside_utils.wait_for_condition(lambda: "0 elements" in get_elements_label_text(asset_editor_widget), 2.0) success = await pyside_utils.wait_for_condition(lambda: "0 elements" in
if success: get_elements_label_text(asset_editor_widget), 2.0)
print("All event groups deleted on clicking the Delete button") Report.result(Tests.all_event_groups_deleted, success)
# 10) Close Asset Editor # 10) Close Asset Editor
print(f"Asset Editor closed: {close_asset_editor()}") Report.result(Tests.asset_editor_closed, close_asset_editor())
run_test()
if __name__ == "__main__":
test = AddRemoveInputEventsTest() from editor_python_test_tools.utils import Report
test.run() Report.start_test(InputBindings_Add_Remove_Input_Events)

@ -5,93 +5,78 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT SPDX-License-Identifier: Apache-2.0 OR MIT
""" """
"""
C24064529: Base Edit Menu Options
"""
import os
import sys
import azlmbr.paths
sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests'))
from editor_python_test_tools.editor_test_helper import EditorTestHelper
import editor_python_test_tools.pyside_utils as pyside_utils
class TestEditMenuOptions(EditorTestHelper):
def __init__(self):
EditorTestHelper.__init__(self, log_prefix="Menus_EditMenuOptions", args=["level"])
def run_test(self):
"""
Summary:
Interact with Edit Menu options and verify if all the options are working.
Expected Behavior:
The Edit menu functions normally.
Test Steps: def Menus_EditMenuOptions_Work():
1) Create a temp level """
2) Interact with Edit Menu options Summary:
Interact with Edit Menu options and verify if all the options are working.
Note:
- This test file must be called from the O3DE Editor command terminal Expected Behavior:
- Any passed and failed tests are written to the Editor.log file. The Edit menu functions normally.
Parsing the file or running a log_monitor are required to observe the test results.
Test Steps:
:return: None 1) Open an existing level
""" 2) Interact with Edit Menu options
edit_menu_options = [
("Undo",), Note:
("Redo",), - This test file must be called from the O3DE Editor command terminal
("Duplicate",), - Any passed and failed tests are written to the Editor.log file.
("Delete",), Parsing the file or running a log_monitor are required to observe the test results.
("Select All",),
("Invert Selection",), :return: None
("Toggle Pivot Location",), """
("Reset Entity Transform",),
("Reset Manipulator",), import editor_python_test_tools.pyside_utils as pyside_utils
("Reset Transform (Local)",), from editor_python_test_tools.utils import Report
("Reset Transform (World)",), from editor_python_test_tools.utils import TestHelper as helper
("Hide Selection",),
("Show All",), edit_menu_options = [
("Modify", "Snap", "Snap angle"), ("Undo",),
("Modify", "Transform Mode", "Move"), ("Redo",),
("Modify", "Transform Mode", "Rotate"), ("Duplicate",),
("Modify", "Transform Mode", "Scale"), ("Delete",),
("Editor Settings", "Global Preferences"), ("Select All",),
("Editor Settings", "Editor Settings Manager"), ("Invert Selection",),
("Editor Settings", "Keyboard Customization", "Customize Keyboard"), ("Toggle Pivot Location",),
("Editor Settings", "Keyboard Customization", "Export Keyboard Settings"), ("Reset Entity Transform",),
("Editor Settings", "Keyboard Customization", "Import Keyboard Settings"), ("Reset Manipulator",),
] ("Reset Transform (Local)",),
("Reset Transform (World)",),
# 1) Create and open the temp level ("Hide Selection",),
self.test_success = self.create_level( ("Show All",),
self.args["level"], ("Modify", "Snap", "Snap angle"),
heightmap_resolution=1024, ("Modify", "Transform Mode", "Move"),
heightmap_meters_per_pixel=1, ("Modify", "Transform Mode", "Rotate"),
terrain_texture_resolution=4096, ("Modify", "Transform Mode", "Scale"),
use_terrain=False, ("Editor Settings", "Global Preferences"),
) ("Editor Settings", "Editor Settings Manager"),
("Editor Settings", "Keyboard Customization", "Customize Keyboard"),
def on_action_triggered(action_name): ("Editor Settings", "Keyboard Customization", "Export Keyboard Settings"),
print(f"{action_name} Action triggered") ("Editor Settings", "Keyboard Customization", "Import Keyboard Settings"),
]
# 2) Interact with Edit Menu options
# 1) Open an existing simple level
helper.init_idle()
helper.open_level("Physics", "Base")
# 2) Interact with Edit Menu options
editor_window = pyside_utils.get_editor_main_window()
for option in edit_menu_options:
try: try:
editor_window = pyside_utils.get_editor_main_window() action = pyside_utils.get_action_for_menu_path(editor_window, "Edit", *option)
for option in edit_menu_options: action.trigger()
action = pyside_utils.get_action_for_menu_path(editor_window, "Edit", *option) action_triggered = True
trig_func = lambda: on_action_triggered(action.iconText())
action.triggered.connect(trig_func)
action.trigger()
action.triggered.disconnect(trig_func)
except Exception as e: except Exception as e:
self.test_success = False action_triggered = False
print(e) print(e)
menu_action_triggered = (
f"{action.iconText()} action triggered successfully",
f"Failed to trigger {action.iconText()} action"
)
Report.result(menu_action_triggered, action_triggered)
if __name__ == "__main__":
test = TestEditMenuOptions() from editor_python_test_tools.utils import Report
test.run() Report.start_test(Menus_EditMenuOptions_Work)

@ -5,80 +5,69 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT SPDX-License-Identifier: Apache-2.0 OR MIT
""" """
import os
import sys
import azlmbr.paths def Menus_FileMenuOptions_Work():
"""
sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests')) Summary:
from editor_python_test_tools.editor_test_helper import EditorTestHelper Interact with File Menu options and verify if all the options are working.
import editor_python_test_tools.pyside_utils as pyside_utils
Expected Behavior:
The File menu functions normally.
class TestFileMenuOptions(EditorTestHelper):
def __init__(self): Test Steps:
EditorTestHelper.__init__(self, log_prefix="file_menu_options: ", args=["level"]) 1) Open level
2) Interact with File Menu options
def run_test(self):
""" Note:
Summary: - This test file must be called from the O3DE Editor command terminal
Interact with File Menu options and verify if all the options are working. - Any passed and failed tests are written to the Editor.log file.
Parsing the file or running a log_monitor are required to observe the test results.
Expected Behavior:
The File menu functions normally. :return: None
"""
Test Steps:
1) Open level import editor_python_test_tools.pyside_utils as pyside_utils
2) Interact with File Menu options from editor_python_test_tools.utils import Report
from editor_python_test_tools.utils import TestHelper as helper
Note:
- This test file must be called from the O3DE Editor command terminal file_menu_options = [
- Any passed and failed tests are written to the Editor.log file. ("New Level",),
Parsing the file or running a log_monitor are required to observe the test results. ("Open Level",),
("Import",),
:return: None ("Save",),
""" ("Save As",),
file_menu_options = [ ("Save Level Statistics",),
("New Level",), ("Edit Project Settings",),
("Open Level",), ("Edit Platform Settings",),
("Import",), ("New Project",),
("Save",), ("Open Project",),
("Save As",), ("Show Log File",),
("Save Level Statistics",), ("Resave All Slices",),
("Edit Project Settings",), ("Exit",),
("Edit Platform Settings",), ]
("New Project",),
("Open Project",), # 1) Open an existing simple level
("Show Log File",), helper.init_idle()
("Resave All Slices",), helper.open_level("Physics", "Base")
("Exit",),
] # 2) Interact with File Menu options
editor_window = pyside_utils.get_editor_main_window()
# 1) Open level for option in file_menu_options:
self.test_success = self.create_level(
self.args["level"],
heightmap_resolution=1024,
heightmap_meters_per_pixel=1,
terrain_texture_resolution=4096,
use_terrain=False,
)
def on_action_triggered(action_name):
print(f"{action_name} Action triggered")
# 2) Interact with File Menu options
try: try:
editor_window = pyside_utils.get_editor_main_window() action = pyside_utils.get_action_for_menu_path(editor_window, "File", *option)
for option in file_menu_options: action.trigger()
action = pyside_utils.get_action_for_menu_path(editor_window, "File", *option) action_triggered = True
trig_func = lambda: on_action_triggered(action.iconText())
action.triggered.connect(trig_func)
action.trigger()
action.triggered.disconnect(trig_func)
except Exception as e: except Exception as e:
self.test_success = False action_triggered = False
print(e) print(e)
menu_action_triggered = (
f"{action.iconText()} action triggered successfully",
f"Failed to trigger {action.iconText()} action"
)
Report.result(menu_action_triggered, action_triggered)
if __name__ == "__main__":
test = TestFileMenuOptions() from editor_python_test_tools.utils import Report
test.run() Report.start_test(Menus_FileMenuOptions_Work)

@ -5,81 +5,66 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT SPDX-License-Identifier: Apache-2.0 OR MIT
""" """
"""
C24064534: The View menu options function normally
"""
import os
import sys
import azlmbr.paths
sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests'))
from editor_python_test_tools.editor_test_helper import EditorTestHelper
import editor_python_test_tools.pyside_utils as pyside_utils
class TestViewMenuOptions(EditorTestHelper):
def __init__(self):
EditorTestHelper.__init__(self, log_prefix="Menus_EditMenuOptions", args=["level"])
def run_test(self):
"""
Summary:
Interact with View Menu options and verify if all the options are working.
Expected Behavior:
The View menu functions normally.
Test Steps: def Menus_ViewMenuOptions_Work():
1) Create a temp level """
2) Interact with View Menu options Summary:
Interact with View Menu options and verify if all the options are working.
Note:
- This test file must be called from the O3DE Editor command terminal Expected Behavior:
- Any passed and failed tests are written to the Editor.log file. The View menu functions normally.
Parsing the file or running a log_monitor are required to observe the test results.
Test Steps:
:return: None 1) Open an existing level
""" 2) Interact with View Menu options
view_menu_options = [
("Center on Selection",), Note:
("Show Quick Access Bar",), - This test file must be called from the O3DE Editor command terminal
("Viewport", "Configure Layout"), - Any passed and failed tests are written to the Editor.log file.
("Viewport", "Go to Position"), Parsing the file or running a log_monitor are required to observe the test results.
("Viewport", "Center on Selection"),
("Viewport", "Go to Location"), :return: None
("Viewport", "Remember Location"), """
("Viewport", "Switch Camera"),
("Viewport", "Show/Hide Helpers"), import editor_python_test_tools.pyside_utils as pyside_utils
("Refresh Style",), from editor_python_test_tools.utils import Report
] from editor_python_test_tools.utils import TestHelper as helper
# 1) Create and open the temp level view_menu_options = [
self.test_success = self.create_level( ("Center on Selection",),
self.args["level"], ("Show Quick Access Bar",),
heightmap_resolution=1024, ("Viewport", "Configure Layout"),
heightmap_meters_per_pixel=1, ("Viewport", "Go to Position"),
terrain_texture_resolution=4096, ("Viewport", "Center on Selection"),
use_terrain=False, ("Viewport", "Go to Location"),
) ("Viewport", "Remember Location"),
("Viewport", "Switch Camera"),
def on_action_triggered(action_name): ("Viewport", "Show/Hide Helpers"),
print(f"{action_name} Action triggered") ("Refresh Style",),
]
# 2) Interact with View Menu options
# 1) Open an existing simple level
helper.init_idle()
helper.open_level("Physics", "Base")
# 2) Interact with View Menu options
editor_window = pyside_utils.get_editor_main_window()
for option in view_menu_options:
try: try:
editor_window = pyside_utils.get_editor_main_window() action = pyside_utils.get_action_for_menu_path(editor_window, "View", *option)
for option in view_menu_options: action.trigger()
action = pyside_utils.get_action_for_menu_path(editor_window, "View", *option) action_triggered = True
trig_func = lambda: on_action_triggered(action.iconText())
action.triggered.connect(trig_func)
action.trigger()
action.triggered.disconnect(trig_func)
except Exception as e: except Exception as e:
self.test_success = False action_triggered = False
print(e) print(e)
menu_action_triggered = (
f"{action.iconText()} action triggered successfully",
f"Failed to trigger {action.iconText()} action"
)
Report.result(menu_action_triggered, action_triggered)
if __name__ == "__main__":
test = TestViewMenuOptions() from editor_python_test_tools.utils import Report
test.run() Report.start_test(Menus_ViewMenuOptions_Work)

@ -0,0 +1,43 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
import os
import pytest
import sys
import ly_test_tools.environment.file_system as file_system
sys.path.append(os.path.dirname(os.path.abspath(__file__)) + '/../automatedtesting_shared')
from base import TestAutomationBase
@pytest.fixture
def remove_test_level(request, workspace, project):
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", "tmp_level")], True, True)
def teardown():
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", "tmp_level")], True, True)
request.addfinalizer(teardown)
@pytest.mark.SUITE_main
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
@pytest.mark.parametrize("project", ["AutomatedTesting"])
class TestAutomation(TestAutomationBase):
def test_BasicEditorWorkflows_LevelEntityComponentCRUD(self, request, workspace, editor, launcher_platform,
remove_test_level):
from .EditorScripts import BasicEditorWorkflows_LevelEntityComponentCRUD as test_module
self._run_test(request, workspace, editor, test_module, batch_mode=False, autotest_mode=False)
@pytest.mark.REQUIRES_gpu
def test_BasicEditorWorkflows_GPU_LevelEntityComponentCRUD(self, request, workspace, editor, launcher_platform,
remove_test_level):
from .EditorScripts import BasicEditorWorkflows_LevelEntityComponentCRUD as test_module
self._run_test(request, workspace, editor, test_module, batch_mode=False, autotest_mode=False,
use_null_renderer=False)

@ -0,0 +1,75 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
import os
import pytest
import ly_test_tools.environment.file_system as file_system
from ly_test_tools.o3de.editor_test import EditorSingleTest, EditorSharedTest, EditorParallelTest, EditorTestSuite
@pytest.mark.xfail(reason="Optimized tests are experimental, we will enable xfail and monitor them temporarily.")
@pytest.mark.SUITE_main
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
@pytest.mark.parametrize("project", ["AutomatedTesting"])
class TestAutomationNoAutoTestMode(EditorTestSuite):
# Disable -autotest_mode and -BatchMode. Tests cannot run in -BatchMode due to UI interactions, and these tests
# interact with modal dialogs
global_extra_cmdline_args = []
class test_BasicEditorWorkflows_LevelEntityComponentCRUD(EditorSingleTest):
# Custom teardown to remove slice asset created during test
def teardown(self, request, workspace, editor, editor_test_results, launcher_platform):
file_system.delete([os.path.join(workspace.paths.engine_root(), "AutomatedTesting", "Levels", "tmp_level")],
True, True)
from .EditorScripts import BasicEditorWorkflows_LevelEntityComponentCRUD as test_module
@pytest.mark.REQUIRES_gpu
class test_BasicEditorWorkflows_GPU_LevelEntityComponentCRUD(EditorSingleTest):
# Disable null renderer
use_null_renderer = False
# Custom teardown to remove slice asset created during test
def teardown(self, request, workspace, editor, editor_test_results, launcher_platform):
file_system.delete([os.path.join(workspace.paths.engine_root(), "AutomatedTesting", "Levels", "tmp_level")],
True, True)
from .EditorScripts import BasicEditorWorkflows_LevelEntityComponentCRUD as test_module
class test_InputBindings_Add_Remove_Input_Events(EditorSharedTest):
from .EditorScripts import InputBindings_Add_Remove_Input_Events as test_module
@pytest.mark.skip(reason="Crashes Editor: ATOM-15493")
class test_AssetPicker_UI_UX(EditorSharedTest):
from .EditorScripts import AssetPicker_UI_UX as test_module
@pytest.mark.xfail(reason="Optimized tests are experimental, we will enable xfail and monitor them temporarily.")
@pytest.mark.SUITE_main
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
@pytest.mark.parametrize("project", ["AutomatedTesting"])
class TestAutomationAutoTestMode(EditorTestSuite):
# Enable only -autotest_mode for these tests. Tests cannot run in -BatchMode due to UI interactions
global_extra_cmdline_args = ["-autotest_mode"]
class test_AssetBrowser_TreeNavigation(EditorSharedTest):
from .EditorScripts import AssetBrowser_TreeNavigation as test_module
@pytest.mark.skip(reason="Crashes Editor: ATOM-15493")
class test_AssetBrowser_SearchFiltering(EditorSharedTest):
from .EditorScripts import AssetBrowser_SearchFiltering as test_module
class test_ComponentCRUD_Add_Delete_Components(EditorSharedTest):
from .EditorScripts import ComponentCRUD_Add_Delete_Components as test_module
class test_Menus_ViewMenuOptions_Work(EditorSharedTest):
from .EditorScripts import Menus_ViewMenuOptions as test_module
@pytest.mark.skip(reason="Times out due to dialogs failing to dismiss: LYN-4208")
class test_Menus_FileMenuOptions_Work(EditorSharedTest):
from .EditorScripts import Menus_FileMenuOptions as test_module

@ -0,0 +1,62 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
import os
import pytest
import sys
import ly_test_tools.environment.file_system as file_system
sys.path.append(os.path.dirname(os.path.abspath(__file__)) + '/../automatedtesting_shared')
from base import TestAutomationBase
@pytest.fixture
def remove_test_level(request, workspace, project):
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", "tmp_level")], True, True)
def teardown():
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", "tmp_level")], True, True)
request.addfinalizer(teardown)
@pytest.mark.SUITE_periodic
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
@pytest.mark.parametrize("project", ["AutomatedTesting"])
class TestAutomation(TestAutomationBase):
def test_AssetBrowser_TreeNavigation(self, request, workspace, editor, launcher_platform):
from .EditorScripts import AssetBrowser_TreeNavigation as test_module
self._run_test(request, workspace, editor, test_module, batch_mode=False)
@pytest.mark.skip(reason="Crashes Editor: ATOM-15493")
def test_AssetBrowser_SearchFiltering(self, request, workspace, editor, launcher_platform):
from .EditorScripts import AssetBrowser_SearchFiltering as test_module
self._run_test(request, workspace, editor, test_module, batch_mode=False)
@pytest.mark.skip(reason="Crashes Editor: ATOM-15493")
def test_AssetPicker_UI_UX(self, request, workspace, editor, launcher_platform):
from .EditorScripts import AssetPicker_UI_UX as test_module
self._run_test(request, workspace, editor, test_module, autotest_mode=False, batch_mode=False)
def test_ComponentCRUD_Add_Delete_Components(self, request, workspace, editor, launcher_platform):
from .EditorScripts import ComponentCRUD_Add_Delete_Components as test_module
self._run_test(request, workspace, editor, test_module, batch_mode=False)
def test_InputBindings_Add_Remove_Input_Events(self, request, workspace, editor, launcher_platform):
from .EditorScripts import InputBindings_Add_Remove_Input_Events as test_module
self._run_test(request, workspace, editor, test_module, batch_mode=False, autotest_mode=False)
def test_Menus_ViewMenuOptions_Work(self, request, workspace, editor, launcher_platform):
from .EditorScripts import Menus_ViewMenuOptions as test_module
self._run_test(request, workspace, editor, test_module, batch_mode=False)
@pytest.mark.skip(reason="Times out due to dialogs failing to dismiss: LYN-4208")
def test_Menus_FileMenuOptions_Work(self, request, workspace, editor, launcher_platform):
from .EditorScripts import Menus_FileMenuOptions as test_module
self._run_test(request, workspace, editor, test_module, batch_mode=False)

@ -0,0 +1,27 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
import os
import pytest
import sys
sys.path.append(os.path.dirname(os.path.abspath(__file__)) + '/../automatedtesting_shared')
from base import TestAutomationBase
@pytest.mark.SUITE_sandbox
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
@pytest.mark.parametrize("project", ["AutomatedTesting"])
class TestAutomation(TestAutomationBase):
def test_Menus_EditMenuOptions_Work(self, request, workspace, editor, launcher_platform):
from .EditorScripts import Menus_EditMenuOptions as test_module
self._run_test(request, workspace, editor, test_module, batch_mode=False)
def test_Docking_BasicDockedTools(self, request, workspace, editor, launcher_platform):
from .EditorScripts import Docking_BasicDockedTools as test_module
self._run_test(request, workspace, editor, test_module, batch_mode=False)

@ -0,0 +1,27 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
import os
import pytest
from ly_test_tools.o3de.editor_test import EditorSingleTest, EditorSharedTest, EditorParallelTest, EditorTestSuite
@pytest.mark.xfail(reason="Optimized tests are experimental, we will enable xfail and monitor them temporarily.")
@pytest.mark.SUITE_sandbox
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
@pytest.mark.parametrize("project", ["AutomatedTesting"])
class TestAutomationAutoTestMode(EditorTestSuite):
# Enable only -autotest_mode for these tests. Tests cannot run in -BatchMode due to UI interactions
global_extra_cmdline_args = ["-autotest_mode"]
class test_Docking_BasicDockedTools(EditorSharedTest):
from .EditorScripts import Docking_BasicDockedTools as test_module
class test_Menus_EditMenuOptions_Work(EditorSharedTest):
from .EditorScripts import Menus_EditMenuOptions as test_module

@ -1,89 +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
"""
"""
C13660195: Asset Browser - File Tree Navigation
"""
import os
import pytest
# Bail on the test if ly_test_tools doesn't exist.
pytest.importorskip('ly_test_tools')
import ly_test_tools.environment.file_system as file_system
import editor_python_test_tools.hydra_test_utils as hydra
test_directory = os.path.join(os.path.dirname(__file__), "EditorScripts")
log_monitor_timeout = 180
@pytest.mark.parametrize('project', ['AutomatedTesting'])
@pytest.mark.parametrize('level', ['tmp_level'])
@pytest.mark.usefixtures("automatic_process_killer")
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
class TestAssetBrowser(object):
@pytest.fixture(autouse=True)
def setup_teardown(self, request, workspace, project, level):
def teardown():
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
request.addfinalizer(teardown)
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
@pytest.mark.test_case_id("C13660195")
@pytest.mark.SUITE_periodic
def test_AssetBrowser_TreeNavigation(self, request, editor, level, launcher_platform):
expected_lines = [
"Collapse/Expand tests: True",
"Asset visibility test: True",
"Scrollbar visibility test: True",
"AssetBrowser_TreeNavigation: result=SUCCESS"
]
hydra.launch_and_validate_results(
request,
test_directory,
editor,
"AssetBrowser_TreeNavigation.py",
expected_lines,
run_python="--runpython",
cfg_args=[level],
timeout=log_monitor_timeout
)
@pytest.mark.test_case_id("C13660194")
@pytest.mark.SUITE_periodic
def test_AssetBrowser_SearchFiltering(self, request, editor, level, launcher_platform):
expected_lines = [
"cedar.fbx asset is filtered in Asset Browser",
"Animation file type(s) is present in the file tree: True",
"FileTag file type(s) and Animation file type(s) is present in the file tree: True",
"FileTag file type(s) is present in the file tree after removing Animation filter: True",
]
unexpected_lines = [
"Asset Browser opened: False",
"Animation file type(s) is present in the file tree: False",
"FileTag file type(s) and Animation file type(s) is present in the file tree: False",
"FileTag file type(s) is present in the file tree after removing Animation filter: False",
]
hydra.launch_and_validate_results(
request,
test_directory,
editor,
"AssetBrowser_SearchFiltering.py",
expected_lines,
unexpected_lines=unexpected_lines,
cfg_args=[level],
auto_test_mode=False,
run_python="--runpython",
timeout=log_monitor_timeout,
)

@ -1,74 +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
"""
"""
C13751579: Asset Picker UI/UX
"""
import os
import pytest
# Bail on the test if ly_test_tools doesn't exist.
pytest.importorskip('ly_test_tools')
import ly_test_tools.environment.file_system as file_system
import editor_python_test_tools.hydra_test_utils as hydra
test_directory = os.path.join(os.path.dirname(__file__), "EditorScripts")
log_monitor_timeout = 90
@pytest.mark.parametrize('project', ['AutomatedTesting'])
@pytest.mark.parametrize('level', ['tmp_level'])
@pytest.mark.usefixtures("automatic_process_killer")
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
class TestAssetPicker(object):
@pytest.fixture(autouse=True)
def setup_teardown(self, request, workspace, project, level):
def teardown():
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
request.addfinalizer(teardown)
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
@pytest.mark.test_case_id("C13751579", "C1508814")
@pytest.mark.SUITE_periodic
@pytest.mark.xfail # ATOM-15493
def test_AssetPicker_UI_UX(self, request, editor, level, launcher_platform):
expected_lines = [
"TestEntity Entity successfully created",
"Mesh component was added to entity",
"Entity has a Mesh component",
"Mesh Asset: Asset Picker title for Mesh: Pick ModelAsset",
"Mesh Asset: Scroll Bar is not visible before expanding the tree: True",
"Mesh Asset: Top level folder initially collapsed: True",
"Mesh Asset: Top level folder expanded: True",
"Mesh Asset: Nested folder initially collapsed: True",
"Mesh Asset: Nested folder expanded: True",
"Mesh Asset: Scroll Bar appeared after expanding tree: True",
"Mesh Asset: Nested folder collapsed: True",
"Mesh Asset: Top level folder collapsed: True",
"Mesh Asset: Expected Assets populated in the file picker: True",
"Widget Move Test: True",
"Widget Resize Test: True",
"Asset assigned for ok option: True",
"Asset assigned for enter option: True",
"AssetPicker_UI_UX: result=SUCCESS"
]
hydra.launch_and_validate_results(
request,
test_directory,
editor,
"AssetPicker_UI_UX.py",
expected_lines,
cfg_args=[level],
run_python="--runpython",
auto_test_mode=False,
timeout=log_monitor_timeout,
)

@ -1,96 +0,0 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
import os
import pytest
# Bail on the test if ly_test_tools doesn't exist.
pytest.importorskip('ly_test_tools')
import ly_test_tools.environment.file_system as file_system
import ly_test_tools._internal.pytest_plugin as internal_plugin
import editor_python_test_tools.hydra_test_utils as hydra
test_directory = os.path.join(os.path.dirname(__file__), "EditorScripts")
log_monitor_timeout = 180
@pytest.mark.parametrize('project', ['AutomatedTesting'])
@pytest.mark.parametrize('level', ['tmp_level'])
@pytest.mark.usefixtures("automatic_process_killer")
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
class TestBasicEditorWorkflows(object):
@pytest.fixture(autouse=True)
def setup_teardown(self, request, workspace, project, level):
def teardown():
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
request.addfinalizer(teardown)
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
@pytest.mark.test_case_id("C6351273", "C6384955", "C16929880", "C15167490", "C15167491")
@pytest.mark.SUITE_main
def test_BasicEditorWorkflows_LevelEntityComponentCRUD(self, request, editor, level, launcher_platform):
# Skip test if running against Debug build
if "debug" in internal_plugin.build_directory:
pytest.skip("Does not execute against debug builds.")
expected_lines = [
"Create and load new level: True",
"New entity creation: True",
"Create entity hierarchy: True",
"Add component: True",
"Component update: True",
"Remove component: True",
"Save and Export: True",
"BasicEditorWorkflows_LevelEntityComponent: result=SUCCESS",
]
hydra.launch_and_validate_results(
request,
test_directory,
editor,
"BasicEditorWorkflows_LevelEntityComponentCRUD.py",
expected_lines,
cfg_args=[level],
timeout=log_monitor_timeout,
auto_test_mode=False
)
@pytest.mark.test_case_id("C6351273", "C6384955", "C16929880", "C15167490", "C15167491")
@pytest.mark.SUITE_main
@pytest.mark.REQUIRES_gpu
def test_BasicEditorWorkflows_GPU_LevelEntityComponentCRUD(self, request, editor, level, launcher_platform):
# Skip test if running against Debug build
if "debug" in internal_plugin.build_directory:
pytest.skip("Does not execute against debug builds.")
expected_lines = [
"Create and load new level: True",
"New entity creation: True",
"Create entity hierarchy: True",
"Add component: True",
"Component update: True",
"Remove component: True",
"Save and Export: True",
"BasicEditorWorkflows_LevelEntityComponent: result=SUCCESS",
]
hydra.launch_and_validate_results(
request,
test_directory,
editor,
"BasicEditorWorkflows_LevelEntityComponentCRUD.py",
expected_lines,
cfg_args=[level],
timeout=log_monitor_timeout,
auto_test_mode=False,
null_renderer=False
)

@ -1,62 +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
"""
"""
C16929880: Add Delete Components
"""
import os
import pytest
# Bail on the test if ly_test_tools doesn't exist.
pytest.importorskip('ly_test_tools')
import ly_test_tools.environment.file_system as file_system
import editor_python_test_tools.hydra_test_utils as hydra
test_directory = os.path.join(os.path.dirname(__file__), "EditorScripts")
log_monitor_timeout = 180
@pytest.mark.parametrize('project', ['AutomatedTesting'])
@pytest.mark.parametrize('level', ['tmp_level'])
@pytest.mark.usefixtures("automatic_process_killer")
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
class TestComponentCRUD(object):
@pytest.fixture(autouse=True)
def setup_teardown(self, request, workspace, project, level):
def teardown():
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
request.addfinalizer(teardown)
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
@pytest.mark.test_case_id("C16929880", "C16877220")
@pytest.mark.SUITE_periodic
@pytest.mark.BAT
def test_ComponentCRUD_Add_Delete_Components(self, request, editor, level, launcher_platform):
expected_lines = [
"Entity Created",
"Box Shape found",
"Box Shape Component added: True",
"Mesh found",
"Mesh Component added: True",
"Mesh Component deleted: True",
"Mesh Component deletion undone: True",
]
hydra.launch_and_validate_results(
request,
test_directory,
editor,
"ComponentCRUD_Add_Delete_Components.py",
expected_lines,
cfg_args=[level],
auto_test_mode=False,
timeout=log_monitor_timeout
)

@ -1,55 +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
C6376081: Basic Function: Docked/Undocked Tools
"""
import os
import pytest
# Bail on the test if ly_test_tools doesn't exist.
pytest.importorskip('ly_test_tools')
import ly_test_tools.environment.file_system as file_system
import editor_python_test_tools.hydra_test_utils as hydra
test_directory = os.path.join(os.path.dirname(__file__), "EditorScripts")
log_monitor_timeout = 180
@pytest.mark.parametrize('project', ['AutomatedTesting'])
@pytest.mark.parametrize('level', ['tmp_level'])
@pytest.mark.usefixtures("automatic_process_killer")
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
class TestDocking(object):
@pytest.fixture(autouse=True)
def setup_teardown(self, request, workspace, project, level):
def teardown():
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
request.addfinalizer(teardown)
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
@pytest.mark.test_case_id("C6376081")
@pytest.mark.SUITE_sandbox
def test_Docking_BasicDockedTools(self, request, editor, level, launcher_platform):
expected_lines = [
"The tools are all docked together in a tabbed widget",
"Entity Outliner works when docked, can select an Entity",
"Entity Inspector works when docked, Entity name changed to DifferentName",
"Hello, world!" # This line verifies the Console is working while docked
]
hydra.launch_and_validate_results(
request,
test_directory,
editor,
"Docking_BasicDockedTools.py",
expected_lines,
cfg_args=[level],
timeout=log_monitor_timeout,
)

@ -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
"""
"""
C1506881: Adding/Removing Event Groups
"""
import os
import pytest
# Bail on the test if ly_test_tools doesn't exist.
pytest.importorskip('ly_test_tools')
import ly_test_tools.environment.file_system as file_system
import editor_python_test_tools.hydra_test_utils as hydra
test_directory = os.path.join(os.path.dirname(__file__), "EditorScripts")
log_monitor_timeout = 180
@pytest.mark.parametrize('project', ['AutomatedTesting'])
@pytest.mark.parametrize('level', ['tmp_level'])
@pytest.mark.usefixtures("automatic_process_killer")
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
class TestInputBindings(object):
@pytest.fixture(autouse=True)
def setup_teardown(self, request, workspace, project, level):
def teardown():
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
request.addfinalizer(teardown)
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
@pytest.mark.test_case_id("C1506881")
@pytest.mark.SUITE_periodic
def test_InputBindings_Add_Remove_Input_Events(self, request, editor, level, launcher_platform):
expected_lines = [
"Asset Editor opened: True",
"New Event Groups added when + is clicked",
"Event Group deleted when the Delete button is clicked on an Event Group",
"All event groups deleted on clicking the Delete button",
"Asset Editor closed: True",
]
unexpected_lines = [
"Asset Editor opened: False",
"Asset Editor closed: False",
]
hydra.launch_and_validate_results(
request,
test_directory,
editor,
"InputBindings_Add_Remove_Input_Events.py",
expected_lines,
unexpected_lines=unexpected_lines,
cfg_args=[level],
run_python="--runpython",
auto_test_mode=False,
timeout=log_monitor_timeout,
)

@ -1,132 +0,0 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
import os
import pytest
# Bail on the test if ly_test_tools doesn't exist.
pytest.importorskip('ly_test_tools')
import ly_test_tools.environment.file_system as file_system
import ly_test_tools.environment.process_utils as process_utils
import editor_python_test_tools.hydra_test_utils as hydra
test_directory = os.path.join(os.path.dirname(__file__), "EditorScripts")
log_monitor_timeout = 180
@pytest.mark.parametrize('project', ['AutomatedTesting'])
@pytest.mark.parametrize('level', ['tmp_level'])
@pytest.mark.usefixtures("automatic_process_killer")
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
class TestMenus(object):
@pytest.fixture(autouse=True)
def setup_teardown(self, request, workspace, project, level):
def teardown():
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
process_utils.kill_processes_named("o3de", ignore_extensions=True) # Kill ProjectManager windows
request.addfinalizer(teardown)
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
@pytest.mark.test_case_id("C16780783", "C2174438")
@pytest.mark.SUITE_sandbox
def test_Menus_EditMenuOptions_Work(self, request, editor, level, launcher_platform):
expected_lines = [
"Undo Action triggered",
"Redo Action triggered",
"Duplicate Action triggered",
"Delete Action triggered",
"Select All Action triggered",
"Invert Selection Action triggered",
"Toggle Pivot Location Action triggered",
"Reset Entity Transform",
"Reset Manipulator",
"Reset Transform (Local) Action triggered",
"Reset Transform (World) Action triggered",
"Hide Selection Action triggered",
"Show All Action triggered",
"Snap angle Action triggered",
"Move Action triggered",
"Rotate Action triggered",
"Scale Action triggered",
"Global Preferences Action triggered",
"Editor Settings Manager Action triggered",
"Customize Keyboard Action triggered",
"Export Keyboard Settings Action triggered",
"Import Keyboard Settings Action triggered",
"Menus_EditMenuOptions: result=SUCCESS"
]
hydra.launch_and_validate_results(
request,
test_directory,
editor,
"Menus_EditMenuOptions.py",
expected_lines,
cfg_args=[level],
run_python="--runpython",
timeout=log_monitor_timeout
)
@pytest.mark.test_case_id("C16780807")
@pytest.mark.SUITE_periodic
def test_Menus_ViewMenuOptions_Work(self, request, editor, level, launcher_platform):
expected_lines = [
"Center on Selection Action triggered",
"Show Quick Access Bar Action triggered",
"Configure Layout Action triggered",
"Go to Position Action triggered",
"Center on Selection Action triggered",
"Go to Location Action triggered",
"Remember Location Action triggered",
"Switch Camera Action triggered",
"Show/Hide Helpers Action triggered",
"Refresh Style Action triggered",
]
hydra.launch_and_validate_results(
request,
test_directory,
editor,
"Menus_ViewMenuOptions.py",
expected_lines,
cfg_args=[level],
run_python="--runpython",
timeout=log_monitor_timeout
)
@pytest.mark.test_case_id("C16780778")
@pytest.mark.SUITE_sandbox
@pytest.mark.xfail # LYN-4208
def test_Menus_FileMenuOptions_Work(self, request, editor, level, launcher_platform):
expected_lines = [
"New Level Action triggered",
"Open Level Action triggered",
"Import Action triggered",
"Save Action triggered",
"Save As Action triggered",
"Save Level Statistics Action triggered",
"Edit Project Settings Action triggered",
"Edit Platform Settings Action triggered",
"New Project Action triggered",
"Open Project Action triggered",
"Show Log File Action triggered",
"Resave All Slices Action triggered",
"Exit Action triggered",
]
hydra.launch_and_validate_results(
request,
test_directory,
editor,
"Menus_FileMenuOptions.py",
expected_lines,
cfg_args=[level],
run_python="--runpython",
timeout=log_monitor_timeout
)

@ -124,13 +124,14 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_
COMPONENT COMPONENT
LargeWorlds LargeWorlds
) )
## LandscapeCanvas ## ## LandscapeCanvas ##
ly_add_pytest( ly_add_pytest(
NAME AutomatedTesting::LandscapeCanvasTests_Main NAME AutomatedTesting::LandscapeCanvasTests_Main
TEST_SERIAL TEST_SERIAL
TEST_SUITE main TEST_SUITE main
PATH ${CMAKE_CURRENT_LIST_DIR}/landscape_canvas/test_LandscapeCanvas_Main.py PATH ${CMAKE_CURRENT_LIST_DIR}/landscape_canvas/TestSuite_Main.py
RUNTIME_DEPENDENCIES RUNTIME_DEPENDENCIES
AZ::AssetProcessor AZ::AssetProcessor
Legacy::Editor Legacy::Editor
@ -143,7 +144,20 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_
NAME AutomatedTesting::LandscapeCanvasTests_Periodic NAME AutomatedTesting::LandscapeCanvasTests_Periodic
TEST_SERIAL TEST_SERIAL
TEST_SUITE periodic TEST_SUITE periodic
PATH ${CMAKE_CURRENT_LIST_DIR}/landscape_canvas/test_LandscapeCanvas_Periodic.py PATH ${CMAKE_CURRENT_LIST_DIR}/landscape_canvas/TestSuite_Periodic.py
RUNTIME_DEPENDENCIES
AZ::AssetProcessor
Legacy::Editor
AutomatedTesting.Assets
COMPONENT
LargeWorlds
)
ly_add_pytest(
NAME AutomatedTesting::LandscapeCanvasTests_Main_Optimized
TEST_SERIAL
TEST_SUITE main
PATH ${CMAKE_CURRENT_LIST_DIR}/landscape_canvas/TestSuite_Main_Optimized.py
RUNTIME_DEPENDENCIES RUNTIME_DEPENDENCIES
AZ::AssetProcessor AZ::AssetProcessor
Legacy::Editor Legacy::Editor
@ -153,11 +167,25 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_
) )
## GradientSignal ## ## GradientSignal ##
ly_add_pytest( ly_add_pytest(
NAME AutomatedTesting::GradientSignalTests_Periodic NAME AutomatedTesting::GradientSignalTests_Periodic
TEST_SERIAL TEST_SERIAL
TEST_SUITE periodic TEST_SUITE periodic
PATH ${CMAKE_CURRENT_LIST_DIR}/gradient_signal/test_GradientSignal_Periodic.py PATH ${CMAKE_CURRENT_LIST_DIR}/gradient_signal/TestSuite_Periodic.py
RUNTIME_DEPENDENCIES
AZ::AssetProcessor
Legacy::Editor
AutomatedTesting.Assets
COMPONENT
LargeWorlds
)
ly_add_pytest(
NAME AutomatedTesting::GradientSignalTests_Periodic_Optimized
TEST_SERIAL
TEST_SUITE periodic
PATH ${CMAKE_CURRENT_LIST_DIR}/gradient_signal/TestSuite_Periodic_Optimized.py
RUNTIME_DEPENDENCIES RUNTIME_DEPENDENCIES
AZ::AssetProcessor AZ::AssetProcessor
Legacy::Editor Legacy::Editor

@ -1,103 +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
"""
"""
Tests that the Gradient Generator components are incompatible with Vegetation Area components
"""
import os
import pytest
pytest.importorskip('ly_test_tools')
import ly_test_tools.environment.file_system as file_system
import editor_python_test_tools.hydra_test_utils as hydra
test_directory = os.path.join(os.path.dirname(__file__), 'EditorScripts')
gradient_generators = [
'Altitude Gradient',
'Constant Gradient',
'FastNoise Gradient',
'Image Gradient',
'Perlin Noise Gradient',
'Random Noise Gradient',
'Shape Falloff Gradient',
'Slope Gradient',
'Surface Mask Gradient'
]
gradient_modifiers = [
'Dither Gradient Modifier',
'Gradient Mixer',
'Invert Gradient Modifier',
'Levels Gradient Modifier',
'Posterize Gradient Modifier',
'Smooth-Step Gradient Modifier',
'Threshold Gradient Modifier'
]
vegetation_areas = [
'Vegetation Layer Spawner',
'Vegetation Layer Blender',
'Vegetation Layer Blocker',
'Vegetation Layer Blocker (Mesh)'
]
all_gradients = gradient_modifiers + gradient_generators
@pytest.mark.parametrize('project', ['AutomatedTesting'])
@pytest.mark.parametrize('level', ['tmp_level'])
@pytest.mark.usefixtures("automatic_process_killer")
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
class TestGradientIncompatibilities(object):
@pytest.fixture(autouse=True)
def setup_teardown(self, request, workspace, project, level):
def teardown():
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
request.addfinalizer(teardown)
@pytest.mark.test_case_id('C2691648', 'C2691649', 'C2691650', 'C2691651',
'C2691653', 'C2691656', 'C2691657', 'C2691658',
'C2691647', 'C2691655')
@pytest.mark.SUITE_periodic
def test_GradientGenerators_Incompatibilities(self, request, editor, level, launcher_platform):
cfg_args = [level]
expected_lines = []
for gradient_generator in gradient_generators:
for vegetation_area in vegetation_areas:
expected_lines.append(f"{gradient_generator} is disabled before removing {vegetation_area} component")
expected_lines.append(f"{gradient_generator} is enabled after removing {vegetation_area} component")
expected_lines.append("GradientGeneratorIncompatibilities: result=SUCCESS")
hydra.launch_and_validate_results(request, test_directory, editor,
'GradientGenerators_Incompatibilities.py',
expected_lines=expected_lines, cfg_args=cfg_args)
@pytest.mark.test_case_id('C3416464', 'C3416546', 'C3961318', 'C3961319',
'C3961323', 'C3961324', 'C3980656', 'C3980657',
'C3980661', 'C3980662', 'C3980666', 'C3980667',
'C2691652')
@pytest.mark.SUITE_periodic
def test_GradientModifiers_Incompatibilities(self, request, editor, level, launcher_platform):
cfg_args = [level]
expected_lines = []
for gradient_modifier in gradient_modifiers:
for vegetation_area in vegetation_areas:
expected_lines.append(f"{gradient_modifier} is disabled before removing {vegetation_area} component")
expected_lines.append(f"{gradient_modifier} is enabled after removing {vegetation_area} component")
for conflicting_gradient in all_gradients:
expected_lines.append(f"{gradient_modifier} is disabled before removing {conflicting_gradient} component")
expected_lines.append(f"{gradient_modifier} is enabled after removing {conflicting_gradient} component")
expected_lines.append("GradientModifiersIncompatibilities: result=SUCCESS")
hydra.launch_and_validate_results(request, test_directory, editor,
'GradientModifiers_Incompatibilities.py',
expected_lines=expected_lines, cfg_args=cfg_args)

@ -1,118 +0,0 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
import os
import pytest
# Bail on the test if ly_test_tools doesn't exist.
pytest.importorskip('ly_test_tools')
import ly_test_tools.environment.file_system as file_system
import editor_python_test_tools.hydra_test_utils as hydra
test_directory = os.path.join(os.path.dirname(__file__), 'EditorScripts')
@pytest.mark.parametrize('project', ['AutomatedTesting'])
@pytest.mark.parametrize('level', ['tmp_level'])
@pytest.mark.usefixtures("automatic_process_killer")
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
class TestGradientPreviewSettings(object):
@pytest.fixture(autouse=True)
def setup_teardown(self, request, workspace, project, level):
def teardown():
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
request.addfinalizer(teardown)
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
@pytest.mark.test_case_id('C3980668', 'C2676825', 'C2676828', 'C2676822', 'C3416547', 'C3961320', 'C3961325',
'C3980658', 'C3980663')
@pytest.mark.SUITE_periodic
def test_GradientPreviewSettings_DefaultPinnedEntityIsSelf(self, request, editor, level, launcher_platform):
expected_lines = [
"Perlin Noise Gradient has Preview pinned to own Entity result: SUCCESS",
"Random Noise Gradient has Preview pinned to own Entity result: SUCCESS",
"FastNoise Gradient has Preview pinned to own Entity result: SUCCESS",
"Dither Gradient Modifier has Preview pinned to own Entity result: SUCCESS",
"Invert Gradient Modifier has Preview pinned to own Entity result: SUCCESS",
"Levels Gradient Modifier has Preview pinned to own Entity result: SUCCESS",
"Posterize Gradient Modifier has Preview pinned to own Entity result: SUCCESS",
"Smooth-Step Gradient Modifier has Preview pinned to own Entity result: SUCCESS",
"Threshold Gradient Modifier has Preview pinned to own Entity result: SUCCESS",
"GradientPreviewSettings_DefaultPinnedEntity: result=SUCCESS"
]
hydra.launch_and_validate_results(
request,
test_directory,
editor,
"GradientPreviewSettings_DefaultPinnedEntityIsSelf.py",
expected_lines,
cfg_args=[level]
)
@pytest.mark.test_case_id("C2676829", "C3961326", "C3980659", "C3980664", "C3980669", "C3416548", "C2676823",
"C3961321", "C2676826")
@pytest.mark.SUITE_periodic
def test_GradientPreviewSettings_ClearingPinnedEntitySetsPreviewToOrigin(self, request, editor, level,
launcher_platform):
expected_lines = [
"Random Noise Gradient entity Created",
"Entity has a Random Noise Gradient component",
"Entity has a Gradient Transform Modifier component",
"Entity has a Box Shape component",
"Random Noise Gradient Preview Settings|Pin Preview to Shape: SUCCESS",
"Random Noise Gradient --- Preview Position set to world origin",
"Random Noise Gradient --- Preview Size set to (1, 1, 1)",
"Levels Gradient Modifier entity Created",
"Entity has a Levels Gradient Modifier component",
"Levels Gradient Modifier Preview Settings|Pin Preview to Shape: SUCCESS",
"Levels Gradient Modifier --- Preview Position set to world origin",
"Posterize Gradient Modifier entity Created",
"Entity has a Posterize Gradient Modifier component",
"Posterize Gradient Modifier Preview Settings|Pin Preview to Shape: SUCCESS",
"Posterize Gradient Modifier --- Preview Position set to world origin",
"Smooth-Step Gradient Modifier entity Created",
"Entity has a Smooth-Step Gradient Modifier component",
"Smooth-Step Gradient Modifier Preview Settings|Pin Preview to Shape: SUCCESS",
"Smooth-Step Gradient Modifier --- Preview Position set to world origin",
"Threshold Gradient Modifier entity Created",
"Entity has a Threshold Gradient Modifier component",
"Threshold Gradient Modifier Preview Settings|Pin Preview to Shape: SUCCESS",
"Threshold Gradient Modifier --- Preview Position set to world origin",
"FastNoise Gradient entity Created",
"Entity has a FastNoise Gradient component",
"FastNoise Gradient Preview Settings|Pin Preview to Shape: SUCCESS",
"FastNoise Gradient --- Preview Position set to world origin",
"FastNoise Gradient --- Preview Size set to (1, 1, 1)",
"Dither Gradient Modifier entity Created",
"Entity has a Dither Gradient Modifier component",
"Dither Gradient Modifier Preview Settings|Pin Preview to Shape: SUCCESS",
"Dither Gradient Modifier --- Preview Position set to world origin",
"Dither Gradient Modifier --- Preview Size set to (1, 1, 1)",
"Invert Gradient Modifier entity Created",
"Entity has a Invert Gradient Modifier component",
"Invert Gradient Modifier Preview Settings|Pin Preview to Shape: SUCCESS",
"Invert Gradient Modifier --- Preview Position set to world origin",
"Perlin Noise Gradient entity Created",
"Entity has a Perlin Noise Gradient component",
"Perlin Noise Gradient Preview Settings|Pin Preview to Shape: SUCCESS",
"Perlin Noise Gradient --- Preview Position set to world origin",
"Perlin Noise Gradient --- Preview Size set to (1, 1, 1)",
]
hydra.launch_and_validate_results(
request,
test_directory,
editor,
"GradientPreviewSettings_ClearingPinnedEntitySetsPreviewToOrigin.py",
expected_lines,
cfg_args=[level]
)

@ -1,86 +0,0 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
import os
import pytest
import logging
# Bail on the test if ly_test_tools doesn't exist.
pytest.importorskip("ly_test_tools")
import ly_test_tools.environment.file_system as file_system
import editor_python_test_tools.hydra_test_utils as hydra
logger = logging.getLogger(__name__)
test_directory = os.path.join(os.path.dirname(__file__), "EditorScripts")
@pytest.mark.parametrize('project', ['AutomatedTesting'])
@pytest.mark.parametrize('level', ['tmp_level'])
@pytest.mark.usefixtures("automatic_process_killer")
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
class TestGradientSampling(object):
@pytest.fixture(autouse=True)
def setup_teardown(self, request, workspace, project, level):
def teardown():
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
request.addfinalizer(teardown)
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
@pytest.mark.test_case_id("C3526311")
@pytest.mark.SUITE_periodic
def test_GradientSampling_GradientReferencesAddRemoveSuccessfully(self, request, editor, level, launcher_platform):
expected_lines = [
"Entity has a Random Noise Gradient component",
"Entity has a Gradient Transform Modifier component",
"Entity has a Box Shape component",
"Entity has a Dither Gradient Modifier component",
"Gradient Generator is pinned to the Dither Gradient Modifier successfully",
"Gradient Generator is cleared from the Dither Gradient Modifier successfully",
"Entity has a Invert Gradient Modifier component",
"Gradient Generator is pinned to the Invert Gradient Modifier successfully",
"Gradient Generator is cleared from the Invert Gradient Modifier successfully",
"Entity has a Levels Gradient Modifier component",
"Gradient Generator is pinned to the Levels Gradient Modifier successfully",
"Gradient Generator is cleared from the Levels Gradient Modifier successfully",
"Entity has a Posterize Gradient Modifier component",
"Gradient Generator is pinned to the Posterize Gradient Modifier successfully",
"Gradient Generator is cleared from the Posterize Gradient Modifier successfully",
"Entity has a Smooth-Step Gradient Modifier component",
"Gradient Generator is pinned to the Smooth-Step Gradient Modifier successfully",
"Gradient Generator is cleared from the Smooth-Step Gradient Modifier successfully",
"Entity has a Threshold Gradient Modifier component",
"Gradient Generator is pinned to the Threshold Gradient Modifier successfully",
"Gradient Generator is cleared from the Threshold Gradient Modifier successfully",
]
unexpected_lines = [
"Failed to pin Gradient Generator to the Dither Gradient Modifier",
"Failed to clear Gradient Generator from the Dither Gradient Modifier",
"Failed to pin Gradient Generator to the Invert Gradient Modifier",
"Failed to clear Gradient Generator from the Invert Gradient Modifier",
"Failed to pin Gradient Generator to the Levels Gradient Modifier",
"Failed to clear Gradient Generator from the Levels Gradient Modifier",
"Failed to pin Gradient Generator to the Posterize Gradient Modifier",
"Failed to clear Gradient Generator from the Posterize Gradient Modifier",
"Failed to pin Gradient Generator to the Smooth-Step Gradient Modifier",
"Failed to clear Gradient Generator from the Smooth-Step Gradient Modifier",
"Failed to pin Gradient Generator to the Threshold Gradient Modifier",
"Failed to clear Gradient Generator from the Threshold Gradient Modifier",
]
hydra.launch_and_validate_results(
request,
test_directory,
editor,
"GradientSampling_GradientReferencesAddRemoveSuccessfully.py",
expected_lines,
unexpected_lines,
cfg_args=[level]
)

@ -1,116 +0,0 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
import os
import pytest
import logging
# Bail on the test if ly_test_tools doesn't exist.
pytest.importorskip("ly_test_tools")
import ly_test_tools.environment.file_system as file_system
import editor_python_test_tools.hydra_test_utils as hydra
logger = logging.getLogger(__name__)
test_directory = os.path.join(os.path.dirname(__file__), "EditorScripts")
@pytest.mark.parametrize("project", ["AutomatedTesting"])
@pytest.mark.parametrize("level", ["tmp_level"])
@pytest.mark.usefixtures("automatic_process_killer")
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
class TestGradientSurfaceTagEmitter(object):
@pytest.fixture(autouse=True)
def setup_teardown(self, request, workspace, project, level):
# Cleanup temp level before and after test runs
def teardown():
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
request.addfinalizer(teardown)
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
@pytest.mark.test_case_id("C3297302")
@pytest.mark.SUITE_periodic
def test_GradientSurfaceTagEmitter_ComponentDependencies(self, request, editor, level, workspace,
launcher_platform):
cfg_args = [level]
expected_lines = [
"GradientSurfaceTagEmitter_ComponentDependencies: test started",
"GradientSurfaceTagEmitter_ComponentDependencies: Gradient Surface Tag Emitter is Disabled",
"GradientSurfaceTagEmitter_ComponentDependencies: Dither Gradient Modifier and Gradient Surface Tag Emitter are enabled",
"GradientSurfaceTagEmitter_ComponentDependencies: Gradient Mixer and Gradient Surface Tag Emitter are enabled",
"GradientSurfaceTagEmitter_ComponentDependencies: Invert Gradient Modifier and Gradient Surface Tag Emitter are enabled",
"GradientSurfaceTagEmitter_ComponentDependencies: Levels Gradient Modifier and Gradient Surface Tag Emitter are enabled",
"GradientSurfaceTagEmitter_ComponentDependencies: Posterize Gradient Modifier and Gradient Surface Tag Emitter are enabled",
"GradientSurfaceTagEmitter_ComponentDependencies: Smooth-Step Gradient Modifier and Gradient Surface Tag Emitter are enabled",
"GradientSurfaceTagEmitter_ComponentDependencies: Threshold Gradient Modifier and Gradient Surface Tag Emitter are enabled",
"GradientSurfaceTagEmitter_ComponentDependencies: Altitude Gradient and Gradient Surface Tag Emitter are enabled",
"GradientSurfaceTagEmitter_ComponentDependencies: Constant Gradient and Gradient Surface Tag Emitter are enabled",
"GradientSurfaceTagEmitter_ComponentDependencies: FastNoise Gradient and Gradient Surface Tag Emitter are enabled",
"GradientSurfaceTagEmitter_ComponentDependencies: Image Gradient and Gradient Surface Tag Emitter are enabled",
"GradientSurfaceTagEmitter_ComponentDependencies: Perlin Noise Gradient and Gradient Surface Tag Emitter are enabled",
"GradientSurfaceTagEmitter_ComponentDependencies: Random Noise Gradient and Gradient Surface Tag Emitter are enabled",
"GradientSurfaceTagEmitter_ComponentDependencies: Reference Gradient and Gradient Surface Tag Emitter are enabled",
"GradientSurfaceTagEmitter_ComponentDependencies: Shape Falloff Gradient and Gradient Surface Tag Emitter are enabled",
"GradientSurfaceTagEmitter_ComponentDependencies: Slope Gradient and Gradient Surface Tag Emitter are enabled",
"GradientSurfaceTagEmitter_ComponentDependencies: Surface Mask Gradient and Gradient Surface Tag Emitter are enabled",
"GradientSurfaceTagEmitter_ComponentDependencies: result=SUCCESS",
]
unexpected_lines = [
"GradientSurfaceTagEmitter_ComponentDependencies: Gradient Surface Tag Emitter is Enabled, but should be Disabled without dependencies met",
"GradientSurfaceTagEmitter_ComponentDependencies: Dither Gradient Modifier and Gradient Surface Tag Emitter are disabled",
"GradientSurfaceTagEmitter_ComponentDependencies: Gradient Mixer and Gradient Surface Tag Emitter are disabled",
"GradientSurfaceTagEmitter_ComponentDependencies: Invert Gradient Modifier and Gradient Surface Tag Emitter are disabled",
"GradientSurfaceTagEmitter_ComponentDependencies: Levels Gradient Modifier and Gradient Surface Tag Emitter are disabled",
"GradientSurfaceTagEmitter_ComponentDependencies: Posterize Gradient Modifier and Gradient Surface Tag Emitter are disabled",
"GradientSurfaceTagEmitter_ComponentDependencies: Smooth-Step Gradient Modifier and Gradient Surface Tag Emitter are disabled",
"GradientSurfaceTagEmitter_ComponentDependencies: Threshold Gradient Modifier and Gradient Surface Tag Emitter are disabled",
"GradientSurfaceTagEmitter_ComponentDependencies: Altitude Gradient and Gradient Surface Tag Emitter are disabled",
"GradientSurfaceTagEmitter_ComponentDependencies: Constant Gradient and Gradient Surface Tag Emitter are disabled",
"GradientSurfaceTagEmitter_ComponentDependencies: FastNoise Gradient and Gradient Surface Tag Emitter are disabled",
"GradientSurfaceTagEmitter_ComponentDependencies: Image Gradient and Gradient Surface Tag Emitter are disabled",
"GradientSurfaceTagEmitter_ComponentDependencies: Perlin Noise Gradient and Gradient Surface Tag Emitter are disabled",
"GradientSurfaceTagEmitter_ComponentDependencies: Random Noise Gradient and Gradient Surface Tag Emitter are disabled",
"GradientSurfaceTagEmitter_ComponentDependencies: Reference Gradient and Gradient Surface Tag Emitter are disabled",
"GradientSurfaceTagEmitter_ComponentDependencies: Shape Falloff Gradient and Gradient Surface Tag Emitter are disabled",
"GradientSurfaceTagEmitter_ComponentDependencies: Slope Gradient and Gradient Surface Tag Emitter are disabled",
"GradientSurfaceTagEmitter_ComponentDependencies: Surface Mask Gradient and Gradient Surface Tag Emitter are disabled",
]
hydra.launch_and_validate_results(
request,
test_directory,
editor,
"GradientSurfaceTagEmitter_ComponentDependencies.py",
expected_lines=expected_lines,
unexpected_lines=unexpected_lines,
cfg_args=cfg_args
)
@pytest.mark.test_case_id("C3297303")
@pytest.mark.SUITE_periodic
def test_GradientSurfaceTagEmitter_SurfaceTagsAddRemoveSuccessfully(self, request, editor, level,
launcher_platform):
expected_lines = [
"Entity has a Gradient Surface Tag Emitter component",
"Entity has a Reference Gradient component",
"Added SurfaceTag: container count is 1",
"Removed SurfaceTag: container count is 0",
"GradientSurfaceTagEmitter_SurfaceTagsAddRemoveSucessfully: result=SUCCESS"
]
hydra.launch_and_validate_results(
request,
test_directory,
editor,
"GradientSurfaceTagEmitter_SurfaceTagsAddRemoveSuccessfully.py",
expected_lines,
cfg_args=[level]
)

@ -1,157 +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
"""
"""
Tests that the Gradient Transform Modifier component isn't enabled unless it has a component on
the same Entity that provides the ShapeService (e.g. box shape, or reference shape)
"""
import os
import pytest
# Bail on the test if ly_test_tools doesn't exist.
pytest.importorskip('ly_test_tools')
import ly_test_tools.environment.file_system as file_system
import editor_python_test_tools.hydra_test_utils as hydra
test_directory = os.path.join(os.path.dirname(__file__), 'EditorScripts')
@pytest.mark.parametrize('project', ['AutomatedTesting'])
@pytest.mark.parametrize('level', ['tmp_level'])
@pytest.mark.usefixtures("automatic_process_killer")
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
class TestGradientTransformRequiresShape(object):
@pytest.fixture(autouse=True)
def setup_teardown(self, request, workspace, project, level):
def teardown():
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
request.addfinalizer(teardown)
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
@pytest.mark.test_case_id('C3430289')
@pytest.mark.SUITE_periodic
def test_GradientTransform_RequiresShape(self, request, editor, level, launcher_platform):
expected_lines = [
"Gradient Transform Modifier component was added to entity, but the component is disabled",
"Gradient Transform component is not active without a Shape component on the Entity",
"Box Shape component was added to entity",
"Gradient Transform Modifier component is active now that the Entity has a Shape",
"GradientTransformRequiresShape: result=SUCCESS"
]
hydra.launch_and_validate_results(
request,
test_directory,
editor,
"GradientTransform_RequiresShape.py",
expected_lines,
cfg_args=[level]
)
@pytest.mark.test_case_id("C3430292")
@pytest.mark.SUITE_periodic
def test_GradientTransform_FrequencyZoomCanBeSetBeyondSliderRange(self, request, editor, level, launcher_platform):
expected_lines = [
"Entity Created",
"Entity has a Random Noise Gradient component",
"Entity has a Gradient Transform Modifier component",
"Entity has a Box Shape component",
"Components added to the entity",
"entity Configuration|Frequency Zoom: SUCCESS",
"Frequency Zoom is equal to expected value",
]
unexpected_lines = ["Frequency Zoom is not equal to expected value"]
hydra.launch_and_validate_results(
request,
test_directory,
editor,
"GradientTransform_FrequencyZoomCanBeSetBeyondSliderRange.py",
expected_lines,
unexpected_lines=unexpected_lines,
cfg_args=[level]
)
@pytest.mark.test_case_id("C3430297")
@pytest.mark.SUITE_periodic
def test_GradientTransform_ComponentIncompatibleWithSpawners(self, request, editor, launcher_platform, level):
# C3430297: Component cannot be active on the same Entity as an active Vegetation Layer Spawner
expected_lines = [
"Entity has a Gradient Transform Modifier component",
"Entity has a Box Shape component",
"New Entity Created",
"Gradient Transform Modifier is Enabled",
"Box Shape is Enabled",
"Entity has a Vegetation Layer Spawner component",
"Vegetation Layer Spawner is incompatible and disabled",
"GradientTransform_ComponentIncompatibleWithSpawners: result=SUCCESS"
]
unexpected_lines = [
"Gradient Transform Modifier is Disabled. But It should be Enabled in an Entity",
"Box Shape is Disabled. But It should be Enabled in an Entity",
"Vegetation Layer Spawner is compatible and enabled. But It should be Incompatible and disabled",
]
hydra.launch_and_validate_results(
request,
test_directory,
editor,
"GradientTransform_ComponentIncompatibleWithSpawners.py",
expected_lines,
unexpected_lines,
cfg_args=[level]
)
@pytest.mark.test_case_id("C4753767")
@pytest.mark.SUITE_periodic
def test_GradientTransform_ComponentIncompatibleWithExpectedGradients(self, request, editor, launcher_platform, level):
expected_lines = [
"Entity has a Gradient Transform Modifier component",
"Entity has a Box Shape component",
"New Entity Created",
"Gradient Transform Modifier is Enabled",
"Box Shape is Enabled",
"Entity has a Constant Gradient component",
"Entity has a Altitude Gradient component",
"Entity has a Gradient Mixer component",
"Entity has a Reference Gradient component",
"Entity has a Shape Falloff Gradient component",
"Entity has a Slope Gradient component",
"Entity has a Surface Mask Gradient component",
"All newly added components are incompatible and disabled",
"GradientTransform_ComponentIncompatibleWithExpectedGradients: result=SUCCESS"
]
unexpected_lines = [
"Gradient Transform Modifier is disabled, but it should be enabled",
"Box Shape is disabled, but it should be enabled",
"Constant Gradient is enabled, but should be disabled",
"Altitude Gradient is enabled, but should be disabled",
"Gradient Mixer is enabled, but should be disabled",
"Reference Gradient is enabled, but should be disabled",
"Shape Falloff Gradient is enabled, but should be disabled",
"Slope Gradient is enabled, but should be disabled",
"Surface Mask Gradient component is enabled, but should be disabled",
]
hydra.launch_and_validate_results(
request,
test_directory,
editor,
"GradientTransform_ComponentIncompatibleWithExpectedGradients.py",
expected_lines,
unexpected_lines,
cfg_args=[level]
)

@ -1,69 +0,0 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
import os
import pytest
# Bail on the test if ly_test_tools doesn't exist.
pytest.importorskip('ly_test_tools')
import ly_test_tools.environment.file_system as file_system
import editor_python_test_tools.hydra_test_utils as hydra
test_directory = os.path.join(os.path.dirname(__file__), 'EditorScripts')
@pytest.mark.parametrize('project', ['AutomatedTesting'])
@pytest.mark.parametrize('level', ['tmp_level'])
@pytest.mark.usefixtures("automatic_process_killer")
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
class TestImageGradientRequiresShape(object):
@pytest.fixture(autouse=True)
def setup_teardown(self, request, workspace, project, level):
def teardown():
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
request.addfinalizer(teardown)
@pytest.mark.test_case_id('C2707570')
@pytest.mark.SUITE_periodic
def test_ImageGradient_RequiresShape(self, request, editor, level, launcher_platform):
cfg_args = [level]
expected_lines = [
"Image Gradient component was added to entity, but the component is disabled",
"Gradient Transform Modifier component was added to entity, but the component is disabled",
"Image Gradient component is not active without a Shape component on the Entity",
"Box Shape component was added to entity",
"Image Gradient component is active now that the Entity has a Shape",
"ImageGradientRequiresShape: result=SUCCESS"
]
hydra.launch_and_validate_results(request, test_directory, editor,
'ImageGradient_RequiresShape.py',
expected_lines=expected_lines, cfg_args=cfg_args)
@pytest.mark.test_case_id("C3829430")
@pytest.mark.SUITE_periodic
def test_ImageGradient_ProcessedImageAssignedSuccessfully(self, request, editor, level, launcher_platform):
expected_lines = [
"Image Gradient Entity created",
"Entity has a Image Gradient component",
"Entity has a Gradient Transform Modifier component",
"Entity has a Box Shape component",
"image_grad_test_gsi.png was found in the workspace",
"Entity Configuration|Image Asset: SUCCESS",
"ImageGradient_ProcessedImageAssignedSucessfully: result=SUCCESS",
]
hydra.launch_and_validate_results(
request,
test_directory,
editor,
"ImageGradient_ProcessedImageAssignedSuccessfully.py",
expected_lines,
cfg_args=[level]
)

@ -17,6 +17,12 @@ from ly_test_tools.o3de.editor_test import EditorSingleTest, EditorSharedTest, E
@pytest.mark.parametrize("project", ["AutomatedTesting"]) @pytest.mark.parametrize("project", ["AutomatedTesting"])
class TestAutomation(EditorTestSuite): class TestAutomation(EditorTestSuite):
class test_LandscapeCanvas_SlotConnections_UpdateComponentReferences(EditorSharedTest):
from .EditorScripts import SlotConnections_UpdateComponentReferences as test_module
class test_LandscapeCanvas_GradientMixer_NodeConstruction(EditorSharedTest):
from .EditorScripts import GradientMixer_NodeConstruction as test_module
class test_LandscapeCanvas_AreaNodes_DependentComponentsAdded(EditorSharedTest): class test_LandscapeCanvas_AreaNodes_DependentComponentsAdded(EditorSharedTest):
from .EditorScripts import AreaNodes_DependentComponentsAdded as test_module from .EditorScripts import AreaNodes_DependentComponentsAdded as test_module
@ -86,4 +92,4 @@ class TestAutomation(EditorTestSuite):
from .EditorScripts import ShapeNodes_EntityCreatedOnNodeAdd as test_module from .EditorScripts import ShapeNodes_EntityCreatedOnNodeAdd as test_module
class test_LandscapeCanvas_ShapeNodes_EntityRemovedOnNodeDelete(EditorSharedTest): class test_LandscapeCanvas_ShapeNodes_EntityRemovedOnNodeDelete(EditorSharedTest):
from .EditorScripts import ShapeNodes_EntityRemovedOnNodeDelete as test_module from .EditorScripts import ShapeNodes_EntityRemovedOnNodeDelete as test_module

@ -1,125 +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
"""
"""
C13815919 - Appropriate component dependencies are automatically added to node entities
C13767844 - All Vegetation Area nodes can be added to a graph
C17605868 - All Vegetation Area nodes can be removed from a graph
C13815873 - All Filters/Modifiers/Selectors can be added to/removed from a Layer node
"""
import os
import pytest
# Bail on the test if ly_test_tools doesn't exist.
pytest.importorskip('ly_test_tools')
import ly_test_tools.environment.file_system as file_system
import editor_python_test_tools.hydra_test_utils as hydra
test_directory = os.path.join(os.path.dirname(__file__), 'EditorScripts')
@pytest.mark.parametrize('project', ['AutomatedTesting'])
@pytest.mark.parametrize('level', ['tmp_level'])
@pytest.mark.usefixtures("automatic_process_killer")
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
class TestAreaNodes(object):
@pytest.fixture(autouse=True)
def setup_teardown(self, request, workspace, project, level):
def teardown():
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
request.addfinalizer(teardown)
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
@pytest.mark.test_case_id('C13815919')
@pytest.mark.SUITE_periodic
def test_LandscapeCanvas_AreaNodes_DependentComponentsAdded(self, request, editor, level, launcher_platform):
cfg_args = [level]
expected_lines = [
"Landscape Canvas pane is open",
"New graph created",
"Graph registered with Landscape Canvas",
"SpawnerAreaNode created new Entity with all required components",
"MeshBlockerAreaNode created new Entity with all required components",
"BlockerAreaNode created new Entity with all required components",
"AreaNodeComponentDependency: result=SUCCESS"
]
hydra.launch_and_validate_results(request, test_directory, editor, 'AreaNodes_DependentComponentsAdded.py',
expected_lines, cfg_args=cfg_args)
@pytest.mark.test_case_id('C13767844')
@pytest.mark.SUITE_periodic
def test_LandscapeCanvas_AreaNodes_EntityCreatedOnNodeAdd(self, request, editor, level, launcher_platform):
"""
Verifies all Area nodes can be successfully added to a Landscape Canvas graph, and the proper entity
creation occurs.
"""
cfg_args = [level]
expected_lines = [
"Landscape Canvas pane is open",
"New graph created",
"Graph registered with Landscape Canvas",
"AreaBlenderNode created new Entity with Vegetation Layer Blender Component",
"BlockerAreaNode created new Entity with Vegetation Layer Blocker Component",
"MeshBlockerAreaNode created new Entity with Vegetation Layer Blocker (Mesh) Component",
"SpawnerAreaNode created new Entity with Vegetation Layer Spawner Component",
"AreaNodeEntityCreate: result=SUCCESS"
]
hydra.launch_and_validate_results(request, test_directory, editor, 'AreaNodes_EntityCreatedOnNodeAdd.py',
expected_lines, cfg_args=cfg_args)
@pytest.mark.test_case_id('C17605868')
@pytest.mark.SUITE_periodic
def test_LandscapeCanvas_AreaNodes_EntityRemovedOnNodeDelete(self, request, editor, level, launcher_platform):
"""
Verifies all Area nodes can be successfully removed from a Landscape Canvas graph, and the proper entity
cleanup occurs.
"""
cfg_args = [level]
expected_lines = [
"Landscape Canvas pane is open",
"New graph created",
"Graph registered with Landscape Canvas",
"AreaBlenderNode corresponding Entity was deleted when node is removed",
"MeshBlockerAreaNode corresponding Entity was deleted when node is removed",
"SpawnerAreaNode corresponding Entity was deleted when node is removed",
"BlockerAreaNode corresponding Entity was deleted when node is removed",
"AreaNodeEntityDelete: result=SUCCESS"
]
hydra.launch_and_validate_results(request, test_directory, editor,
'AreaNodes_EntityRemovedOnNodeDelete.py', expected_lines, cfg_args=cfg_args)
@pytest.mark.test_case_id('C13815873')
@pytest.mark.SUITE_periodic
def test_LandscapeCanvas_LayerExtenderNodes_ComponentEntitySync(self, request, editor, level, launcher_platform):
"""
Verifies all Area Extender nodes can be successfully added to and removed from a Landscape Canvas graph, and the
proper entity creation/cleanup occurs.
"""
cfg_args = [level]
expected_lines = [
"Landscape Canvas pane is open",
"New graph created",
"Graph registered with Landscape Canvas",
"AreaBlenderNode successfully added and removed all filters/modifiers/selectors",
"SpawnerAreaNode successfully added and removed all filters/modifiers/selectors",
"LayerExtenderNodeComponentEntitySync: result=SUCCESS"
]
hydra.launch_and_validate_results(request, test_directory, editor,
'LayerExtenderNodes_ComponentEntitySync.py', expected_lines,
cfg_args=cfg_args)

@ -1,84 +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
"""
"""
C29278563 - Disabled nodes can be successfully duplicated
C30813586 - Editor remains stable after Undoing deletion of a node on a slice entity
"""
import os
import pytest
# Bail on the test if ly_test_tools doesn't exist.
pytest.importorskip('ly_test_tools')
import ly_test_tools.environment.file_system as file_system
import editor_python_test_tools.hydra_test_utils as hydra
test_directory = os.path.join(os.path.dirname(__file__), 'EditorScripts')
@pytest.mark.parametrize('project', ['AutomatedTesting'])
@pytest.mark.parametrize('level', ['tmp_level'])
@pytest.mark.usefixtures("automatic_process_killer")
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
class TestEditFunctionality(object):
@pytest.fixture(autouse=True)
def setup_teardown(self, request, workspace, project, level):
def teardown():
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
request.addfinalizer(teardown)
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
@pytest.mark.test_case_id('C29278563')
@pytest.mark.SUITE_periodic
def test_LandscapeCanvas_DuplicateDisabledNodes(self, request, editor, level, launcher_platform):
cfg_args = [level]
expected_lines = [
"Landscape Canvas pane is open",
"New graph created",
"SpawnerAreaNode duplicated with disabled component",
"SpawnerAreaNode duplicated with deleted component",
"MeshBlockerAreaNode duplicated with disabled component",
"MeshBlockerAreaNode duplicated with deleted component",
"BlockerAreaNode duplicated with disabled component",
"BlockerAreaNode duplicated with deleted component",
"FastNoiseGradientNode duplicated with disabled component",
"FastNoiseGradientNode duplicated with deleted component",
"ImageGradientNode duplicated with disabled component",
"ImageGradientNode duplicated with deleted component",
"PerlinNoiseGradientNode duplicated with disabled component",
"PerlinNoiseGradientNode duplicated with deleted component",
"RandomNoiseGradientNode duplicated with disabled component",
"RandomNoiseGradientNode duplicated with deleted component",
"DisabledNodeDuplication: result=SUCCESS"
]
hydra.launch_and_validate_results(request, test_directory, editor, 'Edit_DisabledNodeDuplication.py',
expected_lines, cfg_args=cfg_args)
@pytest.mark.test_case_id('C30813586')
@pytest.mark.SUITE_periodic
def test_LandscapeCanvas_UndoNodeDelete_SliceEntity(self, request, editor, level, launcher_platform):
cfg_args = [level]
expected_lines = [
"Vegetation Layer Spawner node found on graph",
"Vegetation Layer Spawner node was removed",
"Editor is still responsive",
"UndoNodeDeleteSlice: result=SUCCESS"
]
unexpected_lines = [
"Vegetation Layer Spawner node not found",
"Vegetation Layer Spawner node was not removed"
]
hydra.launch_and_validate_results(request, test_directory, editor, 'Edit_UndoNodeDelete_SliceEntity.py',
expected_lines, unexpected_lines=unexpected_lines, cfg_args=cfg_args)

@ -1,173 +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
"""
"""
C2735988 - Landscape Canvas tool can be opened/closed
C13815862 - New graph can be created
C13767840 - New root entity is created when a new graph is created through Landscape Canvas
"""
import os
import pytest
# Bail on the test if ly_test_tools doesn't exist.
pytest.importorskip("ly_test_tools")
import ly_test_tools.environment.file_system as file_system
import editor_python_test_tools.hydra_test_utils as hydra
test_directory = os.path.join(os.path.dirname(__file__), "EditorScripts")
@pytest.mark.parametrize("project", ["AutomatedTesting"])
@pytest.mark.parametrize("level", ["tmp_level"])
@pytest.mark.usefixtures("automatic_process_killer")
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
class TestGeneralGraphFunctionality(object):
@pytest.fixture(autouse=True)
def setup_teardown(self, request, workspace, project, level):
def teardown():
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "slices", "TestSlice.slice")], True, True)
request.addfinalizer(teardown)
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "slices", "TestSlice.slice")], True, True)
@pytest.mark.test_case_id("C2735988", "C13815862", "C13767840")
@pytest.mark.SUITE_periodic
def test_LandscapeCanvas_NewGraph_CreatedSuccessfully(self, request, editor, level, launcher_platform):
cfg_args = [level]
expected_lines = [
"Landscape Canvas pane is open",
"New graph created",
"Graph registered with Landscape Canvas",
"Root entity has Landscape Canvas component",
"Landscape Canvas pane is closed",
"CreateNewGraph: result=SUCCESS",
]
hydra.launch_and_validate_results(
request,
test_directory,
editor,
"CreateNewGraph.py",
expected_lines,
cfg_args=cfg_args
)
@pytest.mark.test_case_id("C2735990")
@pytest.mark.SUITE_periodic
def test_LandscapeCanvas_Component_AddedRemoved(self, request, editor, level, launcher_platform):
cfg_args = [level]
expected_lines = [
"Landscape Canvas Component added to Entity",
"Landscape Canvas Component removed from Entity",
"LandscapeCanvasComponentAddedRemoved: result=SUCCESS",
]
hydra.launch_and_validate_results(
request,
test_directory,
editor,
"LandscapeCanvasComponent_AddedRemoved.py",
expected_lines,
cfg_args=cfg_args
)
@pytest.mark.test_case_id("C14212352")
@pytest.mark.SUITE_periodic
def test_LandscapeCanvas_GraphClosed_OnLevelChange(self, request, editor, level, launcher_platform):
cfg_args = [level]
expected_lines = [
"Landscape Canvas pane is open",
"New graph created",
"Graph registered with Landscape Canvas",
"Graph is no longer open in Landscape Canvas",
"GraphClosedOnLevelChange: result=SUCCESS",
]
hydra.launch_and_validate_results(
request,
test_directory,
editor,
"GraphClosed_OnLevelChange.py",
expected_lines,
cfg_args=cfg_args
)
@pytest.mark.test_case_id("C17488412")
@pytest.mark.SUITE_periodic
@pytest.mark.xfail(reason="https://github.com/o3de/o3de/issues/2201")
def test_LandscapeCanvas_GraphClosed_OnEntityDelete(self, request, editor, level, launcher_platform):
cfg_args = [level]
expected_lines = [
"Landscape Canvas pane is open",
"Graph registered with Landscape Canvas",
"The graph is no longer open after deleting the Entity",
"GraphClosedOnEntityDelete: result=SUCCESS",
]
hydra.launch_and_validate_results(
request,
test_directory,
editor,
"GraphClosed_OnEntityDelete.py",
expected_lines,
cfg_args=cfg_args
)
@pytest.mark.test_case_id("C15167461")
@pytest.mark.SUITE_periodic
def test_LandscapeCanvas_GraphClosed_TabbedGraphClosesIndependently(self, request, editor, level,
launcher_platform):
cfg_args = [level]
expected_lines = [
"Landscape Canvas pane is open",
"New graph created",
"2nd new graph created",
"3rd new graph created",
"Graphs registered with Landscape Canvas",
"Graph 2 was successfully closed",
"GraphClosedTabbedGraph: result=SUCCESS",
]
hydra.launch_and_validate_results(
request,
test_directory,
editor,
"GraphClosed_TabbedGraph.py",
expected_lines,
cfg_args=cfg_args
)
@pytest.mark.test_case_id("C22602016")
@pytest.mark.SUITE_periodic
def test_LandscapeCanvas_SliceCreateInstantiate(self, request, editor, level, workspace, launcher_platform):
cfg_args = [level]
expected_lines = [
"LandscapeCanvas_SliceCreateInstantiate: test started",
"landscape_canvas_entity Entity successfully created",
"LandscapeCanvas_SliceCreateInstantiate: Slice has been created successfully: True",
"LandscapeCanvas_SliceCreateInstantiate: Slice instantiated: True",
"LandscapeCanvas_SliceCreateInstantiate: result=SUCCESS",
]
hydra.launch_and_validate_results(
request,
test_directory,
editor,
"LandscapeCanvas_SliceCreateInstantiate.py",
expected_lines=expected_lines,
cfg_args=cfg_args
)

@ -1,92 +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
"""
"""
C13767841 - All Gradient Modifier nodes can be added to a graph
C18055051 - All Gradient Modifier nodes can be removed from a graph
"""
import os
import pytest
# Bail on the test if ly_test_tools doesn't exist.
pytest.importorskip('ly_test_tools')
import ly_test_tools.environment.file_system as file_system
import editor_python_test_tools.hydra_test_utils as hydra
test_directory = os.path.join(os.path.dirname(__file__), 'EditorScripts')
@pytest.mark.parametrize('project', ['AutomatedTesting'])
@pytest.mark.parametrize('level', ['tmp_level'])
@pytest.mark.usefixtures("automatic_process_killer")
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
class TestGradientModifierNodes(object):
@pytest.fixture(autouse=True)
def setup_teardown(self, request, workspace, project, level):
def teardown():
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
request.addfinalizer(teardown)
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
@pytest.mark.test_case_id('C13767841')
@pytest.mark.SUITE_periodic
def test_LandscapeCanvas_GradientModifierNodes_EntityCreatedOnNodeAdd(self, request, editor, level,
launcher_platform):
"""
Verifies all Gradient Modifier nodes can be successfully added to a Landscape Canvas graph, and the proper
entity creation occurs.
"""
cfg_args = [level]
expected_lines = [
"Landscape Canvas pane is open",
"New graph created",
"Graph registered with Landscape Canvas",
"DitherGradientModifierNode created new Entity with Dither Gradient Modifier Component",
"GradientMixerNode created new Entity with Gradient Mixer Component",
"InvertGradientModifierNode created new Entity with Invert Gradient Modifier Component",
"LevelsGradientModifierNode created new Entity with Levels Gradient Modifier Component",
"PosterizeGradientModifierNode created new Entity with Posterize Gradient Modifier Component",
"SmoothStepGradientModifierNode created new Entity with Smooth-Step Gradient Modifier Component",
"ThresholdGradientModifierNode created new Entity with Threshold Gradient Modifier Component",
"GradientModifierNodeEntityCreate: result=SUCCESS"
]
hydra.launch_and_validate_results(request, test_directory, editor,
'GradientModifierNodes_EntityCreatedOnNodeAdd.py',
expected_lines, cfg_args=cfg_args)
@pytest.mark.test_case_id('C18055051')
@pytest.mark.SUITE_periodic
def test_LandscapeCanvas_GradientModifierNodes_EntityRemovedOnNodeDelete(self, request, editor, level,
launcher_platform):
"""
Verifies all Gradient Modifier nodes can be successfully removed from a Landscape Canvas graph, and the proper
entity cleanup occurs.
"""
cfg_args = [level]
expected_lines = [
"Landscape Canvas pane is open",
"New graph created",
"Graph registered with Landscape Canvas",
"DitherGradientModifierNode corresponding Entity was deleted when node is removed",
"GradientMixerNode corresponding Entity was deleted when node is removed",
"InvertGradientModifierNode corresponding Entity was deleted when node is removed",
"LevelsGradientModifierNode corresponding Entity was deleted when node is removed",
"PosterizeGradientModifierNode corresponding Entity was deleted when node is removed",
"SmoothStepGradientModifierNode corresponding Entity was deleted when node is removed",
"ThresholdGradientModifierNode corresponding Entity was deleted when node is removed",
"GradientModifierNodeEntityDelete: result=SUCCESS"
]
hydra.launch_and_validate_results(request, test_directory, editor,
'GradientModifierNodes_EntityRemovedOnNodeDelete.py',
expected_lines, cfg_args=cfg_args)

@ -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
"""
"""
C13815920 - Appropriate component dependencies are automatically added to node entities
C13767842 - All Gradient nodes can be added to a graph
C17461363 - All Gradient nodes can be removed from a graph
"""
import os
import pytest
# Bail on the test if ly_test_tools doesn't exist.
pytest.importorskip('ly_test_tools')
import ly_test_tools.environment.file_system as file_system
import editor_python_test_tools.hydra_test_utils as hydra
test_directory = os.path.join(os.path.dirname(__file__), 'EditorScripts')
@pytest.mark.parametrize('project', ['AutomatedTesting'])
@pytest.mark.parametrize('level', ['tmp_level'])
@pytest.mark.usefixtures("automatic_process_killer")
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
class TestGradientNodes(object):
@pytest.fixture(autouse=True)
def setup_teardown(self, request, workspace, project, level):
def teardown():
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
request.addfinalizer(teardown)
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
@pytest.mark.test_case_id('C13815920')
@pytest.mark.SUITE_periodic
def test_LandscapeCanvas_GradientNodes_DependentComponentsAdded(self, request, editor, level, launcher_platform):
cfg_args = [level]
expected_lines = [
"Landscape Canvas pane is open",
"New graph created",
"Graph registered with Landscape Canvas",
"FastNoiseGradientNode created new Entity with all required components",
"ImageGradientNode created new Entity with all required components",
"PerlinNoiseGradientNode created new Entity with all required components",
"RandomNoiseGradientNode created new Entity with all required components"
]
hydra.launch_and_validate_results(request, test_directory, editor, 'GradientNodes_DependentComponentsAdded.py',
expected_lines, cfg_args=cfg_args)
@pytest.mark.test_case_id('C13767842')
@pytest.mark.SUITE_periodic
def test_LandscapeCanvas_GradientNodes_EntityCreatedOnNodeAdd(self, request, editor, level, launcher_platform):
"""
Verifies all Gradient nodes can be successfully added to a Landscape Canvas graph, and the proper entity
creation occurs.
"""
cfg_args = [level]
expected_lines = [
"Landscape Canvas pane is open",
"New graph created",
"Graph registered with Landscape Canvas",
"AltitudeGradientNode created new Entity with Altitude Gradient Component",
"ConstantGradientNode created new Entity with Constant Gradient Component",
"FastNoiseGradientNode created new Entity with FastNoise Gradient Component",
"ImageGradientNode created new Entity with Image Gradient Component",
"PerlinNoiseGradientNode created new Entity with Perlin Noise Gradient Component",
"RandomNoiseGradientNode created new Entity with Random Noise Gradient Component",
"ShapeAreaFalloffGradientNode created new Entity with Shape Falloff Gradient Component",
"SlopeGradientNode created new Entity with Slope Gradient Component",
"SurfaceMaskGradientNode created new Entity with Surface Mask Gradient Component"
]
hydra.launch_and_validate_results(request, test_directory, editor, 'GradientNodes_EntityCreatedOnNodeAdd.py',
expected_lines, cfg_args=cfg_args)
@pytest.mark.test_case_id('C17461363')
@pytest.mark.SUITE_periodic
def test_LandscapeCanvas_GradientNodes_EntityRemovedOnNodeDelete(self, request, editor, level, launcher_platform):
"""
Verifies all Gradient nodes can be successfully removed from a Landscape Canvas graph, and the proper entity
cleanup occurs.
"""
cfg_args = [level]
expected_lines = [
"Landscape Canvas pane is open",
"New graph created",
"Graph registered with Landscape Canvas",
"FastNoiseGradientNode corresponding Entity was deleted when node is removed",
"AltitudeGradientNode corresponding Entity was deleted when node is removed",
"ConstantGradientNode corresponding Entity was deleted when node is removed",
"RandomNoiseGradientNode corresponding Entity was deleted when node is removed",
"ShapeAreaFalloffGradientNode corresponding Entity was deleted when node is removed",
"SlopeGradientNode corresponding Entity was deleted when node is removed",
"PerlinNoiseGradientNode corresponding Entity was deleted when node is removed",
"ImageGradientNode corresponding Entity was deleted when node is removed",
"SurfaceMaskGradientNode corresponding Entity was deleted when node is removed"
]
hydra.launch_and_validate_results(request, test_directory, editor, 'GradientNodes_EntityRemovedOnNodeDelete.py',
expected_lines, cfg_args=cfg_args)

@ -1,167 +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
"""
"""
C4705586 - Altering connections on graph nodes appropriately updates component properties
C22715182 - Components are updated when nodes are added/removed/updated
C22602072 - Graph is updated when underlying components are added/removed
C15987206 - Gradient Mixer Layers are properly setup when constructing in a graph
C21333743 - Vegetation Layer Blenders are properly setup when constructing in a graph
"""
import os
import pytest
# Bail on the test if ly_test_tools doesn't exist.
pytest.importorskip('ly_test_tools')
import ly_test_tools.environment.file_system as file_system
import ly_test_tools._internal.pytest_plugin as internal_plugin
import editor_python_test_tools.hydra_test_utils as hydra
test_directory = os.path.join(os.path.dirname(__file__), 'EditorScripts')
@pytest.mark.parametrize('project', ['AutomatedTesting'])
@pytest.mark.parametrize('level', ['tmp_level'])
@pytest.mark.usefixtures("automatic_process_killer")
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
class TestGraphComponentSync(object):
@pytest.fixture(autouse=True)
def setup_teardown(self, request, workspace, project, level):
def teardown():
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
request.addfinalizer(teardown)
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
@pytest.mark.test_case_id('C4705586')
@pytest.mark.BAT
@pytest.mark.SUITE_main
def test_LandscapeCanvas_SlotConnections_UpdateComponentReferences(self, request, editor, level, launcher_platform):
# Skip test if running against Debug build
if "debug" in internal_plugin.build_directory:
pytest.skip("Does not execute against debug builds.")
cfg_args = [level]
expected_lines = [
"Landscape Canvas pane is open",
"New graph created",
"Graph registered with Landscape Canvas",
"Random Noise Gradient component Preview Entity property set to Box Shape EntityId",
"Dither Gradient Modifier component Inbound Gradient property set to Random Noise Gradient EntityId",
"Gradient Mixer component Inbound Gradient extendable property set to Dither Gradient Modifier EntityId",
"SlotConnectionsUpdateComponents: result=SUCCESS"
]
hydra.launch_and_validate_results(request, test_directory, editor,
'SlotConnections_UpdateComponentReferences.py', expected_lines,
cfg_args=cfg_args)
@pytest.mark.test_case_id('C22715182')
@pytest.mark.SUITE_periodic
def test_LandscapeCanvas_GraphUpdates_UpdateComponents(self, request, editor, level, launcher_platform):
cfg_args = [level]
expected_lines = [
'Rotation Modifier component was removed from entity',
'BushSpawner entity was deleted',
'Gradient Entity Id reference was properly updated',
'GraphUpdatesUpdateComponents: result=SUCCESS'
]
unexpected_lines = [
'Rotation Modifier component is still present on entity',
'Failed to delete BushSpawner entity',
'Gradient Entity Id was not updated properly'
]
hydra.launch_and_validate_results(request, test_directory, editor, 'GraphUpdates_UpdateComponents.py',
expected_lines, unexpected_lines=unexpected_lines,
cfg_args=cfg_args)
@pytest.mark.test_case_id('C22602072')
@pytest.mark.SUITE_periodic
def test_LandscapeCanvas_ComponentUpdates_UpdateGraph(self, request, editor, level, launcher_platform):
cfg_args = [level]
expected_lines = [
"LandscapeCanvas entity found",
"BushSpawner entity found",
"Vegetation Distribution Filter on BushSpawner entity found",
"Graph opened",
"Distribution Filter node found on graph",
"Vegetation Altitude Filter on BushSpawner entity found",
"Altitude Filter node found on graph",
"Vegetation Distribution Filter removed from BushSpawner entity",
"Distribution Filter node was removed from the graph",
"New entity successfully added as a child of the BushSpawner entity",
"Box Shape on Box entity found",
"Box Shape node found on graph",
'ComponentUpdatesUpdateGraph: result=SUCCESS'
]
unexpected_lines = [
"Distribution Filter node not found on graph",
"Distribution Filter node is still present on the graph",
"Altitude Filter node not found on graph",
"New entity added with an unexpected parent",
"Box Shape node not found on graph"
]
hydra.launch_and_validate_results(request, test_directory, editor, 'ComponentUpdates_UpdateGraph.py',
expected_lines, unexpected_lines=unexpected_lines, cfg_args=cfg_args)
@pytest.mark.test_case_id('C15987206')
@pytest.mark.SUITE_main
def test_LandscapeCanvas_GradientMixer_NodeConstruction(self, request, editor, level, launcher_platform):
"""
Verifies a Gradient Mixer can be setup in Landscape Canvas and all references are property set.
"""
# Skip test if running against Debug build
if "debug" in internal_plugin.build_directory:
pytest.skip("Does not execute against debug builds.")
cfg_args = [level]
expected_lines = [
'Landscape Canvas pane is open',
'New graph created',
'Graph registered with Landscape Canvas',
'Perlin Noise Gradient component Preview Entity property set to Box Shape EntityId',
'Gradient Mixer component Inbound Gradient extendable property set to Perlin Noise Gradient EntityId',
'Gradient Mixer component Inbound Gradient extendable property set to FastNoise Gradient EntityId',
'Configuration|Layers|[0]|Operation set to 0',
'Configuration|Layers|[1]|Operation set to 6',
'GradientMixerNodeConstruction: result=SUCCESS'
]
hydra.launch_and_validate_results(request, test_directory, editor, 'GradientMixer_NodeConstruction.py',
expected_lines, cfg_args=cfg_args)
@pytest.mark.test_case_id('C21333743')
@pytest.mark.SUITE_periodic
def test_LandscapeCanvas_LayerBlender_NodeConstruction(self, request, editor, level, launcher_platform):
"""
Verifies a Layer Blender can be setup in Landscape Canvas and all references are property set.
"""
cfg_args = [level]
expected_lines = [
'Landscape Canvas pane is open',
'New graph created',
'Graph registered with Landscape Canvas',
'Vegetation Layer Blender component Vegetation Areas[0] property set to Vegetation Layer Spawner EntityId',
'Vegetation Layer Blender component Vegetation Areas[1] property set to Vegetation Layer Blocker EntityId',
'LayerBlenderNodeConstruction: result=SUCCESS'
]
hydra.launch_and_validate_results(request, test_directory, editor, 'LayerBlender_NodeConstruction.py',
expected_lines, cfg_args=cfg_args)

@ -1,22 +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 pytest
from ly_test_tools.o3de.editor_test import EditorSingleTest, EditorSharedTest, EditorParallelTest, EditorTestSuite
@pytest.mark.SUITE_periodic
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
@pytest.mark.parametrize("project", ["AutomatedTesting"])
class TestAutomation(EditorTestSuite):
class test_LandscapeCanvas_SlotConnections_UpdateComponentReferences(EditorSharedTest):
from .EditorScripts import SlotConnections_UpdateComponentReferences as test_module
class test_LandscapeCanvas_GradientMixer_NodeConstruction(EditorSharedTest):
from .EditorScripts import GradientMixer_NodeConstruction as test_module

@ -1,82 +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
"""
"""
C13767843 - All Shape nodes can be added to a graph
C17412059 - All Shape nodes can be removed from a graph
"""
import os
import pytest
# Bail on the test if ly_test_tools doesn't exist.
pytest.importorskip('ly_test_tools')
import ly_test_tools.environment.file_system as file_system
import editor_python_test_tools.hydra_test_utils as hydra
test_directory = os.path.join(os.path.dirname(__file__), 'EditorScripts')
@pytest.mark.parametrize('project', ['AutomatedTesting'])
@pytest.mark.parametrize('level', ['tmp_level'])
@pytest.mark.usefixtures("automatic_process_killer")
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
class TestShapeNodes(object):
@pytest.fixture(autouse=True)
def setup_teardown(self, request, workspace, project, level):
def teardown():
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
request.addfinalizer(teardown)
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
@pytest.mark.test_case_id('C13767843')
@pytest.mark.SUITE_periodic
def test_LandscapeCanvas_ShapeNodes_EntityCreatedOnNodeAdd(self, request, editor, level, launcher_platform):
cfg_args = [level]
expected_lines = [
"Landscape Canvas pane is open",
"New graph created",
"Graph registered with Landscape Canvas",
"BoxShapeNode created new Entity with Box Shape Component",
"CapsuleShapeNode created new Entity with Capsule Shape Component",
"CompoundShapeNode created new Entity with Compound Shape Component",
"CylinderShapeNode created new Entity with Cylinder Shape Component",
"PolygonPrismShapeNode created new Entity with Polygon Prism Shape Component",
"SphereShapeNode created new Entity with Sphere Shape Component",
"TubeShapeNode created new Entity with Tube Shape Component",
"DiskShapeNode created new Entity with Disk Shape Component",
"ShapeNodeEntityCreate: result=SUCCESS"
]
hydra.launch_and_validate_results(request, test_directory, editor, 'ShapeNodes_EntityCreatedOnNodeAdd.py',
expected_lines, cfg_args=cfg_args)
@pytest.mark.test_case_id('C17412059')
@pytest.mark.SUITE_periodic
def test_LandscapeCanvas_ShapeNodes_EntityRemovedOnNodeDelete(self, request, editor, level, launcher_platform):
cfg_args = [level]
expected_lines = [
"Landscape Canvas pane is open",
"New graph created",
"Graph registered with Landscape Canvas",
"BoxShapeNode corresponding Entity was deleted when node is removed",
"CapsuleShapeNode corresponding Entity was deleted when node is removed",
"CompoundShapeNode corresponding Entity was deleted when node is removed",
"CylinderShapeNode corresponding Entity was deleted when node is removed",
"PolygonPrismShapeNode corresponding Entity was deleted when node is removed",
"SphereShapeNode corresponding Entity was deleted when node is removed",
"TubeShapeNode corresponding Entity was deleted when node is removed",
"DiskShapeNode corresponding Entity was deleted when node is removed",
"ShapeNodeEntityDelete: result=SUCCESS"
]
hydra.launch_and_validate_results(request, test_directory, editor, 'ShapeNodes_EntityRemovedOnNodeDelete.py',
expected_lines, cfg_args=cfg_args)

@ -19,6 +19,19 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS)
COMPONENT COMPONENT
Physics Physics
) )
ly_add_pytest(
NAME AutomatedTesting::PhysicsTests_Main_Optimized
TEST_SUITE main
TEST_SERIAL
PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Main_Optimized.py
TIMEOUT 1500
RUNTIME_DEPENDENCIES
Legacy::Editor
AZ::AssetProcessor
AutomatedTesting.Assets
COMPONENT
Physics
)
ly_add_pytest( ly_add_pytest(
NAME AutomatedTesting::PhysicsTests_Periodic NAME AutomatedTesting::PhysicsTests_Periodic
TEST_SUITE periodic TEST_SUITE periodic

@ -0,0 +1,349 @@
"""
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
import os
import sys
import inspect
from ly_test_tools import LAUNCHERS
from ly_test_tools.o3de.editor_test import EditorSingleTest, EditorSharedTest, EditorParallelTest, EditorTestSuite
from .FileManagement import FileManagement as fm
# Custom test spec, it provides functionality to override files
class EditorSingleTest_WithFileOverrides(EditorSingleTest):
# Specify here what files to override, [(original, override), ...]
files_to_override = [()]
# Base directory of the files (Default path is {ProjectName})
base_dir = None
# True will will search sub-directories for the files in base
search_subdirs = False
@classmethod
def wrap_run(cls, instance, request, workspace, editor, editor_test_results, launcher_platform):
root_path = cls.base_dir
if root_path is not None:
root_path = os.path.join(workspace.paths.engine_root(), root_path)
else:
# Default to project folder
root_path = workspace.paths.project()
# Try to locate both target and source files
original_file_list, override_file_list = zip(*cls.files_to_override)
try:
file_list = fm._find_files(original_file_list + override_file_list, root_path, cls.search_subdirs)
except RuntimeWarning as w:
assert False, (
w.message
+ " Please check use of search_subdirs; make sure you are using the correct parent directory."
)
for f in original_file_list:
fm._restore_file(f, file_list[f])
fm._backup_file(f, file_list[f])
for original, override in cls.files_to_override:
fm._copy_file(override, file_list[override], original, file_list[override])
yield # Run Test
for f in original_file_list:
fm._restore_file(f, file_list[f])
@pytest.mark.xfail(reason="Optimized tests are experimental, we will enable xfail and monitor them temporarly.")
@pytest.mark.SUITE_main
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
@pytest.mark.parametrize("project", ["AutomatedTesting"])
class TestAutomation(EditorTestSuite):
@staticmethod
def get_number_parallel_editors():
return 16
#########################################
# Non-atomic tests: These need to be run in a single editor because they have custom setup and teardown
class C4044459_Material_DynamicFriction(EditorSingleTest_WithFileOverrides):
from . import C4044459_Material_DynamicFriction as test_module
files_to_override = [
('physxsystemconfiguration.setreg', 'C4044459_Material_DynamicFriction.setreg_override')
]
base_dir = "AutomatedTesting/Registry"
class C4982593_PhysXCollider_CollisionLayerTest(EditorSingleTest_WithFileOverrides):
from . import C4982593_PhysXCollider_CollisionLayerTest as test_module
files_to_override = [
('physxsystemconfiguration.setreg', 'C4982593_PhysXCollider_CollisionLayer.setreg_override')
]
base_dir = "AutomatedTesting/Registry"
#########################################
class C111111_RigidBody_EnablingGravityWorksUsingNotificationsPoC(EditorSharedTest):
from . import C111111_RigidBody_EnablingGravityWorksUsingNotificationsPoC as test_module
class C5932041_PhysXForceRegion_LocalSpaceForceOnRigidBodies(EditorSharedTest):
from . import C5932041_PhysXForceRegion_LocalSpaceForceOnRigidBodies as test_module
class C15425929_Undo_Redo(EditorSharedTest):
from . import C15425929_Undo_Redo as test_module
class C4976243_Collision_SameCollisionGroupDiffCollisionLayers(EditorSharedTest):
from . import C4976243_Collision_SameCollisionGroupDiffCollisionLayers as test_module
class C14654881_CharacterController_SwitchLevels(EditorSharedTest):
from . import C14654881_CharacterController_SwitchLevels as test_module
class C17411467_AddPhysxRagdollComponent(EditorSharedTest):
from . import C17411467_AddPhysxRagdollComponent as test_module
class C12712453_ScriptCanvas_MultipleRaycastNode(EditorSharedTest):
from . import C12712453_ScriptCanvas_MultipleRaycastNode as test_module
class C18243586_Joints_HingeLeadFollowerCollide(EditorSharedTest):
from . import C18243586_Joints_HingeLeadFollowerCollide as test_module
class C4982803_Enable_PxMesh_Option(EditorSharedTest):
from . import C4982803_Enable_PxMesh_Option as test_module
class C24308873_CylinderShapeCollider_CollidesWithPhysXTerrain(EditorSharedTest):
from . import C24308873_CylinderShapeCollider_CollidesWithPhysXTerrain as test_module
class C3510642_Terrain_NotCollideWithTerrain(EditorSharedTest):
from . import C3510642_Terrain_NotCollideWithTerrain as test_module
class C4976195_RigidBodies_InitialLinearVelocity(EditorSharedTest):
from . import C4976195_RigidBodies_InitialLinearVelocity as test_module
class C4976206_RigidBodies_GravityEnabledActive(EditorSharedTest):
from . import C4976206_RigidBodies_GravityEnabledActive as test_module
class C4976207_PhysXRigidBodies_KinematicBehavior(EditorSharedTest):
from . import C4976207_PhysXRigidBodies_KinematicBehavior as test_module
class C5932042_PhysXForceRegion_LinearDamping(EditorSharedTest):
from . import C5932042_PhysXForceRegion_LinearDamping as test_module
class C5932043_ForceRegion_SimpleDragOnRigidBodies(EditorSharedTest):
from . import C5932043_ForceRegion_SimpleDragOnRigidBodies as test_module
class C5959760_PhysXForceRegion_PointForceExertion(EditorSharedTest):
from . import C5959760_PhysXForceRegion_PointForceExertion as test_module
class C5959764_ForceRegion_ForceRegionImpulsesCapsule(EditorSharedTest):
from . import C5959764_ForceRegion_ForceRegionImpulsesCapsule as test_module
class C5340400_RigidBody_ManualMomentOfInertia(EditorSharedTest):
from . import C5340400_RigidBody_ManualMomentOfInertia as test_module
class C4976210_COM_ManualSetting(EditorSharedTest):
from . import C4976210_COM_ManualSetting as test_module
class C4976194_RigidBody_PhysXComponentIsValid(EditorSharedTest):
from . import C4976194_RigidBody_PhysXComponentIsValid as test_module
class C5932045_ForceRegion_Spline(EditorSharedTest):
from . import C5932045_ForceRegion_Spline as test_module
class C4982797_Collider_ColliderOffset(EditorSharedTest):
from . import C4982797_Collider_ColliderOffset as test_module
class C4976200_RigidBody_AngularDampingObjectRotation(EditorSharedTest):
from . import C4976200_RigidBody_AngularDampingObjectRotation as test_module
class C5689529_Verify_Terrain_RigidBody_Collider_Mesh(EditorSharedTest):
from . import C5689529_Verify_Terrain_RigidBody_Collider_Mesh as test_module
class C5959810_ForceRegion_ForceRegionCombinesForces(EditorSharedTest):
from . import C5959810_ForceRegion_ForceRegionCombinesForces as test_module
class C5959765_ForceRegion_AssetGetsImpulsed(EditorSharedTest):
from . import C5959765_ForceRegion_AssetGetsImpulsed as test_module
class C6274125_ScriptCanvas_TriggerEvents(EditorSharedTest):
from . import C6274125_ScriptCanvas_TriggerEvents as test_module
# needs to be updated to log for unexpected lines
# expected_lines = test_module.LogLines.expected_lines
class C6090554_ForceRegion_PointForceNegative(EditorSharedTest):
from . import C6090554_ForceRegion_PointForceNegative as test_module
class C6090550_ForceRegion_WorldSpaceForceNegative(EditorSharedTest):
from . import C6090550_ForceRegion_WorldSpaceForceNegative as test_module
class C6090552_ForceRegion_LinearDampingNegative(EditorSharedTest):
from . import C6090552_ForceRegion_LinearDampingNegative as test_module
class C5968760_ForceRegion_CheckNetForceChange(EditorSharedTest):
from . import C5968760_ForceRegion_CheckNetForceChange as test_module
class C12712452_ScriptCanvas_CollisionEvents(EditorSharedTest):
from . import C12712452_ScriptCanvas_CollisionEvents as test_module
class C12868578_ForceRegion_DirectionHasNoAffectOnMagnitude(EditorSharedTest):
from . import C12868578_ForceRegion_DirectionHasNoAffectOnMagnitude as test_module
class C4976204_Verify_Start_Asleep_Condition(EditorSharedTest):
from . import C4976204_Verify_Start_Asleep_Condition as test_module
class C6090546_ForceRegion_SliceFileInstantiates(EditorSharedTest):
from . import C6090546_ForceRegion_SliceFileInstantiates as test_module
class C6090551_ForceRegion_LocalSpaceForceNegative(EditorSharedTest):
from . import C6090551_ForceRegion_LocalSpaceForceNegative as test_module
class C6090553_ForceRegion_SimpleDragForceOnRigidBodies(EditorSharedTest):
from . import C6090553_ForceRegion_SimpleDragForceOnRigidBodies as test_module
class C4976209_RigidBody_ComputesCOM(EditorSharedTest):
from . import C4976209_RigidBody_ComputesCOM as test_module
class C4976201_RigidBody_MassIsAssigned(EditorSharedTest):
from . import C4976201_RigidBody_MassIsAssigned as test_module
class C12868580_ForceRegion_SplineModifiedTransform(EditorSharedTest):
from . import C12868580_ForceRegion_SplineModifiedTransform as test_module
class C12712455_ScriptCanvas_ShapeCastVerification(EditorSharedTest):
from . import C12712455_ScriptCanvas_ShapeCastVerification as test_module
class C4976197_RigidBodies_InitialAngularVelocity(EditorSharedTest):
from . import C4976197_RigidBodies_InitialAngularVelocity as test_module
class C6090555_ForceRegion_SplineFollowOnRigidBodies(EditorSharedTest):
from . import C6090555_ForceRegion_SplineFollowOnRigidBodies as test_module
class C6131473_StaticSlice_OnDynamicSliceSpawn(EditorSharedTest):
from . import C6131473_StaticSlice_OnDynamicSliceSpawn as test_module
class C5959808_ForceRegion_PositionOffset(EditorSharedTest):
from . import C5959808_ForceRegion_PositionOffset as test_module
@pytest.mark.xfail(reason="Something with the CryRenderer disabling is causing this test to fail now.")
class C13895144_Ragdoll_ChangeLevel(EditorSharedTest):
from . import C13895144_Ragdoll_ChangeLevel as test_module
class C5968759_ForceRegion_ExertsSeveralForcesOnRigidBody(EditorSharedTest):
from . import C5968759_ForceRegion_ExertsSeveralForcesOnRigidBody as test_module
@pytest.mark.xfail(reason="This test will sometimes fail as the ball will continue to roll before the timeout is reached.")
class C4976202_RigidBody_StopsWhenBelowKineticThreshold(EditorSharedTest):
from . import C4976202_RigidBody_StopsWhenBelowKineticThreshold as test_module
class C13351703_COM_NotIncludeTriggerShapes(EditorSharedTest):
from . import C13351703_COM_NotIncludeTriggerShapes as test_module
class C5296614_PhysXMaterial_ColliderShape(EditorSharedTest):
from . import C5296614_PhysXMaterial_ColliderShape as test_module
class C4982595_Collider_TriggerDisablesCollision(EditorSharedTest):
from . import C4982595_Collider_TriggerDisablesCollision as test_module
class C14976307_Gravity_SetGravityWorks(EditorSharedTest):
from . import C14976307_Gravity_SetGravityWorks as test_module
class C4044694_Material_EmptyLibraryUsesDefault(EditorSharedTest):
from . import C4044694_Material_EmptyLibraryUsesDefault as test_module
class C15845879_ForceRegion_HighLinearDampingForce(EditorSharedTest):
from . import C15845879_ForceRegion_HighLinearDampingForce as test_module
class C4976218_RigidBodies_InertiaObjectsNotComputed(EditorSharedTest):
from . import C4976218_RigidBodies_InertiaObjectsNotComputed as test_module
class C14902098_ScriptCanvas_PostPhysicsUpdate(EditorSharedTest):
from . import C14902098_ScriptCanvas_PostPhysicsUpdate as test_module
# Note: Test needs to be updated to log for unexpected lines
# unexpected_lines = ["Assert"] + test_module.Lines.unexpected
class C5959761_ForceRegion_PhysAssetExertsPointForce(EditorSharedTest):
from . import C5959761_ForceRegion_PhysAssetExertsPointForce as test_module
# Marking the Test as expected to fail using the xfail decorator due to sporadic failure on Automated Review: SPEC-3146
# The test still runs, but a failure of the test doesn't result in the test run failing
@pytest.mark.xfail(reason="Test Sporadically fails with message [ NOT FOUND ] Success: Bar1 : Expected angular velocity")
class C13352089_RigidBodies_MaxAngularVelocity(EditorSharedTest):
from . import C13352089_RigidBodies_MaxAngularVelocity as test_module
class C18243584_Joints_HingeSoftLimitsConstrained(EditorSharedTest):
from . import C18243584_Joints_HingeSoftLimitsConstrained as test_module
class C18243589_Joints_BallSoftLimitsConstrained(EditorSharedTest):
from . import C18243589_Joints_BallSoftLimitsConstrained as test_module
class C18243591_Joints_BallLeadFollowerCollide(EditorSharedTest):
from . import C18243591_Joints_BallLeadFollowerCollide as test_module
class C19578018_ShapeColliderWithNoShapeComponent(EditorSharedTest):
from . import C19578018_ShapeColliderWithNoShapeComponent as test_module
class C14861500_DefaultSetting_ColliderShape(EditorSharedTest):
from . import C14861500_DefaultSetting_ColliderShape as test_module
class C19723164_ShapeCollider_WontCrashEditor(EditorSharedTest):
from . import C19723164_ShapeColliders_WontCrashEditor as test_module
class C4982800_PhysXColliderShape_CanBeSelected(EditorSharedTest):
from . import C4982800_PhysXColliderShape_CanBeSelected as test_module
class C4982801_PhysXColliderShape_CanBeSelected(EditorSharedTest):
from . import C4982801_PhysXColliderShape_CanBeSelected as test_module
class C4982802_PhysXColliderShape_CanBeSelected(EditorSharedTest):
from . import C4982802_PhysXColliderShape_CanBeSelected as test_module
class C12905528_ForceRegion_WithNonTriggerCollider(EditorSharedTest):
from . import C12905528_ForceRegion_WithNonTriggerCollider as test_module
# Fixme: expected_lines = ["[Warning] (PhysX Force Region) - Please ensure collider component marked as trigger exists in entity"]
class C5932040_ForceRegion_CubeExertsWorldForce(EditorSharedTest):
from . import C5932040_ForceRegion_CubeExertsWorldForce as test_module
class C5932044_ForceRegion_PointForceOnRigidBody(EditorSharedTest):
from . import C5932044_ForceRegion_PointForceOnRigidBody as test_module
class C5959759_RigidBody_ForceRegionSpherePointForce(EditorSharedTest):
from . import C5959759_RigidBody_ForceRegionSpherePointForce as test_module
class C5959809_ForceRegion_RotationalOffset(EditorSharedTest):
from . import C5959809_ForceRegion_RotationalOffset as test_module
class C15096740_Material_LibraryUpdatedCorrectly(EditorSharedTest):
from . import C15096740_Material_LibraryUpdatedCorrectly as test_module
class C4976236_AddPhysxColliderComponent(EditorSharedTest):
from . import C4976236_AddPhysxColliderComponent as test_module
@pytest.mark.xfail(reason="This will fail due to this issue ATOM-15487.")
class C14861502_PhysXCollider_AssetAutoAssigned(EditorSharedTest):
from . import C14861502_PhysXCollider_AssetAutoAssigned as test_module
class C14861501_PhysXCollider_RenderMeshAutoAssigned(EditorSharedTest):
from . import C14861501_PhysXCollider_RenderMeshAutoAssigned as test_module
class C4044695_PhysXCollider_AddMultipleSurfaceFbx(EditorSharedTest):
from . import C4044695_PhysXCollider_AddMultipleSurfaceFbx as test_module
class C14861504_RenderMeshAsset_WithNoPxAsset(EditorSharedTest):
from . import C14861504_RenderMeshAsset_WithNoPxAsset as test_module
class C100000_RigidBody_EnablingGravityWorksPoC(EditorSharedTest):
from . import C100000_RigidBody_EnablingGravityWorksPoC as test_module
class C4982798_Collider_ColliderRotationOffset(EditorSharedTest):
from . import C4982798_Collider_ColliderRotationOffset as test_module
class C15308217_NoCrash_LevelSwitch(EditorSharedTest):
from . import C15308217_NoCrash_LevelSwitch as test_module
class C6090547_ForceRegion_ParentChildForceRegions(EditorSharedTest):
from . import C6090547_ForceRegion_ParentChildForceRegions as test_module
class C19578021_ShapeCollider_CanBeAdded(EditorSharedTest):
from . import C19578021_ShapeCollider_CanBeAdded as test_module
class C15425929_Undo_Redo(EditorSharedTest):
from . import C15425929_Undo_Redo as test_module

@ -1,105 +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 pytest
import os
import sys
import inspect
from ly_test_tools import LAUNCHERS
from ly_test_tools.o3de.editor_test import EditorSingleTest, EditorSharedTest, EditorParallelTest, EditorTestSuite
from .FileManagement import FileManagement as fm
# Custom test spec, it provides functionality to override files
class EditorSingleTest_WithFileOverrides(EditorSingleTest):
# Specify here what files to override, [(original, override), ...]
files_to_override = [()]
# Base directory of the files (Default path is {ProjectName})
base_dir = None
# True will will search sub-directories for the files in base
search_subdirs = False
@classmethod
def wrap_run(cls, instance, request, workspace, editor, editor_test_results, launcher_platform):
root_path = cls.base_dir
if root_path is not None:
root_path = os.path.join(workspace.paths.engine_root(), root_path)
else:
# Default to project folder
root_path = workspace.paths.project()
# Try to locate both target and source files
original_file_list, override_file_list = zip(*cls.files_to_override)
try:
file_list = fm._find_files(original_file_list + override_file_list, root_path, cls.search_subdirs)
except RuntimeWarning as w:
assert False, (
w.message
+ " Please check use of search_subdirs; make sure you are using the correct parent directory."
)
for f in original_file_list:
fm._restore_file(f, file_list[f])
fm._backup_file(f, file_list[f])
for original, override in cls.files_to_override:
fm._copy_file(override, file_list[override], original, file_list[override])
yield # Run Test
for f in original_file_list:
fm._restore_file(f, file_list[f])
@pytest.mark.SUITE_main
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
@pytest.mark.parametrize("project", ["AutomatedTesting"])
class TestAutomation(EditorTestSuite):
class C4044459_Material_DynamicFriction(EditorSingleTest_WithFileOverrides):
from . import C4044459_Material_DynamicFriction as test_module
files_to_override = [
('physxsystemconfiguration.setreg', 'C4044459_Material_DynamicFriction.setreg_override')
]
base_dir = "AutomatedTesting/Registry"
class C4982593_PhysXCollider_CollisionLayerTest(EditorSingleTest_WithFileOverrides):
from . import C4982593_PhysXCollider_CollisionLayerTest as test_module
files_to_override = [
('physxsystemconfiguration.setreg', 'C4982593_PhysXCollider_CollisionLayer.setreg_override')
]
base_dir = "AutomatedTesting/Registry"
class C111111_RigidBody_EnablingGravityWorksUsingNotificationsPoC(EditorSharedTest):
from . import C111111_RigidBody_EnablingGravityWorksUsingNotificationsPoC as test_module
class C5932041_PhysXForceRegion_LocalSpaceForceOnRigidBodies(EditorSharedTest):
from . import C5932041_PhysXForceRegion_LocalSpaceForceOnRigidBodies as test_module
class C15425929_Undo_Redo(EditorSharedTest):
from . import C15425929_Undo_Redo as test_module
class C4976243_Collision_SameCollisionGroupDiffCollisionLayers(EditorSharedTest):
from . import C4976243_Collision_SameCollisionGroupDiffCollisionLayers as test_module
class C14654881_CharacterController_SwitchLevels(EditorSharedTest):
from . import C14654881_CharacterController_SwitchLevels as test_module
class C17411467_AddPhysxRagdollComponent(EditorSharedTest):
from . import C17411467_AddPhysxRagdollComponent as test_module
class C12712453_ScriptCanvas_MultipleRaycastNode(EditorSharedTest):
from . import C12712453_ScriptCanvas_MultipleRaycastNode as test_module
class C18243586_Joints_HingeLeadFollowerCollide(EditorSharedTest):
from . import C18243586_Joints_HingeLeadFollowerCollide as test_module
class C4982803_Enable_PxMesh_Option(EditorSharedTest):
from . import C4982803_Enable_PxMesh_Option as test_module
class C24308873_CylinderShapeCollider_CollidesWithPhysXTerrain(EditorSharedTest):
from . import C24308873_CylinderShapeCollider_CollidesWithPhysXTerrain as test_module

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -64,14 +64,13 @@ include(cmake/Projects.cmake)
if(NOT INSTALLED_ENGINE) if(NOT INSTALLED_ENGINE)
# Add the rest of the targets # Add the rest of the targets
add_subdirectory(Assets)
add_subdirectory(Code) add_subdirectory(Code)
add_subdirectory(python)
add_subdirectory(Registry)
add_subdirectory(scripts) add_subdirectory(scripts)
add_subdirectory(Templates)
# SPEC-1417 will investigate and fix this add_subdirectory(Tools)
if(NOT PAL_PLATFORM_NAME STREQUAL "Mac")
add_subdirectory(Tools/LyTestTools/tests/)
add_subdirectory(Tools/RemoteConsole/ly_remote_console/tests/)
endif()
# Add external subdirectories listed in the engine.json. LY_EXTERNAL_SUBDIRS is a cache variable so the user can add extra # Add external subdirectories listed in the engine.json. LY_EXTERNAL_SUBDIRS is a cache variable so the user can add extra
# external subdirectories # external subdirectories

@ -326,6 +326,7 @@ ActionManager::MenuWrapper ActionManager::FindMenu(const QString& menuId)
return *menuIt; return *menuIt;
} }
AZ_UNUSED(menuId); // Prevent unused warning in release builds
AZ_Warning("ActionManager", false, "Did not find menu with menuId %s", menuId.toUtf8().data()); AZ_Warning("ActionManager", false, "Did not find menu with menuId %s", menuId.toUtf8().data());
return nullptr; return nullptr;
}(); }();

@ -628,7 +628,7 @@ void CAnimationContext::GoToFrameCmd(IConsoleCmdArgs* pArgs)
float targetFrame = (float)atof(pArgs->GetArg(1)); float targetFrame = (float)atof(pArgs->GetArg(1));
if (pSeq->GetTimeRange().start > targetFrame || targetFrame > pSeq->GetTimeRange().end) if (pSeq->GetTimeRange().start > targetFrame || targetFrame > pSeq->GetTimeRange().end)
{ {
gEnv->pLog->LogError("GoToFrame: requested time %f is outside the range of sequence %s (%f, %f)", targetFrame, pSeq->GetName(), pSeq->GetTimeRange().start, pSeq->GetTimeRange().end); gEnv->pLog->LogError("GoToFrame: requested time %f is outside the range of sequence %s (%f, %f)", targetFrame, pSeq->GetName().c_str(), pSeq->GetTimeRange().start, pSeq->GetTimeRange().end);
return; return;
} }
GetIEditor()->GetAnimation()->m_currTime = targetFrame; GetIEditor()->GetAnimation()->m_currTime = targetFrame;

@ -327,7 +327,7 @@ void AzAssetBrowserRequestHandler::AddContextMenuActions(QWidget* caller, QMenu*
if (!vetoOpenerFound) if (!vetoOpenerFound)
{ {
// if we found no valid openers and no veto openers then just allow it to be opened with the operating system itself. // if we found no valid openers and no veto openers then just allow it to be opened with the operating system itself.
menu->addAction(QObject::tr("Open with associated application..."), [this, fullFilePath]() menu->addAction(QObject::tr("Open with associated application..."), [fullFilePath]()
{ {
OpenWithOS(fullFilePath); OpenWithOS(fullFilePath);
}); });

@ -130,7 +130,6 @@ private:
private: private:
ISplineInterpolator* m_pSpline; ISplineInterpolator* m_pSpline;
bool m_bAutoDelete;
bool m_bNoZoom; bool m_bNoZoom;
QRect m_rcClipRect; QRect m_rcClipRect;

@ -298,7 +298,6 @@ Lines CConsoleSCB::s_pendingLines;
CConsoleSCB::CConsoleSCB(QWidget* parent) CConsoleSCB::CConsoleSCB(QWidget* parent)
: QWidget(parent) : QWidget(parent)
, ui(new Ui::Console()) , ui(new Ui::Console())
, m_richEditTextLength(0)
, m_backgroundTheme(gSettings.consoleBackgroundColorTheme) , m_backgroundTheme(gSettings.consoleBackgroundColorTheme)
{ {
m_lines = s_pendingLines; m_lines = s_pendingLines;

@ -191,7 +191,6 @@ private:
void OnEditorNotifyEvent(EEditorNotifyEvent event) override; void OnEditorNotifyEvent(EEditorNotifyEvent event) override;
QScopedPointer<Ui::Console> ui; QScopedPointer<Ui::Console> ui;
int m_richEditTextLength;
Lines m_lines; Lines m_lines;
static Lines s_pendingLines; static Lines s_pendingLines;

@ -30,12 +30,6 @@ namespace ImageHistogram
const QColor kGreenSectionColor = QColor(220, 255, 220); const QColor kGreenSectionColor = QColor(220, 255, 220);
const QColor kBlueSectionColor = QColor(220, 220, 255); const QColor kBlueSectionColor = QColor(220, 220, 255);
const QColor kSplitSeparatorColor = QColor(100, 100, 0); const QColor kSplitSeparatorColor = QColor(100, 100, 0);
const QColor kButtonBackColor = QColor(20, 20, 20);
const QColor kBtnLightColor(200, 200, 200);
const QColor kBtnShadowColor(50, 50, 50);
const int kButtonWidth = 40;
const QColor kButtonTextColor(255, 255, 0);
const int kTextLeftSpacing = 4;
const int kTextFontSize = 70; const int kTextFontSize = 70;
const char* kTextFontFace = "Arial"; const char* kTextFontFace = "Arial";
const QColor kTextColor(255, 255, 255); const QColor kTextColor(255, 255, 255);
@ -194,7 +188,7 @@ void CImageHistogramDisplay::paintEvent([[maybe_unused]] QPaintEvent* event)
float scale = 0; float scale = 0;
i = static_cast<int>(((float)x / graphWidth) * (kNumColorLevels - 1)); i = static_cast<int>(((float)x / graphWidth) * (kNumColorLevels - 1));
i = CLAMP(i, 0, kNumColorLevels - 1); i = AZStd::clamp(i, 0, kNumColorLevels - 1);
switch (m_drawMode) switch (m_drawMode)
{ {
@ -259,7 +253,7 @@ void CImageHistogramDisplay::paintEvent([[maybe_unused]] QPaintEvent* event)
for (size_t x = 0, xCount = abs(rcGraph.width()); x < xCount; ++x) for (size_t x = 0, xCount = abs(rcGraph.width()); x < xCount; ++x)
{ {
i = static_cast<int>(((float)x / graphWidth) * (kNumColorLevels - 1)); i = static_cast<int>(((float)x / graphWidth) * (kNumColorLevels - 1));
i = CLAMP(i, 0, kNumColorLevels - 1); i = AZStd::clamp(i, 0, kNumColorLevels - 1);
crtX = static_cast<UINT>(rcGraph.left() + x + 1); crtX = static_cast<UINT>(rcGraph.left() + x + 1);
scaleR = scaleG = scaleB = scaleA = 0; scaleR = scaleG = scaleB = scaleA = 0;
@ -351,7 +345,7 @@ void CImageHistogramDisplay::paintEvent([[maybe_unused]] QPaintEvent* event)
{ {
pos = (float)x / graphWidth; pos = (float)x / graphWidth;
i = static_cast<int>((float)((int)(pos * kNumColorLevels) % aThirdOfNumColorLevels) / aThirdOfNumColorLevels * kNumColorLevels); i = static_cast<int>((float)((int)(pos * kNumColorLevels) % aThirdOfNumColorLevels) / aThirdOfNumColorLevels * kNumColorLevels);
i = CLAMP(i, 0, kNumColorLevels - 1); i = AZStd::clamp(i, 0, kNumColorLevels - 1);
scale = 0; scale = 0;
// R // R

@ -101,7 +101,7 @@ void CSplineCtrl::PointToTimeValue(const QPoint& point, float& time, float& valu
{ {
time = XOfsToTime(point.x()); time = XOfsToTime(point.x());
float t = float(m_rcSpline.bottom() - point.y()) / m_rcSpline.height(); float t = float(m_rcSpline.bottom() - point.y()) / m_rcSpline.height();
value = LERP(m_fMinValue, m_fMaxValue, t); value = AZ::Lerp(m_fMinValue, m_fMaxValue, t);
} }
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
@ -109,7 +109,7 @@ float CSplineCtrl::XOfsToTime(int x)
{ {
// m_fMinTime to m_fMaxTime time range. // m_fMinTime to m_fMaxTime time range.
float t = float(x - m_rcSpline.left()) / m_rcSpline.width(); float t = float(x - m_rcSpline.left()) / m_rcSpline.width();
return LERP(m_fMinTime, m_fMaxTime, t); return AZ::Lerp(m_fMinTime, m_fMaxTime, t);
} }
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
@ -123,8 +123,6 @@ void CSplineCtrl::paintEvent(QPaintEvent* event)
{ {
QPainter painter(this); QPainter painter(this);
QRect rcClient = rect();
if (m_pSpline) if (m_pSpline)
{ {
m_bSelectedKeys.resize(m_pSpline->GetKeyCount()); m_bSelectedKeys.resize(m_pSpline->GetKeyCount());

@ -819,8 +819,6 @@ void SplineWidget::DrawSpline(QPainter* painter, SSplineInfo& splineInfo, float
{ {
const QPen pOldPen = painter->pen(); const QPen pOldPen = painter->pen();
const QRect rcClip = painter->clipBoundingRect().intersected(m_rcSpline).toRect();
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
ISplineInterpolator* pSpline = splineInfo.pSpline; ISplineInterpolator* pSpline = splineInfo.pSpline;
ISplineInterpolator* pDetailSpline = splineInfo.pDetailSpline; ISplineInterpolator* pDetailSpline = splineInfo.pDetailSpline;

@ -18,11 +18,6 @@
#include "ScopedVariableSetter.h" #include "ScopedVariableSetter.h"
#include "GridUtils.h" #include "GridUtils.h"
static const QColor timeMarkerCol = QColor(255, 0, 255);
static const QColor textCol = QColor(0, 0, 0);
static const QColor ltgrayCol = QColor(110, 110, 110);
QColor InterpolateColor(const QColor& c1, const QColor& c2, float fraction) QColor InterpolateColor(const QColor& c1, const QColor& c2, float fraction)
{ {
const int r = static_cast<int>(static_cast<float>(c2.red() - c1.red()) * fraction + c1.red()); const int r = static_cast<int>(static_cast<float>(c2.red() - c1.red()) * fraction + c1.red());

@ -136,7 +136,6 @@ protected:
void DrawFrameTicks(QPainter* dc); void DrawFrameTicks(QPainter* dc);
private: private:
bool m_bAutoDelete;
QRect m_rcClient; QRect m_rcClient;
QRect m_rcTimeline; QRect m_rcTimeline;
float m_fTimeMarker; float m_fTimeMarker;

@ -14,6 +14,7 @@
#include <QPoint> #include <QPoint>
#include <QRect> #include <QRect>
#include "Cry_Vector2.h" #include "Cry_Vector2.h"
#include <AzCore/Casting/numeric_cast.h>
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
class CWndGridHelper class CWndGridHelper
@ -81,8 +82,6 @@ public:
newzoom.y = 0.01f; newzoom.y = 0.01f;
} }
Vec2 prevz = zoom;
// Zoom to mouse position. // Zoom to mouse position.
float ofsx = origin.x; float ofsx = origin.x;
float ofsy = origin.y; float ofsy = origin.y;

@ -41,8 +41,6 @@ using namespace AZ;
using namespace AzToolsFramework; using namespace AzToolsFramework;
static const char* const s_LUAEditorName = "Lua Editor"; static const char* const s_LUAEditorName = "Lua Editor";
static const char* const s_shortTimeInterval = "debug";
static const char* const s_assetImporterMetricsIdentifier = "AssetImporter";
// top level menu ids // top level menu ids
static const char* const s_fileMenuId = "FileMenu"; static const char* const s_fileMenuId = "FileMenu";
@ -50,7 +48,6 @@ static const char* const s_editMenuId = "EditMenu";
static const char* const s_gameMenuId = "GameMenu"; static const char* const s_gameMenuId = "GameMenu";
static const char* const s_toolMenuId = "ToolMenu"; static const char* const s_toolMenuId = "ToolMenu";
static const char* const s_viewMenuId = "ViewMenu"; static const char* const s_viewMenuId = "ViewMenu";
static const char* const s_awsMenuId = "AwsMenu";
static const char* const s_helpMenuId = "HelpMenu"; static const char* const s_helpMenuId = "HelpMenu";
static bool CompareLayoutNames(const QString& name1, const QString& name2) static bool CompareLayoutNames(const QString& name1, const QString& name2)
@ -157,13 +154,11 @@ namespace
} }
} }
LevelEditorMenuHandler::LevelEditorMenuHandler( LevelEditorMenuHandler::LevelEditorMenuHandler(MainWindow* mainWindow, QtViewPaneManager* const viewPaneManager)
MainWindow* mainWindow, QtViewPaneManager* const viewPaneManager, QSettings& settings)
: QObject(mainWindow) : QObject(mainWindow)
, m_mainWindow(mainWindow) , m_mainWindow(mainWindow)
, m_viewPaneManager(viewPaneManager) , m_viewPaneManager(viewPaneManager)
, m_actionManager(mainWindow->GetActionManager()) , m_actionManager(mainWindow->GetActionManager())
, m_settings(settings)
{ {
#if defined(AZ_PLATFORM_MAC) #if defined(AZ_PLATFORM_MAC)
// Hide the non-native toolbar, then setNativeMenuBar to ensure it is always visible on macOS. // Hide the non-native toolbar, then setNativeMenuBar to ensure it is always visible on macOS.

@ -33,7 +33,7 @@ class LevelEditorMenuHandler
{ {
Q_OBJECT Q_OBJECT
public: public:
LevelEditorMenuHandler(MainWindow* mainWindow, QtViewPaneManager* const viewPaneManager, QSettings& settings); LevelEditorMenuHandler(MainWindow* mainWindow, QtViewPaneManager* const viewPaneManager);
~LevelEditorMenuHandler(); ~LevelEditorMenuHandler();
void Initialize(); void Initialize();
@ -106,7 +106,6 @@ private:
ActionManager::MenuWrapper m_toolsMenu; ActionManager::MenuWrapper m_toolsMenu;
QMenu* m_mostRecentLevelsMenu = nullptr; QMenu* m_mostRecentLevelsMenu = nullptr;
QMenu* m_mostRecentProjectsMenu = nullptr;
QMenu* m_editmenu = nullptr; QMenu* m_editmenu = nullptr;
ActionManager::MenuWrapper m_viewPanesMenu; ActionManager::MenuWrapper m_viewPanesMenu;
@ -117,7 +116,6 @@ private:
int m_viewPaneVersion = 0; int m_viewPaneVersion = 0;
QList<QMenu*> m_topLevelMenus; QList<QMenu*> m_topLevelMenus;
QSettings& m_settings;
}; };
#endif // LEVELEDITORMENUHANDLER_H #endif // LEVELEDITORMENUHANDLER_H

@ -425,7 +425,7 @@ namespace Editor
AZStd::array<BYTE, sizeof(RAWINPUT)> rawInputBytesArray; AZStd::array<BYTE, sizeof(RAWINPUT)> rawInputBytesArray;
LPBYTE rawInputBytes = rawInputBytesArray.data(); LPBYTE rawInputBytes = rawInputBytesArray.data();
const UINT bytesCopied = GetRawInputData((HRAWINPUT)msg->lParam, RID_INPUT, rawInputBytes, &rawInputSize, rawInputHeaderSize); [[maybe_unused]] const UINT bytesCopied = GetRawInputData((HRAWINPUT)msg->lParam, RID_INPUT, rawInputBytes, &rawInputSize, rawInputHeaderSize);
CRY_ASSERT(bytesCopied == rawInputSize); CRY_ASSERT(bytesCopied == rawInputSize);
RAWINPUT* rawInput = (RAWINPUT*)rawInputBytes; RAWINPUT* rawInput = (RAWINPUT*)rawInputBytes;

@ -78,7 +78,6 @@ AZ_POP_DISABLE_WARNING
// CryCommon // CryCommon
#include <CryCommon/ITimer.h> #include <CryCommon/ITimer.h>
#include <CryCommon/IPhysics.h>
#include <CryCommon/ILevelSystem.h> #include <CryCommon/ILevelSystem.h>
// Editor // Editor
@ -448,13 +447,6 @@ void CCryEditApp::RegisterActionHandlers()
ON_COMMAND(ID_OPEN_TRACKVIEW, OnOpenTrackView) ON_COMMAND(ID_OPEN_TRACKVIEW, OnOpenTrackView)
ON_COMMAND(ID_OPEN_UICANVASEDITOR, OnOpenUICanvasEditor) ON_COMMAND(ID_OPEN_UICANVASEDITOR, OnOpenUICanvasEditor)
#if defined(AZ_TOOLS_EXPAND_FOR_RESTRICTED_PLATFORMS)
#define AZ_RESTRICTED_PLATFORM_EXPANSION(CodeName, CODENAME, codename, PrivateName, PRIVATENAME, privatename, PublicName, PUBLICNAME, publicname, PublicAuxName1, PublicAuxName2, PublicAuxName3)\
ON_COMMAND_RANGE(ID_GAME_##CODENAME##_ENABLELOWSPEC, ID_GAME_##CODENAME##_ENABLEHIGHSPEC, OnChangeGameSpec)
AZ_TOOLS_EXPAND_FOR_RESTRICTED_PLATFORMS
#undef AZ_RESTRICTED_PLATFORM_EXPANSION
#endif
ON_COMMAND(ID_OPEN_QUICK_ACCESS_BAR, OnOpenQuickAccessBar) ON_COMMAND(ID_OPEN_QUICK_ACCESS_BAR, OnOpenQuickAccessBar)
ON_COMMAND(ID_FILE_SAVE_LEVEL, OnFileSave) ON_COMMAND(ID_FILE_SAVE_LEVEL, OnFileSave)
@ -3910,11 +3902,19 @@ void CCryEditApp::OpenLUAEditor(const char* files)
AZStd::string_view exePath; AZStd::string_view exePath;
AZ::ComponentApplicationBus::BroadcastResult(exePath, &AZ::ComponentApplicationRequests::GetExecutableFolder); AZ::ComponentApplicationBus::BroadcastResult(exePath, &AZ::ComponentApplicationRequests::GetExecutableFolder);
AZStd::string process = AZStd::string::format("\"%.*s" AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING "LuaIDE" #if defined(AZ_PLATFORM_LINUX)
// On Linux platforms, launching a process is not done through a shell and its arguments are passed in
// separately. There is no need to wrap the process path in case of spaces in the path
constexpr const char* argumentQuoteString = "";
#else
constexpr const char* argumentQuoteString = "\"";
#endif
AZStd::string process = AZStd::string::format("%s%.*s" AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING "LuaIDE"
#if defined(AZ_PLATFORM_WINDOWS) #if defined(AZ_PLATFORM_WINDOWS)
".exe" ".exe"
#endif #endif
"\"", aznumeric_cast<int>(exePath.size()), exePath.data()); "%s", argumentQuoteString, aznumeric_cast<int>(exePath.size()), exePath.data(), argumentQuoteString);
AZStd::string processArgs = AZStd::string::format("%s -engine-path \"%s\"", args.c_str(), engineRoot); AZStd::string processArgs = AZStd::string::format("%s -engine-path \"%s\"", args.c_str(), engineRoot);
StartProcessDetached(process.c_str(), processArgs.c_str()); StartProcessDetached(process.c_str(), processArgs.c_str());

@ -342,7 +342,7 @@ void CCryEditDoc::Load(TDocMultiArchive& arrXmlAr, const QString& szFilename)
// Register this level and its content hash as version // Register this level and its content hash as version
GetIEditor()->GetSettingsManager()->AddToolVersion(fileName, levelHash); GetIEditor()->GetSettingsManager()->AddToolVersion(fileName, levelHash);
GetIEditor()->GetSettingsManager()->RegisterEvent(loadEvent); GetIEditor()->GetSettingsManager()->RegisterEvent(loadEvent);
LOADING_TIME_PROFILE_SECTION(gEnv->pSystem);
CAutoDocNotReady autoDocNotReady; CAutoDocNotReady autoDocNotReady;
HEAP_CHECK HEAP_CHECK
@ -1018,14 +1018,6 @@ bool CCryEditDoc::AfterSaveDocument([[maybe_unused]] const QString& lpszPathName
return bSaved; return bSaved;
} }
static void GetUserSettingsFile(const QString& levelFolder, QString& userSettings)
{
const char* pUserName = GetISystem()->GetUserName();
QString fileName = QStringLiteral("%1_usersettings.editor_xml").arg(pUserName);
userSettings = Path::Make(levelFolder, fileName);
}
static bool TryRenameFile(const QString& oldPath, const QString& newPath, int retryAttempts=10) static bool TryRenameFile(const QString& oldPath, const QString& newPath, int retryAttempts=10)
{ {
QFile(newPath).setPermissions(QFile::ReadOther | QFile::WriteOther); QFile(newPath).setPermissions(QFile::ReadOther | QFile::WriteOther);
@ -1047,7 +1039,7 @@ static bool TryRenameFile(const QString& oldPath, const QString& newPath, int re
bool CCryEditDoc::SaveLevel(const QString& filename) bool CCryEditDoc::SaveLevel(const QString& filename)
{ {
AZ_PROFILE_FUNCTION(AzToolsFramework); AZ_PROFILE_FUNCTION(Editor);
QWaitCursor wait; QWaitCursor wait;
CAutoCheckOutDialogEnableForAll enableForAll; CAutoCheckOutDialogEnableForAll enableForAll;
@ -1067,7 +1059,7 @@ bool CCryEditDoc::SaveLevel(const QString& filename)
{ {
AZ_PROFILE_SCOPE(AzToolsFramework, "CCryEditDoc::SaveLevel BackupBeforeSave"); AZ_PROFILE_SCOPE(Editor, "CCryEditDoc::SaveLevel BackupBeforeSave");
BackupBeforeSave(); BackupBeforeSave();
} }
@ -1178,7 +1170,7 @@ bool CCryEditDoc::SaveLevel(const QString& filename)
CPakFile pakFile; CPakFile pakFile;
{ {
AZ_PROFILE_SCOPE(AzToolsFramework, "CCryEditDoc::SaveLevel Open PakFile"); AZ_PROFILE_SCOPE(Editor, "CCryEditDoc::SaveLevel Open PakFile");
if (!pakFile.Open(tempSaveFile.toUtf8().data(), false)) if (!pakFile.Open(tempSaveFile.toUtf8().data(), false))
{ {
gEnv->pLog->LogWarning("Unable to open pack file %s for writing", tempSaveFile.toUtf8().data()); gEnv->pLog->LogWarning("Unable to open pack file %s for writing", tempSaveFile.toUtf8().data());
@ -1209,7 +1201,7 @@ bool CCryEditDoc::SaveLevel(const QString& filename)
AZ::IO::ByteContainerStream<AZStd::vector<char>> entitySaveStream(&entitySaveBuffer); AZ::IO::ByteContainerStream<AZStd::vector<char>> entitySaveStream(&entitySaveBuffer);
{ {
AZ_PROFILE_SCOPE(AzToolsFramework, "CCryEditDoc::SaveLevel Save Entities To Stream"); AZ_PROFILE_SCOPE(Editor, "CCryEditDoc::SaveLevel Save Entities To Stream");
EBUS_EVENT_RESULT( EBUS_EVENT_RESULT(
savedEntities, AzToolsFramework::EditorEntityContextRequestBus, SaveToStreamForEditor, entitySaveStream, layerEntities, savedEntities, AzToolsFramework::EditorEntityContextRequestBus, SaveToStreamForEditor, entitySaveStream, layerEntities,
instancesInLayers); instancesInLayers);

@ -97,11 +97,6 @@ namespace
} }
} }
const char* PyGetGameFolder()
{
return Path::GetEditingGameDataFolder().c_str();
}
AZStd::string PyGetGameFolderAsString() AZStd::string PyGetGameFolderAsString()
{ {
return Path::GetEditingGameDataFolder(); return Path::GetEditingGameDataFolder();

@ -1,75 +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
*
*/
// Description : Calculate the reference frame for sub-object selections.
#include "EditorDefs.h"
#include "SubObjectSelectionReferenceFrameCalculator.h"
SubObjectSelectionReferenceFrameCalculator::SubObjectSelectionReferenceFrameCalculator(ESubObjElementType selectionType)
: m_anySelected(false)
, pos(0.0f, 0.0f, 0.0f)
, normal(0.0f, 0.0f, 0.0f)
, nNormals(0)
, selectionType(selectionType)
, bUseExplicitFrame(false)
, bExplicitAnySelected(false)
{
}
void SubObjectSelectionReferenceFrameCalculator::SetExplicitFrame(bool bAnySelected, const Matrix34& refFrame)
{
this->m_refFrame = refFrame;
this->bUseExplicitFrame = true;
this->bExplicitAnySelected = bAnySelected;
}
bool SubObjectSelectionReferenceFrameCalculator::GetFrame(Matrix34& refFrame)
{
if (this->bUseExplicitFrame)
{
refFrame = this->m_refFrame;
return this->bExplicitAnySelected;
}
else
{
refFrame.SetIdentity();
if (this->nNormals > 0)
{
this->normal = this->normal / static_cast<float>(this->nNormals);
if (!this->normal.IsZero())
{
this->normal.Normalize();
}
// Average position.
this->pos = this->pos / static_cast<float>(this->nNormals);
refFrame.SetTranslation(this->pos);
}
if (this->m_anySelected)
{
if (!this->normal.IsZero())
{
Vec3 xAxis(1, 0, 0), yAxis(0, 1, 0), zAxis(0, 0, 1);
if (this->normal.IsEquivalent(zAxis) || normal.IsEquivalent(-zAxis))
{
zAxis = xAxis;
}
xAxis = this->normal.Cross(zAxis).GetNormalized();
yAxis = xAxis.Cross(this->normal).GetNormalized();
refFrame.SetFromVectors(xAxis, yAxis, normal, pos);
}
}
return m_anySelected;
}
}

@ -1,42 +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
*
*/
// Description : Calculate the reference frame for sub-object selections.
#ifndef CRYINCLUDE_EDITOR_EDITMODE_SUBOBJECTSELECTIONREFERENCEFRAMECALCULATOR_H
#define CRYINCLUDE_EDITOR_EDITMODE_SUBOBJECTSELECTIONREFERENCEFRAMECALCULATOR_H
#pragma once
#include "ISubObjectSelectionReferenceFrameCalculator.h"
#include "Objects/SubObjSelection.h"
class SubObjectSelectionReferenceFrameCalculator
: public ISubObjectSelectionReferenceFrameCalculator
{
public:
SubObjectSelectionReferenceFrameCalculator(ESubObjElementType selectionType);
virtual void SetExplicitFrame(bool bAnySelected, const Matrix34& refFrame);
bool GetFrame(Matrix34& refFrame);
private:
bool m_anySelected;
Vec3 pos;
Vec3 normal;
int nNormals;
ESubObjElementType selectionType;
std::vector<Vec3> positions;
Matrix34 m_refFrame;
bool bUseExplicitFrame;
bool bExplicitAnySelected;
};
#endif // CRYINCLUDE_EDITOR_EDITMODE_SUBOBJECTSELECTIONREFERENCEFRAMECALCULATOR_H

@ -8,8 +8,6 @@
#pragma once #pragma once
#ifndef CRYINCLUDE_EDITOR_EDITORDEFS_H
#define CRYINCLUDE_EDITOR_EDITORDEFS_H
#include <AzCore/PlatformDef.h> #include <AzCore/PlatformDef.h>
@ -186,5 +184,3 @@
#endif #endif
#endif #endif
#endif // CRYINCLUDE_EDITOR_EDITORDEFS_H

@ -0,0 +1,286 @@
/*
* 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
*
*/
#include <EditorModularViewportCameraComposer.h>
#include <AtomToolsFramework/Viewport/ModularViewportCameraControllerRequestBus.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzFramework/Render/IntersectorInterface.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
#include <EditorViewportSettings.h>
namespace SandboxEditor
{
static AzFramework::TranslateCameraInputChannelIds BuildTranslateCameraInputChannelIds()
{
AzFramework::TranslateCameraInputChannelIds translateCameraInputChannelIds;
translateCameraInputChannelIds.m_leftChannelId = SandboxEditor::CameraTranslateLeftChannelId();
translateCameraInputChannelIds.m_rightChannelId = SandboxEditor::CameraTranslateRightChannelId();
translateCameraInputChannelIds.m_forwardChannelId = SandboxEditor::CameraTranslateForwardChannelId();
translateCameraInputChannelIds.m_backwardChannelId = SandboxEditor::CameraTranslateBackwardChannelId();
translateCameraInputChannelIds.m_upChannelId = SandboxEditor::CameraTranslateUpChannelId();
translateCameraInputChannelIds.m_downChannelId = SandboxEditor::CameraTranslateDownChannelId();
translateCameraInputChannelIds.m_boostChannelId = SandboxEditor::CameraTranslateBoostChannelId();
return translateCameraInputChannelIds;
}
EditorModularViewportCameraComposer::EditorModularViewportCameraComposer(const AzFramework::ViewportId viewportId)
: m_viewportId(viewportId)
{
EditorModularViewportCameraComposerNotificationBus::Handler::BusConnect(viewportId);
}
EditorModularViewportCameraComposer::~EditorModularViewportCameraComposer()
{
EditorModularViewportCameraComposerNotificationBus::Handler::BusDisconnect();
}
AZStd::shared_ptr<AtomToolsFramework::ModularViewportCameraController> EditorModularViewportCameraComposer::
CreateModularViewportCameraController()
{
SetupCameras();
auto controller = AZStd::make_shared<AtomToolsFramework::ModularViewportCameraController>();
controller->SetCameraViewportContextBuilderCallback(
[viewportId = m_viewportId](AZStd::unique_ptr<AtomToolsFramework::ModularCameraViewportContext>& cameraViewportContext)
{
cameraViewportContext = AZStd::make_unique<AtomToolsFramework::ModularCameraViewportContextImpl>(viewportId);
});
controller->SetCameraPriorityBuilderCallback(
[](AtomToolsFramework::CameraControllerPriorityFn& cameraControllerPriorityFn)
{
cameraControllerPriorityFn = AtomToolsFramework::DefaultCameraControllerPriority;
});
controller->SetCameraPropsBuilderCallback(
[](AzFramework::CameraProps& cameraProps)
{
cameraProps.m_rotateSmoothnessFn = []
{
return SandboxEditor::CameraRotateSmoothness();
};
cameraProps.m_translateSmoothnessFn = []
{
return SandboxEditor::CameraTranslateSmoothness();
};
cameraProps.m_rotateSmoothingEnabledFn = []
{
return SandboxEditor::CameraRotateSmoothingEnabled();
};
cameraProps.m_translateSmoothingEnabledFn = []
{
return SandboxEditor::CameraTranslateSmoothingEnabled();
};
});
controller->SetCameraListBuilderCallback(
[this](AzFramework::Cameras& cameras)
{
cameras.AddCamera(m_firstPersonRotateCamera);
cameras.AddCamera(m_firstPersonPanCamera);
cameras.AddCamera(m_firstPersonTranslateCamera);
cameras.AddCamera(m_firstPersonScrollCamera);
cameras.AddCamera(m_orbitCamera);
});
return controller;
}
void EditorModularViewportCameraComposer::SetupCameras()
{
const auto hideCursor = [viewportId = m_viewportId]
{
if (SandboxEditor::CameraCaptureCursorForLook())
{
AzToolsFramework::ViewportInteraction::ViewportMouseCursorRequestBus::Event(
viewportId, &AzToolsFramework::ViewportInteraction::ViewportMouseCursorRequestBus::Events::BeginCursorCapture);
}
};
const auto showCursor = [viewportId = m_viewportId]
{
if (SandboxEditor::CameraCaptureCursorForLook())
{
AzToolsFramework::ViewportInteraction::ViewportMouseCursorRequestBus::Event(
viewportId, &AzToolsFramework::ViewportInteraction::ViewportMouseCursorRequestBus::Events::EndCursorCapture);
}
};
m_firstPersonRotateCamera = AZStd::make_shared<AzFramework::RotateCameraInput>(SandboxEditor::CameraFreeLookChannelId());
m_firstPersonRotateCamera->m_rotateSpeedFn = []
{
return SandboxEditor::CameraRotateSpeed();
};
// default behavior is to hide the cursor but this can be disabled (useful for remote desktop)
// note: See CaptureCursorLook in the Settings Registry
m_firstPersonRotateCamera->SetActivationBeganFn(hideCursor);
m_firstPersonRotateCamera->SetActivationEndedFn(showCursor);
m_firstPersonPanCamera =
AZStd::make_shared<AzFramework::PanCameraInput>(SandboxEditor::CameraFreePanChannelId(), AzFramework::LookPan);
m_firstPersonPanCamera->m_panSpeedFn = []
{
return SandboxEditor::CameraPanSpeed();
};
m_firstPersonPanCamera->m_invertPanXFn = []
{
return SandboxEditor::CameraPanInvertedX();
};
m_firstPersonPanCamera->m_invertPanYFn = []
{
return SandboxEditor::CameraPanInvertedY();
};
const auto translateCameraInputChannelIds = BuildTranslateCameraInputChannelIds();
m_firstPersonTranslateCamera =
AZStd::make_shared<AzFramework::TranslateCameraInput>(AzFramework::LookTranslation, translateCameraInputChannelIds);
m_firstPersonTranslateCamera->m_translateSpeedFn = []
{
return SandboxEditor::CameraTranslateSpeed();
};
m_firstPersonTranslateCamera->m_boostMultiplierFn = []
{
return SandboxEditor::CameraBoostMultiplier();
};
m_firstPersonScrollCamera = AZStd::make_shared<AzFramework::ScrollTranslationCameraInput>();
m_firstPersonScrollCamera->m_scrollSpeedFn = []
{
return SandboxEditor::CameraScrollSpeed();
};
m_orbitCamera = AZStd::make_shared<AzFramework::OrbitCameraInput>(SandboxEditor::CameraOrbitChannelId());
m_orbitCamera->SetLookAtFn(
[viewportId = m_viewportId](const AZ::Vector3& position, const AZ::Vector3& direction) -> AZStd::optional<AZ::Vector3>
{
AZStd::optional<AZ::Vector3> lookAtAfterInterpolation;
AtomToolsFramework::ModularViewportCameraControllerRequestBus::EventResult(
lookAtAfterInterpolation, viewportId,
&AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::LookAtAfterInterpolation);
// initially attempt to use the last set look at point after an interpolation has finished
if (lookAtAfterInterpolation.has_value())
{
return *lookAtAfterInterpolation;
}
const float RayDistance = 1000.0f;
AzFramework::RenderGeometry::RayRequest ray;
ray.m_startWorldPosition = position;
ray.m_endWorldPosition = position + direction * RayDistance;
ray.m_onlyVisible = true;
AzFramework::RenderGeometry::RayResult renderGeometryIntersectionResult;
AzFramework::RenderGeometry::IntersectorBus::EventResult(
renderGeometryIntersectionResult, AzToolsFramework::GetEntityContextId(),
&AzFramework::RenderGeometry::IntersectorBus::Events::RayIntersect, ray);
// attempt a ray intersection with any visible mesh and return the intersection position if successful
if (renderGeometryIntersectionResult)
{
return renderGeometryIntersectionResult.m_worldPosition;
}
// if there is no selection or no intersection, fallback to default camera orbit behavior (ground plane
// intersection)
return {};
});
m_orbitRotateCamera = AZStd::make_shared<AzFramework::RotateCameraInput>(SandboxEditor::CameraOrbitLookChannelId());
m_orbitRotateCamera->m_rotateSpeedFn = []
{
return SandboxEditor::CameraRotateSpeed();
};
m_orbitRotateCamera->m_invertYawFn = []
{
return SandboxEditor::CameraOrbitYawRotationInverted();
};
m_orbitTranslateCamera =
AZStd::make_shared<AzFramework::TranslateCameraInput>(AzFramework::OrbitTranslation, translateCameraInputChannelIds);
m_orbitTranslateCamera->m_translateSpeedFn = []
{
return SandboxEditor::CameraTranslateSpeed();
};
m_orbitTranslateCamera->m_boostMultiplierFn = []
{
return SandboxEditor::CameraBoostMultiplier();
};
m_orbitDollyScrollCamera = AZStd::make_shared<AzFramework::OrbitDollyScrollCameraInput>();
m_orbitDollyScrollCamera->m_scrollSpeedFn = []
{
return SandboxEditor::CameraScrollSpeed();
};
m_orbitDollyMoveCamera =
AZStd::make_shared<AzFramework::OrbitDollyCursorMoveCameraInput>(SandboxEditor::CameraOrbitDollyChannelId());
m_orbitDollyMoveCamera->m_cursorSpeedFn = []
{
return SandboxEditor::CameraDollyMotionSpeed();
};
m_orbitPanCamera = AZStd::make_shared<AzFramework::PanCameraInput>(SandboxEditor::CameraOrbitPanChannelId(), AzFramework::OrbitPan);
m_orbitPanCamera->m_panSpeedFn = []
{
return SandboxEditor::CameraPanSpeed();
};
m_orbitPanCamera->m_invertPanXFn = []
{
return SandboxEditor::CameraPanInvertedX();
};
m_orbitPanCamera->m_invertPanYFn = []
{
return SandboxEditor::CameraPanInvertedY();
};
m_orbitCamera->m_orbitCameras.AddCamera(m_orbitRotateCamera);
m_orbitCamera->m_orbitCameras.AddCamera(m_orbitTranslateCamera);
m_orbitCamera->m_orbitCameras.AddCamera(m_orbitDollyScrollCamera);
m_orbitCamera->m_orbitCameras.AddCamera(m_orbitDollyMoveCamera);
m_orbitCamera->m_orbitCameras.AddCamera(m_orbitPanCamera);
}
void EditorModularViewportCameraComposer::OnEditorModularViewportCameraComposerSettingsChanged()
{
const auto translateCameraInputChannelIds = BuildTranslateCameraInputChannelIds();
m_firstPersonTranslateCamera->SetTranslateCameraInputChannelIds(translateCameraInputChannelIds);
m_orbitTranslateCamera->SetTranslateCameraInputChannelIds(translateCameraInputChannelIds);
m_firstPersonPanCamera->SetPanInputChannelId(SandboxEditor::CameraFreePanChannelId());
m_orbitPanCamera->SetPanInputChannelId(SandboxEditor::CameraOrbitPanChannelId());
m_firstPersonRotateCamera->SetRotateInputChannelId(SandboxEditor::CameraFreeLookChannelId());
m_orbitRotateCamera->SetRotateInputChannelId(SandboxEditor::CameraOrbitLookChannelId());
m_orbitCamera->SetOrbitInputChannelId(SandboxEditor::CameraOrbitChannelId());
m_orbitDollyMoveCamera->SetDollyInputChannelId(SandboxEditor::CameraOrbitDollyChannelId());
}
} // namespace SandboxEditor

@ -0,0 +1,48 @@
/*
* 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
*
*/
#pragma once
#include <AtomToolsFramework/Viewport/ModularViewportCameraController.h>
#include <AzFramework/Viewport/CameraInput.h>
#include <EditorModularViewportCameraComposerBus.h>
#include <SandboxAPI.h>
namespace SandboxEditor
{
//! Type responsible for building the editor's modular viewport camera controller.
class EditorModularViewportCameraComposer : private EditorModularViewportCameraComposerNotificationBus::Handler
{
public:
SANDBOX_API explicit EditorModularViewportCameraComposer(AzFramework::ViewportId viewportId);
SANDBOX_API ~EditorModularViewportCameraComposer();
//! Build a ModularViewportCameraController from the associated camera inputs.
SANDBOX_API AZStd::shared_ptr<AtomToolsFramework::ModularViewportCameraController> CreateModularViewportCameraController();
private:
//! Setup all internal camera inputs.
void SetupCameras();
// EditorModularViewportCameraComposerNotificationBus overrides ...
void OnEditorModularViewportCameraComposerSettingsChanged() override;
AZStd::shared_ptr<AzFramework::RotateCameraInput> m_firstPersonRotateCamera;
AZStd::shared_ptr<AzFramework::PanCameraInput> m_firstPersonPanCamera;
AZStd::shared_ptr<AzFramework::TranslateCameraInput> m_firstPersonTranslateCamera;
AZStd::shared_ptr<AzFramework::ScrollTranslationCameraInput> m_firstPersonScrollCamera;
AZStd::shared_ptr<AzFramework::OrbitCameraInput> m_orbitCamera;
AZStd::shared_ptr<AzFramework::RotateCameraInput> m_orbitRotateCamera;
AZStd::shared_ptr<AzFramework::TranslateCameraInput> m_orbitTranslateCamera;
AZStd::shared_ptr<AzFramework::OrbitDollyScrollCameraInput> m_orbitDollyScrollCamera;
AZStd::shared_ptr<AzFramework::OrbitDollyCursorMoveCameraInput> m_orbitDollyMoveCamera;
AZStd::shared_ptr<AzFramework::PanCameraInput> m_orbitPanCamera;
AzFramework::ViewportId m_viewportId;
};
} // namespace SandboxEditor

@ -0,0 +1,31 @@
/*
* 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
*
*/
#pragma once
#include <AzCore/EBus/EBus.h>
#include <AzFramework/Viewport/ViewportId.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
namespace SandboxEditor
{
//! Notifications for changes to the editor modular viewport camera controller.
class EditorModularViewportCameraComposerNotifications
{
public:
//! Notify any listeners when changes have been made to the modular viewport camera settings.
//! @note This is used to update any cached input channels when controls are modified.
virtual void OnEditorModularViewportCameraComposerSettingsChanged() = 0;
protected:
~EditorModularViewportCameraComposerNotifications() = default;
};
using EditorModularViewportCameraComposerNotificationBus =
AZ::EBus<EditorModularViewportCameraComposerNotifications, AzToolsFramework::ViewportInteraction::ViewportEBusTraits>;
} // namespace SandboxEditor

@ -5,51 +5,220 @@
* SPDX-License-Identifier: Apache-2.0 OR MIT * SPDX-License-Identifier: Apache-2.0 OR MIT
* *
*/ */
#include "EditorDefs.h" #include "EditorDefs.h"
#include "EditorPreferencesPageViewportMovement.h" #include "EditorPreferencesPageViewportMovement.h"
#include <AzCore/std/sort.h>
#include <AzFramework/Input/Buses/Requests/InputDeviceRequestBus.h>
#include <AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h>
#include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
#include <AzQtComponents/Components/StyleManager.h> #include <AzQtComponents/Components/StyleManager.h>
#include <EditorModularViewportCameraComposerBus.h>
// Editor // Editor
#include "Settings.h"
#include "EditorViewportSettings.h" #include "EditorViewportSettings.h"
#include "Settings.h"
static AZStd::vector<AZStd::string> GetInputNamesByDevice(const AzFramework::InputDeviceId inputDeviceId)
{
AzFramework::InputDeviceRequests::InputChannelIdSet availableInputChannelIds;
AzFramework::InputDeviceRequestBus::Event(
inputDeviceId, &AzFramework::InputDeviceRequests::GetInputChannelIds, availableInputChannelIds);
AZStd::vector<AZStd::string> inputChannelNames;
for (const AzFramework::InputChannelId& inputChannelId : availableInputChannelIds)
{
inputChannelNames.push_back(inputChannelId.GetName());
}
AZStd::sort(inputChannelNames.begin(), inputChannelNames.end());
return inputChannelNames;
}
static AZStd::vector<AZStd::string> GetEditorInputNames()
{
// function static to defer having to call GetInputNamesByDevice for every CameraInputSettings member
static bool inputNamesGenerated = false;
static AZStd::vector<AZStd::string> inputNames;
if (!inputNamesGenerated)
{
AZStd::vector<AZStd::string> keyboardInputNames = GetInputNamesByDevice(AzFramework::InputDeviceKeyboard::Id);
AZStd::vector<AZStd::string> mouseInputNames = GetInputNamesByDevice(AzFramework::InputDeviceMouse::Id);
inputNames.insert(inputNames.end(), mouseInputNames.begin(), mouseInputNames.end());
inputNames.insert(inputNames.end(), keyboardInputNames.begin(), keyboardInputNames.end());
inputNamesGenerated = true;
}
return inputNames;
}
void CEditorPreferencesPage_ViewportMovement::Reflect(AZ::SerializeContext& serialize) void CEditorPreferencesPage_ViewportMovement::Reflect(AZ::SerializeContext& serialize)
{ {
serialize.Class<CameraMovementSettings>() serialize.Class<CameraMovementSettings>()
->Version(1) ->Version(2)
->Field("MoveSpeed", &CameraMovementSettings::m_moveSpeed) ->Field("TranslateSpeed", &CameraMovementSettings::m_translateSpeed)
->Field("RotateSpeed", &CameraMovementSettings::m_rotateSpeed) ->Field("RotateSpeed", &CameraMovementSettings::m_rotateSpeed)
->Field("FastMoveSpeed", &CameraMovementSettings::m_fastMoveSpeed) ->Field("BoostMultiplier", &CameraMovementSettings::m_boostMultiplier)
->Field("WheelZoomSpeed", &CameraMovementSettings::m_wheelZoomSpeed) ->Field("ScrollSpeed", &CameraMovementSettings::m_scrollSpeed)
->Field("InvertYAxis", &CameraMovementSettings::m_invertYRotation) ->Field("DollySpeed", &CameraMovementSettings::m_dollySpeed)
->Field("InvertPan", &CameraMovementSettings::m_invertPan); ->Field("PanSpeed", &CameraMovementSettings::m_panSpeed)
->Field("RotateSmoothing", &CameraMovementSettings::m_rotateSmoothing)
->Field("RotateSmoothness", &CameraMovementSettings::m_rotateSmoothness)
->Field("TranslateSmoothing", &CameraMovementSettings::m_translateSmoothing)
->Field("TranslateSmoothness", &CameraMovementSettings::m_translateSmoothness)
->Field("CaptureCursorLook", &CameraMovementSettings::m_captureCursorLook)
->Field("OrbitYawRotationInverted", &CameraMovementSettings::m_orbitYawRotationInverted)
->Field("PanInvertedX", &CameraMovementSettings::m_panInvertedX)
->Field("PanInvertedY", &CameraMovementSettings::m_panInvertedY);
serialize.Class<CEditorPreferencesPage_ViewportMovement>() serialize.Class<CameraInputSettings>()
->Version(1) ->Version(1)
->Field("CameraMovementSettings", &CEditorPreferencesPage_ViewportMovement::m_cameraMovementSettings); ->Field("TranslateForward", &CameraInputSettings::m_translateForwardChannelId)
->Field("TranslateBackward", &CameraInputSettings::m_translateBackwardChannelId)
->Field("TranslateLeft", &CameraInputSettings::m_translateLeftChannelId)
->Field("TranslateRight", &CameraInputSettings::m_translateRightChannelId)
->Field("TranslateUp", &CameraInputSettings::m_translateUpChannelId)
->Field("TranslateDown", &CameraInputSettings::m_translateDownChannelId)
->Field("Boost", &CameraInputSettings::m_boostChannelId)
->Field("Orbit", &CameraInputSettings::m_orbitChannelId)
->Field("FreeLook", &CameraInputSettings::m_freeLookChannelId)
->Field("FreePan", &CameraInputSettings::m_freePanChannelId)
->Field("OrbitLook", &CameraInputSettings::m_orbitLookChannelId)
->Field("OrbitDolly", &CameraInputSettings::m_orbitDollyChannelId)
->Field("OrbitPan", &CameraInputSettings::m_orbitPanChannelId);
serialize.Class<CEditorPreferencesPage_ViewportMovement>()
->Version(1)
->Field("CameraMovementSettings", &CEditorPreferencesPage_ViewportMovement::m_cameraMovementSettings)
->Field("CameraInputSettings", &CEditorPreferencesPage_ViewportMovement::m_cameraInputSettings);
AZ::EditContext* editContext = serialize.GetEditContext(); if (AZ::EditContext* editContext = serialize.GetEditContext())
if (editContext)
{ {
editContext->Class<CameraMovementSettings>("Camera Movement Settings", "") editContext->Class<CameraMovementSettings>("Camera Movement Settings", "")
->DataElement(AZ::Edit::UIHandlers::SpinBox, &CameraMovementSettings::m_moveSpeed, "Camera Movement Speed", "Camera Movement Speed") ->DataElement(
->DataElement(AZ::Edit::UIHandlers::SpinBox, &CameraMovementSettings::m_rotateSpeed, "Camera Rotation Speed", "Camera Rotation Speed") AZ::Edit::UIHandlers::SpinBox, &CameraMovementSettings::m_translateSpeed, "Camera Movement Speed", "Camera movement speed")
->DataElement(AZ::Edit::UIHandlers::SpinBox, &CameraMovementSettings::m_fastMoveSpeed, "Fast Movement Scale", "Fast Movement Scale (holding shift") ->Attribute(AZ::Edit::Attributes::Min, 0.01f)
->DataElement(AZ::Edit::UIHandlers::SpinBox, &CameraMovementSettings::m_wheelZoomSpeed, "Wheel Zoom Speed", "Wheel Zoom Speed") ->DataElement(
->DataElement(AZ::Edit::UIHandlers::CheckBox, &CameraMovementSettings::m_invertYRotation, "Invert Y Axis", "Invert Y Rotation (holding RMB)") AZ::Edit::UIHandlers::SpinBox, &CameraMovementSettings::m_rotateSpeed, "Camera Rotation Speed", "Camera rotation speed")
->DataElement(AZ::Edit::UIHandlers::CheckBox, &CameraMovementSettings::m_invertPan, "Invert Pan", "Invert Pan (holding MMB)"); ->Attribute(AZ::Edit::Attributes::Min, 0.01f)
->DataElement(
editContext->Class<CEditorPreferencesPage_ViewportMovement>("Gizmo Movement Preferences", "Gizmo Movement Preferences") AZ::Edit::UIHandlers::SpinBox, &CameraMovementSettings::m_boostMultiplier, "Camera Boost Multiplier",
"Camera boost multiplier to apply to movement speed")
->Attribute(AZ::Edit::Attributes::Min, 0.01f)
->DataElement(
AZ::Edit::UIHandlers::SpinBox, &CameraMovementSettings::m_scrollSpeed, "Camera Scroll Speed",
"Camera movement speed while using scroll/wheel input")
->Attribute(AZ::Edit::Attributes::Min, 0.01f)
->DataElement(
AZ::Edit::UIHandlers::SpinBox, &CameraMovementSettings::m_dollySpeed, "Camera Dolly Speed",
"Camera movement speed while using mouse motion to move in and out")
->Attribute(AZ::Edit::Attributes::Min, 0.01f)
->DataElement(
AZ::Edit::UIHandlers::SpinBox, &CameraMovementSettings::m_panSpeed, "Camera Pan Speed",
"Camera movement speed while panning using the mouse")
->Attribute(AZ::Edit::Attributes::Min, 0.01f)
->DataElement(
AZ::Edit::UIHandlers::CheckBox, &CameraMovementSettings::m_rotateSmoothing, "Camera Rotate Smoothing",
"Is camera rotation smoothing enabled or disabled")
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::EntireTree)
->DataElement(
AZ::Edit::UIHandlers::SpinBox, &CameraMovementSettings::m_rotateSmoothness, "Camera Rotate Smoothness",
"Amount of camera smoothing to apply while rotating the camera")
->Attribute(AZ::Edit::Attributes::Min, 0.01f)
->Attribute(AZ::Edit::Attributes::Visibility, &CameraMovementSettings::RotateSmoothingVisibility)
->DataElement(
AZ::Edit::UIHandlers::CheckBox, &CameraMovementSettings::m_translateSmoothing, "Camera Translate Smoothing",
"Is camera translation smoothing enabled or disabled")
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::EntireTree)
->DataElement(
AZ::Edit::UIHandlers::SpinBox, &CameraMovementSettings::m_translateSmoothness, "Camera Translate Smoothness",
"Amount of camera smoothing to apply while translating the camera")
->Attribute(AZ::Edit::Attributes::Min, 0.01f)
->Attribute(AZ::Edit::Attributes::Visibility, &CameraMovementSettings::TranslateSmoothingVisibility)
->DataElement(
AZ::Edit::UIHandlers::CheckBox, &CameraMovementSettings::m_orbitYawRotationInverted, "Camera Orbit Yaw Inverted",
"Inverted yaw rotation while orbiting")
->DataElement(
AZ::Edit::UIHandlers::CheckBox, &CameraMovementSettings::m_panInvertedX, "Invert Pan X",
"Invert direction of pan in local X axis")
->DataElement(
AZ::Edit::UIHandlers::CheckBox, &CameraMovementSettings::m_panInvertedY, "Invert Pan Y",
"Invert direction of pan in local Y axis")
->DataElement(
AZ::Edit::UIHandlers::CheckBox, &CameraMovementSettings::m_captureCursorLook, "Camera Capture Look Cursor",
"Should the cursor be captured (hidden) while performing free look");
editContext->Class<CameraInputSettings>("Camera Input Settings", "")
->DataElement(
AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_translateForwardChannelId, "Translate Forward",
"Key/button to move the camera forward")
->Attribute(AZ::Edit::Attributes::StringList, &GetEditorInputNames)
->DataElement(
AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_translateBackwardChannelId, "Translate Backward",
"Key/button to move the camera backward")
->Attribute(AZ::Edit::Attributes::StringList, &GetEditorInputNames)
->DataElement(
AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_translateLeftChannelId, "Translate Left",
"Key/button to move the camera left")
->Attribute(AZ::Edit::Attributes::StringList, &GetEditorInputNames)
->DataElement(
AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_translateRightChannelId, "Translate Right",
"Key/button to move the camera right")
->Attribute(AZ::Edit::Attributes::StringList, &GetEditorInputNames)
->DataElement(
AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_translateUpChannelId, "Translate Up",
"Key/button to move the camera up")
->Attribute(AZ::Edit::Attributes::StringList, &GetEditorInputNames)
->DataElement(
AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_translateDownChannelId, "Translate Down",
"Key/button to move the camera down")
->Attribute(AZ::Edit::Attributes::StringList, &GetEditorInputNames)
->DataElement(
AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_boostChannelId, "Boost",
"Key/button to move the camera more quickly")
->Attribute(AZ::Edit::Attributes::StringList, &GetEditorInputNames)
->DataElement(
AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_orbitChannelId, "Orbit",
"Key/button to begin the camera orbit behavior")
->Attribute(AZ::Edit::Attributes::StringList, &GetEditorInputNames)
->DataElement(
AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_freeLookChannelId, "Free Look",
"Key/button to begin camera free look")
->Attribute(AZ::Edit::Attributes::StringList, &GetEditorInputNames)
->DataElement(
AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_freePanChannelId, "Free Pan", "Key/button to begin camera free pan")
->Attribute(AZ::Edit::Attributes::StringList, &GetEditorInputNames)
->DataElement(
AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_orbitLookChannelId, "Orbit Look",
"Key/button to begin camera orbit look")
->Attribute(AZ::Edit::Attributes::StringList, &GetEditorInputNames)
->DataElement(
AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_orbitDollyChannelId, "Orbit Dolly",
"Key/button to begin camera orbit dolly")
->Attribute(AZ::Edit::Attributes::StringList, &GetEditorInputNames)
->DataElement(
AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_orbitPanChannelId, "Orbit Pan",
"Key/button to begin camera orbit pan")
->Attribute(AZ::Edit::Attributes::StringList, &GetEditorInputNames);
editContext->Class<CEditorPreferencesPage_ViewportMovement>("Viewport Preferences", "Viewport Preferences")
->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ_CRC("PropertyVisibility_ShowChildrenOnly", 0xef428f20)) ->Attribute(AZ::Edit::Attributes::Visibility, AZ_CRC("PropertyVisibility_ShowChildrenOnly", 0xef428f20))
->DataElement(AZ::Edit::UIHandlers::Default, &CEditorPreferencesPage_ViewportMovement::m_cameraMovementSettings, "Camera Movement Settings", "Camera Movement Settings"); ->DataElement(
AZ::Edit::UIHandlers::Default, &CEditorPreferencesPage_ViewportMovement::m_cameraMovementSettings,
"Camera Movement Settings", "Camera Movement Settings")
->DataElement(
AZ::Edit::UIHandlers::Default, &CEditorPreferencesPage_ViewportMovement::m_cameraInputSettings, "Camera Input Settings",
"Camera Input Settings");
} }
} }
CEditorPreferencesPage_ViewportMovement::CEditorPreferencesPage_ViewportMovement() CEditorPreferencesPage_ViewportMovement::CEditorPreferencesPage_ViewportMovement()
{ {
InitializeSettings(); InitializeSettings();
@ -68,21 +237,67 @@ QIcon& CEditorPreferencesPage_ViewportMovement::GetIcon()
void CEditorPreferencesPage_ViewportMovement::OnApply() void CEditorPreferencesPage_ViewportMovement::OnApply()
{ {
SandboxEditor::SetCameraTranslateSpeed(m_cameraMovementSettings.m_moveSpeed); SandboxEditor::SetCameraTranslateSpeed(m_cameraMovementSettings.m_translateSpeed);
SandboxEditor::SetCameraRotateSpeed(m_cameraMovementSettings.m_rotateSpeed); SandboxEditor::SetCameraRotateSpeed(m_cameraMovementSettings.m_rotateSpeed);
SandboxEditor::SetCameraBoostMultiplier(m_cameraMovementSettings.m_fastMoveSpeed); SandboxEditor::SetCameraBoostMultiplier(m_cameraMovementSettings.m_boostMultiplier);
SandboxEditor::SetCameraScrollSpeed(m_cameraMovementSettings.m_wheelZoomSpeed); SandboxEditor::SetCameraScrollSpeed(m_cameraMovementSettings.m_scrollSpeed);
SandboxEditor::SetCameraOrbitYawRotationInverted(m_cameraMovementSettings.m_invertYRotation); SandboxEditor::SetCameraDollyMotionSpeed(m_cameraMovementSettings.m_dollySpeed);
SandboxEditor::SetCameraPanInvertedX(m_cameraMovementSettings.m_invertPan); SandboxEditor::SetCameraPanSpeed(m_cameraMovementSettings.m_panSpeed);
SandboxEditor::SetCameraPanInvertedY(m_cameraMovementSettings.m_invertPan); SandboxEditor::SetCameraRotateSmoothness(m_cameraMovementSettings.m_rotateSmoothness);
SandboxEditor::SetCameraRotateSmoothingEnabled(m_cameraMovementSettings.m_rotateSmoothing);
SandboxEditor::SetCameraTranslateSmoothness(m_cameraMovementSettings.m_translateSmoothness);
SandboxEditor::SetCameraTranslateSmoothingEnabled(m_cameraMovementSettings.m_translateSmoothing);
SandboxEditor::SetCameraCaptureCursorForLook(m_cameraMovementSettings.m_captureCursorLook);
SandboxEditor::SetCameraOrbitYawRotationInverted(m_cameraMovementSettings.m_orbitYawRotationInverted);
SandboxEditor::SetCameraPanInvertedX(m_cameraMovementSettings.m_panInvertedX);
SandboxEditor::SetCameraPanInvertedY(m_cameraMovementSettings.m_panInvertedY);
SandboxEditor::SetCameraTranslateForwardChannelId(m_cameraInputSettings.m_translateForwardChannelId);
SandboxEditor::SetCameraTranslateBackwardChannelId(m_cameraInputSettings.m_translateBackwardChannelId);
SandboxEditor::SetCameraTranslateLeftChannelId(m_cameraInputSettings.m_translateLeftChannelId);
SandboxEditor::SetCameraTranslateRightChannelId(m_cameraInputSettings.m_translateRightChannelId);
SandboxEditor::SetCameraTranslateUpChannelId(m_cameraInputSettings.m_translateUpChannelId);
SandboxEditor::SetCameraTranslateDownChannelId(m_cameraInputSettings.m_translateDownChannelId);
SandboxEditor::SetCameraTranslateBoostChannelId(m_cameraInputSettings.m_boostChannelId);
SandboxEditor::SetCameraOrbitChannelId(m_cameraInputSettings.m_orbitChannelId);
SandboxEditor::SetCameraFreeLookChannelId(m_cameraInputSettings.m_freeLookChannelId);
SandboxEditor::SetCameraFreePanChannelId(m_cameraInputSettings.m_freePanChannelId);
SandboxEditor::SetCameraOrbitLookChannelId(m_cameraInputSettings.m_orbitLookChannelId);
SandboxEditor::SetCameraOrbitDollyChannelId(m_cameraInputSettings.m_orbitDollyChannelId);
SandboxEditor::SetCameraOrbitPanChannelId(m_cameraInputSettings.m_orbitPanChannelId);
SandboxEditor::EditorModularViewportCameraComposerNotificationBus::Broadcast(
&SandboxEditor::EditorModularViewportCameraComposerNotificationBus::Events::OnEditorModularViewportCameraComposerSettingsChanged);
} }
void CEditorPreferencesPage_ViewportMovement::InitializeSettings() void CEditorPreferencesPage_ViewportMovement::InitializeSettings()
{ {
m_cameraMovementSettings.m_moveSpeed = SandboxEditor::CameraTranslateSpeed(); m_cameraMovementSettings.m_translateSpeed = SandboxEditor::CameraTranslateSpeed();
m_cameraMovementSettings.m_rotateSpeed = SandboxEditor::CameraRotateSpeed(); m_cameraMovementSettings.m_rotateSpeed = SandboxEditor::CameraRotateSpeed();
m_cameraMovementSettings.m_fastMoveSpeed = SandboxEditor::CameraBoostMultiplier(); m_cameraMovementSettings.m_boostMultiplier = SandboxEditor::CameraBoostMultiplier();
m_cameraMovementSettings.m_wheelZoomSpeed = SandboxEditor::CameraScrollSpeed(); m_cameraMovementSettings.m_scrollSpeed = SandboxEditor::CameraScrollSpeed();
m_cameraMovementSettings.m_invertYRotation = SandboxEditor::CameraOrbitYawRotationInverted(); m_cameraMovementSettings.m_dollySpeed = SandboxEditor::CameraDollyMotionSpeed();
m_cameraMovementSettings.m_invertPan = SandboxEditor::CameraPanInvertedX() && SandboxEditor::CameraPanInvertedY(); m_cameraMovementSettings.m_panSpeed = SandboxEditor::CameraPanSpeed();
m_cameraMovementSettings.m_rotateSmoothness = SandboxEditor::CameraRotateSmoothness();
m_cameraMovementSettings.m_rotateSmoothing = SandboxEditor::CameraRotateSmoothingEnabled();
m_cameraMovementSettings.m_translateSmoothness = SandboxEditor::CameraTranslateSmoothness();
m_cameraMovementSettings.m_translateSmoothing = SandboxEditor::CameraTranslateSmoothingEnabled();
m_cameraMovementSettings.m_captureCursorLook = SandboxEditor::CameraCaptureCursorForLook();
m_cameraMovementSettings.m_orbitYawRotationInverted = SandboxEditor::CameraOrbitYawRotationInverted();
m_cameraMovementSettings.m_panInvertedX = SandboxEditor::CameraPanInvertedX();
m_cameraMovementSettings.m_panInvertedY = SandboxEditor::CameraPanInvertedY();
m_cameraInputSettings.m_translateForwardChannelId = SandboxEditor::CameraTranslateForwardChannelId().GetName();
m_cameraInputSettings.m_translateBackwardChannelId = SandboxEditor::CameraTranslateBackwardChannelId().GetName();
m_cameraInputSettings.m_translateLeftChannelId = SandboxEditor::CameraTranslateLeftChannelId().GetName();
m_cameraInputSettings.m_translateRightChannelId = SandboxEditor::CameraTranslateRightChannelId().GetName();
m_cameraInputSettings.m_translateUpChannelId = SandboxEditor::CameraTranslateUpChannelId().GetName();
m_cameraInputSettings.m_translateDownChannelId = SandboxEditor::CameraTranslateDownChannelId().GetName();
m_cameraInputSettings.m_boostChannelId = SandboxEditor::CameraTranslateBoostChannelId().GetName();
m_cameraInputSettings.m_orbitChannelId = SandboxEditor::CameraOrbitChannelId().GetName();
m_cameraInputSettings.m_freeLookChannelId = SandboxEditor::CameraFreeLookChannelId().GetName();
m_cameraInputSettings.m_freePanChannelId = SandboxEditor::CameraFreePanChannelId().GetName();
m_cameraInputSettings.m_orbitLookChannelId = SandboxEditor::CameraOrbitLookChannelId().GetName();
m_cameraInputSettings.m_orbitDollyChannelId = SandboxEditor::CameraOrbitDollyChannelId().GetName();
m_cameraInputSettings.m_orbitPanChannelId = SandboxEditor::CameraOrbitPanChannelId().GetName();
} }

@ -5,17 +5,21 @@
* SPDX-License-Identifier: Apache-2.0 OR MIT * SPDX-License-Identifier: Apache-2.0 OR MIT
* *
*/ */
#pragma once #pragma once
#include "Include/IPreferencesPage.h" #include "Include/IPreferencesPage.h"
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/RTTI/RTTI.h> #include <AzCore/RTTI/RTTI.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <QIcon> #include <QIcon>
inline AZ::Crc32 EditorPropertyVisibility(const bool enabled)
{
return enabled ? AZ::Edit::PropertyVisibility::Show : AZ::Edit::PropertyVisibility::Hide;
}
class CEditorPreferencesPage_ViewportMovement class CEditorPreferencesPage_ViewportMovement : public IPreferencesPage
: public IPreferencesPage
{ {
public: public:
AZ_RTTI(CEditorPreferencesPage_ViewportMovement, "{BC593332-7EAF-4171-8A35-1C5DE5B40909}", IPreferencesPage) AZ_RTTI(CEditorPreferencesPage_ViewportMovement, "{BC593332-7EAF-4171-8A35-1C5DE5B40909}", IPreferencesPage)
@ -25,12 +29,22 @@ public:
CEditorPreferencesPage_ViewportMovement(); CEditorPreferencesPage_ViewportMovement();
virtual ~CEditorPreferencesPage_ViewportMovement() = default; virtual ~CEditorPreferencesPage_ViewportMovement() = default;
virtual const char* GetCategory() override { return "Viewports"; } virtual const char* GetCategory() override
{
return "Viewports";
}
virtual const char* GetTitle(); virtual const char* GetTitle();
virtual QIcon& GetIcon() override; virtual QIcon& GetIcon() override;
virtual void OnApply() override; virtual void OnApply() override;
virtual void OnCancel() override {} virtual void OnCancel() override
virtual bool OnQueryCancel() override { return true; } {
}
virtual bool OnQueryCancel() override
{
return true;
}
private: private:
void InitializeSettings(); void InitializeSettings();
@ -39,16 +53,53 @@ private:
{ {
AZ_TYPE_INFO(CameraMovementSettings, "{60B8C07E-5F48-4171-A50B-F45558B5CCA1}") AZ_TYPE_INFO(CameraMovementSettings, "{60B8C07E-5F48-4171-A50B-F45558B5CCA1}")
float m_moveSpeed; float m_translateSpeed;
float m_rotateSpeed; float m_rotateSpeed;
float m_fastMoveSpeed; float m_scrollSpeed;
float m_wheelZoomSpeed; float m_dollySpeed;
bool m_invertYRotation; float m_panSpeed;
bool m_invertPan; float m_boostMultiplier;
float m_rotateSmoothness;
bool m_rotateSmoothing;
float m_translateSmoothness;
bool m_translateSmoothing;
bool m_captureCursorLook;
bool m_orbitYawRotationInverted;
bool m_panInvertedX;
bool m_panInvertedY;
AZ::Crc32 RotateSmoothingVisibility() const
{
return EditorPropertyVisibility(m_rotateSmoothing);
}
AZ::Crc32 TranslateSmoothingVisibility() const
{
return EditorPropertyVisibility(m_translateSmoothing);
}
};
struct CameraInputSettings
{
AZ_TYPE_INFO(struct CameraInputSettings, "{A250FAD4-662E-4896-B030-D4ED03679377}")
AZStd::string m_translateForwardChannelId;
AZStd::string m_translateBackwardChannelId;
AZStd::string m_translateLeftChannelId;
AZStd::string m_translateRightChannelId;
AZStd::string m_translateUpChannelId;
AZStd::string m_translateDownChannelId;
AZStd::string m_boostChannelId;
AZStd::string m_orbitChannelId;
AZStd::string m_freeLookChannelId;
AZStd::string m_freePanChannelId;
AZStd::string m_orbitLookChannelId;
AZStd::string m_orbitDollyChannelId;
AZStd::string m_orbitPanChannelId;
}; };
CameraMovementSettings m_cameraMovementSettings; CameraMovementSettings m_cameraMovementSettings;
CameraInputSettings m_cameraInputSettings;
QIcon m_icon; QIcon m_icon;
}; };

@ -33,6 +33,7 @@ namespace SandboxEditor
constexpr AZStd::string_view CameraTranslateSmoothnessSetting = "/Amazon/Preferences/Editor/Camera/TranslateSmoothness"; constexpr AZStd::string_view CameraTranslateSmoothnessSetting = "/Amazon/Preferences/Editor/Camera/TranslateSmoothness";
constexpr AZStd::string_view CameraTranslateSmoothingSetting = "/Amazon/Preferences/Editor/Camera/TranslateSmoothing"; constexpr AZStd::string_view CameraTranslateSmoothingSetting = "/Amazon/Preferences/Editor/Camera/TranslateSmoothing";
constexpr AZStd::string_view CameraRotateSmoothingSetting = "/Amazon/Preferences/Editor/Camera/RotateSmoothing"; constexpr AZStd::string_view CameraRotateSmoothingSetting = "/Amazon/Preferences/Editor/Camera/RotateSmoothing";
constexpr AZStd::string_view CameraCaptureCursorLookSetting = "/Amazon/Preferences/Editor/Camera/CaptureCursorLook";
constexpr AZStd::string_view CameraTranslateForwardIdSetting = "/Amazon/Preferences/Editor/Camera/CameraTranslateForwardId"; constexpr AZStd::string_view CameraTranslateForwardIdSetting = "/Amazon/Preferences/Editor/Camera/CameraTranslateForwardId";
constexpr AZStd::string_view CameraTranslateBackwardIdSetting = "/Amazon/Preferences/Editor/Camera/CameraTranslateBackwardId"; constexpr AZStd::string_view CameraTranslateBackwardIdSetting = "/Amazon/Preferences/Editor/Camera/CameraTranslateBackwardId";
constexpr AZStd::string_view CameraTranslateLeftIdSetting = "/Amazon/Preferences/Editor/Camera/CameraTranslateLeftId"; constexpr AZStd::string_view CameraTranslateLeftIdSetting = "/Amazon/Preferences/Editor/Camera/CameraTranslateLeftId";
@ -60,9 +61,13 @@ namespace SandboxEditor
AZStd::remove_cvref_t<T> GetRegistry(const AZStd::string_view setting, T&& defaultValue) AZStd::remove_cvref_t<T> GetRegistry(const AZStd::string_view setting, T&& defaultValue)
{ {
AZStd::remove_cvref_t<T> value = AZStd::forward<T>(defaultValue); AZStd::remove_cvref_t<T> value = AZStd::forward<T>(defaultValue);
if (auto* registry = AZ::SettingsRegistry::Get()) if (const auto* registry = AZ::SettingsRegistry::Get())
{ {
registry->Get(value, setting); T potentialValue;
if (registry->Get(potentialValue, setting))
{
value = AZStd::move(potentialValue);
}
} }
return value; return value;
@ -281,6 +286,16 @@ namespace SandboxEditor
SetRegistry(CameraTranslateSmoothingSetting, enabled); SetRegistry(CameraTranslateSmoothingSetting, enabled);
} }
bool CameraCaptureCursorForLook()
{
return GetRegistry(CameraCaptureCursorLookSetting, true);
}
void SetCameraCaptureCursorForLook(const bool capture)
{
SetRegistry(CameraCaptureCursorLookSetting, capture);
}
AzFramework::InputChannelId CameraTranslateForwardChannelId() AzFramework::InputChannelId CameraTranslateForwardChannelId()
{ {
return AzFramework::InputChannelId( return AzFramework::InputChannelId(
@ -352,7 +367,7 @@ namespace SandboxEditor
void SetCameraTranslateBoostChannelId(AZStd::string_view cameraTranslateBoostId) void SetCameraTranslateBoostChannelId(AZStd::string_view cameraTranslateBoostId)
{ {
SetRegistry(CameraTranslateDownIdSetting, cameraTranslateBoostId); SetRegistry(CameraTranslateBoostIdSetting, cameraTranslateBoostId);
} }
AzFramework::InputChannelId CameraOrbitChannelId() AzFramework::InputChannelId CameraOrbitChannelId()
@ -360,7 +375,7 @@ namespace SandboxEditor
return AzFramework::InputChannelId(GetRegistry(CameraOrbitIdSetting, AZStd::string("keyboard_key_modifier_alt_l")).c_str()); return AzFramework::InputChannelId(GetRegistry(CameraOrbitIdSetting, AZStd::string("keyboard_key_modifier_alt_l")).c_str());
} }
void SetCameraOrbitChannelChannelId(AZStd::string_view cameraOrbitId) void SetCameraOrbitChannelId(AZStd::string_view cameraOrbitId)
{ {
SetRegistry(CameraOrbitIdSetting, cameraOrbitId); SetRegistry(CameraOrbitIdSetting, cameraOrbitId);
} }

@ -86,6 +86,9 @@ namespace SandboxEditor
SANDBOX_API bool CameraTranslateSmoothingEnabled(); SANDBOX_API bool CameraTranslateSmoothingEnabled();
SANDBOX_API void SetCameraTranslateSmoothingEnabled(bool enabled); SANDBOX_API void SetCameraTranslateSmoothingEnabled(bool enabled);
SANDBOX_API bool CameraCaptureCursorForLook();
SANDBOX_API void SetCameraCaptureCursorForLook(bool capture);
SANDBOX_API AzFramework::InputChannelId CameraTranslateForwardChannelId(); SANDBOX_API AzFramework::InputChannelId CameraTranslateForwardChannelId();
SANDBOX_API void SetCameraTranslateForwardChannelId(AZStd::string_view cameraTranslateForwardId); SANDBOX_API void SetCameraTranslateForwardChannelId(AZStd::string_view cameraTranslateForwardId);
@ -108,7 +111,7 @@ namespace SandboxEditor
SANDBOX_API void SetCameraTranslateBoostChannelId(AZStd::string_view cameraTranslateBoostId); SANDBOX_API void SetCameraTranslateBoostChannelId(AZStd::string_view cameraTranslateBoostId);
SANDBOX_API AzFramework::InputChannelId CameraOrbitChannelId(); SANDBOX_API AzFramework::InputChannelId CameraOrbitChannelId();
SANDBOX_API void SetCameraOrbitChannelChannelId(AZStd::string_view cameraOrbitId); SANDBOX_API void SetCameraOrbitChannelId(AZStd::string_view cameraOrbitId);
SANDBOX_API AzFramework::InputChannelId CameraFreeLookChannelId(); SANDBOX_API AzFramework::InputChannelId CameraFreeLookChannelId();
SANDBOX_API void SetCameraFreeLookChannelId(AZStd::string_view cameraFreeLookId); SANDBOX_API void SetCameraFreeLookChannelId(AZStd::string_view cameraFreeLookId);

@ -50,10 +50,10 @@
// AtomToolsFramework // AtomToolsFramework
#include <AtomToolsFramework/Viewport/RenderViewportWidget.h> #include <AtomToolsFramework/Viewport/RenderViewportWidget.h>
#include <AtomToolsFramework/Viewport/ModularViewportCameraController.h>
// CryCommon // CryCommon
#include <CryCommon/HMDBus.h> #include <CryCommon/HMDBus.h>
#include <CryCommon/IRenderAuxGeom.h>
// AzFramework // AzFramework
#include <AzFramework/Render/IntersectorInterface.h> #include <AzFramework/Render/IntersectorInterface.h>
@ -69,7 +69,6 @@
#include "Include/IDisplayViewport.h" #include "Include/IDisplayViewport.h"
#include "Objects/ObjectManager.h" #include "Objects/ObjectManager.h"
#include "ProcessInfo.h" #include "ProcessInfo.h"
#include "IPostEffectGroup.h"
#include "EditorPreferencesPageGeneral.h" #include "EditorPreferencesPageGeneral.h"
#include "ViewportManipulatorController.h" #include "ViewportManipulatorController.h"
#include "EditorViewportSettings.h" #include "EditorViewportSettings.h"
@ -99,12 +98,10 @@
#include <QtGui/private/qhighdpiscaling_p.h> #include <QtGui/private/qhighdpiscaling_p.h>
#include <IEntityRenderState.h> #include <IEntityRenderState.h>
#include <IPhysics.h>
#include <IStatObj.h> #include <IStatObj.h>
AZ_CVAR( AZ_CVAR(
bool, ed_visibility_logTiming, false, nullptr, AZ::ConsoleFunctorFlags::Null, "Output the timing of the new IVisibilitySystem query"); bool, ed_visibility_logTiming, false, nullptr, AZ::ConsoleFunctorFlags::Null, "Output the timing of the new IVisibilitySystem query");
AZ_CVAR(bool, ed_showCursorCameraLook, true, nullptr, AZ::ConsoleFunctorFlags::Null, "Show the cursor when using free look with the new camera system");
EditorViewportWidget* EditorViewportWidget::m_pPrimaryViewport = nullptr; EditorViewportWidget* EditorViewportWidget::m_pPrimaryViewport = nullptr;
@ -394,8 +391,6 @@ void EditorViewportWidget::UpdateContent(int flags)
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
void EditorViewportWidget::Update() void EditorViewportWidget::Update()
{ {
FUNCTION_PROFILER(GetIEditor()->GetSystem(), PROFILE_EDITOR);
if (Editor::EditorQtApplication::instance()->isMovingOrResizing()) if (Editor::EditorQtApplication::instance()->isMovingOrResizing())
{ {
return; return;
@ -742,9 +737,13 @@ void EditorViewportWidget::OnBeginPrepareRender()
RenderAll(); RenderAll();
// Draw 2D helpers. // Draw 2D helpers.
#ifdef LYSHINE_ATOM_TODO
TransformationMatrices backupSceneMatrices; TransformationMatrices backupSceneMatrices;
#endif
m_debugDisplay->DepthTestOff(); m_debugDisplay->DepthTestOff();
//m_renderer->Set2DMode(m_rcClient.right(), m_rcClient.bottom(), backupSceneMatrices); #ifdef LYSHINE_ATOM_TODO
m_renderer->Set2DMode(m_rcClient.right(), m_rcClient.bottom(), backupSceneMatrices);
#endif
auto prevState = m_debugDisplay->GetState(); auto prevState = m_debugDisplay->GetState();
m_debugDisplay->SetState(e_Mode3D | e_AlphaBlended | e_FillModeSolid | e_CullModeBack | e_DepthWriteOn | e_DepthTestOn); m_debugDisplay->SetState(e_Mode3D | e_AlphaBlended | e_FillModeSolid | e_CullModeBack | e_DepthWriteOn | e_DepthTestOn);
@ -957,15 +956,11 @@ AzFramework::CameraState EditorViewportWidget::GetCameraState()
AZ::Vector3 EditorViewportWidget::PickTerrain(const AzFramework::ScreenPoint& point) AZ::Vector3 EditorViewportWidget::PickTerrain(const AzFramework::ScreenPoint& point)
{ {
FUNCTION_PROFILER(GetIEditor()->GetSystem(), PROFILE_EDITOR);
return LYVec3ToAZVec3(ViewToWorld(AzToolsFramework::ViewportInteraction::QPointFromScreenPoint(point), nullptr, true)); return LYVec3ToAZVec3(ViewToWorld(AzToolsFramework::ViewportInteraction::QPointFromScreenPoint(point), nullptr, true));
} }
AZ::EntityId EditorViewportWidget::PickEntity(const AzFramework::ScreenPoint& point) AZ::EntityId EditorViewportWidget::PickEntity(const AzFramework::ScreenPoint& point)
{ {
FUNCTION_PROFILER(GetIEditor()->GetSystem(), PROFILE_EDITOR);
PreWidgetRendering(); PreWidgetRendering();
AZ::EntityId entityId; AZ::EntityId entityId;
@ -992,8 +987,6 @@ float EditorViewportWidget::TerrainHeight(const AZ::Vector2& position)
void EditorViewportWidget::FindVisibleEntities(AZStd::vector<AZ::EntityId>& visibleEntitiesOut) void EditorViewportWidget::FindVisibleEntities(AZStd::vector<AZ::EntityId>& visibleEntitiesOut)
{ {
FUNCTION_PROFILER(GetIEditor()->GetSystem(), PROFILE_EDITOR);
visibleEntitiesOut.assign(m_entityVisibilityQuery.Begin(), m_entityVisibilityQuery.End()); visibleEntitiesOut.assign(m_entityVisibilityQuery.Begin(), m_entityVisibilityQuery.End());
} }
@ -1033,216 +1026,6 @@ bool EditorViewportWidget::ShowingWorldSpace()
return BuildKeyboardModifiers(QGuiApplication::queryKeyboardModifiers()).Shift(); return BuildKeyboardModifiers(QGuiApplication::queryKeyboardModifiers()).Shift();
} }
AZStd::shared_ptr<AtomToolsFramework::ModularViewportCameraController> CreateModularViewportCameraController(
const AzFramework::ViewportId viewportId)
{
auto controller = AZStd::make_shared<AtomToolsFramework::ModularViewportCameraController>();
controller->SetCameraViewportContextBuilderCallback(
[viewportId](AZStd::unique_ptr<AtomToolsFramework::ModularCameraViewportContext>& cameraViewportContext)
{
cameraViewportContext = AZStd::make_unique<AtomToolsFramework::ModularCameraViewportContextImpl>(viewportId);
});
controller->SetCameraPriorityBuilderCallback(
[](AtomToolsFramework::CameraControllerPriorityFn& cameraControllerPriorityFn)
{
cameraControllerPriorityFn = AtomToolsFramework::DefaultCameraControllerPriority;
});
controller->SetCameraPropsBuilderCallback(
[](AzFramework::CameraProps& cameraProps)
{
cameraProps.m_rotateSmoothnessFn = []
{
return SandboxEditor::CameraRotateSmoothness();
};
cameraProps.m_translateSmoothnessFn = []
{
return SandboxEditor::CameraTranslateSmoothness();
};
cameraProps.m_rotateSmoothingEnabledFn = []
{
return SandboxEditor::CameraRotateSmoothingEnabled();
};
cameraProps.m_translateSmoothingEnabledFn = []
{
return SandboxEditor::CameraTranslateSmoothingEnabled();
};
});
controller->SetCameraListBuilderCallback(
[viewportId](AzFramework::Cameras& cameras)
{
const auto hideCursor = [viewportId]
{
AzToolsFramework::ViewportInteraction::ViewportMouseCursorRequestBus::Event(
viewportId, &AzToolsFramework::ViewportInteraction::ViewportMouseCursorRequestBus::Events::BeginCursorCapture);
};
const auto showCursor = [viewportId]
{
AzToolsFramework::ViewportInteraction::ViewportMouseCursorRequestBus::Event(
viewportId, &AzToolsFramework::ViewportInteraction::ViewportMouseCursorRequestBus::Events::EndCursorCapture);
};
auto firstPersonRotateCamera = AZStd::make_shared<AzFramework::RotateCameraInput>(SandboxEditor::CameraFreeLookChannelId());
firstPersonRotateCamera->m_rotateSpeedFn = []
{
return SandboxEditor::CameraRotateSpeed();
};
if (!ed_showCursorCameraLook)
{
// default behavior is to hide the cursor but this can be disabled (useful for remote desktop)
firstPersonRotateCamera->SetActivationBeganFn(hideCursor);
firstPersonRotateCamera->SetActivationEndedFn(showCursor);
}
auto firstPersonPanCamera =
AZStd::make_shared<AzFramework::PanCameraInput>(SandboxEditor::CameraFreePanChannelId(), AzFramework::LookPan);
firstPersonPanCamera->m_panSpeedFn = []
{
return SandboxEditor::CameraPanSpeed();
};
firstPersonPanCamera->m_invertPanXFn = []
{
return SandboxEditor::CameraPanInvertedX();
};
firstPersonPanCamera->m_invertPanYFn = []
{
return SandboxEditor::CameraPanInvertedY();
};
AzFramework::TranslateCameraInputChannels translateCameraInputChannels;
translateCameraInputChannels.m_leftChannelId = SandboxEditor::CameraTranslateLeftChannelId();
translateCameraInputChannels.m_rightChannelId = SandboxEditor::CameraTranslateRightChannelId();
translateCameraInputChannels.m_forwardChannelId = SandboxEditor::CameraTranslateForwardChannelId();
translateCameraInputChannels.m_backwardChannelId = SandboxEditor::CameraTranslateBackwardChannelId();
translateCameraInputChannels.m_upChannelId = SandboxEditor::CameraTranslateUpChannelId();
translateCameraInputChannels.m_downChannelId = SandboxEditor::CameraTranslateDownChannelId();
translateCameraInputChannels.m_boostChannelId = SandboxEditor::CameraTranslateBoostChannelId();
auto firstPersonTranslateCamera =
AZStd::make_shared<AzFramework::TranslateCameraInput>(AzFramework::LookTranslation, translateCameraInputChannels);
firstPersonTranslateCamera->m_translateSpeedFn = []
{
return SandboxEditor::CameraTranslateSpeed();
};
firstPersonTranslateCamera->m_boostMultiplierFn = []
{
return SandboxEditor::CameraBoostMultiplier();
};
auto firstPersonWheelCamera = AZStd::make_shared<AzFramework::ScrollTranslationCameraInput>();
firstPersonWheelCamera->m_scrollSpeedFn = []
{
return SandboxEditor::CameraScrollSpeed();
};
auto orbitCamera = AZStd::make_shared<AzFramework::OrbitCameraInput>(SandboxEditor::CameraOrbitChannelId());
orbitCamera->SetLookAtFn(
[viewportId](const AZ::Vector3& position, const AZ::Vector3& direction) -> AZStd::optional<AZ::Vector3>
{
AZStd::optional<AZ::Vector3> lookAtAfterInterpolation;
AtomToolsFramework::ModularViewportCameraControllerRequestBus::EventResult(
lookAtAfterInterpolation, viewportId,
&AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::LookAtAfterInterpolation);
// initially attempt to use the last set look at point after an interpolation has finished
if (lookAtAfterInterpolation.has_value())
{
return *lookAtAfterInterpolation;
}
const float RayDistance = 1000.0f;
AzFramework::RenderGeometry::RayRequest ray;
ray.m_startWorldPosition = position;
ray.m_endWorldPosition = position + direction * RayDistance;
ray.m_onlyVisible = true;
AzFramework::RenderGeometry::RayResult renderGeometryIntersectionResult;
AzFramework::RenderGeometry::IntersectorBus::EventResult(
renderGeometryIntersectionResult, AzToolsFramework::GetEntityContextId(),
&AzFramework::RenderGeometry::IntersectorBus::Events::RayIntersect, ray);
// attempt a ray intersection with any visible mesh and return the intersection position if successful
if (renderGeometryIntersectionResult)
{
return renderGeometryIntersectionResult.m_worldPosition;
}
// if there is no selection or no intersection, fallback to default camera orbit behavior (ground plane
// intersection)
return {};
});
auto orbitRotateCamera = AZStd::make_shared<AzFramework::RotateCameraInput>(SandboxEditor::CameraOrbitLookChannelId());
orbitRotateCamera->m_rotateSpeedFn = []
{
return SandboxEditor::CameraRotateSpeed();
};
orbitRotateCamera->m_invertYawFn = []
{
return SandboxEditor::CameraOrbitYawRotationInverted();
};
auto orbitTranslateCamera =
AZStd::make_shared<AzFramework::TranslateCameraInput>(AzFramework::OrbitTranslation, translateCameraInputChannels);
orbitTranslateCamera->m_translateSpeedFn = []
{
return SandboxEditor::CameraTranslateSpeed();
};
orbitTranslateCamera->m_boostMultiplierFn = []
{
return SandboxEditor::CameraBoostMultiplier();
};
auto orbitDollyWheelCamera = AZStd::make_shared<AzFramework::OrbitDollyScrollCameraInput>();
orbitDollyWheelCamera->m_scrollSpeedFn = []
{
return SandboxEditor::CameraScrollSpeed();
};
auto orbitDollyMoveCamera =
AZStd::make_shared<AzFramework::OrbitDollyCursorMoveCameraInput>(SandboxEditor::CameraOrbitDollyChannelId());
orbitDollyMoveCamera->m_cursorSpeedFn = []
{
return SandboxEditor::CameraDollyMotionSpeed();
};
auto orbitPanCamera = AZStd::make_shared<AzFramework::PanCameraInput>(SandboxEditor::CameraOrbitPanChannelId(), AzFramework::OrbitPan);
orbitPanCamera->m_panSpeedFn = []
{
return SandboxEditor::CameraPanSpeed();
};
orbitPanCamera->m_invertPanXFn = []
{
return SandboxEditor::CameraPanInvertedX();
};
orbitPanCamera->m_invertPanYFn = []
{
return SandboxEditor::CameraPanInvertedY();
};
orbitCamera->m_orbitCameras.AddCamera(orbitRotateCamera);
orbitCamera->m_orbitCameras.AddCamera(orbitTranslateCamera);
orbitCamera->m_orbitCameras.AddCamera(orbitDollyWheelCamera);
orbitCamera->m_orbitCameras.AddCamera(orbitDollyMoveCamera);
orbitCamera->m_orbitCameras.AddCamera(orbitPanCamera);
cameras.AddCamera(firstPersonRotateCamera);
cameras.AddCamera(firstPersonPanCamera);
cameras.AddCamera(firstPersonTranslateCamera);
cameras.AddCamera(firstPersonWheelCamera);
cameras.AddCamera(orbitCamera);
});
return controller;
}
void EditorViewportWidget::SetViewportId(int id) void EditorViewportWidget::SetViewportId(int id)
{ {
CViewport::SetViewportId(id); CViewport::SetViewportId(id);
@ -1287,8 +1070,9 @@ void EditorViewportWidget::SetViewportId(int id)
m_renderViewport->GetControllerList()->Add(AZStd::make_shared<SandboxEditor::ViewportManipulatorController>()); m_renderViewport->GetControllerList()->Add(AZStd::make_shared<SandboxEditor::ViewportManipulatorController>());
m_renderViewport->GetControllerList()->Add(CreateModularViewportCameraController(AzFramework::ViewportId(id))); m_editorModularViewportCameraComposer = AZStd::make_unique<SandboxEditor::EditorModularViewportCameraComposer>(AzFramework::ViewportId(id));
m_renderViewport->GetControllerList()->Add(m_editorModularViewportCameraComposer->CreateModularViewportCameraController());
m_renderViewport->SetViewportSettings(&g_EditorViewportSettings); m_renderViewport->SetViewportSettings(&g_EditorViewportSettings);
UpdateScene(); UpdateScene();
@ -1648,7 +1432,7 @@ void EditorViewportWidget::SetViewTM(const Matrix34& camMatrix, bool bMoveOnly)
{ {
// Should be impossible anyways // Should be impossible anyways
AZ_Assert(false, "Internal logic error - view entity Id and view source type out of sync. Please report this as a bug"); AZ_Assert(false, "Internal logic error - view entity Id and view source type out of sync. Please report this as a bug");
return ShouldUpdateObject::No; return ShouldUpdateObject::No;
} }
// Check that the current view is the same view as the view entity view // Check that the current view is the same view as the view entity view
@ -1741,7 +1525,7 @@ AZ::EntityId EditorViewportWidget::GetCurrentViewEntityId()
&AZ::RPI::ViewProviderBus::Events::GetView &AZ::RPI::ViewProviderBus::Events::GetView
); );
const bool isViewEntityCorrect = viewEntityView == GetCurrentAtomView(); [[maybe_unused]] const bool isViewEntityCorrect = viewEntityView == GetCurrentAtomView();
AZ_Error("EditorViewportWidget", isViewEntityCorrect, AZ_Error("EditorViewportWidget", isViewEntityCorrect,
"GetCurrentViewEntityId called while the current view is being changed. " "GetCurrentViewEntityId called while the current view is being changed. "
"You may get inconsistent results if you make use of the returned entity ID. " "You may get inconsistent results if you make use of the returned entity ID. "
@ -1975,12 +1759,12 @@ Vec3 EditorViewportWidget::ViewToWorld(
{ {
AZ_PROFILE_FUNCTION(Editor); AZ_PROFILE_FUNCTION(Editor);
AZ_UNUSED(collideWithTerrain) AZ_UNUSED(collideWithTerrain);
AZ_UNUSED(onlyTerrain) AZ_UNUSED(onlyTerrain);
AZ_UNUSED(bTestRenderMesh) AZ_UNUSED(bTestRenderMesh);
AZ_UNUSED(bSkipVegetation) AZ_UNUSED(bSkipVegetation);
AZ_UNUSED(bSkipVegetation) AZ_UNUSED(bSkipVegetation);
AZ_UNUSED(collideWithObject) AZ_UNUSED(collideWithObject);
auto ray = m_renderViewport->ViewportScreenToWorldRay(AzToolsFramework::ViewportInteraction::ScreenPointFromQPoint(vp)); auto ray = m_renderViewport->ViewportScreenToWorldRay(AzToolsFramework::ViewportInteraction::ScreenPointFromQPoint(vp));
if (!ray.has_value()) if (!ray.has_value())
@ -2004,82 +1788,15 @@ Vec3 EditorViewportWidget::ViewToWorld(
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
Vec3 EditorViewportWidget::ViewToWorldNormal(const QPoint& vp, bool onlyTerrain, bool bTestRenderMesh) Vec3 EditorViewportWidget::ViewToWorldNormal(const QPoint& vp, bool onlyTerrain, bool bTestRenderMesh)
{ {
AZ_UNUSED(vp) AZ_UNUSED(vp);
AZ_UNUSED(onlyTerrain) AZ_UNUSED(onlyTerrain);
AZ_UNUSED(bTestRenderMesh) AZ_UNUSED(bTestRenderMesh);
AZ_PROFILE_FUNCTION(Editor); AZ_PROFILE_FUNCTION(Editor);
return Vec3(0, 0, 1); return Vec3(0, 0, 1);
} }
//////////////////////////////////////////////////////////////////////////
bool EditorViewportWidget::AdjustObjectPosition(const ray_hit& hit, Vec3& outNormal, Vec3& outPos) const
{
Matrix34A objMat, objMatInv;
Matrix33 objRot, objRotInv;
if (hit.pCollider->GetiForeignData() != PHYS_FOREIGN_ID_STATIC)
{
return false;
}
IRenderNode* pNode = (IRenderNode*) hit.pCollider->GetForeignData(PHYS_FOREIGN_ID_STATIC);
if (!pNode || !pNode->GetEntityStatObj())
{
return false;
}
IStatObj* pEntObject = pNode->GetEntityStatObj(hit.partid, 0, &objMat, false);
if (!pEntObject || !pEntObject->GetRenderMesh())
{
return false;
}
objRot = Matrix33(objMat);
objRot.NoScale(); // No scale.
objRotInv = objRot;
objRotInv.Invert();
float fWorldScale = objMat.GetColumn(0).GetLength(); // GetScale
float fWorldScaleInv = 1.0f / fWorldScale;
// transform decal into object space
objMatInv = objMat;
objMatInv.Invert();
// put into normal object space hit direction of projection
Vec3 invhitn = -(hit.n);
Vec3 vOS_HitDir = objRotInv.TransformVector(invhitn).GetNormalized();
// put into position object space hit position
Vec3 vOS_HitPos = objMatInv.TransformPoint(hit.pt);
vOS_HitPos -= vOS_HitDir * RENDER_MESH_TEST_DISTANCE * fWorldScaleInv;
IRenderMesh* pRM = pEntObject->GetRenderMesh();
AABB aabbRNode;
pRM->GetBBox(aabbRNode.min, aabbRNode.max);
Vec3 vOut(0, 0, 0);
if (!Intersect::Ray_AABB(Ray(vOS_HitPos, vOS_HitDir), aabbRNode, vOut))
{
return false;
}
if (!pRM || !pRM->GetVerticesCount())
{
return false;
}
if (RayRenderMeshIntersection(pRM, vOS_HitPos, vOS_HitDir, outPos, outNormal))
{
outNormal = objRot.TransformVector(outNormal).GetNormalized();
outPos = objMat.TransformPoint(outPos);
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
bool EditorViewportWidget::RayRenderMeshIntersection(IRenderMesh* pRenderMesh, const Vec3& vInPos, const Vec3& vInDir, Vec3& vOutPos, Vec3& vOutNormal) const bool EditorViewportWidget::RayRenderMeshIntersection(IRenderMesh* pRenderMesh, const Vec3& vInPos, const Vec3& vInDir, Vec3& vOutPos, Vec3& vOutNormal) const
{ {
@ -2512,7 +2229,7 @@ void EditorViewportWidget::SetViewFromEntityPerspective(const AZ::EntityId& enti
void EditorViewportWidget::SetViewAndMovementLockFromEntityPerspective(const AZ::EntityId& entityId, [[maybe_unused]] bool lockCameraMovement) void EditorViewportWidget::SetViewAndMovementLockFromEntityPerspective(const AZ::EntityId& entityId, [[maybe_unused]] bool lockCameraMovement)
{ {
// This is an editor event, so is only serviced during edit mode, not play game mode // This is an editor event, so is only serviced during edit mode, not play game mode
// //
if (m_playInEditorState != PlayInEditorState::Editor) if (m_playInEditorState != PlayInEditorState::Editor)
{ {
AZ_Warning("EditorViewportWidget", false, AZ_Warning("EditorViewportWidget", false,

@ -19,6 +19,7 @@
#include "Undo/Undo.h" #include "Undo/Undo.h"
#include "Util/PredefinedAspectRatios.h" #include "Util/PredefinedAspectRatios.h"
#include "EditorViewportSettings.h" #include "EditorViewportSettings.h"
#include "EditorModularViewportCameraComposer.h"
#include <AzCore/Component/EntityId.h> #include <AzCore/Component/EntityId.h>
#include <AzCore/std/optional.h> #include <AzCore/std/optional.h>
@ -220,7 +221,6 @@ private:
// Draw a selected region if it has been selected // Draw a selected region if it has been selected
void RenderSelectedRegion(); void RenderSelectedRegion();
bool AdjustObjectPosition(const ray_hit& hit, Vec3& outNormal, Vec3& outPos) const;
bool RayRenderMeshIntersection(IRenderMesh* pRenderMesh, const Vec3& vInPos, const Vec3& vInDir, Vec3& vOutPos, Vec3& vOutNormal) const; bool RayRenderMeshIntersection(IRenderMesh* pRenderMesh, const Vec3& vInPos, const Vec3& vInDir, Vec3& vOutPos, Vec3& vOutNormal) const;
bool AddCameraMenuItems(QMenu* menu); bool AddCameraMenuItems(QMenu* menu);
@ -370,6 +370,8 @@ private:
// This widget holds a reference to the manipulator manage because its responsible for drawing manipulators // This widget holds a reference to the manipulator manage because its responsible for drawing manipulators
AZStd::shared_ptr<AzToolsFramework::ManipulatorManager> m_manipulatorManager; AZStd::shared_ptr<AzToolsFramework::ManipulatorManager> m_manipulatorManager;
AZStd::unique_ptr<SandboxEditor::EditorModularViewportCameraComposer> m_editorModularViewportCameraComposer;
// Helper for getting EditorEntityNotificationBus events // Helper for getting EditorEntityNotificationBus events
AZStd::unique_ptr<AZ::ViewportHelpers::EditorEntityNotifications> m_editorEntityNotifications; AZStd::unique_ptr<AZ::ViewportHelpers::EditorEntityNotifications> m_editorEntityNotifications;
@ -390,7 +392,3 @@ private:
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
}; };
//! Creates a modular camera controller in the configuration used by the editor viewport.
SANDBOX_API AZStd::shared_ptr<AtomToolsFramework::ModularViewportCameraController> CreateModularViewportCameraController(
const AzFramework::ViewportId viewportId);

@ -356,10 +356,9 @@ void CExportManager::AddMesh(Export::CObject* pObj, const IIndexedMesh* pIndMesh
for (int v = 0; v < meshDesc.m_nCoorCount; ++v) for (int v = 0; v < meshDesc.m_nCoorCount; ++v)
{ {
Export::UV tc; Vec2 uv = meshDesc.m_pTexCoord[v].GetUV();
meshDesc.m_pTexCoord[v].ExportTo(tc.u, tc.v); uv.y = 1.0f - uv.y;
tc.v = 1.0f - tc.v; pObj->m_texCoords.push_back({uv.x,uv.y});
pObj->m_texCoords.push_back(tc);
} }
if (pIndMesh->GetSubSetCount() && !(pIndMesh->GetSubSetCount() == 1 && pIndMesh->GetSubSet(0).nNumIndices == 0)) if (pIndMesh->GetSubSetCount() && !(pIndMesh->GetSubSetCount() == 1 && pIndMesh->GetSubSet(0).nNumIndices == 0))
@ -622,7 +621,7 @@ bool CExportManager::ShowFBXExportDialog()
if (pivotObjectNode && !pivotObjectNode->IsGroupNode()) if (pivotObjectNode && !pivotObjectNode->IsGroupNode())
{ {
m_pivotEntityObject = static_cast<CEntityObject*>(GetIEditor()->GetObjectManager()->FindObject(pivotObjectNode->GetName())); m_pivotEntityObject = static_cast<CEntityObject*>(GetIEditor()->GetObjectManager()->FindObject(pivotObjectNode->GetName().c_str()));
if (m_pivotEntityObject) if (m_pivotEntityObject)
{ {
@ -807,7 +806,7 @@ void CExportManager::FillAnimTimeNode(XmlNodeRef writeNode, CTrackViewAnimNode*
if (numAllTracks > 0) if (numAllTracks > 0)
{ {
XmlNodeRef objNode = writeNode->createNode(CleanXMLText(pObjectNode->GetName()).toUtf8().data()); XmlNodeRef objNode = writeNode->createNode(CleanXMLText(pObjectNode->GetName().c_str()).toUtf8().data());
writeNode->setAttr("time", m_animTimeExportPrimarySequenceCurrentTime); writeNode->setAttr("time", m_animTimeExportPrimarySequenceCurrentTime);
for (unsigned int trackID = 0; trackID < numAllTracks; ++trackID) for (unsigned int trackID = 0; trackID < numAllTracks; ++trackID)
@ -818,7 +817,7 @@ void CExportManager::FillAnimTimeNode(XmlNodeRef writeNode, CTrackViewAnimNode*
if (trackType == AnimParamType::Animation || trackType == AnimParamType::Sound) if (trackType == AnimParamType::Animation || trackType == AnimParamType::Sound)
{ {
QString childName = CleanXMLText(childTrack->GetName()); QString childName = CleanXMLText(childTrack->GetName().c_str());
if (childName.isEmpty()) if (childName.isEmpty())
{ {
@ -976,7 +975,7 @@ bool CExportManager::AddObjectsFromSequence(CTrackViewSequence* pSequence, XmlNo
else else
{ {
// In case of exporting animation/sound times data // In case of exporting animation/sound times data
const QString sequenceName = pSubSequence->GetName(); const QString sequenceName = QString::fromUtf8(pSubSequence->GetName().c_str());
XmlNodeRef subSeqNode2 = seqNode->createNode(sequenceName.toUtf8().data()); XmlNodeRef subSeqNode2 = seqNode->createNode(sequenceName.toUtf8().data());
if (sequenceName == m_animTimeExportPrimarySequenceName) if (sequenceName == m_animTimeExportPrimarySequenceName)
@ -1253,14 +1252,14 @@ void CExportManager::SaveNodeKeysTimeToXML()
m_soundKeyTimeExport = exportDialog.IsSoundExportChecked(); m_soundKeyTimeExport = exportDialog.IsSoundExportChecked();
QString filters = "All files (*.xml)"; QString filters = "All files (*.xml)";
QString defaultName = QString(pSequence->GetName()) + ".xml"; QString defaultName = QString::fromUtf8(pSequence->GetName().c_str()) + ".xml";
QtUtil::QtMFCScopedHWNDCapture cap; QtUtil::QtMFCScopedHWNDCapture cap;
CAutoDirectoryRestoreFileDialog dlg(QFileDialog::AcceptSave, QFileDialog::AnyFile, "xml", defaultName, filters, {}, {}, cap); CAutoDirectoryRestoreFileDialog dlg(QFileDialog::AcceptSave, QFileDialog::AnyFile, "xml", defaultName, filters, {}, {}, cap);
if (dlg.exec()) if (dlg.exec())
{ {
m_animTimeNode = XmlHelpers::CreateXmlNode(pSequence->GetName()); m_animTimeNode = XmlHelpers::CreateXmlNode(pSequence->GetName().c_str());
m_animTimeExportPrimarySequenceName = pSequence->GetName(); m_animTimeExportPrimarySequenceName = QString::fromUtf8(pSequence->GetName().c_str());
m_data.Clear(); m_data.Clear();
m_animTimeExportPrimarySequenceCurrentTime = 0.0; m_animTimeExportPrimarySequenceCurrentTime = 0.0;

@ -17,12 +17,6 @@ AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
#include <ui_FBXExporterDialog.h> #include <ui_FBXExporterDialog.h>
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
namespace
{
const uint kDefaultFPS = 30u;
}
CFBXExporterDialog::CFBXExporterDialog(bool bDisplayOnlyFPSSetting, QWidget* pParent) CFBXExporterDialog::CFBXExporterDialog(bool bDisplayOnlyFPSSetting, QWidget* pParent)
: QDialog(pParent) : QDialog(pParent)
, m_ui(new Ui::FBXExporterDialog) , m_ui(new Ui::FBXExporterDialog)

@ -497,8 +497,7 @@ bool CGameEngine::LoadLevel(
[[maybe_unused]] bool bDeleteAIGraph, [[maybe_unused]] bool bDeleteAIGraph,
bool bReleaseResources) bool bReleaseResources)
{ {
LOADING_TIME_PROFILE_SECTION(GetIEditor()->GetSystem()); m_bLevelLoaded = false;
m_bLevelLoaded = false;
CLogFile::FormatLine("Loading map '%s' into engine...", m_levelPath.toUtf8().data()); CLogFile::FormatLine("Loading map '%s' into engine...", m_levelPath.toUtf8().data());
// Switch the current directory back to the Primary CD folder first. // Switch the current directory back to the Primary CD folder first.
// The engine might have trouble to find some files when the current // The engine might have trouble to find some files when the current

@ -102,7 +102,6 @@ private:
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
bool m_bAutoExportMode; bool m_bAutoExportMode;
int m_numExportedMaterials;
static CGameExporter* m_pCurrentExporter; static CGameExporter* m_pCurrentExporter;
}; };

@ -161,61 +161,6 @@ void* CTriMesh::ReAllocElements(void* old_ptr, int new_elem_num, int size_of_ele
return realloc(old_ptr, new_elem_num * size_of_element); return realloc(old_ptr, new_elem_num * size_of_element);
} }
//////////////////////////////////////////////////////////////////////////
// Unshare all vertices and split on 3 arrays, positions/texcoords.
//////////////////////////////////////////////////////////////////////////
void CTriMesh::SetFromMesh(CMesh& mesh)
{
bbox = mesh.m_bbox;
int maxVerts = mesh.GetIndexCount();
SetVertexCount(maxVerts);
SetUVCount(maxVerts);
if (mesh.m_pColor0)
{
SetColorsCount(maxVerts);
}
SetFacesCount(mesh.GetIndexCount());
int numv = 0;
int numface = 0;
for (int nSubset = 0; nSubset < mesh.GetSubSetCount(); nSubset++)
{
SMeshSubset& subset = mesh.m_subsets[nSubset];
for (int i = subset.nFirstIndexId; i < subset.nFirstIndexId + subset.nNumIndices; i += 3)
{
CTriFace& face = pFaces[numface++];
for (int j = 0; j < 3; j++)
{
int idx = mesh.m_pIndices[i + j];
pVertices[numv].pos = mesh.m_pPositions ? mesh.m_pPositions[idx] : mesh.m_pPositionsF16[idx].ToVec3();
pWeights[numv] = 0.0f;
pUV[numv] = mesh.m_pTexCoord[idx];
if (mesh.m_pColor0)
{
pColors[numv] = mesh.m_pColor0[idx];
}
face.v [j] = numv;
face.uv[j] = numv;
face.n [j] = mesh.m_pNorms[idx].GetN();
face.MatID = static_cast<unsigned char>(subset.nMatID);
face.flags = 0;
numv++;
}
}
}
SetFacesCount(numface);
SharePositions();
ShareUV();
UpdateEdges();
CalcFaceNormals();
}
///////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////
inline int FindVertexInHash(const Vec3& vPosToFind, const CTriVertex* pVectors, std::vector<int>& hash, float fEpsilon) inline int FindVertexInHash(const Vec3& vPosToFind, const CTriVertex* pVectors, std::vector<int>& hash, float fEpsilon)
{ {
@ -360,76 +305,6 @@ void CTriMesh::CalcFaceNormals()
#define TEX_EPS 0.001f #define TEX_EPS 0.001f
#define VER_EPS 0.001f #define VER_EPS 0.001f
//////////////////////////////////////////////////////////////////////////
void CTriMesh::UpdateIndexedMesh(IIndexedMesh* pIndexedMesh) const
{
{
const int maxVerts = nFacesCount * 3;
pIndexedMesh->SetVertexCount(maxVerts);
pIndexedMesh->SetTexCoordCount(maxVerts);
if (pColors)
{
pIndexedMesh->SetColorCount(maxVerts);
}
pIndexedMesh->SetIndexCount(0);
pIndexedMesh->SetFaceCount(nFacesCount);
}
//////////////////////////////////////////////////////////////////////////
// To find really used materials
std::vector<int> usedMaterialIds;
uint16 MatIdToSubset[MAX_SUB_MATERIALS];
uint16 nLastSubsetId = 0;
memset(MatIdToSubset, 0, sizeof(MatIdToSubset));
//////////////////////////////////////////////////////////////////////////
CMesh& mesh = *pIndexedMesh->GetMesh();
AABB bb;
bb.Reset();
for (int i = 0; i < nFacesCount; ++i)
{
const CTriFace& face = pFaces[i];
SMeshFace& meshFace = mesh.m_pFaces[i];
// Remap new used material ID to index of chunk id.
if (!MatIdToSubset[face.MatID])
{
MatIdToSubset[face.MatID] = 1 + nLastSubsetId++;
usedMaterialIds.push_back(face.MatID); // Order of material ids in usedMaterialIds correspond to the indices of chunks.
}
meshFace.nSubset = static_cast<unsigned char>(MatIdToSubset[face.MatID] - 1);
for (int j = 0; j < 3; ++j)
{
const int dstVIdx = i * 3 + j;
mesh.m_pPositions[dstVIdx] = pVertices[face.v[j]].pos;
mesh.m_pNorms[dstVIdx] = SMeshNormal(face.n[j]);
mesh.m_pTexCoord[dstVIdx] = pUV[face.uv[j]];
if (pColors)
{
mesh.m_pColor0[dstVIdx] = pColors[face.v[j]];
}
meshFace.v[j] = dstVIdx;
bb.Add(mesh.m_pPositions[dstVIdx]);
}
}
pIndexedMesh->SetBBox(bb);
pIndexedMesh->SetSubSetCount(static_cast<int>(usedMaterialIds.size()));
for (int i = 0; i < usedMaterialIds.size(); i++)
{
pIndexedMesh->SetSubsetMaterialId(i, usedMaterialIds[i]);
}
pIndexedMesh->Optimize();
}
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
void CTriMesh::CopyStream(CTriMesh& fromMesh, int stream) void CTriMesh::CopyStream(CTriMesh& fromMesh, int stream)
{ {

@ -198,8 +198,6 @@ public:
void GetStreamInfo(int stream, void*& pStream, int& nElementSize) const; void GetStreamInfo(int stream, void*& pStream, int& nElementSize) const;
int GetStreamSize(int stream) const { return m_streamSize[stream]; }; int GetStreamSize(int stream) const { return m_streamSize[stream]; };
void SetFromMesh(CMesh& mesh);
void UpdateIndexedMesh(IIndexedMesh* pIndexedMesh) const;
// Calculate per face normal. // Calculate per face normal.
void CalcFaceNormals(); void CalcFaceNormals();

@ -6,9 +6,6 @@
* *
*/ */
#ifndef CRYINCLUDE_EDITOR_IEDITOR_H
#define CRYINCLUDE_EDITOR_IEDITOR_H
#pragma once #pragma once
#ifdef PLUGIN_EXPORTS #ifdef PLUGIN_EXPORTS
@ -25,6 +22,7 @@
#include <WinWidgetId.h> #include <WinWidgetId.h>
#include <AzCore/Component/EntityId.h> #include <AzCore/Component/EntityId.h>
#include <AzCore/Debug/Budget.h>
class QMenu; class QMenu;
@ -738,4 +736,5 @@ struct IInitializeUIInfo
virtual void SetInfoText(const char* text) = 0; virtual void SetInfoText(const char* text) = 0;
}; };
#endif // CRYINCLUDE_EDITOR_IEDITOR_H AZ_DECLARE_BUDGET(Editor);

@ -405,8 +405,6 @@ void CEditorImpl::Update()
// Make sure this is not called recursively // Make sure this is not called recursively
m_bUpdates = false; m_bUpdates = false;
FUNCTION_PROFILER(GetSystem(), PROFILE_EDITOR);
//@FIXME: Restore this latter. //@FIXME: Restore this latter.
//if (GetGameEngine() && GetGameEngine()->IsLevelLoaded()) //if (GetGameEngine() && GetGameEngine()->IsLevelLoaded())
{ {

@ -27,19 +27,6 @@
namespace namespace
{ {
// Object names in this array must correspond to EObject enumeration.
const char* g_ObjectNames[eStatObject_COUNT] =
{
"Objects/Arrow.cgf",
"Objects/Axis.cgf",
"Objects/Sphere.cgf",
"Objects/Anchor.cgf",
"Objects/entrypoint.cgf",
"Objects/hidepoint.cgf",
"Objects/hidepoint_sec.cgf",
"Objects/reinforcement_point.cgf",
};
const char* g_IconNames[eIcon_COUNT] = const char* g_IconNames[eIcon_COUNT] =
{ {
"Icons/ScaleWarning.png", "Icons/ScaleWarning.png",

@ -15,9 +15,6 @@
#pragma once #pragma once
struct IStatObj;
struct IMaterial;
#include "Include/IIconManager.h" // for IIconManager #include "Include/IIconManager.h" // for IIconManager
#include "IEditor.h" // for IDocListener #include "IEditor.h" // for IDocListener

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

Loading…
Cancel
Save