Переглянути джерело

Auto generated mesh groups are now marked read only (#13701)

* [WIP] Mesh groups for default prefabs should be read-only in the Scene Settings UI

Signed-off-by: AMZN-stankowi <[email protected]>

* Adding missing header file

Signed-off-by: AMZN-stankowi <[email protected]>

* First pass at disabling read only groups

Signed-off-by: AMZN-stankowi <[email protected]>

* Fixed delete buttons not working when disabled

Signed-off-by: AMZN-stankowi <[email protected]>

* Read only status mostly works now

Signed-off-by: AMZN-stankowi <[email protected]>

* Tooltips for groups

Signed-off-by: AMZN-stankowi <[email protected]>

* Fixed mesh SVG to not have a built in background, so it will enter the disabled state correctly.
Removed unused printf.
Fixed prefab group divider.

Signed-off-by: AMZN-stankowi <[email protected]>

* Fixed typo in comment

Signed-off-by: AMZN-stankowi <[email protected]>

* Reverting changes not needed

Signed-off-by: AMZN-stankowi <[email protected]>

* Removed include no longer used

Signed-off-by: AMZN-stankowi <[email protected]>

* Fixed linking and reflection issue with new read only rule

Signed-off-by: AMZN-stankowi <[email protected]>

* Automated test for new read only rule

Signed-off-by: AMZN-stankowi <[email protected]>

* PR feedback: Renamed ReadOnly to Unmodifiable for the rule. Moved pragma to after legal header. Updated tooltip to describe how to remove the groups (doesn't actually work yet)

Signed-off-by: AMZN-stankowi <[email protected]>

* Removing a prefab group removes the mesh groups it added

Signed-off-by: AMZN-stankowi <[email protected]>

* Prefab group removal automated test

Signed-off-by: AMZN-stankowi <[email protected]>

Signed-off-by: AMZN-stankowi <[email protected]>
AMZN-stankowi 2 роки тому
батько
коміт
1c9eef8cf8
42 змінених файлів з 525 додано та 67 видалено
  1. 0 3
      Assets/Editor/Icons/AssetImporter/Mesh.svg
  2. 26 0
      AutomatedTesting/Gem/PythonTests/assetpipeline/scene_settings_tests/scene_settings_tests.py
  3. 1 1
      AutomatedTesting/Gem/PythonTests/assetpipeline/scene_settings_tests/tests/scene_settings_manifest_vector_widget_tests_in_editor.py
  4. 45 0
      AutomatedTesting/Gem/PythonTests/assetpipeline/scene_settings_tests/tests/scene_settings_readonly_rule_test.py
  5. 101 0
      AutomatedTesting/Gem/PythonTests/assetpipeline/scene_settings_tests/tests/scene_settings_remove_prefab_removes_mesh_groups.py
  6. 17 16
      AutomatedTesting/Gem/PythonTests/assetpipeline/scene_settings_tests/tests/scene_settings_test_helpers.py
  7. 44 5
      AutomatedTesting/Gem/PythonTests/assetpipeline/scene_settings_tests/tests/scene_settings_test_messages.py
  8. 3 4
      AutomatedTesting/Gem/PythonTests/assetpipeline/scene_settings_tests/tests/scene_settings_tests_in_editor.py
  9. 9 0
      Code/Tools/SceneAPI/SceneCore/DataTypes/IManifestObject.h
  10. 1 2
      Code/Tools/SceneAPI/SceneCore/DataTypes/Rules/IBlendShapeRule.h
  11. 1 2
      Code/Tools/SceneAPI/SceneCore/DataTypes/Rules/ICommentRule.h
  12. 1 2
      Code/Tools/SceneAPI/SceneCore/DataTypes/Rules/ILodRule.h
  13. 1 2
      Code/Tools/SceneAPI/SceneCore/DataTypes/Rules/IMaterialRule.h
  14. 1 2
      Code/Tools/SceneAPI/SceneCore/DataTypes/Rules/IMeshAdvancedRule.h
  15. 3 2
      Code/Tools/SceneAPI/SceneCore/DataTypes/Rules/IRule.h
  16. 1 2
      Code/Tools/SceneAPI/SceneCore/DataTypes/Rules/ISkeletonProxyRule.h
  17. 1 2
      Code/Tools/SceneAPI/SceneCore/DataTypes/Rules/ISkinRule.h
  18. 28 0
      Code/Tools/SceneAPI/SceneCore/DataTypes/Rules/IUnmodifiableRule.h
  19. 2 0
      Code/Tools/SceneAPI/SceneCore/DllMain.cpp
  20. 1 0
      Code/Tools/SceneAPI/SceneCore/scenecore_files.cmake
  21. 2 0
      Code/Tools/SceneAPI/SceneData/ManifestMetaInfoHandler.cpp
  22. 2 0
      Code/Tools/SceneAPI/SceneData/ReflectionRegistrar.cpp
  23. 1 2
      Code/Tools/SceneAPI/SceneData/Rules/BlendShapeRule.h
  24. 1 2
      Code/Tools/SceneAPI/SceneData/Rules/CommentRule.h
  25. 1 2
      Code/Tools/SceneAPI/SceneData/Rules/LodRule.h
  26. 1 2
      Code/Tools/SceneAPI/SceneData/Rules/MaterialRule.h
  27. 1 2
      Code/Tools/SceneAPI/SceneData/Rules/SkeletonProxyRule.h
  28. 1 2
      Code/Tools/SceneAPI/SceneData/Rules/SkinMeshAdvancedRule.h
  29. 1 2
      Code/Tools/SceneAPI/SceneData/Rules/SkinRule.h
  30. 1 2
      Code/Tools/SceneAPI/SceneData/Rules/StaticMeshAdvancedRule.h
  31. 47 0
      Code/Tools/SceneAPI/SceneData/Rules/UnmodifiableRule.cpp
  32. 37 0
      Code/Tools/SceneAPI/SceneData/Rules/UnmodifiableRule.h
  33. 2 0
      Code/Tools/SceneAPI/SceneData/SceneData_files.cmake
  34. 12 0
      Code/Tools/SceneAPI/SceneUI/RowWidgets/HeaderHandler.cpp
  35. 2 0
      Code/Tools/SceneAPI/SceneUI/RowWidgets/HeaderHandler.h
  36. 59 5
      Code/Tools/SceneAPI/SceneUI/RowWidgets/HeaderWidget.cpp
  37. 2 1
      Code/Tools/SceneAPI/SceneUI/RowWidgets/HeaderWidget.h
  38. 24 0
      Code/Tools/SceneAPI/SceneUI/SceneWidgets/ManifestWidgetPage.cpp
  39. 2 0
      Code/Tools/SceneAPI/SceneUI/SceneWidgets/ManifestWidgetPage.h
  40. 4 0
      Gems/Prefab/PrefabBuilder/PrefabGroup/DefaultProceduralPrefab.cpp
  41. 29 0
      Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroup.cpp
  42. 6 0
      Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroup.h

Різницю між файлами не показано, бо вона завелика
+ 0 - 3
Assets/Editor/Icons/AssetImporter/Mesh.svg


+ 26 - 0
AutomatedTesting/Gem/PythonTests/assetpipeline/scene_settings_tests/scene_settings_tests.py

@@ -48,6 +48,32 @@ class TestAutomation(EditorTestSuite):
 
         from .tests import scene_settings_tests_in_editor as test_module
 
+        
+    class scene_settings_tests_readonly_rule(EditorSingleTest):
+        @classmethod
+        def setup(self, instance, request, workspace):
+            self.test_file_names = ["auto_test_fbx.fbx"]
+            setup_test_files(workspace, self.test_file_names)
+
+        @classmethod
+        def teardown(self, instance, request, workspace, editor_test_results):
+            cleanup_test_files(workspace, self.test_file_names)
+
+        from .tests import scene_settings_readonly_rule_test as test_module
+        
+
+    class scene_settings_remove_prefab_removes_mesh_groups(EditorSingleTest):
+        @classmethod
+        def setup(self, instance, request, workspace):
+            self.test_file_names = ["auto_test_fbx.fbx"]
+            setup_test_files(workspace, self.test_file_names)
+
+        @classmethod
+        def teardown(self, instance, request, workspace, editor_test_results):
+            cleanup_test_files(workspace, self.test_file_names)
+
+        from .tests import scene_settings_remove_prefab_removes_mesh_groups as test_module
+
 
     class scene_settings_manifest_vector_widget_tests_in_editor(EditorSingleTest):
         @classmethod

+ 1 - 1
AutomatedTesting/Gem/PythonTests/assetpipeline/scene_settings_tests/tests/scene_settings_manifest_vector_widget_tests_in_editor.py

@@ -22,7 +22,7 @@ def Scene_Settings_Tests_In_Editor_Change_Manifest_Vector_Widget_Child_Marks_Fil
         path_to_manifest, widget_main_window, reflected_property_root = \
             scene_test_helpers.prepare_scene_ui_for_test( \
                 test_file_name="Jack_Death_Fall_Back_ZUp.fbx", \
-                manifest_should_exist=True)
+                manifest_should_exist=True, should_create_manifest=True)
         
         # Find a toggle that used to not properly mark the UI as dirty because it was parented to a manifest vector widget.
         y_axis = reflected_property_root.findChild(QtWidgets.QWidget,"Ignore Y-Axis transition")

+ 45 - 0
AutomatedTesting/Gem/PythonTests/assetpipeline/scene_settings_tests/tests/scene_settings_readonly_rule_test.py

@@ -0,0 +1,45 @@
+"""
+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
+"""
+    
+def Scene_Settings_Tests_In_Editor_ReadOnly_Rule_Works():
+    import pyside_utils
+
+    @pyside_utils.wrap_async
+    async def run_test():
+        import asyncio
+        from editor_python_test_tools.utils import Report
+        import PySide2
+        from PySide2 import QtWidgets
+        import azlmbr.bus as bus
+        import azlmbr.legacy.general as general
+        import scene_settings_test_messages as tm
+        import scene_settings_test_helpers as scene_test_helpers
+        
+        path_to_manifest, widget_main_window, reflected_property_root = \
+            scene_test_helpers.prepare_scene_ui_for_test(test_file_name="auto_test_fbx.fbx", manifest_should_exist=False, should_create_manifest=False)
+
+        # The default prefab adds mesh groups with coordinate rules, that have advanced settings.
+        # Find that toggle.
+        use_advanced_settings_row = reflected_property_root.findChild(QtWidgets.QWidget,"Use Advanced Settings")
+        Report.critical_result(tm.Test_Messages.scene_settings_found_advanced_settings_row, use_advanced_settings_row is not None)
+
+        check_boxes = use_advanced_settings_row.findChildren(QtWidgets.QCheckBox,"")
+        Report.critical_result(tm.Test_Messages.scene_settings_found_expected_interface_checkbox, check_boxes is not None)
+        Report.critical_result(tm.Test_Messages.scene_settings_found_only_one_checkbox, len(check_boxes) == 1)
+
+        use_advanced_settings_checkbox = check_boxes[0]
+        
+        # Verify that the toggle is already checked, and is disabled, meaning it is read only.
+        Report.critical_result(tm.Test_Messages.scene_settings_read_only_checked, use_advanced_settings_checkbox.isChecked())
+        Report.critical_result(tm.Test_Messages.scene_settings_read_only_disabled, not use_advanced_settings_checkbox.isEnabled())
+        widget_main_window.close()
+
+    run_test()
+
+if __name__ == "__main__":
+    from editor_python_test_tools.utils import Report
+    Report.start_test(Scene_Settings_Tests_In_Editor_ReadOnly_Rule_Works)

+ 101 - 0
AutomatedTesting/Gem/PythonTests/assetpipeline/scene_settings_tests/tests/scene_settings_remove_prefab_removes_mesh_groups.py

@@ -0,0 +1,101 @@
+"""
+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
+"""
+    
+def Scene_Settings_Tests_Remove_Prefab_Removes_Mesh_Groups():
+    import pyside_utils
+
+    @pyside_utils.wrap_async
+    async def run_test():
+        import asyncio
+        from editor_python_test_tools.utils import Report
+        import PySide2
+        from PySide2 import QtWidgets
+        import azlmbr.bus as bus
+        import azlmbr.legacy.general as general
+        import ly_test_tools.o3de.pipeline_utils as utils
+        import scene_settings_test_messages as tm
+        import scene_settings_test_helpers as scene_test_helpers
+        general.idle_enable(True)
+        
+        path_to_manifest, widget_main_window, reflected_property_root = \
+            scene_test_helpers.prepare_scene_ui_for_test(test_file_name="auto_test_fbx.fbx", manifest_should_exist=False, should_create_manifest=False)
+
+        # First, make sure the mesh groups added by the procedural prefab exists
+        name_mesh_labels = widget_main_window.findChildren(QtWidgets.QFrame, "Name mesh")
+
+        editable_mesh_group_name = "auto_test_fbx"
+        # The mesh groups generated should be the same every time, otherwise references would break
+        # when a scene manifest file didn't exist yet.
+        expected_mesh_names = [
+            editable_mesh_group_name,
+            "default_auto_test_fbx_F0FEDC3F_1BDC_546F_93A9_8DD78321B7B0_",
+            "default_auto_test_fbx_2076A10D_F9B1_5F4F_8100_DCEF0AFFB901_",
+            "default_auto_test_fbx_C93B7FD9_93B8_5FBD_864E_B63FCBE1803A_"
+        ]
+
+        found_mesh_names = []
+
+        for name_mesh_label in name_mesh_labels:
+            mesh_name_line_edit = name_mesh_label.findChild(QtWidgets.QLineEdit, "")
+            mesh_name = mesh_name_line_edit.text()
+            found_mesh_names.append(mesh_name)
+            if mesh_name == editable_mesh_group_name:
+                Report.critical_result(tm.Test_Messages.scene_settings_base_mesh_group_enabled, mesh_name_line_edit.isEnabled())
+            else:
+                # All other mesh groups should be read-only
+                Report.critical_result(tm.Test_Messages.scene_settings_prefab_mesh_group_disabled, not mesh_name_line_edit.isEnabled())
+
+        expected_diff, found_diff = utils.get_differences_between_lists(expected_mesh_names, found_mesh_names)
+
+        Report.critical_result(tm.Test_Messages.scene_settings_expected_mesh_groups_found, len(expected_diff) == 0 and len(found_diff) == 0)
+        
+        # Next, go to the prefab tab
+        tab_bar = widget_main_window.findChild(QtWidgets.QTabBar,"")
+        PREFAB_TAB_INDEX = 3
+        tab_bar.setCurrentIndex(PREFAB_TAB_INDEX)
+
+        # Find the default prefab group and delete it
+        ui_headers = widget_main_window.findChildren(QtWidgets.QWidget, "AZ__SceneAPI__UI__HeaderWidget")
+        for ui_header in ui_headers:
+            name_label = ui_header.findChild(QtWidgets.QLabel, "m_nameLabel")
+            if name_label.text() == "Prefab group":
+                delete_button = ui_header.findChild(QtWidgets.QToolButton, "m_deleteButton")
+                delete_button.click()
+                break
+                
+        # Go back to the first tab
+        MESH_TAB_INDEX = 0
+        tab_bar.setCurrentIndex(MESH_TAB_INDEX)
+
+        # Briefly pause so all events get posted
+        general.idle_wait_frames(30)
+        
+        # Verify the groups are gone now
+        
+        # Verify the prefab created groups are gone now and the remaining mesh group is the expected non-prefab one.
+        # Qt keeps objects around and can throw off searches. To work around that, examine the contents of the labels directly.        
+        new_found_mesh_names = []
+        name_mesh_labels = widget_main_window.findChildren(QtWidgets.QFrame, "Name mesh")
+        for name_mesh_label in name_mesh_labels:
+            mesh_name_line_edit = name_mesh_label.findChild(QtWidgets.QLineEdit, "")
+            if mesh_name_line_edit != None:
+                new_found_mesh_names.append(mesh_name_line_edit.text())
+                Report.critical_result(tm.Test_Messages.scene_settings_base_mesh_group_enabled, mesh_name_line_edit.isEnabled())
+
+        Report.critical_result(("", f"new: {new_found_mesh_names}"), len(new_found_mesh_names) == 1)
+        Report.critical_result(tm.Test_Messages.scene_settings_expected_mesh_groups_removed, len(new_found_mesh_names) == 1)
+
+        Report.critical_result(tm.Test_Messages.scene_settings_expected_mesh_group_found, editable_mesh_group_name == new_found_mesh_names[0])
+        
+        scene_test_helpers.save_and_verify_manifest(path_to_manifest, widget_main_window)
+        widget_main_window.close()
+
+    run_test()
+
+if __name__ == "__main__":
+    from editor_python_test_tools.utils import Report
+    Report.start_test(Scene_Settings_Tests_Remove_Prefab_Removes_Mesh_Groups)

+ 17 - 16
AutomatedTesting/Gem/PythonTests/assetpipeline/scene_settings_tests/tests/scene_settings_test_helpers.py

@@ -5,7 +5,7 @@ For complete copyright and license terms please see the LICENSE at the root of t
 SPDX-License-Identifier: Apache-2.0 OR MIT
 """
 
-def prepare_scene_ui_for_test(test_file_name, manifest_should_exist):
+def prepare_scene_ui_for_test(test_file_name, manifest_should_exist, should_create_manifest):
     import asyncio
     from editor_python_test_tools.utils import Report
     import os
@@ -39,25 +39,26 @@ def prepare_scene_ui_for_test(test_file_name, manifest_should_exist):
     widget_main_window = QtWidgets.QWidget.find(window_id)
 
     # When the window is first opened, even if it's to a new scene settings file not yet saved to disk, it shouldn't be marked with unsaved changes.
-    has_unsaved_changes = azlmbr.qt.SceneSettingsRootDisplayScriptRequestBus(azlmbr.bus.Broadcast, "HasUnsavedChanges")
-    Report.critical_result(tm.Test_Messages.scene_settings_update_disabled_on_launch, not has_unsaved_changes)
+    if should_create_manifest:
+        has_unsaved_changes = azlmbr.qt.SceneSettingsRootDisplayScriptRequestBus(azlmbr.bus.Broadcast, "HasUnsavedChanges")
+        Report.critical_result(tm.Test_Messages.scene_settings_update_disabled_on_launch, not has_unsaved_changes)
 
-    card_layout_area = widget_main_window.findChild(QtWidgets.QWidget, "m_cardAreaLayoutWidget")
+        card_layout_area = widget_main_window.findChild(QtWidgets.QWidget, "m_cardAreaLayoutWidget")
     
-    # On initial launch of the Scene Settings UI, it will generate one processing event to load the requested file.
-    # Find the card for this processing event and close it.
-    first_card_push_buttons = card_layout_area.findChildren(QtWidgets.QPushButton,"")
-    Report.critical_result(tm.Test_Messages.scene_settings_only_one_card_in_layout, len(first_card_push_buttons) == 1)
-    Report.critical_result(tm.Test_Messages.scene_settings_status_card_can_be_clicked, first_card_push_buttons[0].isEnabled())
+        # On initial launch of the Scene Settings UI, it will generate one processing event to load the requested file.
+        # Find the card for this processing event and close it.
+        first_card_push_buttons = card_layout_area.findChildren(QtWidgets.QPushButton,"")
+        Report.critical_result(tm.Test_Messages.scene_settings_only_one_card_in_layout, len(first_card_push_buttons) == 1)
+        Report.critical_result(tm.Test_Messages.scene_settings_status_card_can_be_clicked, first_card_push_buttons[0].isEnabled())
 
-    # Click the button
-    first_card_push_buttons[0].click()
-    # Wait a brief period of time after clicking to make sure the status card is removed.
-    general.idle_wait_frames(30)
+        # Click the button
+        first_card_push_buttons[0].click()
+        # Wait a brief period of time after clicking to make sure the status card is removed.
+        general.idle_wait_frames(30)
 
-    # Verify the button no longer exists
-    first_card_push_buttons = card_layout_area.findChildren(QtWidgets.QPushButton,"")
-    Report.critical_result(tm.Test_Messages.scene_setting_card_dismissed_on_click, len(first_card_push_buttons) == 0)
+        # Verify the button no longer exists
+        first_card_push_buttons = card_layout_area.findChildren(QtWidgets.QPushButton,"")
+        Report.critical_result(tm.Test_Messages.scene_setting_card_dismissed_on_click, len(first_card_push_buttons) == 0)
     
     reflected_property_root = widget_main_window.findChild(QtWidgets.QWidget, "m_rootWidget")
 

+ 44 - 5
AutomatedTesting/Gem/PythonTests/assetpipeline/scene_settings_tests/tests/scene_settings_test_messages.py

@@ -53,15 +53,30 @@ class Test_Messages:
         "Found Update materials root object.",
         "Failed to find Update materials root object."
     )
+    
+    scene_settings_found_advanced_settings_row = (
+        "Found 'use advanced settings' root object.",
+        "Failed to find 'use advanced settings' root object."
+    )
 
-    scene_settings_found_update_materials_checkbox = (
+    scene_settings_found_expected_interface_checkbox = (
         "Found the expected interface element in the scene settings UI.",
         "Unable to find the expected interface element in the scene settings UI."
     )
+            
+    scene_settings_found_only_one_checkbox = (
+        "Found single checkbox object.",
+        "Checkbox count is incorrect."
+    )
+    
+    scene_settings_read_only_checked = (
+        "Verified checkbox was checked.",
+        "Checkbox was not checked."
+    )
     
-    scene_settings_found_only_one_update_materials_checkbox = (
-        "Found single Update materials checkbox object.",
-        "Update materials checkbox count is incorrect."
+    scene_settings_read_only_disabled = (
+        "Verified checkbox was not enabled.",
+        "Checkbox was enabled when it was expected to be read-only."
     )
 
     # Make sure the UI became responsive after the save finished.
@@ -96,4 +111,28 @@ class Test_Messages:
         "Found single Ignore Y-Axis Transition checkbox object.",
         "Ignore Y-Axis Transition checkbox count is incorrect."
     )
-    
+
+    scene_settings_base_mesh_group_enabled = (
+        "The base mesh group is enabled.",
+        "The base mesh group is disabled, but should be enabled."
+    )
+
+    scene_settings_prefab_mesh_group_disabled = (
+        "The prefab generated mesh group is disabled.",
+        "The prefab generated mesh group is enabled, but should be disabled."
+    )
+    
+    scene_settings_expected_mesh_groups_found = (
+        "The expected mesh groups were found.",
+        "The expected mesh groups were not found."
+    )
+    
+    scene_settings_expected_mesh_groups_removed = (
+        "The expected mesh groups were removed.",
+        "The expected mesh groups were not removed."
+    )
+
+    scene_settings_expected_mesh_group_found = (
+        "The expected mesh group was found.",
+        "The expected mesh group was not found."
+    )

+ 3 - 4
AutomatedTesting/Gem/PythonTests/assetpipeline/scene_settings_tests/tests/scene_settings_tests_in_editor.py

@@ -19,14 +19,14 @@ def Scene_Settings_Tests_In_Editor_Create_And_Verify_Scene_Settings_File():
         import scene_settings_test_helpers as scene_test_helpers
         
         path_to_manifest, widget_main_window, reflected_property_root = \
-            scene_test_helpers.prepare_scene_ui_for_test(test_file_name="auto_test_fbx.fbx", manifest_should_exist=False)
+            scene_test_helpers.prepare_scene_ui_for_test(test_file_name="auto_test_fbx.fbx", manifest_should_exist=False, should_create_manifest=True)
 
         update_materials_row = reflected_property_root.findChild(QtWidgets.QWidget,"Update materials")
         Report.critical_result(tm.Test_Messages.scene_settings_found_update_materials_row, update_materials_row is not None)
 
         check_boxes = update_materials_row.findChildren(QtWidgets.QCheckBox,"")
-        Report.critical_result(tm.Test_Messages.scene_settings_found_update_materials_checkbox, check_boxes is not None)
-        Report.critical_result(tm.Test_Messages.scene_settings_found_only_one_update_materials_checkbox, len(check_boxes) == 1)
+        Report.critical_result(tm.Test_Messages.scene_settings_found_expected_interface_checkbox, check_boxes is not None)
+        Report.critical_result(tm.Test_Messages.scene_settings_found_only_one_checkbox, len(check_boxes) == 1)
 
         update_material_checkbox = check_boxes[0]
 
@@ -44,7 +44,6 @@ def Scene_Settings_Tests_In_Editor_Create_And_Verify_Scene_Settings_File():
         widget_main_window.close()
 
     run_test()
-    
 
 if __name__ == "__main__":
     from editor_python_test_tools.utils import Report

+ 9 - 0
Code/Tools/SceneAPI/SceneCore/DataTypes/IManifestObject.h

@@ -10,11 +10,16 @@
 
 #include <AzCore/RTTI/RTTI.h>
 #include <AzCore/Serialization/SerializeContext.h>
+#include <AzCore/std/containers/vector.h>
 
 namespace AZ
 {
     namespace SceneAPI
     {
+        namespace Containers
+        {
+            class SceneManifest;
+        }
         namespace DataTypes
         {
             class IManifestObject
@@ -26,6 +31,10 @@ namespace AZ
                 virtual ~IManifestObject() = 0;
                 virtual void OnUserAdded() {};
                 virtual void OnUserRemoved() const {};
+                // Some manifest objects cause other manifest objects to be created.
+                // When those manifest objects are removed, the added manifest objects should be removed, too.
+                virtual void GetManifestObjectsToRemoveOnRemoved(
+                    AZStd::vector<const IManifestObject*>& /*toRemove*/, const Containers::SceneManifest& /*manifest*/) const {}
             };
 
             inline void IManifestObject::Reflect(AZ::ReflectContext* context)

+ 1 - 2
Code/Tools/SceneAPI/SceneCore/DataTypes/Rules/IBlendShapeRule.h

@@ -1,5 +1,3 @@
-#pragma once
-
 /*
  * 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.
@@ -7,6 +5,7 @@
  * SPDX-License-Identifier: Apache-2.0 OR MIT
  *
  */
+#pragma once
 
 #include <AzCore/RTTI/RTTI.h>
 #include <SceneAPI/SceneCore/DataTypes/Rules/IRule.h>

+ 1 - 2
Code/Tools/SceneAPI/SceneCore/DataTypes/Rules/ICommentRule.h

@@ -1,5 +1,3 @@
-#pragma once
-
 /*
  * 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.
@@ -7,6 +5,7 @@
  * SPDX-License-Identifier: Apache-2.0 OR MIT
  *
  */
+#pragma once
 
 #include <AzCore/std/string/string.h>
 #include <AzCore/RTTI/RTTI.h>

+ 1 - 2
Code/Tools/SceneAPI/SceneCore/DataTypes/Rules/ILodRule.h

@@ -1,5 +1,3 @@
-#pragma once
-
 /*
  * 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.
@@ -7,6 +5,7 @@
  * SPDX-License-Identifier: Apache-2.0 OR MIT
  *
  */
+#pragma once
 
 #include <AzCore/RTTI/RTTI.h>
 #include <SceneAPI/SceneCore/DataTypes/Rules/IRule.h>

+ 1 - 2
Code/Tools/SceneAPI/SceneCore/DataTypes/Rules/IMaterialRule.h

@@ -1,5 +1,3 @@
-#pragma once
-
 /*
  * 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.
@@ -7,6 +5,7 @@
  * SPDX-License-Identifier: Apache-2.0 OR MIT
  *
  */
+#pragma once
 
 #include <AzCore/RTTI/RTTI.h>
 #include <SceneAPI/SceneCore/DataTypes/Rules/IRule.h>

+ 1 - 2
Code/Tools/SceneAPI/SceneCore/DataTypes/Rules/IMeshAdvancedRule.h

@@ -1,5 +1,3 @@
-#pragma once
-
 /*
  * 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.
@@ -7,6 +5,7 @@
  * SPDX-License-Identifier: Apache-2.0 OR MIT
  *
  */
+#pragma once
 
 #include <AzCore/std/string/string.h>
 #include <AzCore/RTTI/RTTI.h>

+ 3 - 2
Code/Tools/SceneAPI/SceneCore/DataTypes/Rules/IRule.h

@@ -1,5 +1,3 @@
-#pragma once
-
 /*
  * 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.
@@ -7,6 +5,7 @@
  * SPDX-License-Identifier: Apache-2.0 OR MIT
  *
  */
+#pragma once
 
 #include <memory>
 #include <AzCore/std/string/string.h>
@@ -26,6 +25,8 @@ namespace AZ
                 AZ_RTTI(IRule, "{81267F8B-3963-423B-9FF7-D276D82CD110}", IManifestObject);
 
                 virtual ~IRule() override = default;
+
+                virtual bool ModifyTooltip(AZStd::string& /*tooltip*/) {return false; }
             };
         }  // DataTypes
     }  // SceneAPI

+ 1 - 2
Code/Tools/SceneAPI/SceneCore/DataTypes/Rules/ISkeletonProxyRule.h

@@ -1,5 +1,3 @@
-#pragma once
-
 /*
  * 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.
@@ -7,6 +5,7 @@
  * SPDX-License-Identifier: Apache-2.0 OR MIT
  *
  */
+#pragma once
 
 #include <AzCore/RTTI/RTTI.h>
 #include <SceneAPI/SceneCore/DataTypes/Rules/IRule.h>

+ 1 - 2
Code/Tools/SceneAPI/SceneCore/DataTypes/Rules/ISkinRule.h

@@ -1,5 +1,3 @@
-#pragma once
-
 /*
  * 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.
@@ -7,6 +5,7 @@
  * SPDX-License-Identifier: Apache-2.0 OR MIT
  *
  */
+#pragma once
 
 #include <AzCore/RTTI/RTTI.h>
 #include <AzCore/Settings/SettingsRegistry.h>

+ 28 - 0
Code/Tools/SceneAPI/SceneCore/DataTypes/Rules/IUnmodifiableRule.h

@@ -0,0 +1,28 @@
+/*
+ * 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/RTTI/RTTI.h>
+#include <SceneAPI/SceneCore/DataTypes/Rules/IRule.h>
+
+namespace AZ
+{
+    namespace SceneAPI
+    {
+        namespace DataTypes
+        {
+            // Disables UI interaction for the node and children of the node with this rule.
+            class IUnmodifiableRule : public IRule
+            {
+            public:
+                AZ_RTTI(IUnmodifiableRule, "{071A7F4A-70A5-4D32-AC9C-1D49AFCB1480}", IRule);
+
+                virtual ~IUnmodifiableRule() override = default;
+            };
+        } // namespace DataTypes
+    } // namespace SceneAPI
+} // namespace AZ

+ 2 - 0
Code/Tools/SceneAPI/SceneCore/DllMain.cpp

@@ -41,6 +41,7 @@
 #include <SceneAPI/SceneCore/DataTypes/Rules/ILodRule.h>
 #include <SceneAPI/SceneCore/DataTypes/Rules/ISkeletonProxyRule.h>
 #include <SceneAPI/SceneCore/DataTypes/Rules/IScriptProcessorRule.h>
+#include <SceneAPI/SceneCore/DataTypes/Rules/IUnmodifiableRule.h>
 #include <SceneAPI/SceneCore/DataTypes/GraphData/IAnimationData.h>
 #include <SceneAPI/SceneCore/DataTypes/GraphData/IBlendShapeData.h>
 #include <SceneAPI/SceneCore/DataTypes/GraphData/IBoneData.h>
@@ -166,6 +167,7 @@ namespace AZ
                     context->Class<AZ::SceneAPI::DataTypes::ILodRule, AZ::SceneAPI::DataTypes::IRule>()->Version(1);
                     context->Class<AZ::SceneAPI::DataTypes::ISkeletonProxyRule, AZ::SceneAPI::DataTypes::IRule>()->Version(1);
                     context->Class<AZ::SceneAPI::DataTypes::IScriptProcessorRule, AZ::SceneAPI::DataTypes::IRule>()->Version(1);
+                    context->Class<AZ::SceneAPI::DataTypes::IUnmodifiableRule, AZ::SceneAPI::DataTypes::IRule>()->Version(1);
                     // Register graph data interfaces
                     context->Class<AZ::SceneAPI::DataTypes::IAnimationData, AZ::SceneAPI::DataTypes::IGraphObject>()->Version(1);
                     context->Class<AZ::SceneAPI::DataTypes::IBlendShapeData, AZ::SceneAPI::DataTypes::IGraphObject>()->Version(1);

+ 1 - 0
Code/Tools/SceneAPI/SceneCore/scenecore_files.cmake

@@ -46,6 +46,7 @@ set(FILES
     DataTypes/Rules/ISkeletonProxyRule.h
     DataTypes/Rules/ICoordinateSystemRule.h
     DataTypes/Rules/IClothRule.h
+    DataTypes/Rules/IUnmodifiableRule.h
     DataTypes/Rules/ISkinRule.h
     Components/BehaviorComponent.h
     Components/BehaviorComponent.cpp

+ 2 - 0
Code/Tools/SceneAPI/SceneData/ManifestMetaInfoHandler.cpp

@@ -21,6 +21,7 @@
 #include <SceneAPI/SceneData/Rules/CommentRule.h>
 #include <SceneAPI/SceneData/Rules/LodRule.h>
 #include <SceneAPI/SceneData/Rules/MaterialRule.h>
+#include <SceneAPI/SceneData/Rules/UnmodifiableRule.h>
 #include <SceneAPI/SceneData/Rules/StaticMeshAdvancedRule.h>
 #include <SceneAPI/SceneData/Rules/SkeletonProxyRule.h>
 #include <SceneAPI/SceneData/Rules/TangentsRule.h>
@@ -51,6 +52,7 @@ namespace AZ
             {
                 AZ_TraceContext("Object Type", target.RTTI_GetTypeName());
                 modifiers.push_back(SceneData::CommentRule::TYPEINFO_Uuid());
+                modifiers.push_back(SceneData::UnmodifiableRule::TYPEINFO_Uuid());
 
                 if (target.RTTI_IsTypeOf(DataTypes::IMeshGroup::TYPEINFO_Uuid()))
                 {

+ 2 - 0
Code/Tools/SceneAPI/SceneData/ReflectionRegistrar.cpp

@@ -17,6 +17,7 @@
 #include <SceneAPI/SceneData/Rules/StaticMeshAdvancedRule.h>
 #include <SceneAPI/SceneData/Rules/SkinMeshAdvancedRule.h>
 #include <SceneAPI/SceneData/Rules/MaterialRule.h>
+#include <SceneAPI/SceneData/Rules/UnmodifiableRule.h>
 #include <SceneAPI/SceneData/Rules/ScriptProcessorRule.h>
 #include <SceneAPI/SceneData/Rules/SkeletonProxyRule.h>
 #include <SceneAPI/SceneData/Rules/TangentsRule.h>
@@ -68,6 +69,7 @@ namespace AZ
             SceneData::LodRule::Reflect(context);
             SceneData::StaticMeshAdvancedRule::Reflect(context);
             SceneData::MaterialRule::Reflect(context);
+            SceneData::UnmodifiableRule::Reflect(context);
             SceneData::ScriptProcessorRule::Reflect(context);
             SceneData::SkeletonProxyRule::Reflect(context);
             SceneData::SkinMeshAdvancedRule::Reflect(context);

+ 1 - 2
Code/Tools/SceneAPI/SceneData/Rules/BlendShapeRule.h

@@ -1,5 +1,3 @@
-#pragma once
-
 /*
  * 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.
@@ -7,6 +5,7 @@
  * SPDX-License-Identifier: Apache-2.0 OR MIT
  *
  */
+#pragma once
 
 #include <AzCore/Memory/Memory.h>
 #include <AzCore/std/containers/vector.h>

+ 1 - 2
Code/Tools/SceneAPI/SceneData/Rules/CommentRule.h

@@ -1,5 +1,3 @@
-#pragma once
-
 /*
  * 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.
@@ -7,6 +5,7 @@
  * SPDX-License-Identifier: Apache-2.0 OR MIT
  *
  */
+#pragma once
 
 #include <AzCore/Memory/Memory.h>
 #include <AzCore/std/string/string.h>

+ 1 - 2
Code/Tools/SceneAPI/SceneData/Rules/LodRule.h

@@ -1,5 +1,3 @@
-#pragma once
-
 /*
  * 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.
@@ -7,6 +5,7 @@
  * SPDX-License-Identifier: Apache-2.0 OR MIT
  *
  */
+#pragma once
 
 #include <AzCore/Memory/Memory.h>
 #include <AzCore/std/containers/fixed_vector.h>

+ 1 - 2
Code/Tools/SceneAPI/SceneData/Rules/MaterialRule.h

@@ -1,5 +1,3 @@
-#pragma once
-
 /*
  * 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.
@@ -7,6 +5,7 @@
  * SPDX-License-Identifier: Apache-2.0 OR MIT
  *
  */
+#pragma once
 
 #include <AzCore/Memory/Memory.h>
 #include <SceneAPI/SceneCore/DataTypes/Rules/IMaterialRule.h>

+ 1 - 2
Code/Tools/SceneAPI/SceneData/Rules/SkeletonProxyRule.h

@@ -1,5 +1,3 @@
-#pragma once
-
 /*
  * 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.
@@ -7,6 +5,7 @@
  * SPDX-License-Identifier: Apache-2.0 OR MIT
  *
  */
+#pragma once
 
 #include <AzCore/Memory/Memory.h>
 #include <AzCore/std/containers/fixed_vector.h>

+ 1 - 2
Code/Tools/SceneAPI/SceneData/Rules/SkinMeshAdvancedRule.h

@@ -1,5 +1,3 @@
-#pragma once
-
 /*
  * 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.
@@ -7,6 +5,7 @@
  * SPDX-License-Identifier: Apache-2.0 OR MIT
  *
  */
+#pragma once
 
 #include <AzCore/Memory/Memory.h>
 #include <AzCore/std/containers/fixed_vector.h>

+ 1 - 2
Code/Tools/SceneAPI/SceneData/Rules/SkinRule.h

@@ -1,5 +1,3 @@
-#pragma once
-
 /*
  * 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.
@@ -7,6 +5,7 @@
  * SPDX-License-Identifier: Apache-2.0 OR MIT
  *
  */
+#pragma once
 
 #include <AzCore/Memory/Memory.h>
 #include <SceneAPI/SceneCore/DataTypes/Rules/ISkinRule.h>

+ 1 - 2
Code/Tools/SceneAPI/SceneData/Rules/StaticMeshAdvancedRule.h

@@ -1,5 +1,3 @@
-#pragma once
-
 /*
  * 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.
@@ -7,6 +5,7 @@
  * SPDX-License-Identifier: Apache-2.0 OR MIT
  *
  */
+#pragma once
 
 #include <AzCore/Memory/Memory.h>
 #include <AzCore/std/containers/fixed_vector.h>

+ 47 - 0
Code/Tools/SceneAPI/SceneData/Rules/UnmodifiableRule.cpp

@@ -0,0 +1,47 @@
+/*
+ * 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 <AzCore/Memory/SystemAllocator.h>
+#include <AzCore/RTTI/ReflectContext.h>
+#include <AzCore/Serialization/EditContext.h>
+#include <AzCore/Serialization/SerializeContext.h>
+#include <SceneAPI/SceneData/Rules/UnmodifiableRule.h>
+
+namespace AZ
+{
+    namespace SceneAPI
+    {
+        namespace SceneData
+        {
+            void UnmodifiableRule::Reflect(AZ::ReflectContext* context)
+            {
+                AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
+                if (!serializeContext)
+                {
+                    return;
+                }
+
+                serializeContext->Class<UnmodifiableRule, DataTypes::IUnmodifiableRule>()->Version(1);
+
+                AZ::EditContext* editContext = serializeContext->GetEditContext();
+                if (editContext)
+                {
+                    editContext->Class<UnmodifiableRule>("Unmodifiable", "This rule marks the container as unable to be modified.")
+                        ->ClassElement(Edit::ClassElements::EditorData, "")
+                        ->Attribute(AZ::Edit::Attributes::NameLabelOverride, "");
+                }
+            }
+
+            bool UnmodifiableRule::ModifyTooltip(AZStd::string& tooltip)
+            {
+                tooltip = AZStd::string::format("This group is not modifiable. %.*s", AZ_STRING_ARG(tooltip));
+                return true;
+            }
+        } // namespace SceneData
+    } // namespace SceneAPI
+} // namespace AZ

+ 37 - 0
Code/Tools/SceneAPI/SceneData/Rules/UnmodifiableRule.h

@@ -0,0 +1,37 @@
+/*
+ * 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/Memory/Memory.h>
+#include <AzCore/std/string/string.h>
+#include <SceneAPI/SceneCore/DataTypes/Rules/IUnmodifiableRule.h>
+#include <SceneAPI/SceneData/SceneDataConfiguration.h>
+
+namespace AZ
+{
+    class ReflectContext;
+    namespace SceneAPI
+    {
+        namespace SceneData
+        {
+            // Disables UI interaction for the node and children of the node with this rule.
+            class SCENE_DATA_CLASS UnmodifiableRule : public DataTypes::IUnmodifiableRule
+            {
+            public:
+                AZ_RTTI(UnmodifiableRule, "{6527EBC2-60DF-4E5A-98B4-106F050A186C}", DataTypes::IUnmodifiableRule);
+                AZ_CLASS_ALLOCATOR(UnmodifiableRule, AZ::SystemAllocator, 0)
+
+                SCENE_DATA_API ~UnmodifiableRule() override = default;
+
+                SCENE_DATA_API bool ModifyTooltip(AZStd::string& tooltip) override;
+
+                static void Reflect(ReflectContext* context);
+            };
+        } // namespace DataTypes
+    } // namespace SceneAPI
+} // namespace AZ

+ 2 - 0
Code/Tools/SceneAPI/SceneData/SceneData_files.cmake

@@ -69,6 +69,8 @@ set(FILES
     Rules/SkinRule.cpp
     Rules/TangentsRule.h
     Rules/TangentsRule.cpp
+    Rules/UnmodifiableRule.h
+    Rules/UnmodifiableRule.cpp
     GraphData/CustomPropertyData.h
     GraphData/CustomPropertyData.cpp
     GraphData/MeshData.h

+ 12 - 0
Code/Tools/SceneAPI/SceneUI/RowWidgets/HeaderHandler.cpp

@@ -76,6 +76,18 @@ namespace AZ
                     s_instance = nullptr;
                 }
             }
+
+            bool HeaderHandler::ModifyTooltip(QWidget* widget, QString& toolTipString)
+            {
+                HeaderWidget* headerWidget = qobject_cast<HeaderWidget*>(widget);
+                if (!headerWidget)
+                {
+                    return false;
+                }
+
+                return headerWidget->ModifyTooltip(toolTipString);
+                
+            }
         } // UI
     } // SceneAPI
 } // AZ

+ 2 - 0
Code/Tools/SceneAPI/SceneUI/RowWidgets/HeaderHandler.h

@@ -54,6 +54,8 @@ namespace AZ
                 bool ReadValuesIntoGUI(size_t index, HeaderWidget* GUI, const property_t& instance,
                     AzToolsFramework::InstanceDataNode* node) override;
 
+                bool ModifyTooltip(QWidget* widget, QString& toolTipString) override;
+
                 static void Register();
                 static void Unregister();
 

+ 59 - 5
Code/Tools/SceneAPI/SceneUI/RowWidgets/HeaderWidget.cpp

@@ -17,6 +17,7 @@
 #include <SceneAPI/SceneCore/Containers/Scene.h>
 #include <SceneAPI/SceneCore/Containers/SceneManifest.h>
 #include <SceneAPI/SceneCore/DataTypes/Groups/ISceneNodeGroup.h>
+#include <SceneAPI/SceneCore/DataTypes/Rules/IUnmodifiableRule.h>
 #include <SceneAPI/SceneCore/Utilities/Reporting.h>
 #include <SceneAPI/SceneCore/Events/ManifestMetaInfoBus.h>
 #include <SceneAPI/SceneUI/RowWidgets/HeaderWidget.h>
@@ -43,7 +44,6 @@ namespace AZ
                 : QWidget(parent)
                 , ui(new Ui::HeaderWidget())
                 , m_target(nullptr)
-                , m_nameIsEditable(false)
                 , m_sceneManifest(nullptr)
             {
                 InitSceneUIHeaderWidgetResources();
@@ -54,7 +54,7 @@ namespace AZ
                 ui->m_headerLine->hide();
                 ui->m_headerLineSpacer->hide();
                 
-                ui->m_deleteButton->setIcon(QIcon(":/PropertyEditor/Resources/cross-small.png"));
+                ui->m_deleteButton->setIcon(QIcon(":/stylesheet/img/close_small.svg"));
                 connect(ui->m_deleteButton, &QToolButton::clicked, this, &HeaderWidget::DeleteObject);
                 ui->m_deleteButton->hide();
 
@@ -82,6 +82,39 @@ namespace AZ
                 return m_target;
             }
 
+            bool HeaderWidget::ModifyTooltip(QString& toolTipString)
+            {
+                if (!m_target)
+                {
+                    return false;
+                }
+                if (m_target->RTTI_IsTypeOf(DataTypes::IGroup::TYPEINFO_Uuid()))
+                {
+                    const DataTypes::IGroup* group = azrtti_cast<const DataTypes::IGroup*>(m_target);
+
+                    const Containers::RuleContainer& rules = group->GetRuleContainerConst();
+                    // Multiple rules might change the tooltip, so loop through all rules.
+                    bool ruleChangedTooltip = false;
+                    // Rules don't all have access to Qt
+                    AZStd::string ruleTooltip;
+                    for (size_t ruleIndex = 0; ruleIndex < rules.GetRuleCount(); ++ruleIndex)
+                    {
+                        if (rules.GetRule(ruleIndex)->ModifyTooltip(ruleTooltip))
+                        {
+                            ruleChangedTooltip = true;
+                        }
+                    }
+                    if (ruleChangedTooltip)
+                    {
+                        toolTipString = QString("%1%2").arg(ruleTooltip.c_str()).arg(toolTipString);
+                    }
+
+                    return ruleChangedTooltip;
+                }
+
+                return false;
+            }
+
             void HeaderWidget::DeleteObject()
             {
                 AZ_TraceContext("Delete target", GetSerializedName(m_target));
@@ -91,8 +124,10 @@ namespace AZ
                     Containers::SceneManifest::Index index = m_sceneManifest->FindIndex(m_target);
                     if (index != Containers::SceneManifest::s_invalidIndex)
                     {
-                        AZ_TraceContext("Manifest index", static_cast<int>(index));
                         ManifestWidget* root = ManifestWidget::FindRoot(this);
+
+                        AZStd::vector<const AZ::SceneAPI::DataTypes::IManifestObject*> otherObjectsToRemove;
+                        m_target->GetManifestObjectsToRemoveOnRemoved(otherObjectsToRemove, *m_sceneManifest);
                         // The manifest object could be a root element at the manifest page level so it needs to be
                         //      removed from there as well in that case.
                         if (root->RemoveObject(m_sceneManifest->GetValue(index)) && m_sceneManifest->RemoveEntry(m_target))
@@ -101,6 +136,13 @@ namespace AZ
                             // Hide and disable the button so when users spam the delete button only a single click is recorded.
                             ui->m_deleteButton->hide();
                             ui->m_deleteButton->setEnabled(false);
+
+                            for (auto* toRemove : otherObjectsToRemove)
+                            {
+                                index = m_sceneManifest->FindIndex(toRemove);
+                                root->RemoveObject(m_sceneManifest->GetValue(index));
+                                m_sceneManifest->RemoveEntry(toRemove);
+                            }
                             return;
                         }
                         else
@@ -140,6 +182,19 @@ namespace AZ
             {
                 ui->m_deleteButton->hide();
 
+                // If this widget has the unmodifiable rule, then this can't be deleted.
+                // Even though the delete button would be disabled, it's even more clear it can't be deleted if it's not visible.
+                if (m_target->RTTI_IsTypeOf(DataTypes::IGroup::TYPEINFO_Uuid()))
+                {
+                    const DataTypes::IGroup* sceneNodeGroup = azrtti_cast<const DataTypes::IGroup*>(m_target);
+                    const Containers::RuleContainer& rules = sceneNodeGroup->GetRuleContainerConst();
+                    if (rules.FindFirstByType<AZ::SceneAPI::DataTypes::IUnmodifiableRule>())
+                    {
+                        // This header is unmodifiable, so leave the delete button hidden.
+                        return;
+                    }
+                }
+
                 if (m_sceneManifest)
                 {
                     Containers::SceneManifest::Index index = m_sceneManifest->FindIndex(m_target);
@@ -196,8 +251,7 @@ namespace AZ
                 // show have a visual divider, and potentially the icon.
                 if (target->RTTI_IsTypeOf(DataTypes::IGroup::TYPEINFO_Uuid()))
                 {
-                    sceneNodeGroup = azrtti_cast<const DataTypes::IGroup*>(&target);
-                    
+                    sceneNodeGroup = azrtti_cast<const DataTypes::IGroup*>(target);
                     AZ::SerializeContext* serializeContext = nullptr;
                     AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
                     AZ_Assert(serializeContext, "No serialize context");

+ 2 - 1
Code/Tools/SceneAPI/SceneUI/RowWidgets/HeaderWidget.h

@@ -50,6 +50,8 @@ namespace AZ
                 void SetManifestObject(const DataTypes::IManifestObject* target);
                 const DataTypes::IManifestObject* GetManifestObject() const;
 
+                bool ModifyTooltip(QString& toolTipString);
+
             protected:
                 bool InitSceneManifest();
 
@@ -63,7 +65,6 @@ namespace AZ
                 QScopedPointer<Ui::HeaderWidget> ui;
                 Containers::SceneManifest* m_sceneManifest; // Reference only, does not point to a local instance.
                 const DataTypes::IManifestObject* m_target; // Reference only, does not point to a local instance.
-                bool m_nameIsEditable;
             };
         } // namespace UI
     } // namespace SceneAPI

+ 24 - 0
Code/Tools/SceneAPI/SceneUI/SceneWidgets/ManifestWidgetPage.cpp

@@ -15,7 +15,9 @@
 #include <AzFramework/StringFunc/StringFunc.h>
 #include <AzCore/std/string/conversions.h>
 #include <SceneWidgets/ui_ManifestWidgetPage.h>
+#include <SceneAPI/SceneCore/DataTypes/Groups/IGroup.h>
 #include <SceneAPI/SceneCore/DataTypes/IManifestObject.h>
+#include <SceneAPI/SceneCore/DataTypes/Rules/IUnmodifiableRule.h>
 #include <SceneAPI/SceneCore/Containers/Scene.h>
 #include <SceneAPI/SceneCore/Containers/SceneManifest.h>
 #include <SceneAPI/SceneCore/Containers/Views/PairIterator.h>
@@ -40,6 +42,13 @@ namespace AZ
 
                 m_propertyEditor = new AzToolsFramework::ReflectedPropertyEditor(nullptr);
                 m_propertyEditor->Setup(context, this, true, 250);
+
+                m_propertyEditor->SetReadOnlyQueryFunction(
+                    [this](const AzToolsFramework::InstanceDataNode* node)
+                    {
+                        return SetNodeReadOnlyStatus(node);
+                    });
+
                 ui->m_mainLayout->insertWidget(0, m_propertyEditor);
 
                 BuildAndConnectAddButton();
@@ -417,6 +426,21 @@ namespace AZ
                     }
                 }
             }
+
+            bool ManifestWidgetPage::SetNodeReadOnlyStatus(const AzToolsFramework::InstanceDataNode* node)
+            {
+                if (AzToolsFramework::InstanceDataNode* parentNode = node ? node->GetRoot() : nullptr)
+                {
+                    AZ::SceneAPI::DataTypes::IGroup* group = m_context->Cast<AZ::SceneAPI::DataTypes::IGroup*>(
+                        parentNode->FirstInstance(), parentNode->GetClassMetadata()->m_typeId);
+                    // If this group is unmodifiable, that means it's read only.
+                    if (group && group->GetRuleContainerConst().FindFirstByType<AZ::SceneAPI::DataTypes::IUnmodifiableRule>())
+                    {
+                        return true;
+                    }
+                }
+                return false;
+            }
         } // namespace UI
     } // namespace SceneAPI
 } // namespace AZ

+ 2 - 0
Code/Tools/SceneAPI/SceneUI/SceneWidgets/ManifestWidgetPage.h

@@ -88,6 +88,8 @@ namespace AZ
                 // ManifestMetaInfoBus
                 void ObjectUpdated(const Containers::Scene& scene, const DataTypes::IManifestObject* target, void* sender) override;
 
+                bool SetNodeReadOnlyStatus(const AzToolsFramework::InstanceDataNode* node);
+
                 AZStd::vector<AZ::Uuid> m_classTypeIds;
                 AZStd::vector<AZStd::shared_ptr<DataTypes::IManifestObject>> m_objects;
                 QScopedPointer<Ui::ManifestWidgetPage> ui;

+ 4 - 0
Gems/Prefab/PrefabBuilder/PrefabGroup/DefaultProceduralPrefab.cpp

@@ -25,6 +25,7 @@
 #include <SceneAPI/SceneData/Groups/MeshGroup.h>
 #include <SceneAPI/SceneData/Rules/CoordinateSystemRule.h>
 #include <SceneAPI/SceneData/Rules/LodRule.h>
+#include <SceneAPI/SceneData/Rules/UnmodifiableRule.h>
 #include <AzCore/Component/EntityId.h>
 
 namespace AZ
@@ -349,6 +350,9 @@ namespace AZ::SceneAPI
         // tag this mesh group as a "default mesh group" using this rule
         meshGroup->GetRuleContainer().AddRule(AZStd::make_shared<AZ::SceneAPI::SceneData::ProceduralMeshGroupRule>());
 
+        // Don't let users edit these mesh groups, because they're procedural they'll be re-generated and overwrite any changes.
+        meshGroup->GetRuleContainer().AddRule(AZStd::make_shared<AZ::SceneAPI::SceneData::UnmodifiableRule>());
+
         // this clears out the mesh coordinates each mesh group will be rotated and translated
         // using the attached scene graph node
         auto coordinateSystemRule = AZStd::make_shared<AZ::SceneAPI::SceneData::CoordinateSystemRule>();

+ 29 - 0
Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroup.cpp

@@ -16,6 +16,7 @@
 #include <AzCore/std/smart_ptr/make_shared.h>
 #include <AzCore/std/optional.h>
 #include <AzFramework/FileFunc/FileFunc.h>
+#include <SceneAPI/SceneCore/Containers/SceneManifest.h>
 
 namespace AZ::SceneAPI::SceneData
 {
@@ -67,6 +68,27 @@ namespace AZ::SceneAPI::SceneData
         return m_nodeSelectionList;
     }
 
+    void PrefabGroup::GetManifestObjectsToRemoveOnRemoved(
+        AZStd::vector<const IManifestObject*>& toRemove, const AZ::SceneAPI::Containers::SceneManifest& manifest) const
+    {
+        for (size_t manifestIndex = 0; manifestIndex < manifest.GetEntryCount(); ++manifestIndex)
+        {
+            const AZStd::shared_ptr<const DataTypes::IManifestObject> manifestObjectAtIndex = manifest.GetValue(manifestIndex);
+            if (manifestObjectAtIndex->RTTI_IsTypeOf(DataTypes::IGroup::TYPEINFO_Uuid()))
+            {
+                // Removing shared ownership is useful because this is about to be deleted.
+                const DataTypes::IGroup* sceneNodeGroup = azrtti_cast<const DataTypes::IGroup*>(manifestObjectAtIndex.get());
+                const Containers::RuleContainer& rules = sceneNodeGroup->GetRuleContainerConst();
+                // Anything with the procedural mesh group rule was added automatically for this prefab group, so mark it for removal.
+                if (rules.FindFirstByType<AZ::SceneAPI::SceneData::ProceduralMeshGroupRule>())
+                {
+                    // Add it to the list to remove.
+                    toRemove.push_back(sceneNodeGroup);
+                }
+            }
+        }
+    }
+
     void PrefabGroup::SetPrefabDom(AzToolsFramework::Prefab::PrefabDom prefabDom)
     {
         m_prefabDomData = AZStd::make_shared<Prefab::PrefabDomData>();
@@ -119,6 +141,7 @@ namespace AZ::SceneAPI::SceneData
                     ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
                         ->Attribute("AutoExpand", true)
                         ->Attribute(AZ::Edit::Attributes::NameLabelOverride, "")
+                        ->Attribute(AZ::Edit::Attributes::CategoryStyle, "display divider")
                     ->DataElement(0,
                         &PrefabGroup::m_createProceduralPrefab,
                         "Create default procedural prefab?",
@@ -161,4 +184,10 @@ namespace AZ::SceneAPI::SceneData
                 ->Property("prefabDomData", getPrefabDomData, setPrefabDomData);
         }
     }
+
+    bool ProceduralMeshGroupRule::ModifyTooltip(AZStd::string& tooltip)
+    {
+        tooltip = AZStd::string::format("This group was generated by the procedural prefab. To remove this group, remove the procedural prefab. %.*s", AZ_STRING_ARG(tooltip));
+        return true;
+    }
 }

+ 6 - 0
Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroup.h

@@ -50,6 +50,10 @@ namespace AZ::SceneAPI::SceneData
         DataTypes::ISceneNodeSelectionList& GetSceneNodeSelectionList() override;
         const DataTypes::ISceneNodeSelectionList& GetSceneNodeSelectionList() const override;
 
+        // IManifestObject
+        void GetManifestObjectsToRemoveOnRemoved(
+            AZStd::vector<const IManifestObject*>& toRemove, const AZ::SceneAPI::Containers::SceneManifest& manifest) const override;
+
         // Concrete API
         void SetId(Uuid id);
         void SetName(AZStd::string name);
@@ -77,5 +81,7 @@ namespace AZ::SceneAPI::SceneData
 
         ProceduralMeshGroupRule() = default;
         ~ProceduralMeshGroupRule() override = default;
+
+        bool ModifyTooltip(AZStd::string& tooltip) override;
     };
 }

Деякі файли не було показано, через те що забагато файлів було змінено