diff --git a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Sandbox.py b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Sandbox.py
index 292bb9c19c..f0bb6e72ca 100644
--- a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Sandbox.py
+++ b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Sandbox.py
@@ -12,6 +12,7 @@ import pytest
import ly_test_tools.environment.file_system as file_system
import editor_python_test_tools.hydra_test_utils as hydra
+from ly_test_tools.o3de.editor_test import EditorSharedTest, EditorTestSuite
from Atom.atom_utils.atom_constants import LIGHT_TYPES
logger = logging.getLogger(__name__)
@@ -159,3 +160,17 @@ class TestMaterialEditorBasicTests(object):
enable_prefab_system=False,
)
+
+@pytest.mark.parametrize("project", ["AutomatedTesting"])
+@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
+class TestAutomation(EditorTestSuite):
+
+ enable_prefab_system = False
+
+ @pytest.mark.test_case_id("C36529666")
+ class AtomEditorComponentsLevel_DiffuseGlobalIlluminationAdded(EditorSharedTest):
+ from Atom.tests import hydra_AtomEditorComponentsLevel_DiffuseGlobalIlluminationAdded as test_module
+
+ @pytest.mark.test_case_id("C36525660")
+ class AtomEditorComponentsLevel_DisplayMapperAdded(EditorSharedTest):
+ from Atom.tests import hydra_AtomEditorComponentsLevel_DisplayMapperAdded as test_module
diff --git a/AutomatedTesting/Gem/PythonTests/Atom/atom_utils/atom_constants.py b/AutomatedTesting/Gem/PythonTests/Atom/atom_utils/atom_constants.py
index 290e1cd2e4..c73f75fbc0 100644
--- a/AutomatedTesting/Gem/PythonTests/Atom/atom_utils/atom_constants.py
+++ b/AutomatedTesting/Gem/PythonTests/Atom/atom_utils/atom_constants.py
@@ -18,6 +18,13 @@ LIGHT_TYPES = {
'simple_spot': 7,
}
+# Qualiity Level settings for Diffuse Global Illumination level component
+GLOBAL_ILLUMINATION_QUALITY = {
+ 'Low': 0,
+ 'Medium': 1,
+ 'High': 2,
+}
+
class AtomComponentProperties:
"""
@@ -116,6 +123,21 @@ class AtomComponentProperties:
}
return properties[property]
+ @staticmethod
+ def diffuse_global_illumination(property: str = 'name') -> str:
+ """
+ Diffuse Global Illumination level component properties.
+ Controls global settings for Diffuse Probe Grid components.
+ - 'Quality Level' from atom_constants.py GLOBAL_ILLUMINATION_QUALITY
+ :param property: From the last element of the property tree path. Default 'name' for component name string.
+ :return: Full property path OR component name if no property specified.
+ """
+ properties = {
+ 'name': 'Diffuse Global Illumination',
+ 'Quality Level': 'Controller|Configuration|Quality Level'
+ }
+ return properties[property]
+
@staticmethod
def diffuse_probe_grid(property: str = 'name') -> str:
"""
@@ -148,7 +170,8 @@ class AtomComponentProperties:
@staticmethod
def display_mapper(property: str = 'name') -> str:
"""
- Display Mapper component properties.
+ Display Mapper level component properties.
+ - 'Enable LDR color grading LUT' toggles the use of LDR color grading LUT
- 'LDR color Grading LUT' is the Low Definition Range (LDR) color grading for Look-up Textures (LUT) which is
an Asset.id value corresponding to a lighting asset file.
:param property: From the last element of the property tree path. Default 'name' for component name string.
@@ -156,6 +179,7 @@ class AtomComponentProperties:
"""
properties = {
'name': 'Display Mapper',
+ 'Enable LDR color grading LUT': 'Controller|Configuration|Enable LDR color grading LUT',
'LDR color Grading LUT': 'Controller|Configuration|LDR color Grading LUT',
}
return properties[property]
diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponentsLevel_DiffuseGlobalIlluminationAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponentsLevel_DiffuseGlobalIlluminationAdded.py
new file mode 100644
index 0000000000..ddcd5c3c46
--- /dev/null
+++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponentsLevel_DiffuseGlobalIlluminationAdded.py
@@ -0,0 +1,109 @@
+"""
+Copyright (c) Contributors to the Open 3D Engine Project.
+For complete copyright and license terms please see the LICENSE at the root of this distribution.
+
+SPDX-License-Identifier: Apache-2.0 OR MIT
+"""
+
+class Tests:
+ creation_undo = (
+ "UNDO Level component addition success",
+ "UNDO Level component addition failed")
+ creation_redo = (
+ "REDO Level component addition success",
+ "REDO Level component addition failed")
+ diffuse_global_illumination_component = (
+ "Level has a Diffuse Global Illumination component",
+ "Level failed to find Diffuse Global Illumination component")
+ diffuse_global_illumination_quality = (
+ "Quality Level set",
+ "Quality Level could not be set")
+ enter_game_mode = (
+ "Entered game mode",
+ "Failed to enter game mode")
+ exit_game_mode = (
+ "Exited game mode",
+ "Couldn't exit game mode")
+
+
+def AtomEditorComponentsLevel_DiffuseGlobalIllumination_AddedToEntity():
+ """
+ Summary:
+ Tests the Diffuse Global Illumination level component can be added to the level entity and is stable.
+
+ Test setup:
+ - Wait for Editor idle loop.
+ - Open the "Base" level.
+
+ Expected Behavior:
+ The component can be added, used in game mode, and has accurate required components.
+ Creation and deletion undo/redo should also work.
+
+ Test Steps:
+ 1) Add Diffuse Global Illumination level component to the level entity.
+ 2) UNDO the level component addition.
+ 3) REDO the level component addition.
+ 4) Set Quality Level property to Low
+ 5) Enter/Exit game mode.
+ 6) Look for errors and asserts.
+
+ :return: None
+ """
+
+ import azlmbr.legacy.general as general
+
+ from editor_python_test_tools.editor_entity_utils import EditorLevelEntity
+ from editor_python_test_tools.utils import Report, Tracer, TestHelper
+ from Atom.atom_utils.atom_constants import AtomComponentProperties, GLOBAL_ILLUMINATION_QUALITY
+
+ with Tracer() as error_tracer:
+ # Test setup begins.
+ # Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level.
+ TestHelper.init_idle()
+ TestHelper.open_level("", "Base")
+
+ # Test steps begin.
+ # 1. Add Diffuse Global Illumination level component to the level entity.
+ diffuse_global_illumination_component = EditorLevelEntity.add_component(
+ AtomComponentProperties.diffuse_global_illumination())
+ Report.critical_result(
+ Tests.diffuse_global_illumination_component,
+ EditorLevelEntity.has_component(AtomComponentProperties.diffuse_global_illumination()))
+
+ # 2. UNDO the level component addition.
+ # -> UNDO component addition.
+ general.undo()
+ general.idle_wait_frames(1)
+ Report.result(Tests.creation_undo,
+ not EditorLevelEntity.has_component(AtomComponentProperties.diffuse_global_illumination()))
+
+ # 3. REDO the level component addition.
+ # -> REDO component addition.
+ general.redo()
+ general.idle_wait_frames(1)
+ Report.result(Tests.creation_redo,
+ EditorLevelEntity.has_component(AtomComponentProperties.diffuse_global_illumination()))
+
+ # 4. Set Quality Level property to Low
+ diffuse_global_illumination_component.set_component_property_value(
+ AtomComponentProperties.diffuse_global_illumination('Quality Level', GLOBAL_ILLUMINATION_QUALITY['Low']))
+ quality = diffuse_global_illumination_component.get_component_property_value(
+ AtomComponentProperties.diffuse_global_illumination('Quality Level'))
+ Report.result(diffuse_global_illumination_quality, quality == GLOBAL_ILLUMINATION_QUALITY['Low'])
+
+ # 5. Enter/Exit game mode.
+ TestHelper.enter_game_mode(Tests.enter_game_mode)
+ general.idle_wait_frames(1)
+ TestHelper.exit_game_mode(Tests.exit_game_mode)
+
+ # 6. Look for errors and asserts.
+ TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0)
+ for error_info in error_tracer.errors:
+ Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}")
+ for assert_info in error_tracer.asserts:
+ Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}")
+
+
+if __name__ == "__main__":
+ from editor_python_test_tools.utils import Report
+ Report.start_test(AtomEditorComponentsLevel_DiffuseGlobalIllumination_AddedToEntity)
diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponentsLevel_DisplayMapperAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponentsLevel_DisplayMapperAdded.py
new file mode 100644
index 0000000000..c050dd76d4
--- /dev/null
+++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponentsLevel_DisplayMapperAdded.py
@@ -0,0 +1,124 @@
+"""
+Copyright (c) Contributors to the Open 3D Engine Project.
+For complete copyright and license terms please see the LICENSE at the root of this distribution.
+
+SPDX-License-Identifier: Apache-2.0 OR MIT
+"""
+
+
+class Tests:
+ creation_undo = (
+ "UNDO level component addition success",
+ "UNDO level component addition failed")
+ creation_redo = (
+ "REDO Level component addition success",
+ "REDO Level component addition failed")
+ display_mapper_component = (
+ "Level has a Display Mapper component",
+ "Level failed to find Display Mapper component")
+ ldr_color_grading_lut = (
+ "LDR color Grading LUT asset set",
+ "LDR color Grading LUT asset could not be set")
+ enable_ldr_color_grading_lut = (
+ "Enable LDR color grading LUT set",
+ "Enable LDR color grading LUT could not be set")
+ enter_game_mode = (
+ "Entered game mode",
+ "Failed to enter game mode")
+ exit_game_mode = (
+ "Exited game mode",
+ "Couldn't exit game mode")
+
+
+def AtomEditorComponentsLevel_DisplayMapper_AddedToEntity():
+ """
+ Summary:
+ Tests the Display Mapper level component can be added to the level entity and has the expected functionality.
+
+ Test setup:
+ - Wait for Editor idle loop.
+ - Open the "Base" level.
+
+ Expected Behavior:
+ The component can be added, used in game mode, and has accurate required components.
+ Creation and deletion undo/redo should also work.
+
+ Test Steps:
+ 1) Add Display Mapper level component to the level entity.
+ 2) UNDO the level component addition.
+ 3) REDO the level component addition.
+ 4) Set LDR color Grading LUT asset.
+ 5) Set Enable LDR color grading LUT property True
+ 6) Enter/Exit game mode.
+ 7) Look for errors and asserts.
+
+ :return: None
+ """
+ import os
+
+ import azlmbr.legacy.general as general
+
+ from editor_python_test_tools.asset_utils import Asset
+ from editor_python_test_tools.editor_entity_utils import EditorLevelEntity
+ from editor_python_test_tools.utils import Report, Tracer, TestHelper
+ from Atom.atom_utils.atom_constants import AtomComponentProperties
+
+ with Tracer() as error_tracer:
+ # Test setup begins.
+ # Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level.
+ TestHelper.init_idle()
+ TestHelper.open_level("", "Base")
+
+ # Test steps begin.
+ # 1. Add Display Mapper level component to the level entity.
+ display_mapper_component = EditorLevelEntity.add_component(AtomComponentProperties.display_mapper())
+ Report.critical_result(
+ Tests.display_mapper_component,
+ EditorLevelEntity.has_component(AtomComponentProperties.display_mapper()))
+
+ # 2. UNDO the level component addition.
+ # -> UNDO component addition.
+ general.undo()
+ general.idle_wait_frames(1)
+ Report.result(Tests.creation_undo, not EditorLevelEntity.has_component(AtomComponentProperties.display_mapper()))
+
+ # 3. REDO the level component addition.
+ # -> REDO component addition.
+ general.redo()
+ general.idle_wait_frames(1)
+ Report.result(Tests.creation_redo, EditorLevelEntity.has_component(AtomComponentProperties.display_mapper()))
+
+ # 4. Set LDR color Grading LUT asset.
+ display_mapper_asset_path = os.path.join("TestData", "test.lightingpreset.azasset")
+ display_mapper_asset = Asset.find_asset_by_path(display_mapper_asset_path, False)
+ display_mapper_component.set_component_property_value(
+ AtomComponentProperties.display_mapper('LDR color Grading LUT'), display_mapper_asset.id)
+ Report.result(
+ Tests.ldr_color_grading_lut,
+ display_mapper_component.get_component_property_value(
+ AtomComponentProperties.display_mapper('LDR color Grading LUT')) == display_mapper_asset.id)
+
+ # 5. Set Enable LDR color grading LUT property True
+ display_mapper_component.set_component_property_value(
+ AtomComponentProperties.display_mapper('Enable LDR color grading LUT'), True)
+ Report.result(
+ Test.enable_ldr_color_grading_lut,
+ display_mapper_component.get_component_property_value(
+ AtomComponentProperties.display_mapper('Enable LDR color grading LUT')) is True)
+
+ # 6. Enter/Exit game mode.
+ TestHelper.enter_game_mode(Tests.enter_game_mode)
+ general.idle_wait_frames(1)
+ TestHelper.exit_game_mode(Tests.exit_game_mode)
+
+ # 7. Look for errors and asserts.
+ TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0)
+ for error_info in error_tracer.errors:
+ Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}")
+ for assert_info in error_tracer.asserts:
+ Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}")
+
+
+if __name__ == "__main__":
+ from editor_python_test_tools.utils import Report
+ Report.start_test(AtomEditorComponentsLevel_DisplayMapper_AddedToEntity)
diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DisplayMapperAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DisplayMapperAdded.py
index e2e432364d..558d69046e 100644
--- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DisplayMapperAdded.py
+++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DisplayMapperAdded.py
@@ -7,15 +7,6 @@ SPDX-License-Identifier: Apache-2.0 OR MIT
class Tests:
- camera_creation = (
- "Camera Entity successfully created",
- "Camera Entity failed to be created")
- camera_component_added = (
- "Camera component was added to entity",
- "Camera component failed to be added to entity")
- camera_component_check = (
- "Entity has a Camera component",
- "Entity failed to find Camera component")
creation_undo = (
"UNDO Entity creation success",
"UNDO Entity creation failed")
@@ -43,6 +34,9 @@ class Tests:
ldr_color_grading_lut = (
"LDR color Grading LUT asset set",
"LDR color Grading LUT asset could not be set")
+ enable_ldr_color_grading_lut = (
+ "Enable LDR color grading LUT set",
+ "Enable LDR color grading LUT could not be set")
entity_deleted = (
"Entity deleted",
"Entity was not deleted")
@@ -72,14 +66,15 @@ def AtomEditorComponents_DisplayMapper_AddedToEntity():
2) Add Display Mapper component to Display Mapper entity.
3) UNDO the entity creation and component addition.
4) REDO the entity creation and component addition.
- 5) Enter/Exit game mode.
- 6) Test IsHidden.
- 7) Test IsVisible.
- 8) Set LDR color Grading LUT asset.
- 9) Delete Display Mapper entity.
- 10) UNDO deletion.
- 11) REDO deletion.
- 12) Look for errors and asserts.
+ 5) Set LDR color Grading LUT asset.
+ 6) Set Enable LDR color grading LUT property True
+ 7) Enter/Exit game mode.
+ 8) Test IsHidden.
+ 9) Test IsVisible.
+ 10) Delete Display Mapper entity.
+ 11) UNDO deletion.
+ 12) REDO deletion.
+ 13) Look for errors and asserts.
:return: None
"""
@@ -133,21 +128,7 @@ def AtomEditorComponents_DisplayMapper_AddedToEntity():
general.idle_wait_frames(1)
Report.result(Tests.creation_redo, display_mapper_entity.exists())
- # 5. Enter/Exit game mode.
- TestHelper.enter_game_mode(Tests.enter_game_mode)
- general.idle_wait_frames(1)
- TestHelper.exit_game_mode(Tests.exit_game_mode)
-
- # 6. Test IsHidden.
- display_mapper_entity.set_visibility_state(False)
- Report.result(Tests.is_hidden, display_mapper_entity.is_hidden() is True)
-
- # 7. Test IsVisible.
- display_mapper_entity.set_visibility_state(True)
- general.idle_wait_frames(1)
- Report.result(Tests.is_visible, display_mapper_entity.is_visible() is True)
-
- # 8. Set LDR color Grading LUT asset.
+ # 5. Set LDR color Grading LUT asset.
display_mapper_asset_path = os.path.join("TestData", "test.lightingpreset.azasset")
display_mapper_asset = Asset.find_asset_by_path(display_mapper_asset_path, False)
display_mapper_component.set_component_property_value(
@@ -157,19 +138,41 @@ def AtomEditorComponents_DisplayMapper_AddedToEntity():
display_mapper_component.get_component_property_value(
AtomComponentProperties.display_mapper("LDR color Grading LUT")) == display_mapper_asset.id)
- # 9. Delete Display Mapper entity.
+ # 6. Set Enable LDR color grading LUT property True
+ display_mapper_component.set_component_property_value(
+ AtomComponentProperties.display_mapper('Enable LDR color grading LUT'), True)
+ Report.result(
+ Tests.enable_ldr_color_grading_lut,
+ display_mapper_component.get_component_property_value(
+ AtomComponentProperties.display_mapper('Enable LDR color grading LUT')) is True)
+
+ # 7. Enter/Exit game mode.
+ TestHelper.enter_game_mode(Tests.enter_game_mode)
+ general.idle_wait_frames(1)
+ TestHelper.exit_game_mode(Tests.exit_game_mode)
+
+ # 8. Test IsHidden.
+ display_mapper_entity.set_visibility_state(False)
+ Report.result(Tests.is_hidden, display_mapper_entity.is_hidden() is True)
+
+ # 9. Test IsVisible.
+ display_mapper_entity.set_visibility_state(True)
+ general.idle_wait_frames(1)
+ Report.result(Tests.is_visible, display_mapper_entity.is_visible() is True)
+
+ # 10. Delete Display Mapper entity.
display_mapper_entity.delete()
Report.result(Tests.entity_deleted, not display_mapper_entity.exists())
- # 10. UNDO deletion.
+ # 11. UNDO deletion.
general.undo()
Report.result(Tests.deletion_undo, display_mapper_entity.exists())
- # 11. REDO deletion.
+ # 12. REDO deletion.
general.redo()
Report.result(Tests.deletion_redo, not display_mapper_entity.exists())
- # 12. Look for errors and asserts.
+ # 13. Look for errors and asserts.
TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0)
for error_info in error_tracer.errors:
Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}")
diff --git a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py
index 309601b0ba..2d1c34b9c4 100644
--- a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py
+++ b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py
@@ -107,7 +107,6 @@ class EditorComponent:
return type_ids
-
def convert_to_azvector3(xyz) -> azlmbr.math.Vector3:
"""
Converts a vector3-like element into a azlmbr.math.Vector3
@@ -120,6 +119,7 @@ def convert_to_azvector3(xyz) -> azlmbr.math.Vector3:
else:
raise ValueError("vector must be a 3 element list/tuple or azlmbr.math.Vector3")
+
class EditorEntity:
"""
Entity class is used to create and interact with Editor Entities.
@@ -470,3 +470,99 @@ class EditorEntity:
assert self.id.isValid(), "A valid entity id is required to focus on its owning prefab."
focus_prefab_result = azlmbr.prefab.PrefabFocusPublicRequestBus(bus.Broadcast, "FocusOnOwningPrefab", self.id)
assert focus_prefab_result.IsSuccess(), f"Prefab operation 'FocusOnOwningPrefab' failed. Error: {focus_prefab_result.GetError()}"
+
+
+class EditorLevelEntity:
+ """
+ EditorLevel class used to add and fetch level components.
+ Level entity is a special entity that you do not create/destroy independently of larger systems of level creation.
+ This collects a number of staticmethods that do not rely on entityId since Level entity is found internally by
+ EditorLevelComponentAPIBus requests.
+ """
+
+ @staticmethod
+ def get_type_ids(component_names: list) -> list:
+ """
+ Used to get type ids of given components list for EntityType Level
+ :param: component_names: List of components to get type ids
+ :return: List of type ids of given components.
+ """
+ type_ids = editor.EditorComponentAPIBus(
+ bus.Broadcast, "FindComponentTypeIdsByEntityType", component_names, azlmbr.entity.EntityType().Level
+ )
+ return type_ids
+
+ @staticmethod
+ def add_component(component_name: str) -> EditorComponent:
+ """
+ Used to add new component to Level.
+ :param component_name: String of component name to add.
+ :return: Component object of newly added component.
+ """
+ component = EditorLevelEntity.add_components([component_name])[0]
+ return component
+
+ @staticmethod
+ def add_components(component_names: list) -> List[EditorComponent]:
+ """
+ Used to add multiple components
+ :param: component_names: List of components to add to level
+ :return: List of newly added components to the level
+ """
+ components = []
+ type_ids = EditorLevelEntity.get_type_ids(component_names)
+ for type_id in type_ids:
+ new_comp = EditorComponent()
+ new_comp.type_id = type_id
+ add_component_outcome = editor.EditorLevelComponentAPIBus(
+ bus.Broadcast, "AddComponentsOfType", [type_id]
+ )
+ assert (
+ add_component_outcome.IsSuccess()
+ ), f"Failure: Could not add component: '{new_comp.get_component_name()}' to level"
+ new_comp.id = add_component_outcome.GetValue()[0]
+ components.append(new_comp)
+ return components
+
+ @staticmethod
+ def get_components_of_type(component_names: list) -> List[EditorComponent]:
+ """
+ Used to get components of type component_name that already exists on the level
+ :param component_names: List of names of components to check
+ :return: List of Level Component objects of given component name
+ """
+ component_list = []
+ type_ids = EditorLevelEntity.get_type_ids(component_names)
+ for type_id in type_ids:
+ component = EditorComponent()
+ component.type_id = type_id
+ get_component_of_type_outcome = editor.EditorLevelComponentAPIBus(
+ bus.Broadcast, "GetComponentOfType", type_id
+ )
+ assert (
+ get_component_of_type_outcome.IsSuccess()
+ ), f"Failure: Level does not have component:'{component.get_component_name()}'"
+ component.id = get_component_of_type_outcome.GetValue()
+ component_list.append(component)
+
+ return component_list
+
+ @staticmethod
+ def has_component(component_name: str) -> bool:
+ """
+ Used to verify if the level has the specified component
+ :param component_name: Name of component to check for
+ :return: True, if level has specified component. Else, False
+ """
+ type_ids = EditorLevelEntity.get_type_ids([component_name])
+ return editor.EditorLevelComponentAPIBus(bus.Broadcast, "HasComponentOfType", type_ids[0])
+
+ @staticmethod
+ def count_components_of_type(component_name: str) -> int:
+ """
+ Used to get a count of the specified level component attached to the level
+ :param component_name: Name of component to check for
+ :return: integer count of occurences of level component attached to level or zero if none are present
+ """
+ type_ids = EditorLevelEntity.get_type_ids([component_name])
+ return editor.EditorLevelComponentAPIBus(bus.Broadcast, "CountComponentsOfType", type_ids[0])
diff --git a/Code/Framework/AzCore/Tests/Asset/AssetManagerLoadingTests.cpp b/Code/Framework/AzCore/Tests/Asset/AssetManagerLoadingTests.cpp
index 043eb1aee6..c3be9b886a 100644
--- a/Code/Framework/AzCore/Tests/Asset/AssetManagerLoadingTests.cpp
+++ b/Code/Framework/AzCore/Tests/Asset/AssetManagerLoadingTests.cpp
@@ -914,11 +914,11 @@ namespace UnitTest
m_testAssetManager->SetParallelDependentLoadingEnabled(true);
}
-#if AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS || AZ_TRAIT_DISABLE_FAILED_ASSET_LOAD_TESTS
+#if AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS
TEST_F(AssetJobsFloodTest, DISABLED_LoadTest_SameAsset_DifferentFilters)
#else
TEST_F(AssetJobsFloodTest, LoadTest_SameAsset_DifferentFilters)
-#endif // AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS || AZ_TRAIT_DISABLE_FAILED_ASSET_LOAD_TESTS
+#endif // AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS
{
m_assetHandlerAndCatalog->AssetCatalogRequestBus::Handler::BusConnect();
@@ -1263,11 +1263,11 @@ namespace UnitTest
m_assetHandlerAndCatalog->AssetCatalogRequestBus::Handler::BusDisconnect();
}
-#if AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS || AZ_TRAIT_DISABLE_FAILED_ASSET_LOAD_TESTS
+#if AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS
TEST_F(AssetJobsFloodTest, DISABLED_AssetWithNoLoadReference_LoadDependencies_NoLoadNotLoaded)
#else
TEST_F(AssetJobsFloodTest, AssetWithNoLoadReference_LoadDependencies_NoLoadNotLoaded)
-#endif // AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS || AZ_TRAIT_DISABLE_FAILED_ASSET_LOAD_TESTS
+#endif // AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS
{
m_assetHandlerAndCatalog->AssetCatalogRequestBus::Handler::BusConnect();
// Setup has already created/destroyed assets
@@ -1304,11 +1304,11 @@ namespace UnitTest
m_assetHandlerAndCatalog->AssetCatalogRequestBus::Handler::BusDisconnect();
}
-#if AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS || AZ_TRAIT_DISABLE_FAILED_ASSET_LOAD_TESTS
+#if AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS
TEST_F(AssetJobsFloodTest, DISABLED_AssetWithNoLoadReference_LoadContainerDependencies_LoadAllLoadsNoLoad)
#else
TEST_F(AssetJobsFloodTest, AssetWithNoLoadReference_LoadContainerDependencies_LoadAllLoadsNoLoad)
-#endif // AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS || AZ_TRAIT_DISABLE_FAILED_ASSET_LOAD_TESTS
+#endif // AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS
{
m_assetHandlerAndCatalog->AssetCatalogRequestBus::Handler::BusConnect();
// Setup has already created/destroyed assets
@@ -1343,11 +1343,11 @@ namespace UnitTest
m_assetHandlerAndCatalog->AssetCatalogRequestBus::Handler::BusDisconnect();
}
-#if AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS || AZ_TRAIT_DISABLE_FAILED_ASSET_LOAD_TESTS
+#if AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS
TEST_F(AssetJobsFloodTest, DISABLED_AssetWithNoLoadReference_LoadDependencies_BehaviorObeyed)
#else
TEST_F(AssetJobsFloodTest, AssetWithNoLoadReference_LoadDependencies_BehaviorObeyed)
-#endif // AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS || AZ_TRAIT_DISABLE_FAILED_ASSET_LOAD_TESTS
+#endif // AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS
{
m_assetHandlerAndCatalog->AssetCatalogRequestBus::Handler::BusConnect();
// Setup has already created/destroyed assets
diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Images/Entity/prefab_edit_open_readonly.svg b/Code/Framework/AzQtComponents/AzQtComponents/Images/Entity/prefab_edit_open_readonly.svg
new file mode 100644
index 0000000000..9f16c88211
--- /dev/null
+++ b/Code/Framework/AzQtComponents/AzQtComponents/Images/Entity/prefab_edit_open_readonly.svg
@@ -0,0 +1,3 @@
+
diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Images/Entity/readonly.svg b/Code/Framework/AzQtComponents/AzQtComponents/Images/Entity/readonly.svg
new file mode 100644
index 0000000000..2e942e87f8
--- /dev/null
+++ b/Code/Framework/AzQtComponents/AzQtComponents/Images/Entity/readonly.svg
@@ -0,0 +1,3 @@
+
diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Images/resources.qrc b/Code/Framework/AzQtComponents/AzQtComponents/Images/resources.qrc
index 15049aeb69..7e7fafc9ee 100644
--- a/Code/Framework/AzQtComponents/AzQtComponents/Images/resources.qrc
+++ b/Code/Framework/AzQtComponents/AzQtComponents/Images/resources.qrc
@@ -7,7 +7,9 @@
Entity/prefab.svg
Entity/prefab_edit.svg
Entity/prefab_edit_open.svg
+ Entity/prefab_edit_open_readonly.svg
Entity/prefab_edit_close.svg
+ Entity/readonly.svg
Level/level.svg
diff --git a/Code/Framework/AzTest/AzTest/Platform/Linux/AzTest_Traits_Linux.h b/Code/Framework/AzTest/AzTest/Platform/Linux/AzTest_Traits_Linux.h
index 6d6513043b..94f3dd004a 100644
--- a/Code/Framework/AzTest/AzTest/Platform/Linux/AzTest_Traits_Linux.h
+++ b/Code/Framework/AzTest/AzTest/Platform/Linux/AzTest_Traits_Linux.h
@@ -14,7 +14,6 @@
#define AZ_TRAIT_UNIT_TEST_DILLER_TRIGGER_EVENT_COUNT 100000
#define AZ_TRAIT_DISABLE_FAILED_AP_CONNECTION_TESTS true
-#define AZ_TRAIT_DISABLE_FAILED_ASSET_LOAD_TESTS true
#define AZ_TRAIT_DISABLE_FAILED_ATOM_RPI_TESTS true
#define AZ_TRAIT_DISABLE_FAILED_ARCHIVE_TESTS true
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/ReadOnly/ReadOnlyEntitySystemComponent.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/ReadOnly/ReadOnlyEntitySystemComponent.h
index efc91e9d89..4b8942bcd1 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/ReadOnly/ReadOnlyEntitySystemComponent.h
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/ReadOnly/ReadOnlyEntitySystemComponent.h
@@ -37,9 +37,9 @@ namespace AzToolsFramework
static void Reflect(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
- // ReadOnlyEntityPublicNotifications overrides ...
+ // ReadOnlyEntityPublicInterface overrides ...
bool IsReadOnly(const AZ::EntityId& entityId) override;
-
+
// ReadOnlyEntityQueryInterface overrides ...
void RefreshReadOnlyState(const EntityIdList& entityIds) override;
void RefreshReadOnlyStateForAllEntities() override;
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Logger/TraceLogger.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Logger/TraceLogger.cpp
index ce73826388..8c7150f027 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/Logger/TraceLogger.cpp
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Logger/TraceLogger.cpp
@@ -50,7 +50,7 @@ namespace AzToolsFramework
return false;
}
- void TraceLogger::PrepareLogFile(const AZStd::string& logFileName)
+ void TraceLogger::OpenLogFile(const AZStd::string& logFileName, bool clearLogFile)
{
using namespace AzFramework;
@@ -73,7 +73,7 @@ namespace AzToolsFramework
AZStd::string logPath;
StringFunc::Path::Join(logDirectory.c_str(), logFileName.c_str(), logPath);
- m_logFile.reset(aznew LogFile(logPath.c_str()));
+ m_logFile.reset(aznew LogFile(logPath.c_str(), clearLogFile));
if (m_logFile)
{
m_logFile->SetMachineReadable(false);
@@ -81,7 +81,7 @@ namespace AzToolsFramework
{
m_logFile->AppendLog(LogFile::SEV_NORMAL, message.window.c_str(), message.message.c_str());
}
- m_startupLogSink = {};
+ m_startupLogSink.clear();
m_logFile->FlushLog();
}
}
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Logger/TraceLogger.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Logger/TraceLogger.h
index 10708e3239..639674f69e 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/Logger/TraceLogger.h
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Logger/TraceLogger.h
@@ -23,7 +23,7 @@ namespace AzToolsFramework
~TraceLogger();
//! Open log file and dump log sink into it
- void PrepareLogFile(const AZStd::string& logFileName);
+ void OpenLogFile(const AZStd::string& logFileName, bool clearLogFile);
//! Add filter to ignore messages for windows with matching names
void AddWindowFilter(const AZStd::string& filter);
@@ -55,7 +55,8 @@ namespace AzToolsFramework
AZStd::string window;
AZStd::string message;
};
- AZStd::vector m_startupLogSink;
+
+ AZStd::list m_startupLogSink;
AZStd::unordered_set m_windowFilters;
AZStd::unordered_set m_messageFilters;
AZStd::unique_ptr m_logFile;
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp
index 2ab68bfc10..3426c56c99 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp
@@ -12,6 +12,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -74,6 +75,13 @@ namespace AzToolsFramework::Prefab
"Prefab - PrefabFocusHandler - "
"Focus Mode Interface could not be found. "
"Check that it is being correctly initialized.");
+
+ m_readOnlyEntityQueryInterface = AZ::Interface::Get();
+ AZ_Assert(
+ m_readOnlyEntityQueryInterface,
+ "Prefab - PrefabFocusHandler - "
+ "ReadOnly Entity Query Interface could not be found. "
+ "Check that it is being correctly initialized.");
}
PrefabFocusOperationResult PrefabFocusHandler::FocusOnOwningPrefab(AZ::EntityId entityId)
@@ -186,6 +194,8 @@ namespace AzToolsFramework::Prefab
// Close all container entities in the old path.
CloseInstanceContainers(m_instanceFocusHierarchy);
+ AZ::EntityId previousContainerEntityId = m_focusedInstanceContainerEntityId;
+
// Do not store the container for the root instance, use an invalid EntityId instead.
m_focusedInstanceContainerEntityId = focusedInstance->get().GetParentInstance().has_value() ? focusedInstance->get().GetContainerEntityId() : AZ::EntityId();
m_focusedTemplateId = focusedInstance->get().GetTemplateId();
@@ -201,6 +211,12 @@ namespace AzToolsFramework::Prefab
m_focusModeInterface->SetFocusRoot(containerEntityId);
}
+ // Refresh the read-only cache, if the interface is initialized.
+ if (m_readOnlyEntityQueryInterface)
+ {
+ m_readOnlyEntityQueryInterface->RefreshReadOnlyState({ previousContainerEntityId, m_focusedInstanceContainerEntityId });
+ }
+
// Refresh path variables.
RefreshInstanceFocusList();
RefreshInstanceFocusPath();
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h
index b02106d69d..4f3dbdd708 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h
@@ -22,6 +22,7 @@ namespace AzToolsFramework
{
class ContainerEntityInterface;
class FocusModeInterface;
+ class ReadOnlyEntityQueryInterface;
}
namespace AzToolsFramework::Prefab
@@ -93,6 +94,7 @@ namespace AzToolsFramework::Prefab
ContainerEntityInterface* m_containerEntityInterface = nullptr;
FocusModeInterface* m_focusModeInterface = nullptr;
InstanceEntityMapperInterface* m_instanceEntityMapperInterface = nullptr;
+ ReadOnlyEntityQueryInterface* m_readOnlyEntityQueryInterface = nullptr;
};
} // namespace AzToolsFramework::Prefab
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp
index e5530b49f4..dfabd42cd1 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp
@@ -918,6 +918,18 @@ namespace AzToolsFramework
}
}
+ bool PrefabPublicHandler::IsOwnedByProceduralPrefabInstance(AZ::EntityId entityId) const
+ {
+ if (InstanceOptionalReference instanceReference = m_instanceEntityMapperInterface->FindOwningInstance(entityId);
+ instanceReference.has_value())
+ {
+ TemplateReference templateReference = m_prefabSystemComponentInterface->FindTemplate(instanceReference->get().GetTemplateId());
+ return (templateReference.has_value()) && (templateReference->get().IsProcedural());
+ }
+
+ return false;
+ }
+
bool PrefabPublicHandler::IsInstanceContainerEntity(AZ::EntityId entityId) const
{
InstanceOptionalReference owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(entityId);
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h
index dd071ac09f..41c9b4a292 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h
@@ -54,6 +54,7 @@ namespace AzToolsFramework
PrefabOperationResult GenerateUndoNodesForEntityChangeAndUpdateCache(AZ::EntityId entityId, UndoSystem::URSequencePoint* parentUndoBatch) override;
+ bool IsOwnedByProceduralPrefabInstance(AZ::EntityId entityId) const override;
bool IsInstanceContainerEntity(AZ::EntityId entityId) const override;
bool IsLevelInstanceContainerEntity(AZ::EntityId entityId) const override;
AZ::EntityId GetInstanceContainerEntityId(AZ::EntityId entityId) const override;
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h
index ede857dd2b..2eb0bfa8e6 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h
@@ -101,6 +101,13 @@ namespace AzToolsFramework
*/
virtual PrefabOperationResult GenerateUndoNodesForEntityChangeAndUpdateCache(
AZ::EntityId entityId, UndoSystem::URSequencePoint* parentUndoBatch) = 0;
+
+ /**
+ * Detects if an entity is owned by a procedural prefab.
+ * @param entityId The entity to query.
+ * @return True if the entity is owned by a procedural prefab instance, false otherwise.
+ */
+ virtual bool IsOwnedByProceduralPrefabInstance(AZ::EntityId entityId) const = 0;
/**
* Detects if an entity is the container entity for its owning prefab instance.
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp
index a68a72f00a..284caaa7a2 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp
@@ -47,6 +47,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -313,7 +314,7 @@ namespace AzToolsFramework
if (isEditorOnly)
{
- return QIcon(QString(":/Icons/Entity_Editor_Only.svg"));
+ return QIcon(QString(":/Entity/entity_editoronly.svg"));
}
AZ::Entity* entity = nullptr;
@@ -322,10 +323,10 @@ namespace AzToolsFramework
if (!isInitiallyActive)
{
- return QIcon(QString(":/Icons/Entity_Not_Active.svg"));
+ return QIcon(QString(":/Entity/entity_notactive.svg"));
}
- return QIcon(QString(":/Icons/Entity.svg"));
+ return QIcon(QString(":/Entity/entity.svg"));
}
QVariant EntityOutlinerListModel::GetEntityTooltip(const AZ::EntityId& id) const
@@ -1994,9 +1995,13 @@ namespace AzToolsFramework
, m_lockCheckBoxes(parent, "Lock", EntityOutlinerListModel::PartiallyLockedRole, EntityOutlinerListModel::LockedAncestorRole)
{
m_editorEntityFrameworkInterface = AZ::Interface::Get();
-
AZ_Assert((m_editorEntityFrameworkInterface != nullptr),
"EntityOutlinerItemDelegate requires a EditorEntityFrameworkInterface instance on Construction.");
+
+ m_readOnlyEntityPublicInterface = AZ::Interface::Get();
+ AZ_Assert(
+ (m_readOnlyEntityPublicInterface != nullptr),
+ "EntityOutlinerItemDelegate requires a ReadOnlyEntityPublicInterface instance on Construction.");
}
EntityOutlinerItemDelegate::CheckboxGroup::CheckboxGroup(QWidget* parent, AZStd::string prefix,
@@ -2108,6 +2113,12 @@ namespace AzToolsFramework
}
PaintEntityNameAsRichText(painter, customOption, index);
+
+ // Paint Read-Only icon if necessary
+ if (m_readOnlyEntityPublicInterface->IsReadOnly(entityId))
+ {
+ PaintReadOnlyIcon(painter, option, index);
+ }
}
break;
default:
@@ -2166,10 +2177,10 @@ namespace AzToolsFramework
backgroundPath.addRect(backgroundRect);
- QColor backgroundColor = m_hoverColor;
+ QColor backgroundColor = s_hoverColor;
if (isSelected)
{
- backgroundColor = m_selectedColor;
+ backgroundColor = s_selectedColor;
}
painter->fillPath(backgroundPath, backgroundColor);
@@ -2336,6 +2347,20 @@ namespace AzToolsFramework
EntityOutlinerListModel::s_paintingName = false;
}
+ void EntityOutlinerItemDelegate::PaintReadOnlyIcon(QPainter* painter, const QStyleOptionViewItem& option, [[maybe_unused]] const QModelIndex& index) const
+ {
+ // Build the rect that will be used to paint the icon
+ QRect readOnlyRect = QRect(option.rect.topLeft() + s_readOnlyOffset, QSize(s_readOnlyRadius * 2, s_readOnlyRadius * 2));
+
+ painter->save();
+ painter->setRenderHint(QPainter::Antialiasing, true);
+ painter->setPen(Qt::NoPen);
+ painter->setBrush(s_readOnlyBackgroundColor);
+ painter->drawEllipse(readOnlyRect.center(), s_readOnlyRadius, s_readOnlyRadius);
+ s_readOnlyIcon.paint(painter, readOnlyRect);
+ painter->restore();
+ }
+
QSize EntityOutlinerItemDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& /*index*/) const
{
// Get the height of a tall character...
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx
index 0a46ee4850..b785dcb31e 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx
@@ -38,6 +38,7 @@ namespace AzToolsFramework
{
class EditorEntityUiInterface;
class FocusModeInterface;
+ class ReadOnlyEntityPublicInterface;
namespace EntityOutliner
{
@@ -344,6 +345,9 @@ namespace AzToolsFramework
// Paint the entity name using rich text
void PaintEntityNameAsRichText(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
+ // Paint the read-only icon on the entity
+ void PaintReadOnlyIcon(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
+
struct CheckboxGroup
{
EntityOutlinerCheckBox m_default;
@@ -372,10 +376,17 @@ namespace AzToolsFramework
// this is a cache, and is hence mutable
mutable QRect m_cachedBoundingRectOfTallCharacter;
- const QColor m_selectedColor = QColor(255, 255, 255, 45);
- const QColor m_hoverColor = QColor(255, 255, 255, 30);
+ inline static const QColor s_selectedColor = QColor(255, 255, 255, 45);
+ inline static const QColor s_hoverColor = QColor(255, 255, 255, 30);
+
+ inline static const QColor s_readOnlyBackgroundColor = QColor("#444444");
+ inline static const QPoint s_readOnlyOffset = QPoint(10, 10);
+ inline static const int s_readOnlyRadius = 6;
+
+ QIcon s_readOnlyIcon = QIcon(QString(":/Entity/readonly.svg"));
EditorEntityUiInterface* m_editorEntityFrameworkInterface = nullptr;
+ ReadOnlyEntityPublicInterface* m_readOnlyEntityPublicInterface = nullptr;
};
}
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp
index bc6700aca5..1c60b78322 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp
@@ -27,6 +27,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -1264,7 +1265,14 @@ namespace AzToolsFramework
}
else
{
- s_editorEntityUiInterface->RegisterEntity(entityId, m_prefabUiHandler.GetHandlerId());
+ if (s_prefabPublicInterface->IsOwnedByProceduralPrefabInstance(entityId))
+ {
+ s_editorEntityUiInterface->RegisterEntity(entityId, m_proceduralPrefabUiHandler.GetHandlerId());
+ }
+ else
+ {
+ s_editorEntityUiInterface->RegisterEntity(entityId, m_prefabUiHandler.GetHandlerId());
+ }
// Register entity as a container
s_containerEntityInterface->RegisterEntityAsContainer(entityId);
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h
index 808a0c2408..8589bee942 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h
@@ -18,10 +18,11 @@
#include
#include
#include
-
#include
#include
#include
+#include
+#include
#include
@@ -92,12 +93,18 @@ namespace AzToolsFramework
void ExecuteSavePrefabDialog(TemplateId templateId, bool useSaveAllPrefabsPreference) override;
private:
- // Used to handle the UI for the level root
+ // Used to handle the UI for the level root.
LevelRootUiHandler m_levelRootUiHandler;
- // Used to handle the UI for prefab entities
+ // Used to handle the UI for prefab entities.
PrefabUiHandler m_prefabUiHandler;
+ // Used to handle the UI for procedural prefab entities.
+ ProceduralPrefabUiHandler m_proceduralPrefabUiHandler;
+
+ // Ensures entities owned by procedural prefab instances are marked as read-only correctly.
+ ProceduralPrefabReadOnlyHandler m_proceduralPrefabReadOnlyHandler;
+
// Context menu item handlers
static void ContextMenu_CreatePrefab(AzToolsFramework::EntityIdList selectedEntities);
static void ContextMenu_InstantiatePrefab();
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp
index 8b56b26508..cc0018753e 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp
@@ -23,17 +23,6 @@ namespace AzToolsFramework
{
AzFramework::EntityContextId PrefabUiHandler::s_editorEntityContextId = AzFramework::EntityContextId::CreateNull();
- const QColor PrefabUiHandler::m_backgroundColor = QColor("#444444");
- const QColor PrefabUiHandler::m_backgroundHoverColor = QColor("#5A5A5A");
- const QColor PrefabUiHandler::m_backgroundSelectedColor = QColor("#656565");
- const QColor PrefabUiHandler::m_prefabCapsuleColor = QColor("#1E252F");
- const QColor PrefabUiHandler::m_prefabCapsuleDisabledColor = QColor("#35383C");
- const QColor PrefabUiHandler::m_prefabCapsuleEditColor = QColor("#4A90E2");
- const QString PrefabUiHandler::m_prefabIconPath = QString(":/Entity/prefab.svg");
- const QString PrefabUiHandler::m_prefabEditIconPath = QString(":/Entity/prefab_edit.svg");
- const QString PrefabUiHandler::m_prefabEditOpenIconPath = QString(":/Entity/prefab_edit_open.svg");
- const QString PrefabUiHandler::m_prefabEditCloseIconPath = QString(":/Entity/prefab_edit_close.svg");
-
PrefabUiHandler::PrefabUiHandler()
{
m_prefabPublicInterface = AZ::Interface::Get();
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.h
index bb1c646dbe..a1c624f85a 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.h
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.h
@@ -46,7 +46,7 @@ namespace AzToolsFramework
void OnOutlinerItemCollapse(const QModelIndex& index) const override;
bool OnEntityDoubleClick(AZ::EntityId entityId) const override;
- private:
+ protected:
Prefab::PrefabFocusPublicInterface* m_prefabFocusPublicInterface = nullptr;
Prefab::PrefabPublicInterface* m_prefabPublicInterface = nullptr;
@@ -56,17 +56,17 @@ namespace AzToolsFramework
static AzFramework::EntityContextId s_editorEntityContextId;
- static constexpr int m_prefabCapsuleRadius = 6;
- static constexpr int m_prefabBorderThickness = 2;
- static const QColor m_backgroundColor;
- static const QColor m_backgroundHoverColor;
- static const QColor m_backgroundSelectedColor;
- static const QColor m_prefabCapsuleColor;
- static const QColor m_prefabCapsuleDisabledColor;
- static const QColor m_prefabCapsuleEditColor;
- static const QString m_prefabIconPath;
- static const QString m_prefabEditIconPath;
- static const QString m_prefabEditOpenIconPath;
- static const QString m_prefabEditCloseIconPath;
+ int m_prefabCapsuleRadius = 6;
+ int m_prefabBorderThickness = 2;
+ QColor m_backgroundColor = QColor("#444444");
+ QColor m_backgroundHoverColor = QColor("#5A5A5A");
+ QColor m_backgroundSelectedColor = QColor("#656565");
+ QColor m_prefabCapsuleColor = QColor("#1E252F");
+ QColor m_prefabCapsuleDisabledColor = QColor("#35383C");
+ QColor m_prefabCapsuleEditColor = QColor("#4A90E2");
+ QString m_prefabIconPath = QString(":/Entity/prefab.svg");
+ QString m_prefabEditIconPath = QString(":/Entity/prefab_edit.svg");
+ QString m_prefabEditOpenIconPath = QString(":/Entity/prefab_edit_open.svg");
+ QString m_prefabEditCloseIconPath = QString(":/Entity/prefab_edit_close.svg");
};
} // namespace AzToolsFramework
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/Procedural/ProceduralPrefabReadOnlyHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/Procedural/ProceduralPrefabReadOnlyHandler.cpp
new file mode 100644
index 0000000000..ed962ef4ad
--- /dev/null
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/Procedural/ProceduralPrefabReadOnlyHandler.cpp
@@ -0,0 +1,68 @@
+/*
+ * 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
+
+#include
+#include
+#include
+#include
+
+namespace AzToolsFramework
+{
+ namespace Prefab
+ {
+ ProceduralPrefabReadOnlyHandler::ProceduralPrefabReadOnlyHandler()
+ {
+ m_prefabPublicInterface = AZ::Interface::Get();
+ AZ_Assert(
+ m_prefabPublicInterface != nullptr,
+ "ProceduralPrefabReadOnlyHandler requires a PrefabPublicInterface instance on Initialize.");
+
+ m_prefabFocusPublicInterface = AZ::Interface::Get();
+ AZ_Assert(
+ m_prefabFocusPublicInterface != nullptr,
+ "ProceduralPrefabReadOnlyHandler requires a PrefabFocusPublicInterface instance on Initialize.");
+
+ AzFramework::EntityContextId editorEntityContextId = AzFramework::EntityContextId::CreateNull();
+ EditorEntityContextRequestBus::BroadcastResult(editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId);
+
+ ReadOnlyEntityQueryRequestBus::Handler::BusConnect(editorEntityContextId);
+
+ // Refresh the whole read-only cache
+ if (auto readOnlyEntityQueryInterface = AZ::Interface::Get())
+ {
+ readOnlyEntityQueryInterface->RefreshReadOnlyStateForAllEntities();
+ }
+ }
+
+ ProceduralPrefabReadOnlyHandler ::~ProceduralPrefabReadOnlyHandler()
+ {
+ ReadOnlyEntityQueryRequestBus::Handler::BusDisconnect();
+ }
+
+ void ProceduralPrefabReadOnlyHandler::IsReadOnly(const AZ::EntityId& entityId, bool& isReadOnly)
+ {
+ if(m_prefabPublicInterface->IsOwnedByProceduralPrefabInstance(entityId))
+ {
+ // All entities nested inside a procedural prefabs should always be marked as read-only.
+ if (!m_prefabPublicInterface->IsInstanceContainerEntity(entityId))
+ {
+ isReadOnly = true;
+ }
+
+ // The container entity of a procedural prefab should only be marked as read-only when the prefab is being edited.
+ if (m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId))
+ {
+ isReadOnly = true;
+ }
+ }
+ }
+
+ } // namespace Prefab
+} // namespace AzToolsFramework
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/Procedural/ProceduralPrefabReadOnlyHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/Procedural/ProceduralPrefabReadOnlyHandler.h
new file mode 100644
index 0000000000..a4d13752bd
--- /dev/null
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/Procedural/ProceduralPrefabReadOnlyHandler.h
@@ -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
+ *
+ */
+
+#pragma once
+
+#include
+#include
+
+#include
+
+namespace AzToolsFramework
+{
+ namespace Prefab
+ {
+ class PrefabFocusPublicInterface;
+ class PrefabPublicInterface;
+
+ //! Ensures entities in a procedural prefab are correctly reported as read-only.
+ class ProceduralPrefabReadOnlyHandler
+ : public ReadOnlyEntityQueryRequestBus::Handler
+ {
+ public:
+ AZ_CLASS_ALLOCATOR(ProceduralPrefabReadOnlyHandler, AZ::SystemAllocator, 0);
+ AZ_RTTI(AzToolsFramework::ProceduralPrefabReadOnlyHandler, "{A2D72461-8CA3-45EE-81D2-4976BC0B6AE9}");
+
+ ProceduralPrefabReadOnlyHandler();
+ ~ProceduralPrefabReadOnlyHandler() override;
+
+ // ReadOnlyEntityQueryRequestBus overrides ...
+ void IsReadOnly(const AZ::EntityId& entityId, bool& isReadOnly) override;
+
+ private:
+ PrefabPublicInterface* m_prefabPublicInterface = nullptr;
+ PrefabFocusPublicInterface* m_prefabFocusPublicInterface = nullptr;
+ };
+
+ } // namespace Prefab
+} // namespace AzToolsFramework
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/Procedural/ProceduralPrefabUiHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/Procedural/ProceduralPrefabUiHandler.cpp
new file mode 100644
index 0000000000..57b41d3a4b
--- /dev/null
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/Procedural/ProceduralPrefabUiHandler.cpp
@@ -0,0 +1,32 @@
+/*
+ * 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
+
+#include
+
+namespace AzToolsFramework
+{
+ ProceduralPrefabUiHandler::ProceduralPrefabUiHandler()
+ {
+ m_prefabCapsuleColor = QColor("#361561");
+ m_prefabCapsuleDisabledColor = QColor("#4B3455");
+ m_prefabCapsuleEditColor = QColor("#361561");
+ m_prefabIconPath = QString(":/Entity/prefab_edit.svg");
+ m_prefabEditOpenIconPath = QString(":/Entity/prefab_edit_open_readonly.svg");
+ }
+
+ QString ProceduralPrefabUiHandler::GenerateItemTooltip(AZ::EntityId entityId) const
+ {
+ if (AZ::IO::Path path = m_prefabPublicInterface->GetOwningInstancePrefabPath(entityId); !path.empty())
+ {
+ return QObject::tr("Double click to inspect.\n%1").arg(path.Native().data());
+ }
+ return QString();
+ }
+}
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/Procedural/ProceduralPrefabUiHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/Procedural/ProceduralPrefabUiHandler.h
new file mode 100644
index 0000000000..2b8c90e0eb
--- /dev/null
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/Procedural/ProceduralPrefabUiHandler.h
@@ -0,0 +1,36 @@
+/*
+ * 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
+
+#include
+
+namespace AzToolsFramework
+{
+ namespace Prefab
+ {
+ class PrefabFocusPublicInterface;
+ class PrefabPublicInterface;
+ };
+
+ //! Implements the Editor UI for Procedural Prefabs.
+ class ProceduralPrefabUiHandler
+ : public PrefabUiHandler
+ {
+ public:
+ AZ_CLASS_ALLOCATOR(ProceduralPrefabUiHandler, AZ::SystemAllocator, 0);
+ AZ_RTTI(AzToolsFramework::ProceduralPrefabUiHandler, "{3A3DF9FF-9C2E-4439-B7B4-72173B5A3502}", PrefabUiHandler);
+
+ ProceduralPrefabUiHandler();
+ ~ProceduralPrefabUiHandler() override = default;
+
+ QString GenerateItemTooltip(AZ::EntityId entityId) const override;
+ };
+} // namespace AzToolsFramework
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.cpp
index fa419d5f14..8104aee4fd 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.cpp
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.cpp
@@ -45,6 +45,7 @@ AZ_POP_DISABLE_WARNING
#include
#include
#include
+#include
#include
#include
#include
@@ -497,6 +498,9 @@ namespace AzToolsFramework
m_prefabPublicInterface = AZ::Interface::Get();
AZ_Assert(m_prefabPublicInterface != nullptr, "EntityPropertyEditor requires a PrefabPublicInterface instance on Initialize.");
+ m_readOnlyEntityPublicInterface = AZ::Interface::Get();
+ AZ_Assert(m_readOnlyEntityPublicInterface != nullptr, "EntityPropertyEditor requires a ReadOnlyEntityPublicInterface instance on Initialize.");
+
setObjectName("EntityPropertyEditor");
setAcceptDrops(true);
@@ -535,10 +539,6 @@ namespace AzToolsFramework
model->setItem(row, 0, m_comboItems[row]);
}
m_gui->m_statusComboBox->setModel(model);
- m_gui->m_statusComboBox->setStyleSheet("QComboBox {border: 0px; border-radius:3px; background-color:#555555; color:white}"
- "QComboBox:on {background-color:#e9e9e9; color:black; border:0px}"
- "QComboBox::down-arrow:on {image: url(:/stylesheet/img/dropdowns/black_down_arrow.png)}"
- "QComboBox::drop-down {border-radius: 3p}");
AzQtComponents::ComboBox::addCustomCheckStateStyle(m_gui->m_statusComboBox);
EnableEditor(true);
m_sceneIsNew = true;
@@ -565,6 +565,12 @@ namespace AzToolsFramework
AZ::EntitySystemBus::Handler::BusConnect();
EntityPropertyEditorRequestBus::Handler::BusConnect();
EditorWindowUIRequestBus::Handler::BusConnect();
+
+ AzFramework::EntityContextId editorEntityContextId = AzFramework::EntityContextId::CreateNull();
+ EditorEntityContextRequestBus::BroadcastResult(
+ editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId);
+ ReadOnlyEntityPublicNotificationBus::Handler::BusConnect(editorEntityContextId);
+
m_spacer = nullptr;
m_emptyIcon = QIcon();
@@ -614,6 +620,7 @@ namespace AzToolsFramework
{
qApp->removeEventFilter(this);
+ ReadOnlyEntityPublicNotificationBus::Handler::BusDisconnect();
EditorWindowUIRequestBus::Handler::BusDisconnect();
EntityPropertyEditorRequestBus::Handler::BusDisconnect();
ToolsApplicationEvents::Bus::Handler::BusDisconnect();
@@ -973,7 +980,7 @@ namespace AzToolsFramework
m_gui->m_entityDetailsLabel->setVisible(false);
// If we're in edit mode, make the name field editable.
- m_gui->m_entityNameEditor->setReadOnly(!m_gui->m_componentListContents->isEnabled());
+ m_gui->m_entityNameEditor->setReadOnly(!m_gui->m_componentListContents->isEnabled() || m_selectionContainsReadOnlyEntity);
// get the name of the entity.
auto entity = GetSelectedEntityById(entityId);
@@ -1062,6 +1069,12 @@ namespace AzToolsFramework
bool EntityPropertyEditor::CanAddComponentsToSelection(const SelectionEntityTypeInfo& selectionEntityTypeInfo) const
{
+ if (m_selectionContainsReadOnlyEntity)
+ {
+ // Can't add components if there is a read only entity in the selection
+ return false;
+ }
+
if (selectionEntityTypeInfo == SelectionEntityTypeInfo::Mixed ||
selectionEntityTypeInfo == SelectionEntityTypeInfo::None)
{
@@ -1126,6 +1139,17 @@ namespace AzToolsFramework
m_selectedEntityIds.clear();
GetSelectedEntities(m_selectedEntityIds);
+ // Check if any of the selected entities are marked as read only
+ m_selectionContainsReadOnlyEntity = false;
+ for (const auto& entityId : m_selectedEntityIds)
+ {
+ if (m_readOnlyEntityPublicInterface->IsReadOnly(entityId))
+ {
+ m_selectionContainsReadOnlyEntity = true;
+ break;
+ }
+ }
+
SourceControlFileInfo scFileInfo;
ToolsApplicationRequests::Bus::BroadcastResult(scFileInfo, &ToolsApplicationRequests::GetSceneSourceControlInfo);
@@ -1681,6 +1705,12 @@ namespace AzToolsFramework
componentEditor->UpdateExpandability();
componentEditor->InvalidateAll(!componentInFilter ? m_filterString.c_str() : nullptr);
+ // If we are in read only mode, then show the components as disabled
+ if (m_selectionContainsReadOnlyEntity)
+ {
+ componentEditor->mockDisabledState(true);
+ }
+
if (!componentEditor->GetPropertyEditor()->HasFilteredOutNodes() || componentEditor->GetPropertyEditor()->HasVisibleNodes())
{
for (AZ::Component* componentInstance : componentInstances)
@@ -3077,6 +3107,7 @@ namespace AzToolsFramework
}
}
+ m_gui->m_statusComboBox->setDisabled(m_selectionContainsReadOnlyEntity);
m_gui->m_statusComboBox->setVisible(!m_isSystemEntityEditor && !m_isLevelEntityEditor);
m_gui->m_statusComboBox->style()->unpolish(m_gui->m_statusComboBox);
m_gui->m_statusComboBox->style()->polish(m_gui->m_statusComboBox);
@@ -3304,7 +3335,8 @@ namespace AzToolsFramework
const auto& componentsToEdit = GetSelectedComponents();
const bool hasComponents = !m_selectedEntityIds.empty() && !componentsToEdit.empty();
- const bool allowRemove = hasComponents && AreComponentsRemovable(componentsToEdit);
+ // Don't allow components to be removed/cut/enabled/disabled if read only
+ const bool allowRemove = hasComponents && AreComponentsRemovable(componentsToEdit) && !m_selectionContainsReadOnlyEntity;
const bool allowCopy = hasComponents && AreComponentsCopyable(componentsToEdit);
m_actionToDeleteComponents->setEnabled(allowRemove);
@@ -3366,6 +3398,12 @@ namespace AzToolsFramework
return false;
}
+ if (m_selectionContainsReadOnlyEntity)
+ {
+ // Can't paste components if there is a read only entity in the selection
+ return false;
+ }
+
// Grab component data from clipboard, if exists
const QMimeData* mimeData = ComponentMimeData::GetComponentMimeDataFromClipboard();
@@ -5727,6 +5765,14 @@ namespace AzToolsFramework
SaveComponentEditorState();
}
+ void EntityPropertyEditor::OnReadOnlyEntityStatusChanged(const AZ::EntityId& entityId, [[maybe_unused]] bool readOnly)
+ {
+ if (IsEntitySelected(entityId))
+ {
+ UpdateContents();
+ }
+ }
+
void EntityPropertyEditor::OnEditorModeActivated(
[[maybe_unused]] const AzToolsFramework::ViewportEditorModesInterface& editorModeState, AzToolsFramework::ViewportEditorMode mode)
{
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.hxx b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.hxx
index 8dd0ffc4ee..254b70996b 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.hxx
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.hxx
@@ -29,6 +29,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -62,6 +63,7 @@ namespace AzToolsFramework
class ComponentPaletteWidget;
class ComponentModeCollectionInterface;
struct SourceControlFileInfo;
+ class ReadOnlyEntityPublicInterface;
namespace AssetBrowser
{
@@ -116,6 +118,7 @@ namespace AzToolsFramework
, public AZ::EntitySystemBus::Handler
, public AZ::TickBus::Handler
, private EditorWindowUIRequestBus::Handler
+ , private ReadOnlyEntityPublicNotificationBus::Handler
{
Q_OBJECT;
public:
@@ -253,6 +256,9 @@ namespace AzToolsFramework
// EditorWindowRequestBus overrides
void SetEditorUiEnabled(bool enable) override;
+ // ReadOnlyEntityPublicNotificationBus overrides ...
+ void OnReadOnlyEntityStatusChanged(const AZ::EntityId& entityId, bool readOnly) override;
+
bool IsEntitySelected(const AZ::EntityId& id) const;
bool IsSingleEntitySelected(const AZ::EntityId& id) const;
@@ -623,6 +629,9 @@ namespace AzToolsFramework
Prefab::PrefabPublicInterface* m_prefabPublicInterface = nullptr;
bool m_prefabsAreEnabled = false;
+ ReadOnlyEntityPublicInterface* m_readOnlyEntityPublicInterface = nullptr;
+ bool m_selectionContainsReadOnlyEntity = false;
+
// Reordering row widgets within the RPE.
static constexpr float MoveFadeSeconds = 0.5f;
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.ui b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.ui
index 8a86d429f1..b7cb5a0156 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.ui
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.ui
@@ -191,7 +191,7 @@
- background-color:rgb(51, 51, 51)
+ QWidget#m_darkBox { background-color:rgb(51, 51, 51) }
@@ -444,6 +444,9 @@
Qt::Horizontal
+
+ background-color:rgb(51, 51, 51)
+
-
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake b/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake
index 49eaa9b34a..6922ad26a2 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake
@@ -768,6 +768,10 @@ set(FILES
UI/Prefab/PrefabUiHandler.cpp
UI/Prefab/PrefabViewportFocusPathHandler.h
UI/Prefab/PrefabViewportFocusPathHandler.cpp
+ UI/Prefab/Procedural/ProceduralPrefabReadOnlyHandler.h
+ UI/Prefab/Procedural/ProceduralPrefabReadOnlyHandler.cpp
+ UI/Prefab/Procedural/ProceduralPrefabUiHandler.h
+ UI/Prefab/Procedural/ProceduralPrefabUiHandler.cpp
UI/Notifications/ToastNotificationsView.cpp
UI/Notifications/ToastNotificationsView.h
UI/Notifications/ToastBus.h
diff --git a/Code/Framework/AzToolsFramework/Tests/Entity/ReadOnly/ReadOnlyEntityTests.cpp b/Code/Framework/AzToolsFramework/Tests/Entity/ReadOnly/ReadOnlyEntityTests.cpp
index 532325e208..69a130b33f 100644
--- a/Code/Framework/AzToolsFramework/Tests/Entity/ReadOnly/ReadOnlyEntityTests.cpp
+++ b/Code/Framework/AzToolsFramework/Tests/Entity/ReadOnly/ReadOnlyEntityTests.cpp
@@ -96,4 +96,22 @@ namespace AzToolsFramework
// Verify the child entity is no longer marked as read-only
EXPECT_FALSE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[ChildEntityName]));
}
+
+ TEST_F(ReadOnlyEntityFixture, EnsureCacheIsClearedCorrectlyEvenIfUnchanged)
+ {
+ // Create a handler that sets all entities to read-only.
+ ReadOnlyHandlerAlwaysTrue alwaysTrueHandler;
+
+ {
+ // Create a handler that sets the child entity to read-only.
+ ReadOnlyHandlerEntityId entityIdHandler(m_entityMap[ChildEntityName]);
+
+ // Verify the child entity is marked as read-only
+ EXPECT_TRUE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[ChildEntityName]));
+ }
+ // When the handler goes out of scope, it calls RefreshReadOnlyStateForAllEntities and refreshes the cache.
+
+ // Verify the child entity is still marked as read-only
+ EXPECT_TRUE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[ChildEntityName]));
+ }
}
diff --git a/Code/LauncherUnified/Platform/Android/Launcher_Android.cpp b/Code/LauncherUnified/Platform/Android/Launcher_Android.cpp
index de548a49d7..0890b5a193 100644
--- a/Code/LauncherUnified/Platform/Android/Launcher_Android.cpp
+++ b/Code/LauncherUnified/Platform/Android/Launcher_Android.cpp
@@ -299,7 +299,7 @@ void android_main(android_app* appState)
{
// Adding a start up banner so you can see when the game is starting up in amongst the logcat spam
LOGI("****************************************************************");
- LOGI("* Amazon Lumberyard - Launching Game... *");
+ LOGI("* Launching Game... *");
LOGI("****************************************************************");
// setup the system command handler which are guaranteed to be called on the same
diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp
index 0f21dcb70e..ec365d7a6a 100644
--- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp
+++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp
@@ -175,7 +175,8 @@ namespace AtomToolsFramework
Base::StartCommon(systemEntity);
- m_traceLogger.PrepareLogFile(GetBuildTargetName() + ".log");
+ const bool clearLogFile = GetSettingOrDefault("/O3DE/AtomToolsFramework/Application/ClearLogOnStart", false);
+ m_traceLogger.OpenLogFile(GetBuildTargetName() + ".log", clearLogFile);
AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Handler::BusConnect();
AzToolsFramework::AssetBrowser::AssetDatabaseLocationNotificationBus::Broadcast(
diff --git a/Gems/GraphCanvas/Code/Source/Translation/TranslationSerializer.cpp b/Gems/GraphCanvas/Code/Source/Translation/TranslationSerializer.cpp
index 80e488f3dc..754267b27d 100644
--- a/Gems/GraphCanvas/Code/Source/Translation/TranslationSerializer.cpp
+++ b/Gems/GraphCanvas/Code/Source/Translation/TranslationSerializer.cpp
@@ -15,39 +15,39 @@ namespace GraphCanvas
void AddEntryToDatabase(const AZStd::string& baseKey, const AZStd::string& name, const rapidjson::Value& it, TranslationFormat* translationFormat)
{
- AZStd::string finalKey = baseKey;
if (it.IsString())
{
- if (translationFormat->m_database.find(finalKey) == translationFormat->m_database.end())
+ auto translationDbItr = translationFormat->m_database.find(baseKey);
+ if (translationDbItr == translationFormat->m_database.end())
{
- translationFormat->m_database[finalKey] = it.GetString();
+ translationFormat->m_database[baseKey] = it.GetString();
}
else
{
- AZStd::string existingValue = translationFormat->m_database[finalKey.c_str()];
+ const AZStd::string& existingValue = translationDbItr->second;
// There is a name collision
- AZStd::string error = AZStd::string::format("Unable to store key: %s with value: %s because that key already exists with value: %s (proposed: %s)", finalKey.c_str(), it.GetString(), existingValue.c_str(), it.GetString());
+ const AZStd::string error = AZStd::string::format("Unable to store key: %s with value: %s because that key already exists with value: %s (proposed: %s)", baseKey.c_str(), it.GetString(), existingValue.c_str(), it.GetString());
AZ_Error("TranslationSerializer", false, error.c_str());
}
}
else if (it.IsObject())
{
+ AZStd::string finalKey = baseKey;
if (!name.empty())
{
finalKey.append(".");
finalKey.append(name);
}
- AZStd::string itemKey = finalKey;
+ AZStd::string itemKey;
for (auto objIt = it.MemberBegin(); objIt != it.MemberEnd(); ++objIt)
{
+ itemKey = finalKey;
itemKey.append(".");
itemKey.append(objIt->name.GetString());
AddEntryToDatabase(itemKey, name, objIt->value, translationFormat);
-
- itemKey = finalKey;
}
}
@@ -60,18 +60,21 @@ namespace GraphCanvas
key.append(name);
}
- AZStd::string itemKey = key;
+ AZStd::string itemKey;
const rapidjson::Value& array = it;
for (rapidjson::SizeType i = 0; i < array.Size(); ++i)
{
+ itemKey = key;
+
// if there is a "base" member within the object, then use it, otherwise use the index
- if (array[i].IsObject())
+ const auto& element = array[i];
+ if (element.IsObject())
{
- if (array[i].HasMember(Schema::Field::key))
+ rapidjson::Value::ConstMemberIterator innerKeyItr = element.FindMember(Schema::Field::key);
+ if (innerKeyItr != element.MemberEnd())
{
- AZStd::string innerKey = array[i].FindMember(Schema::Field::key)->value.GetString();
- itemKey.append(AZStd::string::format(".%s", innerKey.c_str()));
+ itemKey.append(AZStd::string::format(".%s", innerKeyItr->value.GetString()));
}
else
{
@@ -79,9 +82,7 @@ namespace GraphCanvas
}
}
- AddEntryToDatabase(itemKey, "", array[i], translationFormat);
-
- itemKey = key;
+ AddEntryToDatabase(itemKey, "", element, translationFormat);
}
}
}
@@ -114,42 +115,32 @@ namespace GraphCanvas
{
const rapidjson::Value::ConstMemberIterator entries = inputValue.FindMember(Schema::Field::entries);
+ AZStd::string keyStr;
+ AZStd::string contextStr;
+ AZStd::string variantStr;
+ AZStd::string baseKey;
+
rapidjson::SizeType entryCount = entries->value.Size();
for (rapidjson::SizeType i = 0; i < entryCount; ++i)
{
const rapidjson::Value& entry = entries->value[i];
- AZStd::string keyStr;
- rapidjson::Value::ConstMemberIterator keyValue;
- if (entry.HasMember(Schema::Field::key))
- {
- keyValue = entry.FindMember(Schema::Field::key);
- keyStr = keyValue->value.GetString();
- }
+ rapidjson::Value::ConstMemberIterator keyItr = entry.FindMember(Schema::Field::key);
+ keyStr = keyItr != entry.MemberEnd() ? keyItr->value.GetString() : "";
- AZStd::string contextStr;
- rapidjson::Value::ConstMemberIterator contextValue;
- if (entry.HasMember(Schema::Field::context))
- {
- contextValue = entry.FindMember(Schema::Field::context);
- contextStr = contextValue->value.GetString();
- }
+ rapidjson::Value::ConstMemberIterator contextItr = entry.FindMember(Schema::Field::context);
+ contextStr = contextItr != entry.MemberEnd() ? contextItr->value.GetString() : "";
- AZStd::string variantStr;
- rapidjson::Value::ConstMemberIterator variantValue;
- if (entry.HasMember(Schema::Field::variant))
- {
- variantValue = entry.FindMember(Schema::Field::variant);
- variantStr = variantValue->value.GetString();
- }
+ rapidjson::Value::ConstMemberIterator variantItr = entry.FindMember(Schema::Field::variant);
+ variantStr = variantItr != entry.MemberEnd() ? variantItr->value.GetString() : "";
- AZStd::string baseKey = contextStr;
if (keyStr.empty())
{
- AZ_Error("TranslationDatabase", false, "Every entry in the Translation data must have a key: %s", baseKey.c_str());
+ AZ_Error("TranslationDatabase", false, "Every entry in the Translation data must have a key: %s", contextStr.c_str());
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Unsupported, "Every entry in the Translation data must have a key");
}
+ baseKey = contextStr;
if (!baseKey.empty())
{
baseKey.append(".");
@@ -167,7 +158,7 @@ namespace GraphCanvas
for (auto it = entry.MemberBegin(); it != entry.MemberEnd(); ++it)
{
// Skip the fixed elements
- if (it == keyValue || it == contextValue || it == variantValue)
+ if (it == keyItr || it == contextItr || it == variantItr)
{
continue;
}
diff --git a/scripts/o3de/o3de/register.py b/scripts/o3de/o3de/register.py
index 2500df1568..62a342f948 100644
--- a/scripts/o3de/o3de/register.py
+++ b/scripts/o3de/o3de/register.py
@@ -411,7 +411,7 @@ def register_project_path(json_data: dict,
if not remove:
# registering a project has the additional step of setting the project.json 'engine' field
- this_engine_json = manifest.get_engine_json_data(engine_path=manifest.get_this_engine_path())
+ this_engine_json = manifest.get_engine_json_data(engine_path=engine_path if engine_path else manifest.get_this_engine_path())
if not this_engine_json:
return 1
project_json_data = manifest.get_project_json_data(project_path=project_path)