Преглед на файлове

Merge branch 'development' into AP_GoToButtonPerf

Signed-off-by: amzn-mike <[email protected]>
amzn-mike преди 2 години
родител
ревизия
2f1db8d01e
променени са 89 файла, в които са добавени 2264 реда и са изтрити 3495 реда
  1. 17 0
      AutomatedTesting/Gem/PythonTests/Atom/atom_utils/atom_constants.py
  2. 120 94
      AutomatedTesting/Gem/PythonTests/Atom/atom_utils/material_canvas_utils.py
  3. 55 76
      AutomatedTesting/Gem/PythonTests/Atom/tests/MaterialCanvas_Atom_BasicTests.py
  4. 1 3
      AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_batch_tests.py
  5. 0 51
      Code/Editor/Core/EditorActionsHandler.cpp
  6. 0 6
      Code/Editor/Core/LevelEditorMenuHandler.cpp
  7. 0 35
      Code/Editor/CryEdit.cpp
  8. 0 3
      Code/Editor/CryEdit.h
  9. 0 1053
      Code/Editor/Export/ExportManager.cpp
  10. 0 195
      Code/Editor/Export/ExportManager.h
  11. 0 284
      Code/Editor/Export/OBJExporter.cpp
  12. 0 37
      Code/Editor/Export/OBJExporter.h
  13. 0 291
      Code/Editor/Export/OCMExporter.cpp
  14. 0 50
      Code/Editor/Export/OCMExporter.h
  15. 0 24
      Code/Editor/GameExporter.cpp
  16. 0 2
      Code/Editor/GameExporter.h
  17. 0 3
      Code/Editor/IEditor.h
  18. 0 15
      Code/Editor/IEditorImpl.cpp
  19. 0 5
      Code/Editor/IEditorImpl.h
  20. 0 182
      Code/Editor/Include/IExportManager.h
  21. 0 1
      Code/Editor/Lib/Tests/IEditorMock.h
  22. 0 3
      Code/Editor/MainWindow.cpp
  23. 0 2
      Code/Editor/Resource.h
  24. 0 49
      Code/Editor/TrackView/TrackViewDialog.cpp
  25. 0 3
      Code/Editor/TrackView/TrackViewDialog.h
  26. 1 261
      Code/Editor/TrackView/TrackViewNodes.cpp
  27. 0 4
      Code/Editor/TrackView/TrackViewNodes.h
  28. 0 7
      Code/Editor/editor_lib_files.cmake
  29. 7 4
      Code/Framework/AzCore/AzCore/IO/GenericStreams.cpp
  30. 4 0
      Code/Framework/AzCore/AzCore/IO/GenericStreams.h
  31. 5 6
      Code/Framework/AzCore/AzCore/IO/SystemFile.cpp
  32. 35 13
      Code/Framework/AzCore/AzCore/IO/SystemFile.h
  33. 77 36
      Code/Framework/AzCore/AzCore/Settings/CommandLine.cpp
  34. 25 8
      Code/Framework/AzCore/AzCore/Settings/CommandLine.h
  35. 1 1
      Code/Framework/AzCore/AzCore/Settings/ConfigParser.cpp
  36. 3 2
      Code/Framework/AzCore/AzCore/Settings/ConfigParser.h
  37. 119 0
      Code/Framework/AzCore/AzCore/Settings/TextParser.cpp
  38. 77 0
      Code/Framework/AzCore/AzCore/Settings/TextParser.h
  39. 2 0
      Code/Framework/AzCore/AzCore/azcore_files.cmake
  40. 100 70
      Code/Framework/AzCore/Platform/Android/AzCore/IO/SystemFile_Android.cpp
  41. 68 56
      Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/IO/SystemFile_UnixLike.cpp
  42. 90 60
      Code/Framework/AzCore/Platform/Common/UnixLikeDefault/AzCore/IO/SystemFile_UnixLikeDefault.cpp
  43. 161 124
      Code/Framework/AzCore/Platform/Common/WinAPI/AzCore/IO/SystemFile_WinAPI.cpp
  44. 5 4
      Code/Framework/AzCore/Tests/Asset/AssetManagerLoadingTests.cpp
  45. 0 5
      Code/Framework/AzCore/Tests/Asset/BaseAssetManagerTest.cpp
  46. 0 3
      Code/Framework/AzCore/Tests/Asset/BaseAssetManagerTest.h
  47. 60 0
      Code/Framework/AzCore/Tests/Platform/Android/Tests/IO/SystemFileTest_Android.cpp
  48. 1 0
      Code/Framework/AzCore/Tests/Platform/Android/platform_android_files.cmake
  49. 60 0
      Code/Framework/AzCore/Tests/Platform/Common/UnixLike/Tests/IO/SystemFileTest_UnixLike.cpp
  50. 67 0
      Code/Framework/AzCore/Tests/Platform/Common/WinAPI/Tests/IO/SystemFileTest_WinAPI.cpp
  51. 1 0
      Code/Framework/AzCore/Tests/Platform/Linux/platform_linux_files.cmake
  52. 1 0
      Code/Framework/AzCore/Tests/Platform/Mac/platform_mac_files.cmake
  53. 1 0
      Code/Framework/AzCore/Tests/Platform/Windows/platform_windows_files.cmake
  54. 1 0
      Code/Framework/AzCore/Tests/Platform/iOS/platform_ios_files.cmake
  55. 21 0
      Code/Framework/AzCore/Tests/Settings/CommandLineTests.cpp
  56. 169 0
      Code/Framework/AzCore/Tests/Settings/TextParserTests.cpp
  57. 61 0
      Code/Framework/AzCore/Tests/SystemFileTest.cpp
  58. 1 0
      Code/Framework/AzCore/Tests/azcoretests_files.cmake
  59. 31 4
      Code/Framework/AzToolsFramework/AzToolsFramework/AssetDatabase/AssetDatabaseConnection.cpp
  60. 5 0
      Code/Framework/AzToolsFramework/AzToolsFramework/AssetDatabase/AssetDatabaseConnection.h
  61. 28 12
      Code/Framework/AzToolsFramework/AzToolsFramework/UI/DocumentPropertyEditor/DocumentPropertyEditor.cpp
  62. 3 2
      Code/Framework/AzToolsFramework/AzToolsFramework/UI/DocumentPropertyEditor/DocumentPropertyEditor.h
  63. 2 0
      Code/Tools/AssetProcessor/assetprocessor_gui_files.cmake
  64. 77 9
      Code/Tools/AssetProcessor/native/AssetDatabase/AssetDatabase.cpp
  65. 2 0
      Code/Tools/AssetProcessor/native/AssetDatabase/AssetDatabase.h
  66. 3 3
      Code/Tools/AssetProcessor/native/AssetManager/SourceAssetReference.cpp
  67. 3 3
      Code/Tools/AssetProcessor/native/AssetManager/SourceAssetReference.h
  68. 40 3
      Code/Tools/AssetProcessor/native/AssetManager/assetProcessorManager.cpp
  69. 9 1
      Code/Tools/AssetProcessor/native/AssetManager/assetProcessorManager.h
  70. 2 0
      Code/Tools/AssetProcessor/native/assetprocessor.h
  71. 64 0
      Code/Tools/AssetProcessor/native/ui/EnabledRelocationTypesModel.cpp
  72. 40 0
      Code/Tools/AssetProcessor/native/ui/EnabledRelocationTypesModel.h
  73. 9 0
      Code/Tools/AssetProcessor/native/ui/MainWindow.cpp
  74. 5 1
      Code/Tools/AssetProcessor/native/ui/MainWindow.h
  75. 262 191
      Code/Tools/AssetProcessor/native/ui/MainWindow.ui
  76. 5 4
      Code/Tools/AssetProcessor/native/ui/ProductAssetDetailsPanel.cpp
  77. 35 36
      Code/Tools/AssetProcessor/native/ui/ProductAssetTreeModel.cpp
  78. 1 1
      Code/Tools/AssetProcessor/native/ui/ProductAssetTreeModel.h
  79. 5 0
      Code/Tools/AssetProcessor/native/ui/style/AssetProcessor.qss
  80. 5 0
      Code/Tools/AssetProcessor/native/utilities/UuidManager.cpp
  81. 5 0
      Code/Tools/AssetProcessor/native/utilities/UuidManager.h
  82. 122 62
      Code/Tools/SerializeContextTools/Dumper.cpp
  83. 3 0
      Code/Tools/SerializeContextTools/Runner.cpp
  84. 2 0
      Code/Tools/TestImpactFramework/Runtime/Common/Code/Include/Static/TestEngine/Common/TestImpactTestEngine.h
  85. 3 0
      Code/Tools/TestImpactFramework/Runtime/Native/Code/Source/TestEngine/Native/TestImpactNativeTestEngine.h
  86. 0 4
      Gems/LmbrCentral/Code/Source/Builders/LevelBuilder/LevelBuilderWorker.cpp
  87. 0 1
      Gems/LyShine/Code/Editor/Animation/UiAnimViewNodes.cpp
  88. 4 0
      Tools/LyTestTools/ly_test_tools/__init__.py
  89. 72 27
      scripts/o3de/o3de/engine_template.py

+ 17 - 0
AutomatedTesting/Gem/PythonTests/Atom/atom_utils/atom_constants.py

@@ -1585,9 +1585,26 @@ class GraphControllerRequestBusEvents(object):
     """
     ADD_NODE = "AddNode"
     REMOVE_NODE = "RemoveNode"
+    GET_POSITION = "GetPosition"
     WRAP_NODE = "WrapNode"
+    WRAP_NODE_ORDERED = "WrapNodeOrdered"
+    UNWRAP_NODE = "UnwrapNode"
+    IS_NODE_WRAPPED = "IsNodeWrapped"
+    SET_WRAPPER_NODE_ACTION_STRING = "SetWrapperNodeActionString"
     ADD_CONNECTION = "AddConnection"
     ADD_CONNECTION_BY_SLOT_ID = "AddConnectionBySlotId"
     ARE_SLOTS_CONNECTED = "AreSlotsConnected"
     REMOVE_CONNECTION = "RemoveConnection"
     EXTEND_SLOT = "ExtendSlot"
+    GET_NODE_BY_ID = "GetNodeById"
+    GET_NODES_FROM_GRAPH_NODE_IDS = "GetNodesFromGraphNodeIds"
+    GET_NODE_IDS_BY_NODE = "GetNodeIdByNode"
+    GET_SLOT_ID_BY_SLOT = "GetSlotIdBySlot"
+    GET_NODES = "GetNodes"
+    GET_SELECTED_NODES = "GetSelectedNodes"
+    SET_SELECTED = "SetSelected"
+    CLEAR_SELECTION = "ClearSelection"
+    ENABLE_NODE = "EnableNode"
+    DISABLE_NODE = "DisableNode"
+    CENTER_ON_NODES = "CenterOnNodes"
+    GET_MAJOR_PITCH = "GetMajorPitch"

+ 120 - 94
AutomatedTesting/Gem/PythonTests/Atom/atom_utils/material_canvas_utils.py

@@ -8,103 +8,129 @@ SPDX-License-Identifier: Apache-2.0 OR MIT
 import azlmbr.atomtools as atomtools
 import azlmbr.editor.graph as graph
 import azlmbr.math as math
+import azlmbr.object
 import azlmbr.bus as bus
 
 from Atom.atom_utils.atom_constants import (
     DynamicNodeManagerRequestBusEvents, GraphControllerRequestBusEvents, GraphDocumentRequestBusEvents)
 
 
-def get_graph_name(document_id: math.Uuid) -> str:
-    """
-    Gets the graph name of the given document_id and returns it as a string.
-    :param document_id: The UUID of a given graph document file.
-    :return: str representing the graph name contained in document_id
-    """
-    return atomtools.GraphDocumentRequestBus(bus.Event, GraphDocumentRequestBusEvents.GET_GRAPH_NAME, document_id)
-
-
-def get_graph_id(document_id: math.Uuid) -> int:
-    """
-    Gets the graph ID of the given document_id and returns it as an int.
-    :param document_id: The UUID of a given graph document file.
-    :return: int representing the graph ID of the graph contained in document_id
-    """
-    return atomtools.GraphDocumentRequestBus(bus.Event, GraphDocumentRequestBusEvents.GET_GRAPH_ID, document_id)
-
-
-def get_graph(document_id: math.Uuid) -> object:
-    """
-    Gets the graph object of the given document_id and returns it.
-    :param document_id: The UUID of a given graph document file.
-    :return: azlmbr.object.PythonProxyObject representing a C++ AZStd::shared_ptr<Graph> object.
-    """
-    return atomtools.GraphDocumentRequestBus(bus.Event, GraphDocumentRequestBusEvents.GET_GRAPH, document_id)
-
-
-def create_node_by_name(graph: object, node_name: str) -> object:
-    """
-    Creates a new node in memory matching the node_name string on the specified graph object.
-    i.e. "World Position" for node_name would create a World Position node.
-    :param graph: An AZStd::shared_ptr<Graph> graph object to create the new node on.
-    :param node_name: String representing the type of node to add to the graph.
-    :return: azlmbr.object.PythonProxyObject representing a C++ AZStd::shared_ptr<Node> object.
-    """
-    return atomtools.DynamicNodeManagerRequestBus(
-        bus.Broadcast, DynamicNodeManagerRequestBusEvents.CREATE_NODE_BY_NAME, graph, node_name)
-
-
-def add_node(graph_id: math.Uuid, node: object, position: math.Vector2) -> int:
-    """
-    Adds a node saved in memory to the current graph document at a specific position on the graph grid.
-    :param graph_id: int representing the ID value of a given graph AZStd::shared_ptr<Graph> object.
-    :param node: An AZStd::shared_ptr<Node> object.
-    :param position: math.Vector2(x,y) value that determines where to place the node on the graph grid.
-    :return: int representing the node ID of the newly placed node.
-    """
-    return graph.GraphControllerRequestBus(
-        bus.Event, GraphControllerRequestBusEvents.ADD_NODE, graph_id, node, position)
-
-
-def get_graph_model_slot_id(slot_name: str) -> object:
-    """
-    Given a slot_name string, return a GraphModelSlotId object representing a node slot.
-    :param slot_name: String representing the name of the slot to target on the node.
-    :return: An GraphModelSlotId object.
-    """
-    return graph.GraphModelSlotId(slot_name)
-
-
-def add_connection_by_slot_id(
-        graph_id: math.Uuid,
-        source_node: object, source_slot: object,
-        target_node: object, target_slot: object) -> object:
-    """
-    Adds a new connection between a source node slot and a target node slot.
-    :param graph_id: int representing the ID value of a given graph AZStd::shared_ptr<Graph> object.
-    :param source_node: A proxy AZStd::shared_ptr<Node> object for the source node to start the connection from.
-    :param source_slot: A proxy GraphModelSlotId object for the slot on the source node to use for the connection.
-    :param target_node: A proxy AZStd::shared_ptr<Node> object for the target node to end the connection to.
-    :param target_slot: A proxy GraphModelSlotId object for the slot on the target node to use for the connection.
-    :return: azlmbr.object.PythonProxyObject representing a C++ AZStd::shared_ptr<Connection> object.
-    """
-    return graph.GraphControllerRequestBus(
-        bus.Event, GraphControllerRequestBusEvents.ADD_CONNECTION_BY_SLOT_ID, graph_id,
-        source_node, source_slot, target_node, target_slot)
-
-
-def are_slots_connected(
-        graph_id: math.Uuid,
-        source_node: object, source_slot: object,
-        target_node: object, target_slot: object) -> bool:
-    """
-    Determines if 2 nodes have a connection formed between them and returns a boolean for success/failure.
-    :param graph_id: int representing the ID value of a given graph AZStd::shared_ptr<Graph> object.
-    :param source_node: An AZStd::shared_ptr<Node> object representing the source node for the connection.
-    :param source_slot: An GraphModelSlotId object representing the slot on the source node the connection uses.
-    :param target_node: An AZStd::shared_ptr<Node> object representing the target node for the connection.
-    :param target_slot: An GraphModelSlotId object representing the slot on the target node the connection uses.
-    :return: bool representing success (True) or failure (False).
-    """
-    return graph.GraphControllerRequestBus(
-        bus.Event, GraphControllerRequestBusEvents.ARE_SLOTS_CONNECTED, graph_id,
-        source_node, source_slot, target_node, target_slot)
+class Node(object):
+    """
+    Represents a node inside of a graph and contains node related functions.
+    """
+
+    def __init__(self, node_python_proxy: azlmbr.object.PythonProxyObject):
+        """
+        :param node_python_proxy: azlmbr.object.PythonProxyObject representing a node object.
+        """
+        self.node_python_proxy = node_python_proxy
+
+    def get_slots(self) -> dict:
+        """
+        Returns a dictionary mapping of all slots on this Node.
+        :return: a dict containing slot name keys with graph.GraphModelSlotId slot values.
+        """
+        mapped_node_slots = {}
+        raw_node_slots_dict = self.node_python_proxy.invoke('GetSlots')
+
+        for k, v in raw_node_slots_dict.items():
+            mapped_node_slots[k.name] = graph.GraphModelSlotId(k.name)
+
+        return mapped_node_slots
+
+
+class Graph(object):
+    """
+    Binds this Graph object to the opened graph matching the document_id passed to construct this object.
+    """
+
+    def __init__(self, document_id: math.Uuid):
+        self.document_id = document_id
+
+    def get_graph_id(self) -> math.Uuid:
+        """
+        Returns the graph ID value for this Graph object.
+        :return: math.Uuid which represents the graph ID value.
+        """
+        return atomtools.GraphDocumentRequestBus(
+            bus.Event, GraphDocumentRequestBusEvents.GET_GRAPH_ID, self.document_id)
+
+    def get_graph_name(self) -> str:
+        """
+        Returns the graph name for this Graph object.
+        :return: string representing the name of this Graph object.
+        """
+        return atomtools.GraphDocumentRequestBus(
+            bus.Event, GraphDocumentRequestBusEvents.GET_GRAPH_NAME, self.document_id)
+
+    def get_graph(self) -> azlmbr.object.PythonProxyObject:
+        """
+        Returns the AZStd::shared_ptr<Graph> object for this Graph object.
+        :return: azlmbr.object.PythonProxyObject containing a AZStd::shared_ptr<Graph> object.
+        """
+        return atomtools.GraphDocumentRequestBus(bus.Event, GraphDocumentRequestBusEvents.GET_GRAPH, self.document_id)
+
+    def get_nodes(self) -> list[azlmbr.object.PythonProxyObject]:
+        """
+        Gets the current Nodes in this Graph and returns them as a list of azlmbr.object.PythonProxyObject objects.
+        :return: list of azlmbr.object.PythonProxyObject objects each representing a Node in this Graph.
+        """
+        return graph.GraphControllerRequestBus(
+            bus.Event, GraphControllerRequestBusEvents.GET_NODES, self.get_graph_id())
+
+    def create_node_by_name(self, node_name: str) -> azlmbr.object.PythonProxyObject:
+        """
+        Creates a new node in memory matching the node_name string on the specified graph object.
+        i.e. "World Position" for node_name would create a World Position node.
+        We create them in memory for use with the GraphControllerRequestBusEvents.ADD_NODE bus call on this Graph.
+        :param node_name: String representing the type of node to add to the graph.
+        :return: azlmbr.object.PythonProxyObject representing a C++ AZStd::shared_ptr<Node> object.
+        """
+        return atomtools.DynamicNodeManagerRequestBus(
+            bus.Broadcast, DynamicNodeManagerRequestBusEvents.CREATE_NODE_BY_NAME, self.get_graph(), node_name)
+
+    def add_node(self, node: azlmbr.object.PythonProxyObject, position: math.Vector2) -> Node:
+        """
+        Adds a node generated from the NodeManager to this Graph, then returns a Node object for that node.
+        :param node: A azlmbr.object.PythonProxyObject containing the node we wish to add to the graph.
+        :param position: math.Vector2(x,y) value that determines where to place the node on the Graph's grid.
+        :return: A Node object containing node object and node id via azlmbr.object.PythonProxyObject objects.
+        """
+        graph.GraphControllerRequestBus(
+            bus.Event, GraphControllerRequestBusEvents.ADD_NODE, self.get_graph_id(), node, position)
+        return Node(node)
+
+    def add_connection_by_slot_id(self,
+                                  source_node: Node,
+                                  source_slot: graph.GraphModelSlotId,
+                                  target_node: Node,
+                                  target_slot: graph.GraphModelSlotId) -> azlmbr.object.PythonProxyObject:
+        """
+        Connects a starting source_slot to a destination target_slot.
+        :param source_node: A Node() object for the source node we are connecting from.
+        :param source_slot: A graph.GraphModelSlotId representing a slot on the source_node.
+        :param target_node: A Node() object for the target node we are connecting to.
+        :param target_slot: A graph.GraphModelSlotId representing a slot on the target_node.
+        :return: azlmbr.object.PythonProxyObject representing a C++ AZStd::shared_ptr<Connection> object.
+        """
+        return graph.GraphControllerRequestBus(
+            bus.Event, GraphControllerRequestBusEvents.ADD_CONNECTION_BY_SLOT_ID, self.get_graph_id(),
+            source_node.node_python_proxy, source_slot, target_node.node_python_proxy, target_slot)
+
+    def are_slots_connected(self,
+                            source_node: Node,
+                            source_slot: graph.GraphModelSlotId,
+                            target_node: Node,
+                            target_slot: graph.GraphModelSlotId) -> bool:
+        """
+        Determines if the source_slot is connected to the target_slot on the target_node.
+        :param source_node: A Node() object for the source node that should be connected to the target node.
+        :param source_slot: A graph.GraphModelSlotId representing a slot on the source_node.
+        :param target_node: A Node() object for the target node that should be connected to the source node.
+        :param target_slot: A graph.GraphModelSlotId representing a slot on the target_node..
+        :return: True if the source_slot and target_slot are connected, False otherwise.
+        """
+        return graph.GraphControllerRequestBus(
+            bus.Event, GraphControllerRequestBusEvents.ARE_SLOTS_CONNECTED, self.get_graph_id(),
+            source_node.node_python_proxy, source_slot, target_node.node_python_proxy, target_slot)

+ 55 - 76
AutomatedTesting/Gem/PythonTests/Atom/tests/MaterialCanvas_Atom_BasicTests.py

@@ -25,9 +25,6 @@ class Tests:
     node_palette_pane_visible = (
         "Node Palette pane is visible.",
         "P0: Failed to verify Node Palette pane is visible.")
-    material_graph_created = (
-        "New AZStd::shared_ptr<Graph> object created.",
-        "P0: Failed to create new AZStd::shared_ptr<Graph> object.")
     material_graph_name_is_test1 = (
         "Verified material graph name is 'test1'",
         "P0: Failed to verify material graph name is 'test1'")
@@ -55,17 +52,13 @@ def MaterialCanvas_BasicFunctionalityChecks_AllChecksPass():
     2) Close the selected material graph document.
     3) Open multiple material graph documents then use the CloseAllDocuments bus call to close them all.
     4) Open multiple material graph documents then verify all material documents are opened.
-    5) Use the CloseAllDocumentsExcept bus call to close all but one.
+    5) Use the CloseAllDocumentsExcept bus call to close all but test_1_material_graph.
     6) Verify Node Palette pane visibility.
-    7) Verify material graph name is 'test1'.
-    8) Create a new material_graph from material_graph_document_ids[0].
-    9) Create a new world_position_node in memory.
-    10) Add the world_position_node to the material_graph.
-    11) Create a new standard_pbr_node in memory.
-    12) Add the standard_pbr_node to the material_graph
-    13) Create outbound_slot for our world_position_node and inbound_slot for our standard_pbr_node.
-    14) Create a node connection between world_position_node and standard_pbr_node successfully.
-    15) Look for errors and asserts.
+    7) Verify test_1_material_graph.name is 'test1'.
+    8) Create a new world_position_node inside test_1_material_graph.
+    9) Create a new standard_pbr_node inside test_1_material_graph.
+    10) Create a node connection between world_position_node outbound slot and standard_pbr_node inbound slot.
+    11) Look for errors and asserts.
 
     :return: None
     """
@@ -75,7 +68,7 @@ def MaterialCanvas_BasicFunctionalityChecks_AllChecksPass():
     import azlmbr.math as math
 
     import Atom.atom_utils.atom_tools_utils as atom_tools_utils
-    import Atom.atom_utils.material_canvas_utils as material_canvas_utils
+    from Atom.atom_utils.material_canvas_utils import Graph
     from editor_python_test_tools.utils import Report, Tracer, TestHelper
 
     with Tracer() as error_tracer:
@@ -86,51 +79,52 @@ def MaterialCanvas_BasicFunctionalityChecks_AllChecksPass():
         atom_tools_utils.disable_graph_compiler_settings()
 
         # Set constants before starting test steps.
-        test_1_material_graph = os.path.join(atom_tools_utils.MATERIALCANVAS_GRAPH_PATH, "test1.materialgraph")
-        test_2_material_graph = os.path.join(atom_tools_utils.MATERIALCANVAS_GRAPH_PATH, "test2.materialgraph")
-        test_3_material_graph = os.path.join(atom_tools_utils.MATERIALCANVAS_GRAPH_PATH, "test3.materialgraph")
-        test_4_material_graph = os.path.join(atom_tools_utils.MATERIALCANVAS_GRAPH_PATH, "test4.materialgraph")
-        test_5_material_graph = os.path.join(atom_tools_utils.MATERIALCANVAS_GRAPH_PATH, "test5.materialgraph")
+        test_1_material_graph_file = os.path.join(atom_tools_utils.MATERIALCANVAS_GRAPH_PATH, "test1.materialgraph")
+        test_2_material_graph_file = os.path.join(atom_tools_utils.MATERIALCANVAS_GRAPH_PATH, "test2.materialgraph")
+        test_3_material_graph_file = os.path.join(atom_tools_utils.MATERIALCANVAS_GRAPH_PATH, "test3.materialgraph")
+        test_4_material_graph_file = os.path.join(atom_tools_utils.MATERIALCANVAS_GRAPH_PATH, "test4.materialgraph")
+        test_5_material_graph_file = os.path.join(atom_tools_utils.MATERIALCANVAS_GRAPH_PATH, "test5.materialgraph")
 
         # 1. Open an existing material graph document.
-        material_graph_document_id = atom_tools_utils.open_document(test_1_material_graph)
+        test_1_material_graph = Graph(atom_tools_utils.open_document(test_1_material_graph_file))
         Report.result(
             Tests.open_existing_material_graph,
-            atom_tools_utils.is_document_open(material_graph_document_id) is True)
+            atom_tools_utils.is_document_open(test_1_material_graph.document_id) is True)
 
         # 2. Close the selected material graph document.
-        atom_tools_utils.close_document(material_graph_document_id)
+        atom_tools_utils.close_document(test_1_material_graph.document_id)
         Report.result(
             Tests.close_opened_material_graph,
-            atom_tools_utils.is_document_open(material_graph_document_id) is False)
+            atom_tools_utils.is_document_open(test_1_material_graph.document_id) is False)
 
         # 3. Open multiple material graph documents then use the CloseAllDocuments bus call to close them all.
-        for material_graph_document in [test_1_material_graph, test_2_material_graph, test_3_material_graph,
-                                        test_4_material_graph, test_5_material_graph]:
-            atom_tools_utils.open_document(os.path.join(material_graph_document))
+        material_graph_document_files = [
+            test_1_material_graph_file, test_2_material_graph_file, test_3_material_graph_file,
+            test_4_material_graph_file, test_5_material_graph_file]
+        for material_graph_document_file in material_graph_document_files:
+            Graph(atom_tools_utils.open_document(material_graph_document_file))
         Report.result(
             Tests.close_all_opened_material_graphs,
             atom_tools_utils.close_all_documents() is True)
 
         # 4. Open multiple material graph documents then verify all material documents are opened.
-        material_graph_document_ids = []
-        test_material_graphs = [test_1_material_graph, test_2_material_graph, test_3_material_graph,
-                                test_4_material_graph, test_5_material_graph]
-        for test_material_graph in test_material_graphs:
-            material_graph_document_id = atom_tools_utils.open_document(test_material_graph)
+        test_material_graphs = []
+        for material_graph_document_file in material_graph_document_files:
+            test_material_graph = Graph(atom_tools_utils.open_document(material_graph_document_file))
             Report.result(Tests.verify_all_material_graphs_are_opened,
-                          atom_tools_utils.is_document_open(material_graph_document_id) is True)
-            material_graph_document_ids.append(material_graph_document_id)
+                          atom_tools_utils.is_document_open(test_material_graph.document_id) is True)
+            test_material_graphs.append(test_material_graph)
 
-        # 5. Use the CloseAllDocumentsExcept bus call to close all but one.
-        atom_tools_utils.close_all_except_selected(material_graph_document_ids[0])
+        # 5. Use the CloseAllDocumentsExcept bus call to close all but test_1_material_graph.
+        test_1_material_graph = test_material_graphs[0]
+        atom_tools_utils.close_all_except_selected(test_1_material_graph.document_id)
         Report.result(
             Tests.close_all_opened_material_graphs_except_one,
-            atom_tools_utils.is_document_open(material_graph_document_ids[0]) is True and
-            atom_tools_utils.is_document_open(material_graph_document_ids[1]) is False and
-            atom_tools_utils.is_document_open(material_graph_document_ids[2]) is False and
-            atom_tools_utils.is_document_open(material_graph_document_ids[3]) is False and
-            atom_tools_utils.is_document_open(material_graph_document_ids[4]) is False)
+            atom_tools_utils.is_document_open(test_1_material_graph.document_id) is True and
+            atom_tools_utils.is_document_open(test_material_graphs[1].document_id) is False and
+            atom_tools_utils.is_document_open(test_material_graphs[2].document_id) is False and
+            atom_tools_utils.is_document_open(test_material_graphs[3].document_id) is False and
+            atom_tools_utils.is_document_open(test_material_graphs[4].document_id) is False)
 
         # 6. Verify Node Palette pane visibility.
         atom_tools_utils.set_pane_visibility("Node Palette", True)
@@ -138,52 +132,37 @@ def MaterialCanvas_BasicFunctionalityChecks_AllChecksPass():
             Tests.node_palette_pane_visible,
             atom_tools_utils.is_pane_visible("Node Palette") is True)
 
-        # 7. Verify material graph name is 'test1'.
-        material_graph_name = material_canvas_utils.get_graph_name(material_graph_document_ids[0])
+        # 7. Verify test_1_material_graph.name is 'test1'.
         Report.result(
             Tests.material_graph_name_is_test1,
-            material_graph_name == "test1")
+            test_1_material_graph.get_graph_name() == "test1")
 
-        # 8. Create a new material_graph from material_graph_document_ids[0].
-        material_graph = material_canvas_utils.get_graph(material_graph_document_ids[0])
-        material_graph_id = material_canvas_utils.get_graph_id(material_graph_document_ids[0])
-        Report.result(
-            Tests.material_graph_created,
-            material_graph.typename == "AZStd::shared_ptr<Graph>")
-
-        # 9. Create a new world_position_node in memory.
-        world_position_node = material_canvas_utils.create_node_by_name(material_graph, "World Position")
+        # 8. Create a new world_position_node inside test_1_material_graph.
+        created_world_position_node = test_1_material_graph.create_node_by_name('World Position')
+        world_position_node = test_1_material_graph.add_node(created_world_position_node, math.Vector2(-200.0, 10.0))
         Report.result(
             Tests.world_position_node_created,
-            world_position_node.typename == "AZStd::shared_ptr<Node>")
-
-        # 10. Add the world_position_node to the material_graph.
-        # This test will be verified when the nodes are connected as if it doesn't exist the connection won't be made.
-        material_canvas_utils.add_node(material_graph_id, world_position_node, math.Vector2(-200.0, 10.0))
+            world_position_node.node_python_proxy.typename == "AZStd::shared_ptr<Node>")
 
-        # 11. Create a new standard_pbr_node in memory.
-        standard_pbr_node = material_canvas_utils.create_node_by_name(material_graph, "Standard PBR")
+        # 9. Create a new standard_pbr_node inside test_1_material_graph.
+        created_standard_pbr_node = test_1_material_graph.create_node_by_name('Standard PBR')
+        standard_pbr_node = test_1_material_graph.add_node(created_standard_pbr_node, math.Vector2(10.0, 220.0))
         Report.result(
             Tests.standard_pbr_node_created,
-            standard_pbr_node.typename == "AZStd::shared_ptr<Node>")
-
-        # 12. Add the standard_pbr_node to the material_graph.
-        # This test will be verified when the nodes are connected as if it doesn't exist the connection won't be made.
-        material_canvas_utils.add_node(material_graph_id, standard_pbr_node, math.Vector2(10.0, 220.0))
-
-        # 13. Create outbound_slot for our world_position_node and inbound_slot for our standard_pbr_node.
-        # This test will be verified when the nodes are connected as if it doesn't exist the connection won't be made.
-        outbound_slot = material_canvas_utils.get_graph_model_slot_id('outPosition')
-        inbound_slot = material_canvas_utils.get_graph_model_slot_id('inPositionOffset')
-
-        # 14. Create a node connection between world_position_node and standard_pbr_node successfully.
-        material_canvas_utils.add_connection_by_slot_id(
-            material_graph_id, world_position_node, outbound_slot, standard_pbr_node, inbound_slot)
-        are_slots_connected = material_canvas_utils.are_slots_connected(
-                material_graph_id, world_position_node, outbound_slot, standard_pbr_node, inbound_slot)
+            standard_pbr_node.node_python_proxy.typename == "AZStd::shared_ptr<Node>")
+
+        # 10. Create a node connection between world_position_node outbound slot and standard_pbr_node inbound slot.
+        world_position_node_slots = world_position_node.get_slots()
+        standard_pbr_node_slots = standard_pbr_node.get_slots()
+        test_1_material_graph.add_connection_by_slot_id(
+            world_position_node, world_position_node_slots['outPosition'],
+            standard_pbr_node, standard_pbr_node_slots['inPositionOffset'])
+        are_slots_connected = test_1_material_graph.are_slots_connected(
+            world_position_node, world_position_node_slots['outPosition'],
+            standard_pbr_node, standard_pbr_node_slots['inPositionOffset'])
         Report.result(Tests.nodes_connected, are_slots_connected is True)
 
-        # 15. Look for errors and asserts.
+        # 11. 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}")

+ 1 - 3
AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_batch_tests.py

@@ -336,9 +336,7 @@ class TestsAssetProcessorBatch_AllPlatforms(object):
                                    os.path.join(source_folder, "b.fbx")]
 
             result, output = asset_processor.batch_process(capture_output=True,
-                                                           skip_atom_output=True,
-                                                           extra_params=[f"--reprocessFileList={reprocess_file_list}",
-                                                                         "--regset=\"/Amazon/AssetProcessor/Settings/Jobs/maxJobs=1\""])
+                                                           skip_atom_output=True)
 
             # If the test fails, it's helpful to have the output from asset processor in the logs, to track the failure down.
             if not result:

+ 0 - 51
Code/Editor/Core/EditorActionsHandler.cpp

@@ -974,55 +974,6 @@ void EditorActionsHandler::OnActionRegistrationHook()
         m_actionManagerInterface->AssignModeToAction(AzToolsFramework::DefaultActionContextModeIdentifier, actionIdentifier);
     }
 
-    // Export Selected Objects
-    {
-        constexpr AZStd::string_view actionIdentifier = "o3de.action.game.exportSelectedObjects";
-        AzToolsFramework::ActionProperties actionProperties;
-        actionProperties.m_name = "Export Selected Objects";
-        actionProperties.m_description = "Export Selected Objects.";
-        actionProperties.m_category = "Game";
-        actionProperties.m_menuVisibility = AzToolsFramework::ActionVisibility::AlwaysShow;
-
-        m_actionManagerInterface->RegisterAction(
-            EditorIdentifiers::MainWindowActionContextIdentifier,
-            actionIdentifier,
-            actionProperties,
-            [cryEdit = m_cryEditApp]
-            {
-                cryEdit->OnExportSelectedObjects();
-            }
-        );
-
-        m_actionManagerInterface->InstallEnabledStateCallback(actionIdentifier, AreEntitiesSelected);
-        m_actionManagerInterface->AddActionToUpdater(EditorIdentifiers::EntitySelectionChangedUpdaterIdentifier, actionIdentifier);
-
-        // This action is only accessible outside of Component Modes
-        m_actionManagerInterface->AssignModeToAction(AzToolsFramework::DefaultActionContextModeIdentifier, actionIdentifier);
-    }
-
-    // Export Occlusion Mesh
-    {
-        constexpr AZStd::string_view actionIdentifier = "o3de.action.game.exportOcclusionMesh";
-        AzToolsFramework::ActionProperties actionProperties;
-        actionProperties.m_name = "Export Occlusion Mesh";
-        actionProperties.m_description = "Export Occlusion Mesh.";
-        actionProperties.m_category = "Game";
-        actionProperties.m_menuVisibility = AzToolsFramework::ActionVisibility::AlwaysShow;
-
-        m_actionManagerInterface->RegisterAction(
-            EditorIdentifiers::MainWindowActionContextIdentifier,
-            actionIdentifier,
-            actionProperties,
-            [cryEdit = m_cryEditApp]
-            {
-                cryEdit->OnFileExportOcclusionMesh();
-            }
-        );
-
-        // This action is only accessible outside of Component Modes
-        m_actionManagerInterface->AssignModeToAction(AzToolsFramework::DefaultActionContextModeIdentifier, actionIdentifier);
-    }
-
     // Move Player and Camera Separately
     {
         constexpr AZStd::string_view actionIdentifier = "o3de.action.game.movePlayerAndCameraSeparately";
@@ -1940,8 +1891,6 @@ void EditorActionsHandler::OnMenuBindingHook()
         }
         m_menuManagerInterface->AddActionToMenu(EditorIdentifiers::GameMenuIdentifier, "o3de.action.game.simulate", 200);
         m_menuManagerInterface->AddSeparatorToMenu(EditorIdentifiers::GameMenuIdentifier, 300);
-        m_menuManagerInterface->AddActionToMenu(EditorIdentifiers::GameMenuIdentifier, "o3de.action.game.exportSelectedObjects", 400);
-        m_menuManagerInterface->AddActionToMenu(EditorIdentifiers::GameMenuIdentifier, "o3de.action.game.exportOcclusionMesh", 500);
         m_menuManagerInterface->AddSeparatorToMenu(EditorIdentifiers::GameMenuIdentifier, 600);
         m_menuManagerInterface->AddActionToMenu(EditorIdentifiers::GameMenuIdentifier, "o3de.action.game.movePlayerAndCameraSeparately", 700);
         m_menuManagerInterface->AddSeparatorToMenu(EditorIdentifiers::GameMenuIdentifier, 800);

+ 0 - 6
Code/Editor/Core/LevelEditorMenuHandler.cpp

@@ -599,12 +599,6 @@ QMenu* LevelEditorMenuHandler::CreateGameMenu()
         gameMenu.AddAction(ID_FILE_EXPORTTOGAMENOSURFACETEXTURE);
     }
 
-    // Export Selected Objects
-    gameMenu.AddAction(ID_FILE_EXPORT_SELECTEDOBJECTS);
-
-    // Export Occlusion Mesh
-    gameMenu.AddAction(ID_FILE_EXPORTOCCLUSIONMESH);
-
     gameMenu.AddSeparator();
 
     // Synchronize Player with Camera

+ 0 - 35
Code/Editor/CryEdit.cpp

@@ -130,8 +130,6 @@ AZ_POP_DISABLE_WARNING
 
 #include "QuickAccessBar.h"
 
-#include "Export/ExportManager.h"
-
 #include "LevelFileDialog.h"
 #include "LevelIndependentFileMan.h"
 #include "WelcomeScreen/WelcomeScreenDialog.h"
@@ -365,7 +363,6 @@ void CCryEditApp::RegisterActionHandlers()
     ON_COMMAND(ID_DOCUMENTATION_GAMEDEVBLOG, OnDocumentationGameDevBlog)
     ON_COMMAND(ID_DOCUMENTATION_FORUMS, OnDocumentationForums)
     ON_COMMAND(ID_DOCUMENTATION_AWSSUPPORT, OnDocumentationAWSSupport)
-    ON_COMMAND(ID_FILE_EXPORT_SELECTEDOBJECTS, OnExportSelectedObjects)
     ON_COMMAND(ID_EDIT_HOLD, OnEditHold)
     ON_COMMAND(ID_EDIT_FETCH, OnEditFetch)
     ON_COMMAND(ID_FILE_EXPORTTOGAMENOSURFACETEXTURE, OnFileExportToGameNoSurfaceTexture)
@@ -421,7 +418,6 @@ void CCryEditApp::RegisterActionHandlers()
     ON_COMMAND(ID_OPEN_QUICK_ACCESS_BAR, OnOpenQuickAccessBar)
 
     ON_COMMAND(ID_FILE_SAVE_LEVEL, OnFileSave)
-    ON_COMMAND(ID_FILE_EXPORTOCCLUSIONMESH, OnFileExportOcclusionMesh)
 
     // Project Manager
     ON_COMMAND(ID_FILE_PROJECT_MANAGER_SETTINGS, OnOpenProjectManagerSettings)
@@ -2610,37 +2606,6 @@ void CCryEditApp::OnViewSwitchToGameFullScreen()
     OnViewSwitchToGame();
 }
 
-//////////////////////////////////////////////////////////////////////////
-void CCryEditApp::OnExportSelectedObjects()
-{
-    CExportManager* pExportManager = static_cast<CExportManager*> (GetIEditor()->GetExportManager());
-    QString filename = "untitled";
-    CBaseObject* pObj = GetIEditor()->GetSelectedObject();
-    if (pObj)
-    {
-        filename = pObj->GetName();
-    }
-    else
-    {
-        QString levelName = GetIEditor()->GetGameEngine()->GetLevelName();
-        if (!levelName.isEmpty())
-        {
-            filename = levelName;
-        }
-    }
-    QString levelPath = GetIEditor()->GetGameEngine()->GetLevelPath();
-    pExportManager->Export(filename.toUtf8().data(), "obj", levelPath.toUtf8().data());
-}
-
-//////////////////////////////////////////////////////////////////////////
-void CCryEditApp::OnFileExportOcclusionMesh()
-{
-    CExportManager* pExportManager = static_cast<CExportManager*> (GetIEditor()->GetExportManager());
-    QString levelName = GetIEditor()->GetGameEngine()->GetLevelName();
-    QString levelPath = GetIEditor()->GetGameEngine()->GetLevelPath();
-    pExportManager->Export(levelName.toUtf8().data(), "ocm", levelPath.toUtf8().data(), false, false, true);
-}
-
 //////////////////////////////////////////////////////////////////////////
 void CCryEditApp::OnOpenAssetImporter()
 {

+ 0 - 3
Code/Editor/CryEdit.h

@@ -196,7 +196,6 @@ public:
     void OnDocumentationAWSSupport();
     void OnCommercePublish();
     void OnCommerceMerch();
-    void OnExportSelectedObjects();
     void OnEditHold();
     void OnEditFetch();
     void OnFileExportToGameNoSurfaceTexture();
@@ -395,8 +394,6 @@ private:
 public:
     void ExportLevel(bool bExportToGame, bool bExportTexture, bool bAutoExport);
     static bool Command_ExportToEngine();
-
-    void OnFileExportOcclusionMesh();
 };
 
 //////////////////////////////////////////////////////////////////////////

+ 0 - 1053
Code/Editor/Export/ExportManager.cpp

@@ -1,1053 +0,0 @@
-/*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
-
-
-#include "EditorDefs.h"
-
-#include "ExportManager.h"
-
-// Qt
-#include <QMessageBox>
-
-// Maestro
-#include <Maestro/Types/AnimParamType.h>
-
-// Editor
-#include "ViewManager.h"
-#include "OBJExporter.h"
-#include "OCMExporter.h"
-#include "FBXExporterDialog.h"
-#include "TrackViewExportKeyTimeDlg.h"
-#include "AnimationContext.h"
-#include "TrackView/DirectorNodeAnimator.h"
-#include "Util/AutoDirectoryRestoreFileDialog.h"
-#include "QtUI/WaitCursor.h"
-#include "WaitProgress.h"
-#include "Objects/SelectionGroup.h"
-#include "Include/IObjectManager.h"
-#include "TrackView/TrackViewTrack.h"
-#include "TrackView/TrackViewSequenceManager.h"
-#include "Resource.h"
-#include "Plugins/ComponentEntityEditorPlugin/Objects/ComponentEntityObject.h"
-
-namespace
-{
-    const float kTangentDelta = 0.01f;
-    const float kAspectRatio = 1.777778f;
-    const int kReserveCount = 7; // x,y,z,rot_x,rot_y,rot_z,fov
-    const QString kPrimaryCameraName = "PrimaryCamera";
-} // namespace
-
-
-
-//////////////////////////////////////////////////////////
-// CMesh
-Export::CMesh::CMesh()
-{
-    ::ZeroMemory(&material, sizeof(material));
-    material.opacity = 1.0f;
-}
-
-
-//////////////////////////////////////////////////////////
-// CObject
-Export::CObject::CObject(const char* pName)
-    : m_MeshHash(0)
-{
-    pos.x = pos.y = pos.z = 0;
-    rot.v.x = rot.v.y = rot.v.z = 0;
-    rot.w = 1.0f;
-    scale.x = scale.y = scale.z = 1.0f;
-
-    nParent = -1;
-
-    azstrcpy(name, AZ_ARRAY_SIZE(name), pName);
-
-    materialName[0] = '\0';
-
-    entityType = Export::eEntity;
-
-    cameraTargetNodeName[0] = '\0';
-
-    m_pLastObject = nullptr;
-}
-
-
-void Export::CObject::SetMaterialName(const char* pName)
-{
-    azstrcpy(materialName, AZ_ARRAY_SIZE(materialName), pName);
-}
-
-
-// CExportData
-void Export::CData::Clear()
-{
-    m_objects.clear();
-}
-
-
-// CExportManager
-CExportManager::CExportManager()
-    : m_isPrecaching(false)
-    , m_fScale(100.0f)
-    , m_bAnimationExport(false)
-    , m_pBaseObj(nullptr)
-    ,                 // this scale is used by CryEngine RC
-    m_FBXBakedExportFPS(0.0f)
-    , m_bExportLocalCoords(false)
-    , m_bExportOnlyPrimaryCamera(false)
-    , m_numberOfExportFrames(0)
-    , m_pivotEntityObject(nullptr)
-    , m_bBakedKeysSequenceExport(true)
-    , m_animTimeExportPrimarySequenceCurrentTime(0.0f)
-    , m_animKeyTimeExport(true)
-    , m_soundKeyTimeExport(true)
-{
-    CExportManager::RegisterExporter(new COBJExporter());
-    CExportManager::RegisterExporter(new COCMExporter());
-}
-
-
-CExportManager::~CExportManager()
-{
-    m_data.Clear();
-    for (TExporters::iterator ppExporter = m_exporters.begin(); ppExporter != m_exporters.end(); ++ppExporter)
-    {
-        (*ppExporter)->Release();
-    }
-}
-
-
-bool CExportManager::RegisterExporter(IExporter* pExporter)
-{
-    if (!pExporter)
-    {
-        return false;
-    }
-
-    m_exporters.push_back(pExporter);
-    return true;
-}
-
-inline f32 Sandbox2MayaFOVDeg(const f32 fov, const f32 ratio) { return RAD2DEG(2.0f * atan_tpl(tan(DEG2RAD(fov) / 2.0f) * ratio)); };
-inline f32 Sandbox2MayaFOVRad2Deg(const f32 fov, const f32 ratio) { return RAD2DEG(2.0f * atan_tpl(tan(fov / 2.0f) * ratio)); };
-
-void CExportManager::AddEntityAnimationData(const CTrackViewTrack* pTrack, Export::CObject* pObj, AnimParamType entityTrackParamType)
-{
-    int keyCount = pTrack->GetKeyCount();
-    pObj->m_entityAnimData.reserve(keyCount * kReserveCount);
-
-    for (int keyNumber = 0; keyNumber < keyCount; keyNumber++)
-    {
-        const CTrackViewKeyConstHandle currentKeyHandle = pTrack->GetKey(keyNumber);
-        const float fCurrentKeytime = currentKeyHandle.GetTime();
-
-        float trackValue = 0.0f;
-        pTrack->GetValue(fCurrentKeytime, trackValue);
-
-        ISplineInterpolator* pSpline = pTrack->GetSpline();
-
-        if (pSpline)
-        {
-            Export::EntityAnimData entityData;
-            entityData.dataType = (Export::AnimParamType)pTrack->GetParameterType().GetType();
-            entityData.keyValue = trackValue;
-            entityData.keyTime = fCurrentKeytime;
-
-            if (entityTrackParamType == AnimParamType::Position)
-            {
-                entityData.keyValue *= 100.0f;
-            }
-            else if (entityTrackParamType == AnimParamType::FOV)
-            {
-                entityData.keyValue = Sandbox2MayaFOVDeg(entityData.keyValue, kAspectRatio);
-            }
-
-
-            ISplineInterpolator::ValueType tin;
-            ISplineInterpolator::ValueType tout;
-            ZeroStruct(tin);
-            ZeroStruct(tout);
-            pSpline->GetKeyTangents(keyNumber, tin, tout);
-
-            float fInTantentX = tin[0];
-            float fInTantentY = tin[1];
-
-            float fOutTantentX = tout[0];
-            float fOutTantentY = tout[1];
-
-            if (fInTantentX == 0.0f)
-            {
-                fInTantentX = kTangentDelta;
-            }
-
-            if (fOutTantentX == 0.0f)
-            {
-                fOutTantentX = kTangentDelta;
-            }
-
-            entityData.leftTangent = fInTantentY / fInTantentX;
-            entityData.rightTangent = fOutTantentY / fOutTantentX;
-
-            if (entityTrackParamType == AnimParamType::Position)
-            {
-                entityData.leftTangent *= 100.0f;
-                entityData.rightTangent *= 100.0f;
-            }
-
-            float fPrevKeyTime = 0.0f;
-            float fNextKeyTime = 0.0f;
-
-            bool bIsFirstKey = false;
-            bool bIsMiddleKey = false;
-            bool bIsLastKey = false;
-
-            if (keyNumber == 0 && keyNumber < (keyCount - 1))
-            {
-                const CTrackViewKeyConstHandle nextKeyHandle = pTrack->GetKey(keyNumber + 1);
-                fNextKeyTime = nextKeyHandle.GetTime();
-
-                if (fNextKeyTime != 0.0f)
-                {
-                    bIsFirstKey = true;
-                }
-            }
-            else if (keyNumber > 0)
-            {
-                const CTrackViewKeyConstHandle prevKeyHandle = pTrack->GetKey(keyNumber - 1);
-                fPrevKeyTime = prevKeyHandle.GetTime();
-
-                if (keyNumber < (keyCount - 1))
-                {
-                    const CTrackViewKeyConstHandle nextKeyHandle = pTrack->GetKey(keyNumber + 1);
-                    fNextKeyTime = nextKeyHandle.GetTime();
-
-                    if (fNextKeyTime != 0.0f)
-                    {
-                        bIsMiddleKey = true;
-                    }
-                }
-                else
-                {
-                    bIsLastKey = true;
-                }
-            }
-
-            float fLeftTangentWeightValue = 0.0f;
-            float fRightTangentWeightValue = 0.0f;
-
-            if (bIsFirstKey)
-            {
-                fRightTangentWeightValue = fOutTantentX / fNextKeyTime;
-            }
-            else if (bIsMiddleKey)
-            {
-                fLeftTangentWeightValue = fInTantentX / (fCurrentKeytime - fPrevKeyTime);
-                fRightTangentWeightValue = fOutTantentX / (fNextKeyTime - fCurrentKeytime);
-            }
-            else if (bIsLastKey)
-            {
-                fLeftTangentWeightValue = fInTantentX / (fCurrentKeytime - fPrevKeyTime);
-            }
-
-            entityData.leftTangentWeight = fLeftTangentWeightValue;
-            entityData.rightTangentWeight = fRightTangentWeightValue;
-
-            pObj->m_entityAnimData.push_back(entityData);
-        }
-    }
-}
-
-
-void CExportManager::ProcessEntityAnimationTrack(
-    const AZ::EntityId entityId, Export::CObject* pObj, AnimParamType entityTrackParamType)
-{
-    CTrackViewAnimNode* pEntityNode = GetIEditor()->GetSequenceManager()->GetActiveAnimNode(entityId);
-    CTrackViewTrack* pEntityTrack = (pEntityNode ? pEntityNode->GetTrackForParameter(entityTrackParamType) : nullptr);
-
-    if (!pEntityTrack)
-    {
-        return;
-    }
-
-    if (pEntityTrack->GetParameterType() == AnimParamType::FOV)
-    {
-        AddEntityAnimationData(pEntityTrack, pObj, entityTrackParamType);
-        return;
-    }
-
-    for (unsigned int trackNumber = 0; trackNumber < pEntityTrack->GetChildCount(); ++trackNumber)
-    {
-        CTrackViewTrack* pSubTrack = static_cast<CTrackViewTrack*>(pEntityTrack->GetChild(trackNumber));
-
-        if (pSubTrack)
-        {
-            AddEntityAnimationData(pSubTrack, pObj, entityTrackParamType);
-        }
-    }
-}
-
-
-void CExportManager::AddEntityAnimationData(AZ::EntityId entityId)
-{
-    Export::CObject* pObj = new Export::CObject(m_pBaseObj->GetName().toUtf8().data());
-
-    ProcessEntityAnimationTrack(entityId, pObj, AnimParamType::Position);
-    ProcessEntityAnimationTrack(entityId, pObj, AnimParamType::Rotation);
-}
-
-bool CExportManager::AddObject(CBaseObject* pBaseObj)
-{
-    if (m_isOccluder)
-    {
-        return false;
-    }
-
-    m_pBaseObj = pBaseObj;
-
-    if (m_bAnimationExport)
-    {
-        if (pBaseObj->GetType() == OBJTYPE_AZENTITY)
-        {
-            const auto componentEntityObject = static_cast<CComponentEntityObject*>(pBaseObj);
-            AddEntityAnimationData(componentEntityObject->GetAssociatedEntityId());
-            return true;
-        }
-    }
-
-    if (m_isPrecaching)
-    {
-        return true;
-    }
-
-    Export::CObject* pObj = new Export::CObject(m_pBaseObj->GetName().toUtf8().data());
-
-    AddPosRotScale(pObj, pBaseObj);
-    m_data.m_objects.push_back(pObj);
-
-    m_objectMap[pBaseObj] = int(m_data.m_objects.size() - 1);
-
-    m_pBaseObj = nullptr;
-
-    return true;
-}
-
-
-void CExportManager::AddPosRotScale(Export::CObject* pObj, const CBaseObject* pBaseObj)
-{
-    Vec3 pos = pBaseObj->GetPos();
-    pObj->pos.x = pos.x * m_fScale;
-    pObj->pos.y = pos.y * m_fScale;
-    pObj->pos.z = pos.z * m_fScale;
-
-    Quat rot = pBaseObj->GetRotation();
-    pObj->rot.v.x = rot.v.x;
-    pObj->rot.v.y = rot.v.y;
-    pObj->rot.v.z = rot.v.z;
-    pObj->rot.w = rot.w;
-
-    Vec3 scale = pBaseObj->GetScale();
-    pObj->scale.x = scale.x;
-    pObj->scale.y = scale.y;
-    pObj->scale.z = scale.z;
-}
-
-void CExportManager::AddEntityData(Export::CObject* pObj, Export::AnimParamType dataType, const float fValue, const float fTime)
-{
-    Export::EntityAnimData entityData;
-    entityData.dataType = dataType;
-    entityData.leftTangent = kTangentDelta;
-    entityData.rightTangent = kTangentDelta;
-    entityData.rightTangentWeight = 0.0f;
-    entityData.leftTangentWeight = 0.0f;
-    entityData.keyValue = fValue;
-    entityData.keyTime = fTime;
-    pObj->m_entityAnimData.push_back(entityData);
-}
-
-
-void CExportManager::SolveHierarchy()
-{
-    for (TObjectMap::iterator it = m_objectMap.begin(); it != m_objectMap.end(); ++it)
-    {
-        CBaseObject* pObj = it->first;
-        int index = it->second;
-        if (pObj && pObj->GetParent())
-        {
-            CBaseObject* pParent = pObj->GetParent();
-            TObjectMap::iterator itFind = m_objectMap.find(pParent);
-            if (itFind != m_objectMap.end())
-            {
-                int indexOfParent = itFind->second;
-                if (indexOfParent >= 0 && index >= 0)
-                {
-                    m_data.m_objects[index]->nParent = indexOfParent;
-                }
-            }
-        }
-    }
-
-    m_objectMap.clear();
-}
-
-bool CExportManager::ShowFBXExportDialog()
-{
-    CTrackViewSequence* pSequence = GetIEditor()->GetAnimation()->GetSequence();
-
-    if (!pSequence)
-    {
-        return false;
-    }
-
-    CFBXExporterDialog fpsDialog;
-
-    CTrackViewNode* pivotObjectNode = pSequence->GetFirstSelectedNode();
-
-    if (pivotObjectNode && !pivotObjectNode->IsGroupNode())
-    {
-        m_pivotEntityObject = static_cast<CEntityObject*>(GetIEditor()->GetObjectManager()->FindObject(pivotObjectNode->GetName().c_str()));
-
-        if (m_pivotEntityObject)
-        {
-            fpsDialog.SetExportLocalCoordsCheckBoxEnable(true);
-        }
-    }
-
-    if (fpsDialog.exec() != QDialog::Accepted)
-    {
-        return false;
-    }
-
-    SetFBXExportSettings(fpsDialog.GetExportCoordsLocalToTheSelectedObject(), fpsDialog.GetExportOnlyPrimaryCamera(), fpsDialog.GetFPS());
-
-    return true;
-}
-
-bool CExportManager::ProcessObjectsForExport()
-{
-    Export::CObject* pObj = new Export::CObject(kPrimaryCameraName.toUtf8().data());
-    pObj->entityType = Export::eCamera;
-    m_data.m_objects.push_back(pObj);
-
-    float fpsTimeInterval = 1.0f / m_FBXBakedExportFPS;
-    float timeValue = 0.0f;
-
-    GetIEditor()->GetAnimation()->SetRecording(false);
-    GetIEditor()->GetAnimation()->SetPlaying(false);
-
-    int startFrame = 0;
-    timeValue = startFrame * fpsTimeInterval;
-
-    for (int frameID = startFrame; frameID <= m_numberOfExportFrames; ++frameID)
-    {
-        GetIEditor()->GetAnimation()->SetTime(timeValue);
-
-        for (size_t objectID = 0; objectID < m_data.m_objects.size(); ++objectID)
-        {
-            Export::CObject* pObj2 =  m_data.m_objects[objectID];
-            CBaseObject* pObject = nullptr;
-
-            if (QString::compare(pObj2->name, kPrimaryCameraName) == 0)
-            {
-                pObject = GetIEditor()->GetObjectManager()->FindObject(GetIEditor()->GetViewManager()->GetCameraObjectId());
-            }
-            else
-            {
-                if (m_bExportOnlyPrimaryCamera && pObj2->entityType != Export::eCameraTarget)
-                {
-                    continue;
-                }
-
-                pObject = pObj2->GetLastObjectPtr();
-            }
-
-            if (!pObject)
-            {
-                continue;
-            }
-
-            Quat rotation(pObject->GetRotation());
-
-            if (pObject->GetParent())
-            {
-                CBaseObject* pParentObject = pObject->GetParent();
-                Quat parentWorldRotation;
-
-                const Vec3& parentScale = pParentObject->GetScale();
-                float threshold = 0.0003f;
-
-                bool bParentScaled = false;
-
-                if ((fabsf(parentScale.x - 1.0f) + fabsf(parentScale.y - 1.0f) + fabsf(parentScale.z - 1.0f)) >= threshold)
-                {
-                    bParentScaled = true;
-                }
-
-                if (bParentScaled)
-                {
-                    Matrix34 tm = pParentObject->GetWorldTM();
-                    tm.OrthonormalizeFast();
-                    parentWorldRotation = Quat(tm);
-                }
-                else
-                {
-                    parentWorldRotation = Quat(pParentObject->GetWorldTM());
-                }
-
-                rotation = parentWorldRotation * rotation;
-            }
-
-            Vec3 objectPos = pObject->GetWorldPos();
-
-            if (m_bExportLocalCoords && m_pivotEntityObject && m_pivotEntityObject != pObject)
-            {
-                Matrix34 currentObjectTM = pObject->GetWorldTM();
-                Matrix34 invParentTM = m_pivotEntityObject->GetWorldTM();
-                invParentTM.Invert();
-                currentObjectTM = invParentTM * currentObjectTM;
-
-                objectPos = currentObjectTM.GetTranslation();
-                rotation = Quat(currentObjectTM);
-            }
-
-            Ang3 worldAngles = RAD2DEG(Ang3::GetAnglesXYZ(Matrix33(rotation)));
-
-            Export::EntityAnimData entityData;
-            entityData.keyTime = timeValue;
-            entityData.leftTangentWeight = 0.0f;
-            entityData.rightTangentWeight = 0.0f;
-            entityData.leftTangent = 0.0f;
-            entityData.rightTangent = 0.0f;
-
-            entityData.keyValue = objectPos.x;
-            entityData.keyValue *= 100.0f;
-            entityData.dataType = (Export::AnimParamType)AnimParamType::PositionX;
-            pObj2->m_entityAnimData.push_back(entityData);
-
-            entityData.keyValue = objectPos.y;
-            entityData.keyValue *= 100.0f;
-            entityData.dataType = (Export::AnimParamType)AnimParamType::PositionY;
-            pObj2->m_entityAnimData.push_back(entityData);
-
-            entityData.keyValue = objectPos.z;
-            entityData.keyValue *= 100.0f;
-            entityData.dataType = (Export::AnimParamType)AnimParamType::PositionZ;
-            pObj2->m_entityAnimData.push_back(entityData);
-
-            entityData.keyValue = worldAngles.x;
-            entityData.dataType = (Export::AnimParamType)AnimParamType::RotationX;
-            pObj2->m_entityAnimData.push_back(entityData);
-
-            entityData.keyValue = worldAngles.y;
-            entityData.dataType = (Export::AnimParamType)AnimParamType::RotationY;
-            pObj2->m_entityAnimData.push_back(entityData);
-
-            entityData.dataType = (Export::AnimParamType)AnimParamType::RotationZ;
-            entityData.keyValue = worldAngles.z;
-            pObj2->m_entityAnimData.push_back(entityData);
-        }
-
-        timeValue += fpsTimeInterval;
-    }
-
-    return true;
-}
-
-bool CExportManager::IsDuplicateObjectBeingAdded(const QString& newObjectName)
-{
-    for (int objectID = 0; objectID < m_data.m_objects.size(); ++objectID)
-    {
-        if (newObjectName.compare(m_data.m_objects[objectID]->name, Qt::CaseInsensitive) == 0)
-        {
-            return true;
-        }
-    }
-
-    return false;
-}
-
-QString CExportManager::CleanXMLText(const QString& text)
-{
-    QString outText(text);
-    outText.replace("\\", "_");
-    outText.replace("/", "_");
-    outText.replace(" ", "_");
-    outText.replace(":", "-");
-    outText.replace(";", "-");
-    return outText;
-}
-
-void CExportManager::FillAnimTimeNode(XmlNodeRef writeNode, CTrackViewAnimNode* pObjectNode, [[maybe_unused]] CTrackViewSequence* currentSequence)
-{
-    if (!writeNode)
-    {
-        return;
-    }
-
-    CTrackViewTrackBundle allTracks = pObjectNode->GetAllTracks();
-    const unsigned int numAllTracks = allTracks.GetCount();
-    bool bCreatedSubNodes = false;
-
-    if (numAllTracks > 0)
-    {
-        XmlNodeRef objNode = writeNode->createNode(CleanXMLText(pObjectNode->GetName().c_str()).toUtf8().data());
-        writeNode->setAttr("time", m_animTimeExportPrimarySequenceCurrentTime);
-
-        for (unsigned int trackID = 0; trackID < numAllTracks; ++trackID)
-        {
-            CTrackViewTrack* childTrack = allTracks.GetTrack(trackID);
-
-            AnimParamType trackType = childTrack->GetParameterType().GetType();
-
-            if (trackType == AnimParamType::Animation || trackType == AnimParamType::Sound)
-            {
-                QString childName = CleanXMLText(childTrack->GetName().c_str());
-
-                if (childName.isEmpty())
-                {
-                    continue;
-                }
-
-                XmlNodeRef subNode = objNode->createNode(childName.toUtf8().data());
-                CTrackViewKeyBundle keyBundle = childTrack->GetAllKeys();
-                uint keysNumber = keyBundle.GetKeyCount();
-
-                for (uint keyID = 0; keyID < keysNumber; ++keyID)
-                {
-                    const CTrackViewKeyHandle& keyHandle = keyBundle.GetKey(keyID);
-
-                    QString keyContentName;
-                    float keyStartTime = 0.0f;
-                    float keyEndTime = 0.0f;
-                    ;
-                    float keyTime = 0.0f;
-                    float keyDuration = 0.0f;
-
-                    if (trackType == AnimParamType::Animation)
-                    {
-                        if (!m_animKeyTimeExport)
-                        {
-                            continue;
-                        }
-
-                        ICharacterKey animationKey;
-                        keyHandle.GetKey(&animationKey);
-                        keyStartTime = animationKey.m_startTime;
-                        keyEndTime = animationKey.m_endTime;
-                        keyTime = animationKey.time;
-                        keyContentName = CleanXMLText(animationKey.m_animation.c_str());
-                        keyDuration = animationKey.GetActualDuration();
-                    }
-                    else if (trackType == AnimParamType::Sound)
-                    {
-                        if (!m_soundKeyTimeExport)
-                        {
-                            continue;
-                        }
-
-                        ISoundKey soundKey;
-                        keyHandle.GetKey(&soundKey);
-                        keyTime = soundKey.time;
-                        keyContentName = CleanXMLText(soundKey.sStartTrigger.c_str());
-                        keyDuration = soundKey.fDuration;
-                    }
-
-                    if (keyContentName.isEmpty())
-                    {
-                        continue;
-                    }
-
-                    XmlNodeRef keyNode = subNode->createNode(keyContentName.toUtf8().data());
-
-                    float keyGlobalTime = m_animTimeExportPrimarySequenceCurrentTime + keyTime;
-                    keyNode->setAttr("keyTime", keyGlobalTime);
-
-                    if (keyStartTime > 0)
-                    {
-                        keyNode->setAttr("startTime", keyStartTime);
-                    }
-
-                    if (keyEndTime > 0)
-                    {
-                        keyNode->setAttr("endTime", keyEndTime);
-                    }
-
-                    if (keyDuration > 0)
-                    {
-                        keyNode->setAttr("duration", keyDuration);
-                    }
-
-                    subNode->addChild(keyNode);
-                    objNode->addChild(subNode);
-                    bCreatedSubNodes = true;
-                }
-            }
-        }
-
-        if (bCreatedSubNodes)
-        {
-            writeNode->addChild(objNode);
-        }
-    }
-}
-
-
-bool CExportManager::AddObjectsFromSequence(CTrackViewSequence* pSequence, XmlNodeRef seqNode)
-{
-    CTrackViewAnimNodeBundle allNodes = pSequence->GetAllAnimNodes();
-    const unsigned int numAllNodes = allNodes.GetCount();
-
-    for (unsigned int nodeID = 0; nodeID < numAllNodes; ++nodeID)
-    {
-        CTrackViewAnimNode* pAnimNode = allNodes.GetNode(nodeID);
-
-        if (seqNode && pAnimNode)
-        {
-            FillAnimTimeNode(seqNode, pAnimNode, pSequence);
-        }
-
-        AZ::Entity* entity = nullptr;
-        AZ::ComponentApplicationBus::BroadcastResult(
-            entity, &AZ::ComponentApplicationBus::Events::FindEntity, pAnimNode->GetAzEntityId());
-
-        if (entity)
-        {
-            QString addObjectName = entity->GetName().c_str();
-
-            if (IsDuplicateObjectBeingAdded(addObjectName))
-            {
-                continue;
-            }
-
-            Export::CObject* pObj = new Export::CObject(entity->GetName().c_str());
-
-            pObj->m_entityAnimData.reserve(m_numberOfExportFrames * kReserveCount);
-            m_data.m_objects.push_back(pObj);
-        }
-    }
-
-    CTrackViewTrackBundle trackBundle = pSequence->GetTracksByParam(AnimParamType::Sequence);
-
-    const uint numSequenceTracks = trackBundle.GetCount();
-    for (uint i = 0; i < numSequenceTracks; ++i)
-    {
-        CTrackViewTrack* pSequenceTrack = trackBundle.GetTrack(i);
-        if (pSequenceTrack->IsDisabled())
-        {
-            continue;
-        }
-
-        const uint numKeys = pSequenceTrack->GetKeyCount();
-        for (uint keyIndex = 0; keyIndex < numKeys; ++keyIndex)
-        {
-            const CTrackViewKeyHandle& keyHandle = pSequenceTrack->GetKey(keyIndex);
-            ISequenceKey sequenceKey;
-            keyHandle.GetKey(&sequenceKey);
-
-            CTrackViewSequence* pSubSequence = CDirectorNodeAnimator::GetSequenceFromSequenceKey(sequenceKey);
-
-            if (pSubSequence)
-            {
-                if (pSubSequence && !pSubSequence->IsDisabled())
-                {
-                    XmlNodeRef subSeqNode = nullptr;
-
-                    if (!seqNode)
-                    {
-                        AddObjectsFromSequence(pSubSequence);
-                    }
-                    else
-                    {
-                        // In case of exporting animation/sound times data
-                        const QString sequenceName = QString::fromUtf8(pSubSequence->GetName().c_str());
-                        XmlNodeRef subSeqNode2 = seqNode->createNode(sequenceName.toUtf8().data());
-
-                        if (sequenceName == m_animTimeExportPrimarySequenceName)
-                        {
-                            m_animTimeExportPrimarySequenceCurrentTime = sequenceKey.time;
-                        }
-                        else
-                        {
-                            m_animTimeExportPrimarySequenceCurrentTime += sequenceKey.time;
-                        }
-
-                        AddObjectsFromSequence(pSubSequence, subSeqNode2);
-                        seqNode->addChild(subSeqNode2);
-                    }
-                }
-            }
-        }
-    }
-
-    return false;
-}
-
-bool CExportManager::AddSelectedEntityObjects()
-{
-    CTrackViewSequence* pSequence = GetIEditor()->GetAnimation()->GetSequence();
-    if (!pSequence)
-    {
-        return false;
-    }
-
-    CTrackViewAnimNodeBundle selectedNodes = pSequence->GetSelectedAnimNodes();
-    const unsigned int numSelectedNodes = selectedNodes.GetCount();
-
-    for (unsigned int nodeNumber = 0; nodeNumber < numSelectedNodes; ++nodeNumber)
-    {
-        if (m_bAnimationExport)
-        {
-            CTrackViewAnimNode* pAnimNode = selectedNodes.GetNode(nodeNumber);
-            AddEntityAnimationData(pAnimNode->GetAzEntityId());
-        }
-    }
-
-    return true;
-}
-
-bool CExportManager::AddSelectedRegionObjects()
-{
-    AABB box;
-    GetIEditor()->GetSelectedRegion(box);
-    if (box.IsEmpty())
-    {
-        return false;
-    }
-
-    std::vector<CBaseObject*> objects;
-    GetIEditor()->GetObjectManager()->FindObjectsInAABB(box, objects);
-
-    const size_t numObjects = objects.size();
-    if (numObjects > m_data.m_objects.size())
-    {
-        m_data.m_objects.reserve(numObjects);
-    }
-    // First run pipeline to precache geometry
-    m_isPrecaching = true;
-    for (size_t i = 0; i < numObjects; ++i)
-    {
-        AddObject(objects[i]);
-    }
-
-    // Repeat pipeline to collect geometry
-    m_isPrecaching = false;
-    for (size_t i = 0; i < numObjects; ++i)
-    {
-        AddObject(objects[i]);
-    }
-
-    return true;
-}
-
-bool CExportManager::ExportToFile(const char* filename, bool bClearDataAfterExport)
-{
-    bool bRet = false;
-    QString ext = PathUtil::GetExt(filename);
-
-    if (m_data.GetObjectCount() == 0)
-    {
-        QMessageBox::warning(QApplication::activeWindow(), QString(), QObject::tr("Track View selection does not exist as an object."));
-        return false;
-    }
-
-    for (int i = 0; i < m_exporters.size(); ++i)
-    {
-        IExporter* pExporter = m_exporters[i];
-        if (!QString::compare(ext, pExporter->GetExtension(), Qt::CaseInsensitive))
-        {
-            bRet = pExporter->ExportToFile(filename, &m_data);
-            break;
-        }
-    }
-
-    if (bClearDataAfterExport)
-    {
-        m_data.Clear();
-    }
-    return bRet;
-}
-
-
-bool CExportManager::Export(const char* defaultName, const char* defaultExt, const char* defaultPath, [[maybe_unused]] bool isSelectedObjects, bool isSelectedRegionObjects, bool isOccluder, bool bAnimationExport)
-{
-    m_bAnimationExport = bAnimationExport;
-
-    m_isOccluder    =   isOccluder;
-    const float OldScale    =   m_fScale;
-    if (isOccluder)
-    {
-        m_fScale    =   1.f;
-    }
-
-    m_data.Clear();
-    m_objectMap.clear();
-
-    QString filters;
-    for (TExporters::iterator ppExporter = m_exporters.begin(); ppExporter != m_exporters.end(); ++ppExporter)
-    {
-        IExporter* pExporter = (*ppExporter);
-        if (pExporter)
-        {
-            const QString ext = pExporter->GetExtension();
-            const QString newFilter = QString("%1 (*.%2)").arg(pExporter->GetShortDescription(), ext);
-            if (filters.isEmpty())
-            {
-                filters = newFilter;
-            }
-            else if (ext == defaultExt) // the default extension should be the first item in the list, so it's the default export options
-            {
-                filters = newFilter + ";;" + filters;
-            }
-            else
-            {
-                filters = filters + ";;" + newFilter;
-            }
-        }
-    }
-    filters += ";;All files (*)";
-
-    bool returnRes = false;
-
-    QString newFilename(defaultName);
-    if (m_bAnimationExport || CFileUtil::SelectSaveFile(filters, defaultExt, defaultPath, newFilename))
-    {
-        WaitCursor wait;
-        if (isSelectedRegionObjects)
-        {
-            AddSelectedRegionObjects();
-        }
-
-        if (!bAnimationExport)
-        {
-            SolveHierarchy();
-        }
-
-        if (m_bAnimationExport)
-        {
-            CTrackViewSequence* pSequence = GetIEditor()->GetAnimation()->GetSequence();
-
-            if (pSequence)
-            {
-                // Save To FBX custom selected nodes
-                if (!m_bBakedKeysSequenceExport)
-                {
-                    returnRes = AddSelectedEntityObjects();
-                }
-                else
-                {
-                    // Export the whole sequence with baked keys
-                    if (ShowFBXExportDialog())
-                    {
-                        m_numberOfExportFrames = static_cast<int>(pSequence->GetTimeRange().end * m_FBXBakedExportFPS);
-
-                        if (!m_bExportOnlyPrimaryCamera)
-                        {
-                            AddObjectsFromSequence(pSequence);
-                        }
-
-                        returnRes = ProcessObjectsForExport();
-                        SolveHierarchy();
-                    }
-                }
-            }
-
-            if (returnRes)
-            {
-                returnRes = ExportToFile(defaultName);
-            }
-        }
-        else
-        {
-            returnRes = ExportToFile(newFilename.toStdString().c_str());
-        }
-    }
-
-    m_fScale = OldScale;
-    m_bBakedKeysSequenceExport = true;
-    m_FBXBakedExportFPS = 0.0f;
-
-    return returnRes;
-}
-
-void CExportManager::SetFBXExportSettings(bool bLocalCoordsToSelectedObject, bool bExportOnlyPrimaryCamera, const float fps)
-{
-    m_bExportLocalCoords = bLocalCoordsToSelectedObject;
-    m_bExportOnlyPrimaryCamera = bExportOnlyPrimaryCamera;
-    m_FBXBakedExportFPS = fps;
-}
-
-Export::Object* Export::CData::AddObject(const char* objectName)
-{
-    for (size_t objectID = 0; objectID < m_objects.size(); ++objectID)
-    {
-        if (strcmp(m_objects[objectID]->name, objectName) == 0)
-        {
-            return m_objects[objectID];
-        }
-    }
-
-    CObject* pObj = new CObject(objectName);
-    m_objects.push_back(pObj);
-    return m_objects[m_objects.size() - 1];
-}
-
-bool CExportManager::ImportFromFile(const char* filename)
-{
-    bool bRet = false;
-    QString ext = PathUtil::GetExt(filename);
-
-    m_data.Clear();
-
-    for (size_t handlerID = 0; handlerID < m_exporters.size(); ++handlerID)
-    {
-        IExporter* pExporter = m_exporters[handlerID];
-        if (!QString::compare(ext, pExporter->GetExtension(), Qt::CaseInsensitive))
-        {
-            bRet = pExporter->ImportFromFile(filename, &m_data);
-            break;
-        }
-    }
-
-    return bRet;
-}
-
-void CExportManager::SaveNodeKeysTimeToXML()
-{
-    CTrackViewSequence* pSequence = GetIEditor()->GetAnimation()->GetSequence();
-    if (!pSequence)
-    {
-        return;
-    }
-
-    CTrackViewExportKeyTimeDlg exportDialog;
-
-    if (exportDialog.exec() == QDialog::Accepted)
-    {
-        m_animKeyTimeExport = exportDialog.IsAnimationExportChecked();
-        m_soundKeyTimeExport = exportDialog.IsSoundExportChecked();
-
-        QString filters = "All files (*.xml)";
-        QString defaultName = QString::fromUtf8(pSequence->GetName().c_str()) + ".xml";
-
-        QtUtil::QtMFCScopedHWNDCapture cap;
-        CAutoDirectoryRestoreFileDialog dlg(QFileDialog::AcceptSave, QFileDialog::AnyFile, "xml", defaultName, filters, {}, {}, cap);
-        if (dlg.exec())
-        {
-            m_animTimeNode = XmlHelpers::CreateXmlNode(pSequence->GetName().c_str());
-            m_animTimeExportPrimarySequenceName = QString::fromUtf8(pSequence->GetName().c_str());
-
-            m_data.Clear();
-            m_animTimeExportPrimarySequenceCurrentTime = 0.0;
-
-            AddObjectsFromSequence(pSequence, m_animTimeNode);
-
-            m_animTimeNode->saveToFile(dlg.selectedFiles().constFirst().toStdString().c_str());
-            QMessageBox::information(QApplication::activeWindow(), QString(), QObject::tr("Export Finished"));
-        }
-    }
-}

+ 0 - 195
Code/Editor/Export/ExportManager.h

@@ -1,195 +0,0 @@
-/*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
-
-
-// Description : Manager for export geometry
-
-
-#ifndef CRYINCLUDE_EDITOR_EXPORT_EXPORTMANAGER_H
-#define CRYINCLUDE_EDITOR_EXPORT_EXPORTMANAGER_H
-#pragma once
-
-
-#include <AzCore/Component/EntityId.h>
-#include <IExportManager.h>
-
-class CExportManager;
-class CTrackViewTrack;
-class CTrackViewSequence;
-class CTrackViewAnimNode;
-class CEntityObject;
-
-typedef std::vector<IExporter*> TExporters;
-
-
-namespace Export
-{
-    class CMesh
-        : public Mesh
-        , public CRefCountBase
-    {
-    public:
-        CMesh();
-
-        int GetFaceCount() const override { return static_cast<int>(m_faces.size()); }
-        const Face* GetFaceBuffer() const override { return !m_faces.empty() ? &m_faces[0] : nullptr; }
-
-    private:
-        std::vector<Face> m_faces;
-
-        friend CExportManager;
-    };
-
-
-    class CObject
-        : public Object
-        , public CRefCountBase
-    {
-    public:
-        CObject(const char* pName);
-
-        int GetVertexCount() const override { return static_cast<int>(m_vertices.size()); }
-        const Vector3D* GetVertexBuffer() const override { return !m_vertices.empty() ? &m_vertices[0] : nullptr; }
-
-        int GetNormalCount() const override { return static_cast<int>(m_normals.size()); }
-        const Vector3D* GetNormalBuffer() const override { return !m_normals.empty() ? &m_normals[0] : nullptr; }
-
-        int GetTexCoordCount() const override { return static_cast<int>(m_texCoords.size()); }
-        const UV* GetTexCoordBuffer() const override { return !m_texCoords.empty() ? &m_texCoords[0] : nullptr; }
-
-        int GetMeshCount() const override { return static_cast<int>(m_meshes.size()); }
-        Mesh* GetMesh(int index) const override { return m_meshes[index]; }
-
-        size_t  MeshHash() const override{return m_MeshHash; }
-
-        void SetMaterialName(const char* pName);
-        int GetEntityAnimationDataCount() const override {return static_cast<int>(m_entityAnimData.size()); }
-        const EntityAnimData* GetEntityAnimationData(int index) const override {return &m_entityAnimData[index]; }
-        void SetEntityAnimationData(EntityAnimData entityData) override{ m_entityAnimData.push_back(entityData); };
-        void SetLastPtr(CBaseObject* pObject){m_pLastObject = pObject; };
-        CBaseObject* GetLastObjectPtr(){return m_pLastObject; };
-
-    private:
-        CBaseObject* m_pLastObject;
-        std::vector<Vector3D> m_vertices;
-        std::vector<Vector3D> m_normals;
-        std::vector<UV> m_texCoords;
-        std::vector< _smart_ptr<CMesh> > m_meshes;
-        std::vector<EntityAnimData> m_entityAnimData;
-
-        size_t  m_MeshHash;
-
-        friend CExportManager;
-    };
-
-
-    class CData
-        : public IData
-    {
-    public:
-        virtual ~CData() = default;
-
-        int GetObjectCount() const override { return static_cast<int>(m_objects.size()); }
-        Object* GetObject(int index) const override { return m_objects[index]; }
-        Object* AddObject(const char* objectName) override;
-        void Clear();
-
-    private:
-        std::vector< _smart_ptr<Export::CObject> > m_objects;
-
-        friend CExportManager;
-    };
-}
-
-
-typedef std::map<CBaseObject*, int> TObjectMap;
-
-class CExportManager
-    : public IExportManager
-{
-public:
-
-    CExportManager();
-    virtual ~CExportManager();
-
-    //! Register exporter
-    //! return true if succeed, otherwise false
-    bool RegisterExporter(IExporter* pExporter) override;
-
-    //! Export specified geometry
-    //! return true if succeed, otherwise false
-    bool Export(const char* defaultName, const char* defaultExt = "", const char* defaultPath = "", bool isSelectedObjects = true,
-        bool isSelectedRegionObjects = false, bool isOccluder = false, bool bAnimationExport = false);
-
-    bool AddSelectedEntityObjects();
-
-    //! Add to Export Data geometry from objects inside selected region volume
-    //! return true if succeed, otherwise false
-    bool AddSelectedRegionObjects();
-
-    //! Export to file collected data, using specified exporter throughout the file extension
-    //! return true if succeed, otherwise false
-    bool ExportToFile(const char* filename, bool bClearDataAfterExport = true);
-
-    bool ImportFromFile(const char* filename);
-    const Export::CData& GetData() const {return m_data; };
-
-    void SetBakedKeysSequenceExport(bool bBaked){m_bBakedKeysSequenceExport = bBaked; };
-
-    void SaveNodeKeysTimeToXML();
-
-private:
-    bool AddObject(CBaseObject* pBaseObj);
-    void SolveHierarchy();
-
-    void AddEntityAnimationData(AZ::EntityId entityId);
-    void ProcessEntityAnimationTrack(AZ::EntityId entityId, Export::CObject* pObj, AnimParamType entityTrackParamType);
-    void AddEntityAnimationData(const CTrackViewTrack* pTrack, Export::CObject* pObj, AnimParamType entityTrackParamType);
-
-    void AddPosRotScale(Export::CObject* pObj, const CBaseObject* pBaseObj);
-    void AddEntityData(Export::CObject* pObj, Export::AnimParamType dataType, const float fValue, const float fTime);
-
-    bool AddObjectsFromSequence(CTrackViewSequence* pSequence, XmlNodeRef seqNode = 0);
-    bool IsDuplicateObjectBeingAdded(const QString& newObject);
-    void SetFBXExportSettings(bool bLocalCoordsToSelectedObject, bool bExportOnlyPrimaryCamera, const float fps);
-    bool ProcessObjectsForExport();
-
-    bool ShowFBXExportDialog();
-
-    void FillAnimTimeNode(XmlNodeRef writeNode, CTrackViewAnimNode* pObjectNode, CTrackViewSequence* currentSequence);
-    QString CleanXMLText(const QString& text);
-
-private:
-
-    TExporters m_exporters;
-    Export::CData m_data;
-    bool m_isPrecaching;
-    bool m_isOccluder;
-    float m_fScale;
-    TObjectMap m_objectMap;
-    bool m_bAnimationExport;
-
-    CBaseObject* m_pBaseObj;
-
-    float m_FBXBakedExportFPS;
-    bool m_bExportLocalCoords;
-    bool m_bExportOnlyPrimaryCamera;
-    int m_numberOfExportFrames;
-    CEntityObject* m_pivotEntityObject;
-    bool m_bBakedKeysSequenceExport;
-
-    QString m_animTimeExportPrimarySequenceName;
-    float m_animTimeExportPrimarySequenceCurrentTime;
-    XmlNodeRef m_animTimeNode;
-
-    bool m_animKeyTimeExport;
-    bool m_soundKeyTimeExport;
-};
-
-
-#endif // CRYINCLUDE_EDITOR_EXPORT_EXPORTMANAGER_H

+ 0 - 284
Code/Editor/Export/OBJExporter.cpp

@@ -1,284 +0,0 @@
-/*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
-
-
-#include "EditorDefs.h"
-
-#include "OBJExporter.h"
-
-
-const char* COBJExporter::GetExtension() const
-{
-    return "obj";
-}
-
-
-const char* COBJExporter::GetShortDescription() const
-{
-    return "Object files";
-}
-
-
-bool COBJExporter::ExportToFile(const char* filename, const Export::IData* pExportData)
-{
-    CLogFile::FormatLine("Exporting OBJ file to '%s'", filename);
-
-    FILE* hFile = nullptr;
-    azfopen(&hFile, filename, "w");
-    if (!hFile)
-    {
-        CLogFile::FormatLine("Error while opening file '%s'!", filename);
-        assert(hFile);
-        return false;
-    }
-
-    // Write header
-    fprintf(hFile, "# Object file exported by Sandbox\n");
-    fprintf(hFile, "# Attention: while import to 3DS Max Unify checkbox for normals must be unchecked.\n");
-    fprintf(hFile, "#\n");
-
-    // Create MTL library filename
-    QString materialFilename = filename;
-    materialFilename = Path::ReplaceExtension(materialFilename, "mtl");
-    materialFilename = Path::GetFile(materialFilename);
-
-    // Write material library import statement
-    fprintf(hFile, "mtllib %s\n", materialFilename.toUtf8().data());
-    fprintf(hFile, "#\n");
-
-    int numObjects = pExportData->GetObjectCount();
-    for (int i = 0; i < numObjects; ++i)
-    {
-        const Export::Object* pObj = pExportData->GetObject(i);
-
-        Vec3 pos(pObj->pos.x, pObj->pos.y, pObj->pos.z);
-        Quat rot(pObj->rot.w, pObj->rot.v.x, pObj->rot.v.y, pObj->rot.v.z);
-        Vec3 scale(pObj->scale.x, pObj->scale.y, pObj->scale.z);
-
-        Matrix34 tm = Matrix33::CreateScale(scale) * Matrix34(rot);
-        tm.SetTranslation(pos);
-
-        int nParent = pObj->nParent;
-        while (nParent >= 0 && nParent < pExportData->GetObjectCount())
-        {
-            const Export::Object* pParentObj = pExportData->GetObject(nParent);
-            assert(nullptr != pParentObj);
-
-            Vec3 pos2(pParentObj->pos.x, pParentObj->pos.y, pParentObj->pos.z);
-            Quat rot2(pParentObj->rot.w, pParentObj->rot.v.x, pParentObj->rot.v.y, pParentObj->rot.v.z);
-            Vec3 scale2(pParentObj->scale.x, pParentObj->scale.y, pParentObj->scale.z);
-
-            Matrix34 parentTm = Matrix33::CreateScale(scale2) * Matrix34(rot2);
-            parentTm.SetScale(scale2);
-            parentTm.SetTranslation(pos2);
-
-            tm = tm * parentTm;
-            nParent = pParentObj->nParent;
-        }
-
-        fprintf(hFile, "g %s\n", pObj->name); //For XSI
-        fprintf(hFile, "# object %s\n", pObj->name);
-        fprintf(hFile, "#\n");
-
-        int numVertices = pObj->GetVertexCount();
-        const Export::Vector3D* pVerts = pObj->GetVertexBuffer();
-
-        for (int ii = 0; ii < numVertices; ++ii)
-        {
-            const Export::Vector3D& vertex = pVerts[ii];
-
-            Vec3 vec(vertex.x, vertex.y, vertex.z);
-            vec = tm.TransformPoint(vec);
-
-            fprintf(hFile, "v %s %s %s\n", TrimFloat(vec.x), TrimFloat(vec.y), TrimFloat(vec.z));
-        }
-        fprintf(hFile, "# %i vertices\n\n", numVertices);
-
-        // Write object texture coordinates
-        int numTexCoords = pObj->GetTexCoordCount();
-        const Export::UV* pTexCoord = pObj->GetTexCoordBuffer();
-        for (int ii = 0; ii < numTexCoords; ++ii)
-        {
-            const Export::UV& textCoord = pTexCoord[ii];
-            fprintf(hFile, "vt %s %s 0\n", TrimFloat(textCoord.u), TrimFloat(textCoord.v));
-        }
-        fprintf(hFile, "# %i texture vertices\n\n", numTexCoords);
-
-        // Write object normals
-        int numNormals = pObj->GetNormalCount();
-        const Export::Vector3D* pNormals = pObj->GetNormalBuffer();
-        for (int ii = 0; ii < numNormals; ++ii)
-        {
-            const Export::Vector3D& normal = pNormals[ii];
-            fprintf(hFile, "vn %s %s %s\n", TrimFloat(normal.x), TrimFloat(normal.y), TrimFloat(normal.z));
-        }
-        fprintf(hFile, "# %i vertex normals\n\n", numNormals);
-
-
-        // Write submeshes
-        int numMeshes = pObj->GetMeshCount();
-        for (int j = 0; j < numMeshes; ++j)
-        {
-            const Export::Mesh* pMesh = pObj->GetMesh(j);
-            if (pMesh->material.name[0] != '\0')
-            {
-                fprintf(hFile, "usemtl %s\n", pMesh->material.name);
-            }
-
-            // TODO: if it's a smoth group fix it to bit difference
-            fprintf(hFile, "s %d\n", j);
-
-            // Write all faces, convert the indices to one based indices
-            int numFaces = pMesh->GetFaceCount();
-            const Export::Face* pFaceBuf = pMesh->GetFaceBuffer();
-            for (int ii = 0; ii < numFaces; ++ii)
-            {
-                const Export::Face& face = pFaceBuf[ii];
-                fprintf(hFile, "f %i/%i/%i %i/%i/%i %i/%i/%i\n",
-                    face.idx[0] - numVertices, face.idx[0] - numTexCoords, face.idx[0] - numNormals,
-                    face.idx[1] - numVertices, face.idx[1] - numTexCoords, face.idx[1] - numNormals,
-                    face.idx[2] - numVertices, face.idx[2] - numTexCoords, face.idx[2] - numNormals);
-            }
-            fprintf(hFile, "# %i faces\n\n", numFaces);
-        }
-    }
-
-    fprintf(hFile, "g\n");
-    fclose(hFile);
-
-
-    // Export Material
-    materialFilename = Path::ReplaceExtension(filename, "mtl");
-
-    // Open the material file
-    hFile = nullptr;
-    azfopen(&hFile, materialFilename.toUtf8().data(), "w");
-    if (!hFile)
-    {
-        CLogFile::FormatLine("Error while opening file '%s'!", materialFilename.toUtf8().data());
-        assert(hFile);
-        return false;
-    }
-
-    // Write header
-    fprintf(hFile, "# Material file exported by Sandbox\n\n");
-
-    for (int i = 0; i < numObjects; ++i)
-    {
-        const Export::Object* pObj = pExportData->GetObject(i);
-
-        int numMeshes = pObj->GetMeshCount();
-        for (int j = 0; j < numMeshes; j++)
-        {
-            const Export::Mesh* pMesh = pObj->GetMesh(j);
-            if (pMesh->material.name[0] != '\0')
-            {
-                // Write material
-                const Export::Material& mtl = pMesh->material;
-                fprintf(hFile, "newmtl %s\n", mtl.name);
-                fprintf(hFile, "Ka %s %s %s\n", TrimFloat(mtl.diffuse.r), TrimFloat(mtl.diffuse.g), TrimFloat(mtl.diffuse.b));
-                fprintf(hFile, "Kd %s %s %s\n", TrimFloat(mtl.diffuse.r), TrimFloat(mtl.diffuse.g), TrimFloat(mtl.diffuse.b));
-                fprintf(hFile, "Ks %s %s %s\n", TrimFloat(mtl.specular.r), TrimFloat(mtl.specular.g), TrimFloat(mtl.specular.b));
-                fprintf(hFile, "d %s\n", TrimFloat(1.0f - mtl.opacity));
-                fprintf(hFile, "Tr %s\n", TrimFloat(1.0f - mtl.opacity));
-                fprintf(hFile, "Ns %s\n", TrimFloat(mtl.smoothness));
-                if (strlen(mtl.mapDiffuse))
-                {
-                    fprintf(hFile, "map_Kd %s\n", MakeRelativePath(filename, mtl.mapDiffuse).toUtf8().constData());
-                }
-                if (strlen(mtl.mapSpecular))
-                {
-                    fprintf(hFile, "map_Ns %s\n", MakeRelativePath(filename, mtl.mapSpecular).toUtf8().constData());
-                }
-                if (strlen(mtl.mapOpacity))
-                {
-                    fprintf(hFile, "map_d %s\n", MakeRelativePath(filename, mtl.mapOpacity).toUtf8().constData());
-                }
-                if (strlen(mtl.mapNormals))
-                {
-                    fprintf(hFile, "bump %s\n", MakeRelativePath(filename, mtl.mapNormals).toUtf8().constData());
-                }
-                if (strlen(mtl.mapDecal))
-                {
-                    fprintf(hFile, "decal %s\n", MakeRelativePath(filename, mtl.mapDecal).toUtf8().constData());
-                }
-                if (strlen(mtl.mapDisplacement))
-                {
-                    fprintf(hFile, "disp %s\n", MakeRelativePath(filename, mtl.mapDisplacement).toUtf8().constData());
-                }
-                fprintf(hFile, "\n");
-            }
-        }
-    }
-
-    fclose(hFile);
-
-    return true;
-}
-
-
-QString COBJExporter::MakeRelativePath(const char* pMainFileName, const char* pFileName) const
-{
-    const char* ch = strrchr(pMainFileName, '\\');
-    if (ch)
-    {
-        if (strlen(pFileName) > static_cast<size_t>(ch - pMainFileName) && !_strnicmp(pMainFileName, pFileName, ch - pMainFileName))
-        {
-            return QString(pFileName + (ch - pMainFileName) + 1);
-        }
-    }
-
-    return QString(pFileName);
-}
-
-
-const char* COBJExporter::TrimFloat(float fValue) const
-{
-    // Convert a float into a string representation and remove all
-    // uneccessary zeroes and the decimal dot if it is not needed
-    const int nMaxAccessInTime = 4;
-
-    static char ppBufs[nMaxAccessInTime][16];
-    static int nCurBuf = 0;
-
-    if (nCurBuf >= nMaxAccessInTime)
-    {
-        nCurBuf = 0;
-    }
-
-    char* pBuf = ppBufs[nCurBuf];
-    size_t bufSize = sizeof(ppBufs[nCurBuf]);
-    ++nCurBuf;
-    sprintf_s(pBuf, bufSize, "%f", fValue);
-
-    for (int i = static_cast<int>(strlen(pBuf)) - 1; i > 0; --i)
-    {
-        if (pBuf[i] == '0')
-        {
-            pBuf[i] = 0;
-        }
-        else if (pBuf[i] == '.')
-        {
-            pBuf[i] = 0;
-            break;
-        }
-        else
-        {
-            break;
-        }
-    }
-
-    return pBuf;
-}
-
-
-
-void COBJExporter::Release()
-{
-    delete this;
-}

+ 0 - 37
Code/Editor/Export/OBJExporter.h

@@ -1,37 +0,0 @@
-/*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
-
-
-// Description : Export geometry OBJ file format
-
-
-#ifndef CRYINCLUDE_EDITOR_EXPORT_OBJEXPORTER_H
-#define CRYINCLUDE_EDITOR_EXPORT_OBJEXPORTER_H
-#pragma once
-
-
-#include <IExportManager.h>
-
-
-class COBJExporter
-    : public IExporter
-{
-public:
-    virtual const char* GetExtension() const;
-    virtual const char* GetShortDescription() const;
-    virtual bool ExportToFile(const char* filename, const Export::IData* pExportData);
-    virtual bool ImportFromFile([[maybe_unused]] const char* filename, [[maybe_unused]] Export::IData* pData){return false; };
-    virtual void Release();
-
-private:
-    const char* TrimFloat(float fValue) const;
-    QString MakeRelativePath(const char* pMainFileName, const char* pFileName) const;
-};
-
-
-#endif // CRYINCLUDE_EDITOR_EXPORT_OBJEXPORTER_H

+ 0 - 291
Code/Editor/Export/OCMExporter.cpp

@@ -1,291 +0,0 @@
-/*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
-
-
-#include "EditorDefs.h"
-
-#include "OCMExporter.h"
-
-
-class CFileEndianWriter
-{
-    std::vector<uint8>              m_Data;
-    size_t                                      m_Offset;
-public:
-    CFileEndianWriter()
-        : m_Offset(0){}
-    void                                            Seek(size_t Offset){m_Offset = Offset; }
-    void                                            Write(const void* pData, size_t Size)
-    {
-        if (m_Offset + Size > m_Data.size())
-        {
-            m_Data.resize(m_Offset + Size);
-        }
-        memcpy(&m_Data[m_Offset], pData, Size);
-        m_Offset += Size;
-    }
-    size_t                                      Pos() const{return m_Offset; }
-    template<size_t Padding>
-    void                                            Align(){m_Offset = (m_Offset + (Padding - 1)) & ~(Padding - 1); }
-    template<class T>
-    void                                            Write(T D){Write(&D, sizeof(T)); }
-
-
-    const std::vector<uint8>&   Data() const{return m_Data; }
-};
-
-const char* COCMExporter::GetExtension() const
-{
-    return "ocm";
-}
-
-const char* COCMExporter::GetShortDescription() const
-{
-    return "occlusion culler mesh";
-}
-
-Matrix44 MatRotate(f32 a, f32 b, f32 g)
-{
-    const f32   CosR    =   cosf(a);
-    const f32   SinR    =   sinf(a);
-    const f32   CosP    =   cosf(b);
-    const f32   SinP    =   sinf(b);
-    const f32   CosY    =   cosf(g);
-    const f32   SinY    =   sinf(g);
-    const f32   SRSP    =   SinR * SinP;
-    const f32   CRSP    =   CosR * SinP;
-
-    Matrix44 Mat;
-    Mat.m00 = CosP * CosY;
-    Mat.m01 = CosP * SinY;
-    Mat.m02 = -SinP;
-    Mat.m03 = 0.f;
-    Mat.m10 = SRSP * CosY - CosR * SinY;
-    Mat.m11 = SRSP * SinY + CosR * CosY;
-    Mat.m12 = SinR * CosP;
-    Mat.m13 = 0.f;
-    Mat.m20 = CRSP * CosY + SinR * SinY;
-    Mat.m21 = CRSP * SinY - SinR * CosY;
-    Mat.m22 = CosR * CosP;
-    Mat.m23 = 0.f;
-    Mat.m30 = 0.f;
-    Mat.m31 = 0.f;
-    Mat.m32 = 0.f;
-    Mat.m33 = 1.f;
-    return Mat;
-};
-
-void COCMExporter::Extends(const Matrix44& rTransform, const Export::Object* pMesh, f32& rMinX, f32& rMaxX, f32& rMinY, f32& rMaxY, f32& rMinZ, f32& rMaxZ)    const
-{
-    rMinX = rMinY = rMinZ   =   FLT_MAX;
-    rMaxX = rMaxY = rMaxZ   =   -FLT_MAX;
-    const uint32 TriCount   =   pMesh->GetVertexCount();
-    const Export::Vector3D* pVerts = pMesh->GetVertexBuffer();
-    for (size_t a = 0; a < TriCount; a++)
-    {
-        const Export::Vector3D& rV = pVerts[a];
-        const Vec3  VO(rV.x, rV.y, rV.z);
-        const Vec3  V   =   rTransform.TransformPoint(VO);
-        rMinX   =   std::min(rMinX, V.x);
-        rMinY   =   std::min(rMinY, V.y);
-        rMinZ   =   std::min(rMinZ, V.z);
-        rMaxX   =   std::max(rMaxX, V.x);
-        rMaxY   =   std::max(rMaxY, V.y);
-        rMaxZ   =   std::max(rMaxZ, V.z);
-    }
-}
-
-Matrix44 COCMExporter::CalcOBB(const Export::Object* pMesh)
-{
-    Matrix44 OBBMat(IDENTITY);
-    f32 MinX, MaxX, MinY, MaxY, MinZ, MaxZ;
-    Extends(OBBMat, pMesh, MinX, MaxX, MinY, MaxY, MinZ, MaxZ);
-
-    OBBMat.m03  =   -(MaxX + MinX) * 0.5f;
-    OBBMat.m13  =   -(MaxY + MinY) * 0.5f;
-    OBBMat.m23  =   -(MaxZ + MinZ) * 0.5f;
-
-    return OBBMat;
-}
-
-size_t COCMExporter::SaveMesh(CFileEndianWriter& rWriter, const Export::Object* pMesh, Matrix44& rOBBMat)
-{
-    const size_t PosStart   =   rWriter.Pos();
-    rOBBMat =   CalcOBB(pMesh);
-    const Export::Vector3D* pVerts = pMesh->GetVertexBuffer();
-    std::vector<float> Pos;
-    const uint32 SubMeshCount = pMesh->GetMeshCount();
-    for (uint32 b = 0; b < SubMeshCount; b++)
-    {
-        const Export::Mesh* pSubMesh = pMesh->GetMesh(b);
-        const uint32 FaceCount  = pSubMesh->GetFaceCount();
-        const Export::Face* pFaces = pSubMesh->GetFaceBuffer();
-        Pos.reserve(Pos.size() + FaceCount * 9);
-        for (size_t a = 0; a < FaceCount; a++)
-        {
-            const Export::Vector3D& rV0 = pVerts[pFaces[a].idx[0]];
-            const Export::Vector3D& rV1 = pVerts[pFaces[a].idx[1]];
-            const Export::Vector3D& rV2 = pVerts[pFaces[a].idx[2]];
-            const Vec3  VO0(rV0.x, rV0.y, rV0.z);
-            const Vec3  VO1(rV1.x, rV1.y, rV1.z);
-            const Vec3  VO2(rV2.x, rV2.y, rV2.z);
-            const Vec3  V0  =   rOBBMat.TransformPoint(VO0);
-            const Vec3  V1  =   rOBBMat.TransformPoint(VO1);
-            const Vec3  V2  =   rOBBMat.TransformPoint(VO2);
-            const Vec3  N   =   (V2 - V0).cross(V1 - V0);
-            if (fabsf(N.dot(N)) <= FLT_EPSILON)//degenerated?
-            {
-                continue;
-            }
-            Pos.push_back(V0.x);
-            Pos.push_back(V0.y);
-            Pos.push_back(V0.z);
-            Pos.push_back(1.f);
-            Pos.push_back(V1.x);
-            Pos.push_back(V1.y);
-            Pos.push_back(V1.z);
-            Pos.push_back(1.f);
-            Pos.push_back(V2.x);
-            Pos.push_back(V2.y);
-            Pos.push_back(V2.z);
-            Pos.push_back(1.f);
-        }
-    }
-    const uint32 TriCount       =   static_cast<uint16>(std::min<size_t>(65535, Pos.size() / 4));//vertex count at 4float/vertex
-    rWriter.Write(TriCount);
-    rWriter.Align<16>();
-    rWriter.Write(&Pos[0], 4 * TriCount * sizeof(float));
-    rWriter.Align<4>();
-    const size_t PosEnd =   rWriter.Pos();
-    return PosEnd - PosStart;//sizeof(TriCount)+sizeof(Pos[0])*Pos.size();
-}
-
-void COCMExporter::SaveInstance(CFileEndianWriter& rWriter, const Export::Object* pInstance, const SOCMeshInfo& rMeshInfo)
-{
-    Vec3 pos(pInstance->pos.x, pInstance->pos.y, pInstance->pos.z);
-    Quat rot(pInstance->rot.w, pInstance->rot.v.x, pInstance->rot.v.y, pInstance->rot.v.z);
-    Vec3 scale(pInstance->scale.x, pInstance->scale.y, pInstance->scale.z);
-
-    Matrix44 tm = Matrix33::CreateScale(scale) * Matrix34(rot);
-    tm.SetTranslation(pos);
-
-    tm  =   tm * rMeshInfo.m_OBBMat.GetInverted();
-
-    //const uint32 MeshID   =   pInstance->MeshToUse()->ID();
-    rWriter.Write(rMeshInfo.m_Offset);
-    for (size_t a = 0; a < 12; a++)//just savin the 3x4 matrix
-    {
-        rWriter.Write(*(reinterpret_cast<float*>(&tm) + a));
-    }
-}
-
-bool COCMExporter::ExportToFile(const char* filename, const Export::IData* pExportData)
-{
-    CLogFile::FormatLine("Exporting OCM file to '%s'", filename);
-
-    FILE* pFile = nullptr;
-    azfopen(&pFile, filename, "wb");
-    if (!pFile)
-    {
-        CLogFile::FormatLine("Error while opening file '%s'!", filename);
-        assert(pFile);
-        return false;
-    }
-
-    CFileEndianWriter Writer;
-    // Write header
-    const uint32 Version        =   ~(4u << 24);
-    const uint32 MeshCount  =   pExportData->GetObjectCount();
-    const uint32 InstCount  =   pExportData->GetObjectCount();
-    uint32  OffsetInstances =   0;
-    Writer.Write(Version);
-    Writer.Write(MeshCount);
-    Writer.Write(InstCount);
-    Writer.Write(OffsetInstances);//keep header end aligned to 16byte
-
-    tdMeshOffset    MeshOffsets;
-    MeshOffsets.reserve(MeshCount);
-    size_t Offset = 16;
-    //std::map<void*,size_t> MeshO
-    for (size_t a = 0; a < MeshCount; a++)
-    {
-        SOCMeshInfo MeshInfo;
-        MeshInfo.m_MeshHash =    pExportData->GetObject(static_cast<int>(a))->MeshHash();
-        const tdMeshOffset::iterator it =   std::find(MeshOffsets.begin(), MeshOffsets.end(), MeshInfo);
-        if (it != MeshOffsets.end())
-        {
-            MeshInfo    =   *it;
-        }
-        else
-        {
-            MeshInfo.m_Offset   = static_cast<uint32>(Offset);
-            Offset += SaveMesh(Writer, pExportData->GetObject(static_cast<int>(a)), MeshInfo.m_OBBMat);
-        }
-        MeshOffsets.push_back(MeshInfo);
-    }
-    OffsetInstances =   static_cast<uint32>(Offset);
-    for (size_t a = 0; a < InstCount; a++)
-    {
-        SaveInstance(Writer, pExportData->GetObject(static_cast<int>(a)), MeshOffsets[a]);
-    }
-    Writer.Seek(4);
-    Writer.Write(static_cast<uint32>(MeshOffsets.size()));
-    Writer.Seek(12);
-    Writer.Write(OffsetInstances);
-
-    fwrite(&Writer.Data()[0], 1, Writer.Data().size(), pFile);
-
-    fclose(pFile);
-    return true;
-}
-
-const char* COCMExporter::TrimFloat(float fValue) const
-{
-    // Convert a float into a string representation and remove all
-    // unnecessary zeros and the decimal dot if it is not needed
-    const int nMaxAccessInTime = 4;
-
-    static char ppBufs[nMaxAccessInTime][16];
-    static int nCurBuf = 0;
-
-    if (nCurBuf >= nMaxAccessInTime)
-    {
-        nCurBuf = 0;
-    }
-
-    char* pBuf = ppBufs[nCurBuf];
-    size_t bufSize = sizeof(ppBufs[nCurBuf]);
-    ++nCurBuf;
-    sprintf_s(pBuf, bufSize, "%f", fValue);
-
-    for (int i = static_cast<int>(strlen(pBuf)) - 1; i > 0; --i)
-    {
-        if (pBuf[i] == '0')
-        {
-            pBuf[i] = 0;
-        }
-        else if (pBuf[i] == '.')
-        {
-            pBuf[i] = 0;
-            break;
-        }
-        else
-        {
-            break;
-        }
-    }
-
-    return pBuf;
-}
-
-
-
-void COCMExporter::Release()
-{
-    delete this;
-}

+ 0 - 50
Code/Editor/Export/OCMExporter.h

@@ -1,50 +0,0 @@
-/*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
-
-
-// Description : Export geometry OCM file format
-
-
-#ifndef CRYINCLUDE_EDITOR_EXPORT_OCMEXPORTER_H
-#define CRYINCLUDE_EDITOR_EXPORT_OCMEXPORTER_H
-#pragma once
-
-#include <IExportManager.h>
-
-class CFileEndianWriter;
-
-struct SOCMeshInfo
-{
-    Matrix44    m_OBBMat;
-    uint32      m_Offset;
-    size_t      m_MeshHash;
-    bool    operator==(const SOCMeshInfo& rOther) const{return m_MeshHash == rOther.m_MeshHash; }
-};
-typedef std::vector<SOCMeshInfo>    tdMeshOffset;
-
-class COCMExporter
-    : public IExporter
-{
-public:
-    virtual const char* GetExtension() const;
-    virtual const char* GetShortDescription() const;
-    virtual bool ExportToFile(const char* filename, const Export::IData* pExportData);
-    virtual bool ImportFromFile([[maybe_unused]] const char* filename, [[maybe_unused]] Export::IData* pData){return false; };
-    virtual void Release();
-
-private:
-    const char* TrimFloat(float fValue) const;
-    void SaveInstance(CFileEndianWriter& rWriter, const Export::Object* pInstance, const SOCMeshInfo& rMeshInfo);
-    size_t SaveMesh(CFileEndianWriter& rWriter, const Export::Object* pMesh, Matrix44& rOBBMat);
-
-    void Extends(const Matrix44& rTransform, const Export::Object* pMesh, f32& rMinX, f32& rMaxX, f32& rMinY, f32& rMaxY, f32& rMinZ, f32& rMaxZ)  const;
-    Matrix44 CalcOBB(const Export::Object* pMesh);
-};
-
-
-#endif // CRYINCLUDE_EDITOR_EXPORT_OCMEXPORTER_H

+ 0 - 24
Code/Editor/GameExporter.cpp

@@ -178,7 +178,6 @@ bool CGameExporter::Export(unsigned int flags, [[maybe_unused]] EEndian eExportE
             ////////////////////////////////////////////////////////////////////////
             // Exporting map setttings
             ////////////////////////////////////////////////////////////////////////
-            ExportOcclusionMesh(sLevelPath.toUtf8().data());
 
             //! Export Level data.
             CLogFile::WriteLine("Exporting leveldata.xml");
@@ -235,29 +234,6 @@ bool CGameExporter::Export(unsigned int flags, [[maybe_unused]] EEndian eExportE
     return exportSuccessful;
 }
 
-//////////////////////////////////////////////////////////////////////////
-void CGameExporter::ExportOcclusionMesh(const char* pszGamePath)
-{
-    IEditor* pEditor = GetIEditor();
-    pEditor->SetStatusText(QObject::tr("including Occluder Mesh \"occluder.ocm\" if available"));
-
-    char resolvedLevelPath[AZ_MAX_PATH_LEN] = { 0 };
-    AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(pszGamePath, resolvedLevelPath, AZ_MAX_PATH_LEN);
-    QString levelDataFile = QString(resolvedLevelPath) + "occluder.ocm";
-    QFile FileIn(levelDataFile);
-    if (FileIn.open(QFile::ReadOnly))
-    {
-        CMemoryBlock Temp;
-        const size_t Size   =   FileIn.size();
-        Temp.Allocate(static_cast<int>(Size));
-        FileIn.read(reinterpret_cast<char*>(Temp.GetBuffer()), Size);
-        FileIn.close();
-        CCryMemFile FileOut;
-        FileOut.Write(Temp.GetBuffer(), static_cast<int>(Size));
-        m_levelPak.m_pakFile.UpdateFile(levelDataFile.toUtf8().data(), FileOut);
-    }
-}
-
 //////////////////////////////////////////////////////////////////////////
 void CGameExporter::ExportLevelData(const QString& path, bool /*bExportMission*/)
 {

+ 0 - 2
Code/Editor/GameExporter.h

@@ -83,8 +83,6 @@ private:
     void ExportLevelData(const QString& path, bool bExportMission = true);
     void ExportLevelInfo(const QString& path);
 
-    void ExportOcclusionMesh(const char* pszGamePath);
-
     void ExportLevelResourceList(const QString& path);
     void ExportLevelUsedResourceList(const QString& path);
     void ExportFileList(const QString& path, const QString& levelName);

+ 0 - 3
Code/Editor/IEditor.h

@@ -53,7 +53,6 @@ class CDialog;
 class C3DConnexionDriver;
 #endif
 class CSettingsManager;
-struct IExportManager;
 class CDisplaySettings;
 class CLevelIndependentFileMan;
 class CSelectionTreeManager;
@@ -634,8 +633,6 @@ struct IEditor
 
     virtual void ReduceMemory() = 0;
 
-    //! Export manager for exporting objects and a terrain from the game to DCC tools
-    virtual IExportManager* GetExportManager() = 0;
     virtual ESystemConfigPlatform GetEditorConfigPlatform() const = 0;
     virtual void ReloadTemplates() = 0;
     virtual void ShowStatusText(bool bEnable) = 0;

+ 0 - 15
Code/Editor/IEditorImpl.cpp

@@ -41,7 +41,6 @@
 #include "ViewManager.h"
 #include "DisplaySettings.h"
 #include "KeyboardCustomizationSettings.h"
-#include "Export/ExportManager.h"
 #include "LevelIndependentFileMan.h"
 #include "TrackView/TrackViewSequenceManager.h"
 #include "AnimationContext.h"
@@ -112,7 +111,6 @@ CEditorImpl::CEditorImpl()
     , m_pConsoleSync(nullptr)
     , m_pSettingsManager(nullptr)
     , m_pLevelIndependentFileMan(nullptr)
-    , m_pExportManager(nullptr)
     , m_bMatEditMode(false)
     , m_bShowStatusText(true)
     , m_bInitialized(false)
@@ -267,9 +265,6 @@ CEditorImpl::~CEditorImpl()
     SAFE_DELETE(m_pViewManager)
     SAFE_DELETE(m_pObjectManager) // relies on prefab manager
 
-    // some plugins may be exporter - this must be above plugin manager delete.
-    SAFE_DELETE(m_pExportManager);
-
     SAFE_DELETE(m_pPluginManager)
     SAFE_DELETE(m_pAnimationContext) // relies on undo manager
     SAFE_DELETE(m_pUndoManager)
@@ -1370,16 +1365,6 @@ void CEditorImpl::ReduceMemory()
 #endif
 }
 
-IExportManager* CEditorImpl::GetExportManager()
-{
-    if (!m_pExportManager)
-    {
-        m_pExportManager = new CExportManager();
-    }
-
-    return m_pExportManager;
-}
-
 ESystemConfigPlatform CEditorImpl::GetEditorConfigPlatform() const
 {
     return m_pSystem->GetConfigPlatform();

+ 0 - 5
Code/Editor/IEditorImpl.h

@@ -36,7 +36,6 @@ class QMenu;
 class CObjectManager;
 class CUndoManager;
 class CGameEngine;
-class CExportManager;
 class CErrorsDlg;
 class CTrackViewSequenceManager;
 class CEditorFileMonitor;
@@ -262,8 +261,6 @@ public:
     //! Setup Material Editor mode
     void SetMatEditMode(bool bIsMatEditMode);
     void ReduceMemory() override;
-    // Get Export manager
-    IExportManager* GetExportManager() override;
     ESystemConfigPlatform GetEditorConfigPlatform() const override;
     void ReloadTemplates() override;
     void AddErrorMessage(const QString& text, const QString& caption);
@@ -339,8 +336,6 @@ protected:
 
     CLevelIndependentFileMan* m_pLevelIndependentFileMan;
 
-    //! Export manager for exporting objects and a terrain from the game to DCC tools
-    CExportManager* m_pExportManager;
     std::unique_ptr<CEditorFileMonitor> m_pEditorFileMonitor;
     QString m_selectFileBuffer;
     QString m_levelNameBuffer;

+ 0 - 182
Code/Editor/Include/IExportManager.h

@@ -1,182 +0,0 @@
-/*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
-
-
-// Description : Export geometry interfaces
-#pragma once
-
-#define EXP_NAMESIZE 32
-enum class AnimParamType;
-
-namespace Export
-{
-    struct Vector3D
-    {
-        float x, y, z;
-    };
-
-
-    struct Quat
-    {
-        Vector3D v;
-        float w;
-    };
-
-
-    struct UV
-    {
-        float u, v;
-    };
-
-
-    struct Face
-    {
-        uint32 idx[3];
-    };
-
-
-    struct Color
-    {
-        float r, g, b, a;
-    };
-
-    typedef char TPath[_MAX_PATH];
-
-    struct Material
-    {
-        Color diffuse;
-        Color specular;
-        float opacity;
-        float smoothness;
-        char name[EXP_NAMESIZE];
-        TPath mapDiffuse;
-        TPath mapSpecular;
-        TPath mapOpacity;
-        TPath mapNormals;
-        TPath mapDecal;
-        TPath mapDisplacement;
-    };
-
-
-    struct Mesh
-    {
-        Material material;
-
-        virtual int GetFaceCount() const = 0;
-        virtual const Face* GetFaceBuffer() const = 0;
-    };
-
-    // The numbers in this enum list must reflect the one from AnimParamType.h
-    enum AnimParamType
-    {
-        FOV         = 0,
-        PositionX   = 51,
-        PositionY   = 52,
-        PositionZ   = 53,
-        RotationX   = 54,
-        RotationY   = 55,
-        RotationZ   = 56,
-
-        // FocalLength is an exceptional case for FBX importing from Maya. In engine we use FoV, not Focal Length, therefore
-        // there is no equivalent AnimParamType::FocalLength in IMovieSystem.h. However we enumerate it here so we can detect
-        // and convert it to FoV during import
-        FocalLength,
-    };
-
-    enum EEntityObjectType
-    {
-        eEntity = 0,
-        eCamera = 1,
-        eCameraTarget = 2,
-    };
-
-    struct EntityAnimData
-    {
-        AnimParamType dataType;
-        float keyTime;
-        float keyValue;
-        float leftTangent;
-        float rightTangent;
-        float leftTangentWeight;
-        float rightTangentWeight;
-    };
-
-    struct Object
-    {
-        Vector3D pos;
-        Quat rot;
-        Vector3D scale;
-        char name[EXP_NAMESIZE];
-        char materialName[EXP_NAMESIZE];
-        int nParent;
-        EEntityObjectType entityType;
-        char cameraTargetNodeName[EXP_NAMESIZE];
-
-        virtual int GetVertexCount() const = 0;
-        virtual const Vector3D* GetVertexBuffer() const = 0;
-
-        virtual int GetNormalCount() const = 0;
-        virtual const Vector3D* GetNormalBuffer() const = 0;
-
-        virtual int GetTexCoordCount() const = 0;
-        virtual const UV* GetTexCoordBuffer() const = 0;
-
-        virtual int GetMeshCount() const = 0;
-        virtual Mesh* GetMesh(int index) const = 0;
-
-        virtual size_t  MeshHash() const =   0;
-
-        virtual int GetEntityAnimationDataCount() const = 0;
-        virtual const EntityAnimData* GetEntityAnimationData(int index) const = 0;
-        virtual void SetEntityAnimationData(EntityAnimData entityData) = 0;
-    };
-
-
-    // IData: Collection of data like object meshes, materials, animations, etc.
-    // used for export
-    // This data is collected by Export Manager implementation
-    struct IData
-    {
-        virtual int GetObjectCount() const = 0;
-        virtual Object* GetObject(int index) const = 0;
-        virtual Object* AddObject(const char* objectName) = 0;
-    };
-} // namespace Export
-
-
-
-// IExporter: interface to present an exporter
-// Exporter is responding to export data from object of IData type
-// to file with specified format
-// Exporter could be provided by user through plug-in system
-struct IExporter
-{
-    virtual ~IExporter() = default;
-    
-    // Get file extension of exporter type, f.i. "obj"
-    virtual const char* GetExtension() const = 0;
-
-    // Get short format description for showing it in FileSave dialog
-    // Example: "Object format"
-    virtual const char* GetShortDescription() const = 0;
-
-    // Implementation of en exporting data to the file
-    virtual bool ExportToFile(const char* filename, const Export::IData* pData) = 0;
-    virtual bool ImportFromFile(const char* filename, Export::IData* pData) = 0;
-
-    // Before Export Manager is destroyed Release will be called
-    virtual void Release() = 0;
-};
-
-// IExportManager: interface to export manager
-struct IExportManager
-{
-    //! Register exporter
-    //! return true if succeed, otherwise false
-    virtual bool RegisterExporter(IExporter* pExporter) = 0;
-};

+ 0 - 1
Code/Editor/Lib/Tests/IEditorMock.h

@@ -159,7 +159,6 @@ public:
     MOCK_METHOD0(IsSourceControlAvailable, bool());
     MOCK_METHOD0(IsSourceControlConnected, bool());
     MOCK_METHOD0(ReduceMemory, void());
-    MOCK_METHOD0(GetExportManager, IExportManager* ());
     MOCK_CONST_METHOD0(GetEditorConfigPlatform, ESystemConfigPlatform());
     MOCK_METHOD0(ReloadTemplates, void());
     MOCK_METHOD1(ShowStatusText, void(bool ));

+ 0 - 3
Code/Editor/MainWindow.cpp

@@ -686,9 +686,6 @@ void MainWindow::InitActions()
             .RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdateDocumentReady);
     }
 
-    am->AddAction(ID_FILE_EXPORT_SELECTEDOBJECTS, tr("Export Selected &Objects"))
-        .RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdateSelected);
-    am->AddAction(ID_FILE_EXPORTOCCLUSIONMESH, tr("Export Occlusion Mesh"));
     am->AddAction(ID_FILE_EDITLOGFILE, tr("Show Log File"));
 #ifdef ENABLE_SLICE_EDITOR
     am->AddAction(ID_FILE_RESAVESLICES, tr("Resave All Slices"));

+ 0 - 2
Code/Editor/Resource.h

@@ -182,7 +182,6 @@
 #define ID_MODIFY_AIPOINT_PICKIMPASSLINK           33865
 #define ID_FILE_EXPORTSELECTION                    33875
 #define ID_EDIT_PASTE_WITH_LINKS                   33893
-#define ID_FILE_EXPORT_SELECTEDOBJECTS             33911
 #define ID_SPLINE_PREVIOUS_KEY                     33916
 #define ID_SPLINE_NEXT_KEY                         33917
 #define ID_SPLINE_FLATTEN_ALL                      33918
@@ -247,7 +246,6 @@
 #define ID_SET_TIME_TO_KEY                         34206
 #define ID_TOGGLE_SCRUB_UNITS                      34207
 #define ID_TOGGLE_PREVIEW_UNITS                    34208
-#define ID_FILE_EXPORTOCCLUSIONMESH                34209
 #define ID_MANN_RELOAD_ANIMS                       34210
 #define ID_FILE_ANIMDBEDITOR                       34211
 #define ID_SNAP_TO_ANGLE_RANGE_BEGIN    34323

+ 0 - 49
Code/Editor/TrackView/TrackViewDialog.cpp

@@ -1018,21 +1018,6 @@ void CTrackViewDialog::OnSyncSelectedTracksFromBase()
     }
 }
 
-void CTrackViewDialog::OnExportFBXSequence()
-{
-    SaveCurrentSequenceToFBX();
-}
-
-void CTrackViewDialog::OnExportNodeKeysGlobalTime()
-{
-    CExportManager* pExportManager = static_cast<CExportManager*>(GetIEditor()->GetExportManager());
-
-    if (pExportManager)
-    {
-        pExportManager->SaveNodeKeysTimeToXML();
-    }
-}
-
 //////////////////////////////////////////////////////////////////////////
 void CTrackViewDialog::OnAddSequence()
 {
@@ -2321,40 +2306,6 @@ void CTrackViewDialog::EndUndoTransaction()
     m_bDoingUndoOperation = false;
 }
 
-void CTrackViewDialog::SaveCurrentSequenceToFBX()
-{
-    CTrackViewSequence* sequence = GetIEditor()->GetAnimation()->GetSequence();
-    if (!sequence)
-    {
-        return;
-    }
-
-    QString selectedSequenceFBXStr = QString::fromUtf8(sequence->GetName().c_str()) + ".fbx";
-    CExportManager* pExportManager = static_cast<CExportManager*>(GetIEditor()->GetExportManager());
-    const char szFilters[] = "FBX Files (*.fbx)";
-
-    CFBXExporterDialog fpsDialog;
-
-    CTrackViewTrackBundle allTracks = sequence->GetAllTracks();
-
-    for (unsigned int trackID = 0; trackID < allTracks.GetCount(); ++trackID)
-    {
-        CTrackViewTrack* pCurrentTrack = allTracks.GetTrack(trackID);
-
-        if (!pCurrentTrack->GetParentNode()->IsSelected())
-        {
-            continue;
-        }
-    }
-
-    QString filename = AzQtComponents::FileDialog::GetSaveFileName(this, tr("Export Selected Nodes To FBX File"), selectedSequenceFBXStr, szFilters);
-    if (!filename.isEmpty())
-    {
-        pExportManager->SetBakedKeysSequenceExport(true);
-        pExportManager->Export(filename.toUtf8().data(), "", "", false, false, false, true);
-    }
-}
-
 void CTrackViewDialog::AfterEntitySelectionChanged(
     [[maybe_unused]] const AzToolsFramework::EntityIdList& newlySelectedEntities,
     [[maybe_unused]] const AzToolsFramework::EntityIdList& newlyDeselectedEntities)

+ 0 - 3
Code/Editor/TrackView/TrackViewDialog.h

@@ -101,8 +101,6 @@ protected slots:
     void OnSyncSelectedTracksToBase();
     void OnSyncSelectedTracksFromBase();
     void OnAddSequence();
-    void OnExportFBXSequence();
-    void OnExportNodeKeysGlobalTime();
     void OnDelSequence();
     void OnEditSequence();
     void OnSequenceComboBox();
@@ -211,7 +209,6 @@ private:
 
     void BeginUndoTransaction() override;
     void EndUndoTransaction() override;
-    void SaveCurrentSequenceToFBX();
     void SaveSequenceTimingToXML();
 
     // ToolsApplicationNotificationBus ...

+ 1 - 261
Code/Editor/TrackView/TrackViewNodes.cpp

@@ -1004,54 +1004,7 @@ void CTrackViewNodesCtrl::OnNMRclick(QPoint point)
 
     float scrollPos = SaveVerticalScrollPos();
 
-    if (cmd == eMI_SaveToFBX)
-    {
-        CExportManager* pExportManager = static_cast<CExportManager*>(GetIEditor()->GetExportManager());
-
-        if (pExportManager)
-        {
-            CTrackViewSequence* sequence2 = GetIEditor()->GetAnimation()->GetSequence();
-            if (!sequence2)
-            {
-                return;
-            }
-
-            CTrackViewAnimNodeBundle selectedNodes = sequence2->GetSelectedAnimNodes();
-            const unsigned int numSelectedNodes = selectedNodes.GetCount();
-            if (numSelectedNodes == 0)
-            {
-                return;
-            }
-
-            QString file = QString::fromUtf8(sequence2->GetName().c_str()) + QString(".fbx");
-            QString selectedSequenceFBXStr = QString::fromUtf8(sequence2->GetName().c_str()) + ".fbx";
-
-            if (numSelectedNodes > 1)
-            {
-                file = selectedSequenceFBXStr;
-            }
-            else
-            {
-                file = QString::fromUtf8(selectedNodes.GetNode(0)->GetName().c_str()) + QString(".fbx");
-            }
-
-            QString path = AzQtComponents::FileDialog::GetSaveFileName(this, tr("Export Selected Nodes To FBX File"), QString(), tr("FBX Files (*.fbx)"));
-
-            if (!path.isEmpty())
-            {
-                pExportManager->SetBakedKeysSequenceExport(false);
-                pExportManager->Export(path.toUtf8().data(), "", "", false, false, false, true);
-            }
-        }
-    }
-    else if (cmd == eMI_ImportFromFBX)
-    {
-        if (animNode)
-        {
-            ImportFromFBX();
-        }
-    }
-    else if (cmd == eMI_SetAsViewCamera)
+    if (cmd == eMI_SetAsViewCamera)
     {
         if (animNode && animNode->GetType() == AnimNodeType::Camera)
         {
@@ -1540,219 +1493,6 @@ void CTrackViewNodesCtrl::OnItemDblClick(QTreeWidgetItem* item, int)
     }
 }
 
-CTrackViewTrack* CTrackViewNodesCtrl::GetTrackViewTrack(const Export::EntityAnimData* pAnimData, CTrackViewTrackBundle trackBundle, const QString& nodeName)
-{
-    for (unsigned int trackID = 0; trackID < trackBundle.GetCount(); ++trackID)
-    {
-        CTrackViewTrack* pTrack = trackBundle.GetTrack(trackID);
-        const QString bundleTrackName = QString::fromUtf8(pTrack->GetAnimNode()->GetName().c_str());
-
-        if (bundleTrackName.compare(nodeName, Qt::CaseInsensitive) != 0)
-        {
-            continue;
-        }
-
-        // Position, Rotation
-        if (pTrack->IsCompoundTrack())
-        {
-            for (unsigned int childTrackID = 0; childTrackID < pTrack->GetChildCount(); ++childTrackID)
-            {
-                CTrackViewTrack* childTrack = static_cast<CTrackViewTrack*>(pTrack->GetChild(childTrackID));
-                // Have to cast GetType to int since the enum it returns is not the same enum that pAnimData->dataType is
-                if (static_cast<int>(childTrack->GetParameterType().GetType()) == pAnimData->dataType)
-                {
-                    return childTrack;
-                }
-            }
-        }
-
-        // FOV
-        // Have to cast GetType to int since the enum it returns is not the same enum that pAnimData->dataType is
-        if (static_cast<int>(pTrack->GetParameterType().GetType()) == pAnimData->dataType)
-        {
-            return pTrack;
-        }
-    }
-
-    return nullptr;
-}
-
-//////////////////////////////////////////////////////////////////////////
-void CTrackViewNodesCtrl::ImportFromFBX()
-{
-    CExportManager* pExportManager = static_cast<CExportManager*>(GetIEditor()->GetExportManager());
-
-    CAutoDirectoryRestoreFileDialog dlg(QFileDialog::AcceptOpen, QFileDialog::AnyFile, {}, {}, "FBX Files (*.fbx)", {}, {}, this);
-
-    if (dlg.exec())
-    {
-        bool bImportResult = pExportManager->ImportFromFile(dlg.selectedFiles().first().toStdString().c_str());
-
-        if (!bImportResult)
-        {
-            return;
-        }
-    }
-    else
-    {
-        return;
-    }
-
-    CTrackViewSequence* sequence = GetIEditor()->GetAnimation()->GetSequence();
-
-    if (sequence)
-    {
-        AzToolsFramework::ScopedUndoBatch undoBatch("Replace Keys");
-        CTrackViewTrackBundle tracks = sequence->GetAllTracks();
-        const unsigned int numTracks = tracks.GetCount();
-
-        for (unsigned int trackID = 0; trackID < numTracks; ++trackID)
-        {
-            undoBatch.MarkEntityDirty(sequence->GetSequenceComponentEntityId());
-        }
-
-        CTrackViewTrackBundle trackBundle = sequence->GetAllTracks();
-
-        const Export::CData pData = pExportManager->GetData();
-        const int objectsCount = pData.GetObjectCount();
-
-        CTrackViewFBXImportPreviewDialog importSelectionDialog;
-
-        for (int objectID = 0; objectID < objectsCount; ++objectID)
-        {
-            importSelectionDialog.AddTreeItem(pData.GetObject(objectID)->name);
-        }
-
-        if (importSelectionDialog.exec() != QDialog::Accepted)
-        {
-            return;
-        }
-
-        // Remove all keys from the affected tracks
-        for (int objectID = 0; objectID < objectsCount; ++objectID)
-        {
-            Export::Object* pObject = pData.GetObject(objectID);
-
-            if (pObject)
-            {
-                // Clear only the selected tracks for which we have AnimNodes
-                if (!importSelectionDialog.IsObjectSelected(pObject->name))
-                {
-                    continue;
-                }
-                const char* updatedNodeName = pObject->name;
-                if (sequence->GetAnimNodesByName(updatedNodeName).GetCount() == 0)
-                {
-                    continue;
-                }
-
-                int animatonDataCount = pObject->GetEntityAnimationDataCount();
-                for (int animDataID = 0; animDataID < animatonDataCount; ++animDataID)
-                {
-                    const Export::EntityAnimData* pAnimData = pObject->GetEntityAnimationData(animDataID);
-
-                    CTrackViewTrack* pTrack = GetTrackViewTrack(pAnimData, trackBundle, (QString)updatedNodeName);
-
-                    if (pTrack)
-                    {
-                        CTrackViewKeyBundle keys = pTrack->GetAllKeys();
-
-                        for (int deleteKeyID = (int)keys.GetKeyCount() - 1; deleteKeyID >= 0; --deleteKeyID)
-                        {
-                            CTrackViewKeyHandle key = keys.GetKey(deleteKeyID);
-                            key.Delete();
-                        }
-                    }
-                }
-            }
-        }
-        // Add keys from FBX file
-        for (int objectID = 0; objectID < objectsCount; ++objectID)
-        {
-            const Export::Object* pObject = pData.GetObject(objectID);
-
-            if (pObject)
-            {
-                // only process selected nodes from file for which we have AnimNodes
-                if (!importSelectionDialog.IsObjectSelected(pObject->name))
-                {
-                    continue;
-                }
-                const char* updatedNodeName = pObject->name;
-                if (sequence->GetAnimNodesByName(updatedNodeName).GetCount() == 0)
-                {
-                    continue;
-                }
-
-                const int animatonDataCount = pObject->GetEntityAnimationDataCount();
-
-                // Add keys from the imported file to the selected tracks
-                for (int animDataID = 0; animDataID < animatonDataCount; ++animDataID)
-                {
-                    const Export::EntityAnimData* pAnimData = pObject->GetEntityAnimationData(animDataID);
-                    CTrackViewTrack* pTrack = GetTrackViewTrack(pAnimData, trackBundle, (QString)updatedNodeName);
-
-                    if (pTrack)
-                    {
-                        CTrackViewKeyHandle key = pTrack->CreateKey(pAnimData->keyTime);
-                        I2DBezierKey bezierKey;
-                        key.GetKey(&bezierKey);
-                        bezierKey.value = Vec2(pAnimData->keyTime, pAnimData->keyValue);
-                        key.SetKey(&bezierKey);
-                    }
-                }
-
-                // After all keys are added, we are able to add the left and right tangents to the imported keys
-                for (int animDataID = 0; animDataID < animatonDataCount; ++animDataID)
-                {
-                    const Export::EntityAnimData* pAnimData = pObject->GetEntityAnimationData(animDataID);
-
-                    CTrackViewTrack* pTrack = GetTrackViewTrack(pAnimData, trackBundle, (QString)updatedNodeName);
-
-                    if (pTrack)
-                    {
-                        CTrackViewKeyHandle key = pTrack->GetKeyByTime(pAnimData->keyTime);
-                        ISplineInterpolator* pSpline = pTrack->GetSpline();
-
-                        if (pSpline)
-                        {
-                            const int keyIndex = key.GetIndex();
-
-                            ISplineInterpolator::ValueType inTangent;
-                            ISplineInterpolator::ValueType outTangent;
-                            ZeroStruct(inTangent);
-                            ZeroStruct(outTangent);
-
-                            float currentKeyTime = 0.0f;
-                            pSpline->SetKeyFlags(keyIndex, SPLINE_KEY_TANGENT_BROKEN);
-
-                            if (keyIndex > 0)
-                            {
-                                currentKeyTime = key.GetTime() - key.GetPrevKey().GetTime();
-                                inTangent[0] = pAnimData->leftTangentWeight * currentKeyTime;
-                                inTangent[1] = inTangent[0] * pAnimData->leftTangent;
-                                pSpline->SetKeyInTangent(keyIndex, inTangent);
-                            }
-
-                            if (keyIndex < static_cast<int>(pTrack->GetKeyCount() - 1))
-                            {
-                                CTrackViewKeyHandle nextKey = key.GetNextKey();
-                                if (nextKey.IsValid())
-                                {
-                                    currentKeyTime = nextKey.GetTime() - key.GetTime();
-                                    outTangent[0] = pAnimData->rightTangentWeight * currentKeyTime;
-                                    outTangent[1] = outTangent[0] * pAnimData->rightTangent;
-                                    pSpline->SetKeyOutTangent(keyIndex, outTangent);
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    }
-}
-
 //////////////////////////////////////////////////////////////////////////
 void CTrackViewNodesCtrl::EditEvents()
 {

+ 0 - 4
Code/Editor/TrackView/TrackViewNodes.h

@@ -20,7 +20,6 @@
 #include "TrackViewNode.h"
 #include "TrackViewSequence.h"
 #include "Undo/Undo.h"
-#include "Export/ExportManager.h"
 
 #include <IMovieSystem.h>
 #include <QMap>
@@ -131,9 +130,6 @@ private:
     void CreateFolder(CTrackViewAnimNode* pGroupNode);
     void EditEvents();
 
-    void ImportFromFBX();
-    CTrackViewTrack* GetTrackViewTrack(const Export::EntityAnimData* pAnimData, CTrackViewTrackBundle trackBundle, const QString& nodeName);
-
     void AddMenuSeperatorConditional(QMenu& menu, bool& bAppended);
     void AddGroupNodeAddItems(struct SContextMenu& contextMenu, CTrackViewAnimNode* pAnimNode);
     int ShowPopupMenuSingleSelection(struct SContextMenu& contextMenu, CTrackViewSequence* pSequence, CTrackViewNode* pNode);

+ 0 - 7
Code/Editor/editor_lib_files.cmake

@@ -276,7 +276,6 @@ set(FILES
     Include/ICommandManager.h
     Include/IDisplayViewport.h
     Include/IEditorClassFactory.h
-    Include/IExportManager.h
     Include/ILogFile.h
     Include/IKeyTimeSet.h
     Include/IObjectManager.h
@@ -414,12 +413,6 @@ set(FILES
     Dialogs/PythonScriptsDialog.ui
     Dialogs/Generic/UserOptions.cpp
     Dialogs/Generic/UserOptions.h
-    Export/ExportManager.cpp
-    Export/ExportManager.h
-    Export/OBJExporter.cpp
-    Export/OBJExporter.h
-    Export/OCMExporter.cpp
-    Export/OCMExporter.h
     EditorFileMonitor.cpp
     EditorFileMonitor.h
     Include/IEditorFileMonitor.h

+ 7 - 4
Code/Framework/AzCore/AzCore/IO/GenericStreams.cpp

@@ -160,10 +160,13 @@ namespace AZ::IO
         Open(path, mode);
     }
 
-    SystemFileStream::~SystemFileStream()
-    {
-        m_file.Close();
-    }
+    // Let the SystemFile destructor close or not close any files based
+    // on its destruction logic
+    SystemFileStream::~SystemFileStream() = default;
+
+    // Default the move constructor and assignment operator
+    SystemFileStream::SystemFileStream(SystemFileStream&&) = default;
+    SystemFileStream& SystemFileStream::operator=(SystemFileStream&&) = default;
 
     bool SystemFileStream::IsOpen() const
     {

+ 4 - 0
Code/Framework/AzCore/AzCore/IO/GenericStreams.h

@@ -80,6 +80,10 @@ namespace AZ::IO
         SystemFileStream(SystemFile&& file, OpenMode mode);
         ~SystemFileStream() override;
 
+        //! Move constructor and assignment which is defaulted in the cpp file
+        SystemFileStream(SystemFileStream&&);
+        SystemFileStream& operator=(SystemFileStream&&);
+
         bool Open(const char* path, OpenMode mode);
 
         void Close() override;

+ 5 - 6
Code/Framework/AzCore/AzCore/IO/SystemFile.cpp

@@ -74,7 +74,7 @@ namespace AZ::IO
 
     SystemFile::~SystemFile()
     {
-        if (IsOpen())
+        if (IsOpen() && m_closeOnDestruction)
         {
             Close();
         }
@@ -114,6 +114,10 @@ namespace AZ::IO
 
         AZ_Assert(!IsOpen(), "This file (%s) is already open!", m_fileName.c_str());
 
+        // Sets the close on destruction option if
+        // the skip close on destruction mode is set
+        m_closeOnDestruction = (mode & OpenMode::SF_SKIP_CLOSE_ON_DESTRUCTION) == 0;
+
         return PlatformOpen(mode, platformFlags);
     }
 
@@ -385,11 +389,6 @@ namespace AZ::IO
 namespace AZ::IO
 {
     // Captures File Descriptor output through a pipe
-    FileDescriptorCapturer::FileDescriptorCapturer(int sourceDescriptor)
-        : m_sourceDescriptor(sourceDescriptor)
-    {
-    }
-
     FileDescriptorCapturer::~FileDescriptorCapturer()
     {
         Reset();

+ 35 - 13
Code/Framework/AzCore/AzCore/IO/SystemFile.h

@@ -42,6 +42,7 @@ namespace AZ
                 SF_OPEN_CREATE      = (1 << 5),   ///< Create a file, if file exists it will overwrite it's content.
                 SF_OPEN_TRUNCATE    = (1 << 6),   ///< Opens a file and truncate it's size to zero. If file doesn't exist an error is returned.
                 SF_OPEN_CREATE_PATH = (1 << 7),   ///< Also create any intermediate paths that are part of the file path. Must be used in conjunction with SF_OPEN_CREATE or SF_OPEN_CREATE_NEW.
+                SF_SKIP_CLOSE_ON_DESTRUCTION = (1 << 8), ///< SystemFile destructor will not invoke Close() on an open handle
             };
 
             enum SeekMode
@@ -94,43 +95,62 @@ namespace AZ
             /// Return disc offset if possible, otherwise 0
             SizeType DiskOffset() const;
             /// Return file name or NULL if file is not open.
-            AZ_FORCE_INLINE const char* Name() const    { return m_fileName.c_str(); }
+            const char* Name() const { return m_fileName.c_str(); }
             bool IsOpen() const;
 
             /// Return native handle to the file.
-            AZ_FORCE_INLINE const FileHandleType&   NativeHandle() const    { return m_handle; }
+            const FileHandleType& NativeHandle() const { return m_handle; }
 
             //////////////////////////////////////////////////////////////////////////
             //////////////////////////////////////////////////////////////////////////
             // Utility functions
             /// Check if a file or directory exists.
-            static bool     Exists(const char* path);
+            static bool Exists(const char* path);
             /// Check if path is a directory
-            static bool     IsDirectory(const char* path);
+            static bool IsDirectory(const char* path);
             /// FindFiles
-            typedef AZStd::function<bool /* true to continue to enumerate otherwise false */ (const char* /* fileName*/, bool /* true if file, false if folder*/)>  FindFileCB;
-            static void     FindFiles(const char* filter, FindFileCB cb);
+            using FindFileCB = AZStd::function<bool /* true to continue to enumerate otherwise false */ (const char* /* fileName*/, bool /* true if file, false if folder*/)>;
+            static void FindFiles(const char* filter, FindFileCB cb);
             /// Get the time the file was last modified.
-            static AZ::u64  ModificationTime(const char* fileName);
+            static AZ::u64 ModificationTime(const char* fileName);
             /// Return a length of a file. 0 if files has 0 length or doesn't exits.
             static SizeType Length(const char* fileName);
             /// Read content from a file. If byteSize is 0 it reads the entire file.
             static SizeType Read(const char* fileName, void* buffer, SizeType byteSize = 0, SizeType byteOffset = 0);
             /// Delete a file, returns true if file was actually deleted.
-            static bool     Delete(const char* fileName);
+            static bool Delete(const char* fileName);
             /// Rename a file, returns true if the file was successfully renamed. If overwrite is true, rename even if target exists.
-            static bool     Rename(const char* sourceFileName, const char* targetFileName, bool overwrite = false);
+            static bool Rename(const char* sourceFileName, const char* targetFileName, bool overwrite = false);
             /// Returns true if a file is writable, false if it doesn't exist or is read only
-            static bool     IsWritable(const char* sourceFileName);
+            static bool IsWritable(const char* sourceFileName);
             /// Returns true if able to modify readonly attribute. Can fail if file doesn't exist.
-            static bool     SetWritable(const char* sourceFileName, bool writable);
+            static bool SetWritable(const char* sourceFileName, bool writable);
             /// Recursively creates a directory hierarchy
-            static bool     CreateDir(const char* dirName);
+            static bool CreateDir(const char* dirName);
             /// Delete a directory
-            static bool     DeleteDir(const char* dirName);
+            static bool DeleteDir(const char* dirName);
             /// Retrieves platform specific Null device path (ex: "/dev/null")
             static const char* GetNullFilename();
 
+            //! Opens stdin for read
+            //! The destructor of SystemFile will not close the handle
+            //! This allows stdin to continue to remain open even if the instance scope ends
+            //! NOTE: However the handle can be explicitly closed using the Close() function
+            //! Invoking that function will close the stdin handle
+            static SystemFile GetStdin();
+            //! Opens stdout for write
+            //! The destructor of SystemFile will not close the handle
+            //! This allows stdout to continue to remain open even if the instance scope ends
+            //! NOTE: However the handle can be explicitly closed using the Close() function
+            //! Invoking that function will close the stdout handle
+            static SystemFile GetStdout();
+            //! Opens stderr for write
+            //! The destructor of SystemFile will not close the handle
+            //! This allows stderr to continue to remain open even if the instance scope ends
+            //! NOTE: However the handle can be explicitly closed using the Close() function
+            //! Invoking that function will close the stderr handle
+            static SystemFile GetStderr();
+
         private:
             static void CreatePath(const char * fileName);
 
@@ -139,6 +159,8 @@ namespace AZ
 
             FileHandleType m_handle;
             AZ::IO::FixedMaxPathString m_fileName;
+            //! If true the destructor will invoke Close if the Handle is open
+            bool m_closeOnDestruction{ true };
         };
 
         /**

+ 77 - 36
Code/Framework/AzCore/AzCore/Settings/CommandLine.cpp

@@ -16,6 +16,10 @@ namespace AZ
 {
     namespace
     {
+        // Any arguments seen after matching the double dash separator
+        // will be parsed as positional arguments
+        static constexpr AZStd::string_view PositionalArgSeparator = "--";
+
         static const AZStd::string m_emptyValue;
         // helper utility to return a lower version of the string without altering the original.
         // regular to_lower operates directly on the input.
@@ -43,6 +47,19 @@ namespace AZ
         }
     }
 
+    struct CommandLine::ArgumentParserState
+    {
+        //! Set to true if the psuedo argument '--' has been parsed
+        //! This indicates to the argument parser that the remaining
+        //! arguments should be treated as positional arguments only
+        bool m_parseRemainAsPositional{};
+        //! Stores the current option being parsed
+        //! This is used for maintaining state for space separated options
+        //! Ex. --foo bar
+        //! When the "bar" argument is parsed the current switch would be set to "foo"
+        AZStd::string m_currentOption;
+    };
+
     CommandLine::CommandLine()
         : m_commandLineOptionPrefix(AZ_TRAIT_COMMAND_LINE_OPTION_PREFIX)
     {
@@ -100,50 +117,67 @@ namespace AZ
         }
     }
 
-    void CommandLine::AddArgument(AZStd::string_view currentArg, AZStd::string& currentSwitch)
+    void CommandLine::AddArgument(AZStd::string_view currentArg,
+        ArgumentParserState& argumentParserState)
     {
         currentArg = AZ::StringFunc::StripEnds(currentArg);
         if (!currentArg.empty())
         {
-            if (m_commandLineOptionPrefix.contains(currentArg.front()))
+            if (!argumentParserState.m_parseRemainAsPositional)
             {
-                // its possible that its a key-value-pair like -blah=whatever
-                // we support this too, for compatibility.
+                // Parse option arguments
 
-                currentArg = currentArg.substr(1);
-                if (currentArg[0] == '-') // for -- extra
+                // If the `--` argument is seen, update the
+                // the parser to force parsing remaining arguments as positional
+                if (currentArg == PositionalArgSeparator)
                 {
-                    currentArg = currentArg.substr(1);
+                    argumentParserState.m_parseRemainAsPositional = true;
+                    argumentParserState.m_currentOption.clear();
+                    return;
                 }
 
-                AZStd::size_t foundPos = AZ::StringFunc::Find(currentArg, "=");
-                if (foundPos != AZStd::string::npos)
+                // If the `--` argument has been parsed, treat all remaining arguments positional
+                if (m_commandLineOptionPrefix.contains(currentArg.front()))
                 {
-                    AZStd::string_view argumentView{ currentArg };
-                    AZStd::string_view option = AZ::StringFunc::StripEnds(argumentView.substr(0, foundPos));
-                    AZStd::string_view value = AZ::StringFunc::StripEnds(argumentView.substr(foundPos + 1));
-                    ParseOptionArgument(ToLower(option), value, nullptr);
-                    currentSwitch.clear();
-                }
-                else
-                {
-                    // its in this format -switchName switchvalue
-                    // (no equals)
-                    currentSwitch = ToLower(currentArg);
-                    m_allValues.push_back({ currentSwitch, "" });
+                    // its possible that its a key-value-pair like -blah=whatever
+                    // we support this too, for compatibility.
+
+                    currentArg = currentArg.substr(1);
+                    if (currentArg[0] == '-') // for -- extra
+                    {
+                        currentArg = currentArg.substr(1);
+                    }
+
+                    AZStd::size_t foundPos = AZ::StringFunc::Find(currentArg, "=");
+                    if (foundPos != AZStd::string::npos)
+                    {
+                        AZStd::string_view argumentView{ currentArg };
+                        AZStd::string_view option = AZ::StringFunc::StripEnds(argumentView.substr(0, foundPos));
+                        AZStd::string_view value = AZ::StringFunc::StripEnds(argumentView.substr(foundPos + 1));
+                        ParseOptionArgument(ToLower(option), value, nullptr);
+                        argumentParserState.m_currentOption.clear();
+                    }
+                    else
+                    {
+                        // its in this format -switchName switchvalue
+                        // (no equals)
+                        argumentParserState.m_currentOption = ToLower(currentArg);
+                        m_allValues.push_back({ argumentParserState.m_currentOption, "" });
+                    }
+                    return;
                 }
             }
+
+            if (argumentParserState.m_currentOption.empty())
+            {
+                // Parse positional argument
+                m_allValues.push_back({ "", UnquoteArgument(currentArg) });
+            }
             else
             {
-                if (currentSwitch.empty())
-                {
-                    m_allValues.push_back({ "", UnquoteArgument(currentArg) });
-                }
-                else
-                {
-                    ParseOptionArgument(currentSwitch, currentArg, &m_allValues.back());
-                    currentSwitch.clear();
-                }
+                // Finish parsing of values for option argument
+                ParseOptionArgument(argumentParserState.m_currentOption, currentArg, &m_allValues.back());
+                argumentParserState.m_currentOption.clear();
             }
         }
     }
@@ -152,14 +186,17 @@ namespace AZ
     {
         m_allValues.clear();
 
-        AZStd::string currentSwitch;
+        // Stores the state of arguments being parsed
+        // Allows the AddArgument function to update the state
+        ArgumentParserState parseState;
+
         // Start on 1 because 0 is the executable name
         for (int i = 1; i < argc; ++i)
         {
             if (argv[i])
             {
                 AZStd::string_view currentArg = argv[i]; // this eats the / or -
-                AddArgument(currentArg, currentSwitch);
+                AddArgument(currentArg, parseState);
             }
         }
     }
@@ -168,11 +205,13 @@ namespace AZ
     {
         m_allValues.clear();
 
+        // Stores the state of arguments being parsed
+        ArgumentParserState parseState;
+
         // This version of Parse does not skip over 0th index
-        AZStd::string currentSwitch;
         for (int i = 0; i < commandLine.size(); ++i)
         {
-            AddArgument(commandLine[i], currentSwitch);
+            AddArgument(commandLine[i], parseState);
         }
     }
 
@@ -180,11 +219,13 @@ namespace AZ
     {
         m_allValues.clear();
 
+        // Stores the state of arguments being parsed
+        ArgumentParserState parseState;
+
         // This version of Parse does not skip over 0th index
-        AZStd::string currentSwitch;
         for (int i = 0; i < commandLine.size(); ++i)
         {
-            AddArgument(commandLine[i], currentSwitch);
+            AddArgument(commandLine[i], parseState);
         }
     }
 

+ 25 - 8
Code/Framework/AzCore/AzCore/Settings/CommandLine.h

@@ -19,14 +19,30 @@ namespace AZ
 {
     /**
     * Given a commandline, this allows uniform parsing of it into parameter values and extra values.
-    * When parsed, the commandline becomes a series of "switches" or "extra values"
-    * For example, a switch may be specified as either 
-    *      /switchName=value1[,value2...]
-    *      /switchname value[,value2...]
-    *      --switchname value[,value2...]
+    * When parsed, the commandline becomes a series of "options" or "extra values"
+    * For example, a option may be specified as either
+    *      /optionName=value1[,value2...]
+    *      /optionname value[,value2...]
+    *      --optionname value[,value2...]
     *    or other combinations of the above
-    * You may NOT use a colon as a switch separator since file names may easily contain them.
-    * any additional parameters which are not associated with any switch are considered "misc values"
+    * You may NOT use a colon as a option separator since file names may easily contain them.
+    * any additional parameters which are not associated with any option are considered "misc values"
+    *
+    * NOTE: "flags" options which doesn't specify a value need care when using
+    * This is because the Parse() methods don't accept external metadata indicating actions to perform
+    * on flag arguments unlike python argparse
+    * https://docs.python.org/3/library/argparse.html#action
+    * For example the command line of
+    * `app.exe --verbose <PositionalArg>` would parse <PositionalArg> as the value of the "verbose" flag
+    * The workaround is to either
+    * 1. specify the flag options at the end of the command line
+    * `app.exe <PositionalArg> --verbose`
+    * 2. specify second option right after the previous flag option with and add a value to it
+    * `app.exe --verbose --count 2 <PositionalArg>`
+    * 3. specify a value for the 'verbose'option
+    *     here only the 1 is parse as part of the verbose option, the <PositionalArg>
+    *     separated by whitespace is a new argument
+    * `app.exe --verbose 1 <PositionalArg>`  or `app.exe --verbose=1 <PositionalArg>`
     */
     class CommandLine
     {
@@ -132,7 +148,8 @@ namespace AZ
         auto crend() const -> ArgumentVector::const_reverse_iterator;
 
     private:
-        void AddArgument(AZStd::string_view currentArg, AZStd::string& currentSwitch);
+        struct ArgumentParserState;
+        void AddArgument(AZStd::string_view currentArg, ArgumentParserState&);
         void ParseOptionArgument(AZStd::string_view newOption, AZStd::string_view newValue, CommandArgument* inProgressArgument);
 
         ArgumentVector m_allValues;

+ 1 - 1
Code/Framework/AzCore/AzCore/Settings/ConfigParser.cpp

@@ -78,7 +78,7 @@ namespace AZ::Settings
 
     ParseOutcome ParseConfigFile(AZ::IO::GenericStream& configStream, const ConfigParserSettings& configParserSettings)
     {
-        // The trace system is not being used here as this function INI system is available
+        // The trace system is not being used here as this function is available
         // before any allocators or initialization of any systems take place
 
         ParseOutcome parseOutcome;

+ 3 - 2
Code/Framework/AzCore/AzCore/Settings/ConfigParser.h

@@ -67,8 +67,9 @@ namespace AZ::Settings
         //! So it is recommmended that users binding a lambda bind at most 2 reference or pointer members
         //! to avoid dynamic heap allocations
         //!
+        //! NOTE: This function will not be called if the key is empty
         //! @return True should be returned from this function to indicate that parsing of the config entry
-        //! has a succeed
+        //! has succeeded
         using ParseConfigEntryFunc = AZStd::function<bool(const ConfigEntry&)>;
 
         ParseConfigEntryFunc m_parseConfigEntryFunc;
@@ -128,7 +129,7 @@ namespace AZ::Settings
 
         //! Function which is invoked on the config line after filtering through the SectionHeaderFunc
         //! to determine the delimiter of the line
-        //! The structure contains a functor which returns true if a character is a valid delimiter
+        //! The structure contains a functor which returns a ConfigKeyValuePair to split the key value pair
         DelimiterFunc m_delimiterFunc = &DefaultDelimiterFunc;
     };
 

+ 119 - 0
Code/Framework/AzCore/AzCore/Settings/TextParser.cpp

@@ -0,0 +1,119 @@
+/*
+ * 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/IO/GenericStreams.h>
+#include <AzCore/Settings/TextParser.h>
+#include <AzCore/std/string/conversions.h>
+#include <AzCore/std/string/fixed_string.h>
+#include <AzCore/std/containers/fixed_vector.h>
+#include <AzCore/StringFunc/StringFunc.h>
+
+namespace AZ::Settings
+{
+    // Text Parsing support
+    bool TextParserSettings::DefaultTokenDelimiterFunc(char element)
+    {
+        return element == '\n';
+    }
+
+    TextParseOutcome ParseTextFile(AZ::IO::GenericStream& textStream, const TextParserSettings& textParserSettings)
+    {
+        TextParseOutcome parseOutcome;
+        if (!textParserSettings.m_parseTextEntryFunc)
+        {
+            return AZStd::unexpected(TextParseErrorString::format("The Parse Text Entry function is not valid for parsing a text file"));
+        }
+
+        // The TextFile is parsed into a fixed_vector of TextEntryBufferMaxSize below
+        // which avoids performing any dynamic memory allocations during parsing
+        // Only the user supplied callback functions as part of the TexctParserSettings can potentially allocate memory
+        size_t remainingFileLength = textStream.GetLength();
+        if (remainingFileLength == 0)
+        {
+            return AZStd::unexpected(TextParseErrorString::format(R"(text stream "%s" is empty, no text entries exist)" "\n", textStream.GetFilename()));
+        }
+
+        constexpr size_t TextEntryBufferMaxSize = 4096;
+        AZStd::fixed_vector<char, TextEntryBufferMaxSize> textBuffer;
+        size_t readSize = (AZStd::min)(textBuffer.max_size(), remainingFileLength);
+        textBuffer.resize_no_construct(readSize);
+
+        size_t bytesRead = textStream.Read(textBuffer.size(), textBuffer.data());
+        remainingFileLength -= bytesRead;
+
+        do
+        {
+            decltype(textBuffer)::iterator frontIter{};
+            for (frontIter = textBuffer.begin(); frontIter != textBuffer.end();)
+            {
+                if (AZStd::isspace(*frontIter, {}))
+                {
+                    ++frontIter;
+                    continue;
+                }
+
+                // Find end of text entry
+                auto lineStartIter = frontIter;
+                auto lineEndIter = AZStd::find_if(frontIter, textBuffer.end(), textParserSettings.m_tokenDelimiterFunc);
+                bool foundTextEntry = lineEndIter != textBuffer.end();
+                if (!foundTextEntry && remainingFileLength > 0)
+                {
+                    // No newline has been found in the remaining characters in the buffer,
+                    // Read the next chunk(up to TextBufferMaxSize) of the text file and look for a new line
+                    // if the file has remaining content
+                    // Otherwise if the file has no more remaining content, then it is improperly terminated
+                    // text file according to the posix standard
+                    // Therefore the final text after the final newline will be parsed
+                    break;
+                }
+
+
+                AZStd::string_view line(lineStartIter, lineEndIter);
+
+                //! Invoke the parse text entry function to allow the caller to parse a single entry
+                //! If m_invokeParseTextEntryForEmptyLine is false, empty entries are skipped
+                if ((!line.empty() || textParserSettings.m_invokeParseTextEntryForEmptyLine)
+                    && !textParserSettings.m_parseTextEntryFunc(line))
+                {
+                    parseOutcome = AZStd::unexpected(TextParseErrorString::format("The ParseTextEntry callback returned false"
+                        R"( indicating that the parsing of text entry (value="%.*s") has failed)",
+                        AZ_STRING_ARG(line)));
+                    // Do not break and continue to parse the text file
+                }
+
+                // Skip past the text entry character if found
+                frontIter = lineEndIter + (foundTextEntry ? 1 : 0);
+            }
+
+            // Read in more data from the text file if available.
+            // If the parsing was in the middle of a parsing line, then move the remaining data of that line to the beginning
+            // of the fixed_vector buffer
+            if (frontIter == textBuffer.begin())
+            {
+                parseOutcome = AZStd::unexpected(TextParseErrorString::format(R"(The text stream "%s")"
+                    "contains a line which is longer than the max line length of %zu.\n"
+                    R"(Parsing will halt.)" "\n", textStream.GetFilename(), textBuffer.max_size()));
+                break;
+            }
+            const size_t readOffset = AZStd::distance(frontIter, textBuffer.end());
+            if (readOffset > 0)
+            {
+                memmove(textBuffer.begin(), frontIter, readOffset);
+            }
+
+            // Read up to minimum of remaining file length or 4K in the textBuffer
+            readSize = (AZStd::min)(textBuffer.max_size() - readOffset, remainingFileLength);
+            bytesRead = textStream.Read(readSize, textBuffer.data() + readOffset);
+            // resize_no_construct fixes the size value
+            textBuffer.resize_no_construct(readOffset + readSize);
+            remainingFileLength -= bytesRead;
+        } while (bytesRead > 0);
+
+        return parseOutcome;
+    }
+} // namespace AZ::Settings

+ 77 - 0
Code/Framework/AzCore/AzCore/Settings/TextParser.h

@@ -0,0 +1,77 @@
+/*
+ * 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/std/functional.h>
+#include <AzCore/std/string/string_view.h>
+#include <AzCore/std/utility/expected.h>
+
+namespace AZStd
+{
+    // Forward declare basic_fixed_string to avoid including fixed_string.h
+    // in the header
+    template <class Element, size_t MaxElementCount, class Traits>
+    class basic_fixed_string;
+}
+
+namespace AZ::IO
+{
+    class GenericStream;
+}
+
+namespace AZ::Settings
+{
+    //! Settings structure for parsing a text file.
+    //! By default the user supplied ParseTextEntryFunc callback is invoked for every line parsed
+    //! The TokenDelimiterFunc can be modified to change the token used to determine a text entry
+    //! For example if the TokenDelimiterFunc returns true of '\0', then every time a NUL delimited blob is found
+    //! the ParseTextEntryFunc will be invoked
+    struct TextParserSettings
+    {
+        //! Function which is invoked for each "text entry" of the text file
+        //! A text entry is determined as a block of text delimited by the result of the TokenDelimiterFunc
+        //! By default a "text entry" is line based, but other delimiters can be used such as NUL('\0')
+        using ParseTextEntryFunc = AZStd::function<bool(AZStd::string_view token)>;
+
+        //! Callback function which is invoked for each text entry found in the text file
+        //! This is only required member that needs to be set for the TextParserSettings
+        ParseTextEntryFunc m_parseTextEntryFunc;
+
+        //! Returns if the character matches the linefeed character
+        //! @param element character being checked
+        //! @return true if the character matches line feed
+        static bool DefaultTokenDelimiterFunc(char element);
+        //! Token Delimiter which determines a token from a text file
+        //! The default token delimiter is a linefeed '\n'
+        using TokenDelimiterFunc = AZStd::function<bool(char element)>;
+
+        //! Callback function which is invoked to determine the end of the text entry
+        TokenDelimiterFunc m_tokenDelimiterFunc = &DefaultTokenDelimiterFunc;
+
+        //! If set to true, the ParseTextEntryFunc callback will be invoked even when
+        //! the empty text is empty("line" size = 0)
+        bool m_invokeParseTextEntryForEmptyLine = false;
+    };
+
+    // Expected structure which encapsulates the result of the ParseConfigFile function
+    using TextParseErrorString = AZStd::basic_fixed_string<char, 512, AZStd::char_traits<char>>;
+    using TextParseOutcome = AZStd::expected<void, TextParseErrorString>;
+
+    //! Loads text file and invokes parse entry config callback
+    //! This function will NOT allocate any dynamic memory.
+    //! It is safe to call without any AZ Allocators
+    //! NOTE: The max length for any one text entry is 4096
+    //!
+    //! @param stream GenericStream derived class where the configuration data will be read from
+    //! @param textParserSettings structure which contains functions on how the to split "lines" of text file
+    //!        as well as the callback function to invoke on each split "line"
+    //! @return success outcome if the text file was parsed without error,
+    //! otherwise a failure string is provided with the parse error
+    TextParseOutcome ParseTextFile(AZ::IO::GenericStream& stream, const TextParserSettings& textParserSettings);
+} // namespace AZ::Settings

+ 2 - 0
Code/Framework/AzCore/AzCore/azcore_files.cmake

@@ -639,6 +639,8 @@ set(FILES
     Settings/SettingsRegistryScriptUtils.h
     Settings/SettingsRegistryVisitorUtils.cpp
     Settings/SettingsRegistryVisitorUtils.h
+    Settings/TextParser.cpp
+    Settings/TextParser.h
     Slice/SliceAsset.cpp
     Slice/SliceAsset.h
     Slice/SliceAssetHandler.cpp

+ 100 - 70
Code/Framework/AzCore/Platform/Android/AzCore/IO/SystemFile_Android.cpp

@@ -28,10 +28,7 @@
 #include <AzCore/Android/APKFileHandler.h>
 #include <AzCore/Android/Utils.h>
 
-namespace AZ::IO
-{
-
-namespace UnixLikePlatformUtil
+namespace AZ::IO::UnixLikePlatformUtil
 {
     bool CanCreateDirRecursive(char* dirPath)
     {
@@ -43,99 +40,134 @@ namespace UnixLikePlatformUtil
     }
 }
 
-namespace
+namespace AZ::IO
 {
-    static const SystemFile::FileHandleType PlatformSpecificInvalidHandle = AZ_TRAIT_SYSTEMFILE_INVALID_HANDLE;
+    namespace
+    {
+        static const SystemFile::FileHandleType PlatformSpecificInvalidHandle = AZ_TRAIT_SYSTEMFILE_INVALID_HANDLE;
+    }
 }
 
-bool SystemFile::PlatformOpen(int mode, int platformFlags)
+namespace AZ::IO
 {
-    const char* openMode = nullptr;
-    if ((mode & SF_OPEN_READ_ONLY) == SF_OPEN_READ_ONLY)
+     SystemFile SystemFile::GetStdin()
+    {
+        SystemFile systemFile;
+        systemFile.m_handle = stdin;
+        systemFile.m_fileName = "/dev/stdin";
+        // The destructor of the SystemFile will not close the stdin handle
+        systemFile.m_closeOnDestruction = false;
+        return systemFile;
+    }
+
+    SystemFile SystemFile::GetStdout()
     {
-        openMode = "r";
+        SystemFile systemFile;
+        systemFile.m_handle = stdout;
+        systemFile.m_fileName = "/dev/stdout";
+        // The destructor of the SystemFile will not close the stdout handle
+        systemFile.m_closeOnDestruction = false;
+        return systemFile;
     }
-    else if ((mode & SF_OPEN_WRITE_ONLY) == SF_OPEN_WRITE_ONLY)
+    SystemFile SystemFile::GetStderr()
     {
-        if ((mode & SF_OPEN_APPEND) == SF_OPEN_APPEND)
+        SystemFile systemFile;
+        systemFile.m_handle = stderr;
+        systemFile.m_fileName = "/dev/stderr";
+        // The destructor of the SystemFile will not close the stderr handle
+        systemFile.m_closeOnDestruction = false;
+        return systemFile;
+    }
+
+    bool SystemFile::PlatformOpen(int mode, int platformFlags)
+    {
+        const char* openMode = nullptr;
+        if ((mode & SF_OPEN_READ_ONLY) == SF_OPEN_READ_ONLY)
         {
-            openMode = "a+";
+            openMode = "r";
+        }
+        else if ((mode & SF_OPEN_WRITE_ONLY) == SF_OPEN_WRITE_ONLY)
+        {
+            if ((mode & SF_OPEN_APPEND) == SF_OPEN_APPEND)
+            {
+                openMode = "a+";
+            }
+            else if (mode & (SF_OPEN_TRUNCATE | SF_OPEN_CREATE_NEW | SF_OPEN_CREATE))
+            {
+                openMode = "w+";
+            }
+            else
+            {
+                openMode = "w";
+            }
         }
-        else if (mode & (SF_OPEN_TRUNCATE | SF_OPEN_CREATE_NEW | SF_OPEN_CREATE))
+        else if ((mode & SF_OPEN_READ_WRITE) == SF_OPEN_READ_WRITE)
         {
-            openMode = "w+";
+            if ((mode & SF_OPEN_APPEND) == SF_OPEN_APPEND)
+            {
+                openMode = "a+";
+            }
+            else if (mode & (SF_OPEN_TRUNCATE | SF_OPEN_CREATE_NEW | SF_OPEN_CREATE))
+            {
+                openMode = "w+";
+            }
+            else
+            {
+                openMode = "r+";
+            }
         }
         else
         {
-            openMode = "w";
+            return false;
         }
-    }
-    else if ((mode & SF_OPEN_READ_WRITE) == SF_OPEN_READ_WRITE)
-    {
-        if ((mode & SF_OPEN_APPEND) == SF_OPEN_APPEND)
+
+        bool createPath = false;
+        if (mode & (SF_OPEN_CREATE_NEW | SF_OPEN_CREATE))
+        {
+            createPath = (mode & SF_OPEN_CREATE_PATH) == SF_OPEN_CREATE_PATH;
+        }
+
+        bool isApkFile = AZ::Android::Utils::IsApkPath(m_fileName.c_str());
+
+        if (createPath)
         {
-            openMode = "a+";
+            if (isApkFile)
+            {
+                return false;
+            }
+
+            CreatePath(m_fileName.c_str());
         }
-        else if (mode & (SF_OPEN_TRUNCATE | SF_OPEN_CREATE_NEW | SF_OPEN_CREATE))
+
+        if (isApkFile)
         {
-            openMode = "w+";
+            AZ::u64 size = 0;
+            m_handle = AZ::Android::APKFileHandler::Open(m_fileName.c_str(), openMode, size);
         }
         else
         {
-            openMode = "r+";
+            m_handle = fopen(m_fileName.c_str(), openMode);
         }
-    }
-    else
-    {
-        return false;
-    }
-
-    bool createPath = false;
-    if (mode & (SF_OPEN_CREATE_NEW | SF_OPEN_CREATE))
-    {
-        createPath = (mode & SF_OPEN_CREATE_PATH) == SF_OPEN_CREATE_PATH;
-    }
-
-    bool isApkFile = AZ::Android::Utils::IsApkPath(m_fileName.c_str());
 
-    if (createPath)
-    {
-        if (isApkFile)
+        if (m_handle == PlatformSpecificInvalidHandle)
         {
             return false;
         }
 
-        CreatePath(m_fileName.c_str());
-    }
-
-    if (isApkFile)
-    {
-        AZ::u64 size = 0;
-        m_handle = AZ::Android::APKFileHandler::Open(m_fileName.c_str(), openMode, size);
-    }
-    else
-    {
-        m_handle = fopen(m_fileName.c_str(), openMode);
-    }
-
-    if (m_handle == PlatformSpecificInvalidHandle)
-    {
-        return false;
+        return true;
     }
 
-    return true;
-}
-
-void SystemFile::PlatformClose()
-{
-    if (m_handle != PlatformSpecificInvalidHandle)
+    void SystemFile::PlatformClose()
     {
-        fclose(m_handle);
-        m_handle = PlatformSpecificInvalidHandle;
+        if (m_handle != PlatformSpecificInvalidHandle)
+        {
+            fclose(m_handle);
+            m_handle = PlatformSpecificInvalidHandle;
+        }
     }
-}
+} // namespace AZ::IO
 
-namespace Platform::Internal
+namespace AZ::IO::Platform::Internal
 {
     void FindFilesOnDisk(const char* filter, const SystemFile::FindFileCB& cb)
     {
@@ -191,9 +223,9 @@ namespace Platform::Internal
         };
         AZ::Android::APKFileHandler::ParseDirectory(filterDir.c_str(), ParseDirectoryFindFile);
     }
-}
+} // namespace AZ::IO::Platform::Internal
 
-namespace Platform
+namespace AZ::IO::Platform
 {
     using FileHandleType = SystemFile::FileHandleType;
 
@@ -354,8 +386,6 @@ namespace Platform
     }
 } // namespace AZ::IO::Platform
 
-} // namespace AZ::IO
-
 namespace AZ::IO::PosixInternal
 {
     int Pipe(int(&pipeFileDescriptors)[2], int, OpenFlags pipeFlags)

+ 68 - 56
Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/IO/SystemFile_UnixLike.cpp

@@ -25,80 +25,83 @@
 
 namespace AZ::IO
 {
+    const char* SystemFile::GetNullFilename()
+    {
+        return "/dev/null";
+    }
+} // namespace AZ::IO
 
-const char* SystemFile::GetNullFilename()
-{
-    return "/dev/null";
-}
-
-namespace UnixLikePlatformUtil
+namespace AZ::IO::UnixLikePlatformUtil
 {
     // Platform specific helpers
     bool CanCreateDirRecursive(char* dirPath);
-}
+} // AZ::IO::UnixLikePlatformUtil
 
-namespace
+namespace AZ::IO
 {
-    //=========================================================================
-    //  Internal utility to create a folder hierarchy recursively without
-    //  any additional string copies.
-    //  If this function fails (returns false), the error will be available
-    //   via errno on Unix platforms
-    //=========================================================================
-    bool CreateDirRecursive(char* dirPath)
+    namespace
     {
-        if (!UnixLikePlatformUtil::CanCreateDirRecursive(dirPath))
+        //=========================================================================
+        //  Internal utility to create a folder hierarchy recursively without
+        //  any additional string copies.
+        //  If this function fails (returns false), the error will be available
+        //   via errno on Unix platforms
+        //=========================================================================
+        bool CreateDirRecursive(char* dirPath)
         {
-            // Our current platform has told us we have failed
-            return false;
-        }
+            if (!UnixLikePlatformUtil::CanCreateDirRecursive(dirPath))
+            {
+                // Our current platform has told us we have failed
+                return false;
+            }
 
-        int result = mkdir(dirPath, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
-        if (result == 0)
-        {
-            return true;    // Created without error
-        }
-        else if (result == -1)
-        {
-            // If result == -1, the error is stored in errno
-            // http://pubs.opengroup.org/onlinepubs/007908799/xsh/mkdir.html
-            result = errno;
-        }
+            int result = mkdir(dirPath, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
+            if (result == 0)
+            {
+                return true;    // Created without error
+            }
+            else if (result == -1)
+            {
+                // If result == -1, the error is stored in errno
+                // http://pubs.opengroup.org/onlinepubs/007908799/xsh/mkdir.html
+                result = errno;
+            }
 
-        if (result == ENOTDIR || result == ENOENT)
-        {
-            // try to create our parent hierarchy
-            for (size_t i = strlen(dirPath); i > 0; --i)
+            if (result == ENOTDIR || result == ENOENT)
             {
-                if (dirPath[i] == '/' || dirPath[i] == '\\')
+                // try to create our parent hierarchy
+                for (size_t i = strlen(dirPath); i > 0; --i)
                 {
-                    char delimiter = dirPath[i];
-                    dirPath[i] = 0; // null-terminate at the previous slash
-                    bool ret = CreateDirRecursive(dirPath);
-                    dirPath[i] = delimiter; // restore slash
-                    if (ret)
+                    if (dirPath[i] == '/' || dirPath[i] == '\\')
                     {
-                        // now that our parent is created, try to create again
-                        return mkdir(dirPath, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == 0;
+                        char delimiter = dirPath[i];
+                        dirPath[i] = 0; // null-terminate at the previous slash
+                        bool ret = CreateDirRecursive(dirPath);
+                        dirPath[i] = delimiter; // restore slash
+                        if (ret)
+                        {
+                            // now that our parent is created, try to create again
+                            return mkdir(dirPath, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == 0;
+                        }
+                        return false;
                     }
-                    return false;
                 }
+                // if we reach here then there was no parent folder to create, so we failed for other reasons
             }
-            // if we reach here then there was no parent folder to create, so we failed for other reasons
-        }
-        else if (result == EEXIST)
-        {
-            struct stat s;
-            if (stat(dirPath, &s) == 0)
+            else if (result == EEXIST)
             {
-                return s.st_mode & S_IFDIR;
+                struct stat s;
+                if (stat(dirPath, &s) == 0)
+                {
+                    return s.st_mode & S_IFDIR;
+                }
             }
+            return false;
         }
-        return false;
     }
-}
+} // namespace AZ::IO
 
-namespace Platform
+namespace AZ::IO::Platform
 {
     AZ::u64 ModificationTime(const char* fileName)
     {
@@ -211,7 +214,6 @@ namespace Platform
         return false;
     }
 } // namespace AZ::IO::Platform
-} // namespace AZ::IO
 
 namespace AZ::IO::PosixInternal
 {
@@ -228,6 +230,12 @@ namespace AZ::IO::PosixInternal
 
 namespace AZ::IO
 {
+    FileDescriptorCapturer::FileDescriptorCapturer(int sourceDescriptor)
+        : m_sourceDescriptor(sourceDescriptor)
+        , m_pipeData(-1)
+    {
+    }
+
     bool FileDescriptorCapturer::Flush()
     {
         constexpr int PipeBufferSize = DefaultPipeSize;
@@ -290,7 +298,11 @@ namespace AZ::IO
         }
 
         m_redirectCallback = {};
-        m_pipeData = -1;
+        if (m_pipeData != 1)
+        {
+            PosixInternal::Close(static_cast<int>(m_pipeData));
+            m_pipeData = -1;
+        }
 
         // Reset the file descriptors after any flush thread
         // operations have been complete, so correct descriptor value
@@ -310,4 +322,4 @@ namespace AZ::IO
         m_redirectState = RedirectState::Idle;
         // At this point it is now safe to call Start() again on this Capturer
     }
-}
+} // namespace AZ::IO

+ 90 - 60
Code/Framework/AzCore/Platform/Common/UnixLikeDefault/AzCore/IO/SystemFile_UnixLikeDefault.cpp

@@ -23,10 +23,7 @@
 #include <dirent.h>
 
 
-namespace AZ::IO
-{
-
-namespace UnixLikePlatformUtil
+namespace AZ::IO::UnixLikePlatformUtil
 {
     bool CanCreateDirRecursive(char*)
     {
@@ -35,80 +32,115 @@ namespace UnixLikePlatformUtil
     }
 }
 
-namespace
+namespace AZ::IO
 {
-    static const SystemFile::FileHandleType PlatformSpecificInvalidHandle = AZ_TRAIT_SYSTEMFILE_INVALID_HANDLE;
+    namespace
+    {
+        static const SystemFile::FileHandleType PlatformSpecificInvalidHandle = AZ_TRAIT_SYSTEMFILE_INVALID_HANDLE;
+    }
 }
 
-bool SystemFile::PlatformOpen(int mode, int platformFlags)
+namespace AZ::IO
 {
-    int desiredAccess = 0;
-    int permissions = S_IRWXU | S_IRGRP | S_IROTH;
-
-    bool createPath = false;
-    if ((mode & SF_OPEN_READ_WRITE) == SF_OPEN_READ_WRITE)
+     SystemFile SystemFile::GetStdin()
     {
-        desiredAccess = O_RDWR;
-    }
-    else if ((mode & SF_OPEN_READ_ONLY) == SF_OPEN_READ_ONLY)
-    {
-        desiredAccess = O_RDONLY;
-    }
-    else if ((mode & SF_OPEN_WRITE_ONLY) == SF_OPEN_WRITE_ONLY || (mode & SF_OPEN_APPEND))
-    {
-        desiredAccess = O_WRONLY;
-    }
-    else
-    {
-        return false;
+        SystemFile systemFile;
+        systemFile.m_handle = STDIN_FILENO;
+        systemFile.m_fileName = "/dev/stdin";
+        // The destructor of the SystemFile will not close the stdin handle
+        systemFile.m_closeOnDestruction = false;
+        return systemFile;
     }
 
-    if ((mode & SF_OPEN_CREATE_NEW) == SF_OPEN_CREATE_NEW)
+    SystemFile SystemFile::GetStdout()
     {
-        desiredAccess |= O_CREAT | O_EXCL;
-        createPath = (mode & SF_OPEN_CREATE_PATH) == SF_OPEN_CREATE_PATH;
+        SystemFile systemFile;
+        systemFile.m_handle = STDOUT_FILENO;
+        systemFile.m_fileName = "/dev/stdout";
+        // The destructor of the SystemFile will not close the stdout handle
+        systemFile.m_closeOnDestruction = false;
+        return systemFile;
     }
-    else if ((mode & SF_OPEN_CREATE) ==  SF_OPEN_CREATE)
+    SystemFile SystemFile::GetStderr()
     {
-        desiredAccess |= O_CREAT | O_TRUNC;
-        createPath = (mode & SF_OPEN_CREATE_PATH) == SF_OPEN_CREATE_PATH;
-    }
-    else if ((mode & SF_OPEN_TRUNCATE))
-    {
-        desiredAccess |= O_TRUNC;
+        SystemFile systemFile;
+        systemFile.m_handle = STDERR_FILENO;
+        systemFile.m_fileName = "/dev/stderr";
+        // The destructor of the SystemFile will not close the stderr handle
+        systemFile.m_closeOnDestruction = false;
+        return systemFile;
     }
 
-    if (createPath)
+    bool SystemFile::PlatformOpen(int mode, int platformFlags)
     {
-        CreatePath(m_fileName.c_str());
-    }
-    m_handle = open(m_fileName.c_str(), desiredAccess, permissions);
+        int desiredAccess = 0;
+        int permissions = S_IRWXU | S_IRGRP | S_IROTH;
 
-    if (m_handle == PlatformSpecificInvalidHandle)
-    {
-        return false;
-    }
-    else
-    {
-        if (mode & SF_OPEN_APPEND)
+        bool createPath = false;
+        if ((mode & SF_OPEN_READ_WRITE) == SF_OPEN_READ_WRITE)
         {
-            lseek(m_handle, 0, SEEK_END);
+            desiredAccess = O_RDWR;
+        }
+        else if ((mode & SF_OPEN_READ_ONLY) == SF_OPEN_READ_ONLY)
+        {
+            desiredAccess = O_RDONLY;
+        }
+        else if ((mode & SF_OPEN_WRITE_ONLY) == SF_OPEN_WRITE_ONLY || (mode & SF_OPEN_APPEND))
+        {
+            desiredAccess = O_WRONLY;
+        }
+        else
+        {
+            return false;
         }
-    }
 
-    return true;
-}
+        if ((mode & SF_OPEN_CREATE_NEW) == SF_OPEN_CREATE_NEW)
+        {
+            desiredAccess |= O_CREAT | O_EXCL;
+            createPath = (mode & SF_OPEN_CREATE_PATH) == SF_OPEN_CREATE_PATH;
+        }
+        else if ((mode & SF_OPEN_CREATE) ==  SF_OPEN_CREATE)
+        {
+            desiredAccess |= O_CREAT | O_TRUNC;
+            createPath = (mode & SF_OPEN_CREATE_PATH) == SF_OPEN_CREATE_PATH;
+        }
+        else if ((mode & SF_OPEN_TRUNCATE))
+        {
+            desiredAccess |= O_TRUNC;
+        }
 
-void SystemFile::PlatformClose()
-{
-    if (m_handle != PlatformSpecificInvalidHandle)
+        if (createPath)
+        {
+            CreatePath(m_fileName.c_str());
+        }
+        m_handle = open(m_fileName.c_str(), desiredAccess, permissions);
+
+        if (m_handle == PlatformSpecificInvalidHandle)
+        {
+            return false;
+        }
+        else
+        {
+            if (mode & SF_OPEN_APPEND)
+            {
+                lseek(m_handle, 0, SEEK_END);
+            }
+        }
+
+        return true;
+    }
+
+    void SystemFile::PlatformClose()
     {
-        close(m_handle);
-        m_handle = PlatformSpecificInvalidHandle;
+        if (m_handle != PlatformSpecificInvalidHandle)
+        {
+            close(m_handle);
+            m_handle = PlatformSpecificInvalidHandle;
+        }
     }
-}
+} // namespace AZ::IO
 
-namespace Platform
+namespace AZ::IO::Platform
 {
     using FileHandleType = AZ::IO::SystemFile::FileHandleType;
 
@@ -240,6 +272,4 @@ namespace Platform
         }
         return false;
     }
-}
-
-} // namespace AZ::IO
+} // namespace AZ::IO::Platform

+ 161 - 124
Code/Framework/AzCore/Platform/Common/WinAPI/AzCore/IO/SystemFile_WinAPI.cpp

@@ -19,156 +19,188 @@
 namespace AZ::IO
 {
     using FixedMaxPathWString = AZStd::fixed_wstring<MaxPathLength>;
-namespace
-{
-    //=========================================================================
-    // GetAttributes
-    //  Internal utility to avoid code duplication. Returns result of win32
-    //  GetFileAttributes
-    //=========================================================================
-    DWORD GetAttributes(const char* fileName)
-    {
-        FixedMaxPathWString fileNameW;
-        AZStd::to_wstring(fileNameW, fileName);
-        return GetFileAttributesW(fileNameW.c_str());
-    }
+    namespace
+    {
+        //=========================================================================
+        // GetAttributes
+        //  Internal utility to avoid code duplication. Returns result of win32
+        //  GetFileAttributes
+        //=========================================================================
+        DWORD GetAttributes(const char* fileName)
+        {
+            FixedMaxPathWString fileNameW;
+            AZStd::to_wstring(fileNameW, fileName);
+            return GetFileAttributesW(fileNameW.c_str());
+        }
+
+        //=========================================================================
+        // SetAttributes
+        //  Internal utility to avoid code duplication. Returns result of win32
+        //  SetFileAttributes
+        //=========================================================================
+        BOOL SetAttributes(const char* fileName, DWORD fileAttributes)
+        {
+            FixedMaxPathWString fileNameW;
+            AZStd::to_wstring(fileNameW, fileName);
+            return SetFileAttributesW(fileNameW.c_str(), fileAttributes);
+        }
+
+        //=========================================================================
+        // CreateDirRecursive
+        // [2/3/2013]
+        //  Internal utility to create a folder hierarchy recursively without
+        //  any additional string copies.
+        //  If this function fails (returns false), the error will be available via:
+        //   * GetLastError() on Windows-like platforms
+        //   * errno on Unix platforms
+        //=========================================================================
+        bool CreateDirRecursive(AZ::IO::FixedMaxPathWString& dirPath)
+        {
+            if (CreateDirectoryW(dirPath.c_str(), nullptr))
+            {
+                return true;    // Created without error
+            }
+            DWORD error = GetLastError();
+            if (error == ERROR_PATH_NOT_FOUND)
+            {
+                // try to create our parent hierarchy
+                if (size_t i = dirPath.find_last_of(LR"(/\)"); i != FixedMaxPathWString::npos)
+                {
+                    wchar_t delimiter = dirPath[i];
+                    dirPath[i] = 0; // null-terminate at the previous slash
+                    const bool ret = CreateDirRecursive(dirPath);
+                    dirPath[i] = delimiter; // restore slash
+                    if (ret)
+                    {
+                        // now that our parent is created, try to create again
+                        return CreateDirectoryW(dirPath.c_str(), nullptr) != 0;
+                    }
+                }
+                // if we reach here then there was no parent folder to create, so we failed for other reasons
+            }
+            else if (error == ERROR_ALREADY_EXISTS)
+            {
+                DWORD attributes = GetFileAttributesW(dirPath.c_str());
+                return attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
+            }
+            return false;
+        }
 
-    //=========================================================================
-    // SetAttributes
-    //  Internal utility to avoid code duplication. Returns result of win32
-    //  SetFileAttributes
-    //=========================================================================
-    BOOL SetAttributes(const char* fileName, DWORD fileAttributes)
-    {
-        FixedMaxPathWString fileNameW;
-        AZStd::to_wstring(fileNameW, fileName);
-        return SetFileAttributesW(fileNameW.c_str(), fileAttributes);
+        static const SystemFile::FileHandleType PlatformSpecificInvalidHandle = INVALID_HANDLE_VALUE;
     }
+} // namespace AZ::IO
 
-    //=========================================================================
-    // CreateDirRecursive
-    // [2/3/2013]
-    //  Internal utility to create a folder hierarchy recursively without
-    //  any additional string copies.
-    //  If this function fails (returns false), the error will be available via:
-    //   * GetLastError() on Windows-like platforms
-    //   * errno on Unix platforms
-    //=========================================================================
-    bool CreateDirRecursive(AZ::IO::FixedMaxPathWString& dirPath)
+namespace AZ::IO
+{
+    bool SystemFile::PlatformOpen(int mode, int platformFlags)
     {
-        if (CreateDirectoryW(dirPath.c_str(), nullptr))
+        DWORD dwDesiredAccess = 0;
+        DWORD dwShareMode = FILE_SHARE_READ;
+        DWORD dwFlagsAndAttributes = platformFlags;
+        DWORD dwCreationDisposition = OPEN_EXISTING;
+
+        bool createPath = false;
+        if (mode & SF_OPEN_READ_ONLY)
         {
-            return true;    // Created without error
+            dwDesiredAccess |= GENERIC_READ;
+            // Always open files for reading with file shared write flag otherwise it may result in a sharing violation if
+            // either some process has already opened the file for writing or if some process will later on try to open the file for writing.
+            dwShareMode |= FILE_SHARE_WRITE;
         }
-        DWORD error = GetLastError();
-        if (error == ERROR_PATH_NOT_FOUND)
+        if (mode & SF_OPEN_READ_WRITE)
         {
-            // try to create our parent hierarchy
-            if (size_t i = dirPath.find_last_of(LR"(/\)"); i != FixedMaxPathWString::npos)
-            {
-                wchar_t delimiter = dirPath[i];
-                dirPath[i] = 0; // null-terminate at the previous slash
-                const bool ret = CreateDirRecursive(dirPath);
-                dirPath[i] = delimiter; // restore slash
-                if (ret)
-                {
-                    // now that our parent is created, try to create again
-                    return CreateDirectoryW(dirPath.c_str(), nullptr) != 0;
-                }
-            }
-            // if we reach here then there was no parent folder to create, so we failed for other reasons
+            dwDesiredAccess |= GENERIC_READ;
         }
-        else if (error == ERROR_ALREADY_EXISTS)
+        if ((mode & SF_OPEN_WRITE_ONLY) || (mode & SF_OPEN_READ_WRITE) || (mode & SF_OPEN_APPEND))
         {
-            DWORD attributes = GetFileAttributesW(dirPath.c_str());
-            return attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
+            dwDesiredAccess |= GENERIC_WRITE;
         }
-        return false;
-    }
 
-    static const SystemFile::FileHandleType PlatformSpecificInvalidHandle = INVALID_HANDLE_VALUE;
-}
+        if ((mode & SF_OPEN_CREATE_NEW))
+        {
+            dwCreationDisposition = CREATE_NEW;
+            createPath = (mode & SF_OPEN_CREATE_PATH) == SF_OPEN_CREATE_PATH;
+        }
+        else if ((mode & SF_OPEN_CREATE))
+        {
+            dwCreationDisposition = CREATE_ALWAYS;
+            createPath = (mode & SF_OPEN_CREATE_PATH) == SF_OPEN_CREATE_PATH;
+        }
+        else if ((mode & SF_OPEN_TRUNCATE))
+        {
+            dwCreationDisposition = TRUNCATE_EXISTING;
+        }
 
+        if (createPath)
+        {
+            CreatePath(m_fileName.c_str());
+        }
 
-bool SystemFile::PlatformOpen(int mode, int platformFlags)
-{
-    DWORD dwDesiredAccess = 0;
-    DWORD dwShareMode = FILE_SHARE_READ;
-    DWORD dwFlagsAndAttributes = platformFlags;
-    DWORD dwCreationDisposition = OPEN_EXISTING;
-
-    bool createPath = false;
-    if (mode & SF_OPEN_READ_ONLY)
-    {
-        dwDesiredAccess |= GENERIC_READ;
-        // Always open files for reading with file shared write flag otherwise it may result in a sharing violation if 
-        // either some process has already opened the file for writing or if some process will later on try to open the file for writing. 
-        dwShareMode |= FILE_SHARE_WRITE;
-    }
-    if (mode & SF_OPEN_READ_WRITE)
-    {
-        dwDesiredAccess |= GENERIC_READ;
-    }
-    if ((mode & SF_OPEN_WRITE_ONLY) || (mode & SF_OPEN_READ_WRITE) || (mode & SF_OPEN_APPEND))
-    {
-        dwDesiredAccess |= GENERIC_WRITE;
-    }
+        AZ::IO::FixedMaxPathWString fileNameW;
+        AZStd::to_wstring(fileNameW, m_fileName);
+        m_handle = INVALID_HANDLE_VALUE;
+        m_handle = CreateFileW(fileNameW.c_str(), dwDesiredAccess, dwShareMode, 0, dwCreationDisposition, dwFlagsAndAttributes, 0);
 
-    if ((mode & SF_OPEN_CREATE_NEW))
-    {
-        dwCreationDisposition = CREATE_NEW;
-        createPath = (mode & SF_OPEN_CREATE_PATH) == SF_OPEN_CREATE_PATH;
-    }
-    else if ((mode & SF_OPEN_CREATE))
-    {
-        dwCreationDisposition = CREATE_ALWAYS;
-        createPath = (mode & SF_OPEN_CREATE_PATH) == SF_OPEN_CREATE_PATH;
+        if (m_handle == INVALID_HANDLE_VALUE)
+        {
+            return false;
+        }
+        else
+        {
+            if (mode & SF_OPEN_APPEND)
+            {
+                SetFilePointer(m_handle, 0, NULL, FILE_END);
+            }
+        }
+
+        return true;
     }
-    else if ((mode & SF_OPEN_TRUNCATE))
+
+    void SystemFile::PlatformClose()
     {
-        dwCreationDisposition = TRUNCATE_EXISTING;
+        if (m_handle != PlatformSpecificInvalidHandle)
+        {
+            CloseHandle(m_handle);
+            m_handle = INVALID_HANDLE_VALUE;
+        }
     }
 
-    if (createPath)
+    const char* SystemFile::GetNullFilename()
     {
-        CreatePath(m_fileName.c_str());
+        return "NUL";
     }
 
-    AZ::IO::FixedMaxPathWString fileNameW;
-    AZStd::to_wstring(fileNameW, m_fileName);
-    m_handle = INVALID_HANDLE_VALUE;
-    m_handle = CreateFileW(fileNameW.c_str(), dwDesiredAccess, dwShareMode, 0, dwCreationDisposition, dwFlagsAndAttributes, 0);
-
-    if (m_handle == INVALID_HANDLE_VALUE)
+    SystemFile SystemFile::GetStdin()
     {
-        return false;
+        SystemFile systemFile;
+        systemFile.m_handle = GetStdHandle(STD_INPUT_HANDLE);
+        systemFile.m_fileName = "/dev/stdin";
+        // The destructor of the SystemFile will not close the stdin handle
+        systemFile.m_closeOnDestruction = false;
+        return systemFile;
     }
-    else
+
+    SystemFile SystemFile::GetStdout()
     {
-        if (mode & SF_OPEN_APPEND)
-        {
-            SetFilePointer(m_handle, 0, NULL, FILE_END);
-        }
+        SystemFile systemFile;
+        systemFile.m_handle = GetStdHandle(STD_OUTPUT_HANDLE);
+        systemFile.m_fileName = "/dev/stdout";
+        // The destructor of the SystemFile will not close the stdout handle
+        systemFile.m_closeOnDestruction = false;
+        return systemFile;
     }
-
-    return true;
-}
-
-void SystemFile::PlatformClose()
-{
-    if (m_handle != PlatformSpecificInvalidHandle)
+    SystemFile SystemFile::GetStderr()
     {
-        CloseHandle(m_handle);
-        m_handle = INVALID_HANDLE_VALUE;
+        SystemFile systemFile;
+        systemFile.m_handle = GetStdHandle(STD_ERROR_HANDLE);
+        systemFile.m_fileName = "/dev/stderr";
+        // The destructor of the SystemFile will not close the stderr handle
+        systemFile.m_closeOnDestruction = false;
+        return systemFile;
     }
-}
-
-const char* SystemFile::GetNullFilename()
-{
-    return "NUL";
-}
+} // namespace AZ::IO
 
-namespace Platform
+namespace AZ::IO::Platform
 {
     using FileHandleType = AZ::IO::SystemFile::FileHandleType;
 
@@ -477,8 +509,6 @@ namespace Platform
 
         return false;
     }
-
-}
 } // namespace AZ::IO
 
 namespace AZ::IO::PosixInternal
@@ -517,6 +547,13 @@ namespace AZ::IO::Internal
 namespace AZ::IO
 {
     // FileDescriptorCapturer WinAPI Impl
+
+    FileDescriptorCapturer::FileDescriptorCapturer(int sourceDescriptor)
+        : m_sourceDescriptor(sourceDescriptor)
+        , m_pipeData(0)
+    {
+    }
+
     void FileDescriptorCapturer::Start(OutputRedirectVisitor redirectCallback,
         AZStd::chrono::milliseconds waitTimeout,
         int pipeSize)

+ 5 - 4
Code/Framework/AzCore/Tests/Asset/AssetManagerLoadingTests.cpp

@@ -1079,14 +1079,11 @@ namespace UnitTest
                 }
                 AZStd::this_thread::yield();
             }
-            EXPECT_EQ(m_testAssetManager->GetRemainingJobs(), 0);
 
             EXPECT_EQ(asset1Container->IsReady(), true);
             EXPECT_EQ(asset2Container->IsReady(), true);
             EXPECT_EQ(asset3Container->IsReady(), true);
 
-            EXPECT_EQ(m_testAssetManager->GetRemainingJobs(), 0);
-
             auto rootAsset = asset1Container->GetRootAsset();
             EXPECT_EQ(rootAsset->GetId(), MyAsset1Id);
             EXPECT_EQ(rootAsset->GetType(), azrtti_typeid<AssetWithAssetReference>());
@@ -1116,7 +1113,6 @@ namespace UnitTest
 
             // We've now created the dependencies for each asset in the container as well
             EXPECT_EQ(m_assetHandlerAndCatalog->m_numCreations, NumTestAssets * AssetsPerContainer);
-            EXPECT_EQ(m_testAssetManager->GetRemainingJobs(), 0);
             EXPECT_EQ(asset1CopyContainer->GetDependencies().size(), 1);
 
             asset1Container = {};
@@ -1126,6 +1122,11 @@ namespace UnitTest
 
             asset1CopyContainer = {};
             asset1 = {};
+
+            // Make sure events are dispatched after releasing the asset handles, so they get destroyed.
+            // This addresses a rare race condition, a test failure roughly once every 2,000 runs on Linux.
+            m_testAssetManager->DispatchEvents();
+
             // We've released the references for one asset and its dependency
             EXPECT_EQ(m_assetHandlerAndCatalog->m_numDestructions, AssetsPerContainer);
             asset1 = m_testAssetManager->FindOrCreateAsset(MyAsset1Id, azrtti_typeid<AssetWithAssetReference>(), AZ::Data::AssetLoadBehavior::Default);

+ 0 - 5
Code/Framework/AzCore/Tests/Asset/BaseAssetManagerTest.cpp

@@ -45,11 +45,6 @@ namespace UnitTest
         return AZ::Data::AssetData::AssetStatus::NotLoaded;
     }
 
-    size_t TestAssetManager::GetRemainingJobs() const
-    {
-        return m_activeJobs.size();
-    }
-
     const AZ::Data::AssetManager::OwnedAssetContainerMap& TestAssetManager::GetAssetContainers() const
     {
         return m_ownedAssetContainers;

+ 0 - 3
Code/Framework/AzCore/Tests/Asset/BaseAssetManagerTest.h

@@ -35,9 +35,6 @@ namespace UnitTest
         // Find the current status of the reload
         AZ::Data::AssetData::AssetStatus GetReloadStatus(const AssetId& assetId);
 
-        // Get the number of jobs left to process
-        size_t GetRemainingJobs() const;
-
         const AZ::Data::AssetManager::OwnedAssetContainerMap& GetAssetContainers() const;
 
         const AssetMap& GetAssets() const;

+ 60 - 0
Code/Framework/AzCore/Tests/Platform/Android/Tests/IO/SystemFileTest_Android.cpp

@@ -0,0 +1,60 @@
+/*
+ * 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/IO/SystemFile.h>
+#include <AzCore/IO/Path/Path.h>
+#include <AzCore/UnitTest/TestTypes.h>
+#include <AzTest/Utils.h>
+
+namespace UnitTest
+{
+    class SystemFilePlatformFixture
+        : public LeakDetectionFixture
+    {
+    protected:
+        AZ::Test::ScopedAutoTempDirectory m_tempDirectory;
+    };
+
+    TEST_F(SystemFilePlatformFixture, SkipCloseOnDestructionMode_DoesNotCloseOpenHandle_Succeeds)
+    {
+        constexpr AZStd::string_view testData{ "Testing 1 2 3\n"};
+        auto testFileName = m_tempDirectory.GetDirectoryAsFixedMaxPath() / "TestFile.txt";
+        {
+            // Seed the test file with data
+            AZ::IO::SystemFile testFile(testFileName.c_str(),
+                AZ::IO::SystemFile::SF_OPEN_CREATE | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY);
+            ASSERT_TRUE(testFile.IsOpen());
+
+            testFile.Write(testData.data(), testData.size());
+        }
+
+        AZ::IO::SystemFile::FileHandleType fileHandle;
+        {
+            // Open the test file in read mode and retrieve the file handle
+            // The skip close on destruction option should skip closing the file
+            AZ::IO::SystemFile testFile(testFileName.c_str(),
+                AZ::IO::SystemFile::SF_OPEN_READ_ONLY | AZ::IO::SystemFile::SF_SKIP_CLOSE_ON_DESTRUCTION);
+            ASSERT_TRUE(testFile.IsOpen());
+
+            fileHandle = testFile.NativeHandle();
+        }
+
+        // Use the PosixInternal::Read function to read from from the open descriptor
+        AZStd::string readData;
+        auto ReadFromFile = [fileHandle](char* buffer, size_t capacity) -> size_t
+        {
+            return fread(buffer, 1, capacity, fileHandle);
+        };
+        // Make sure the capacity can fit all the data written to the test file
+        readData.resize_and_overwrite(testData.size(), AZStd::move(ReadFromFile));
+
+        // Close the file descriptor
+        fclose(fileHandle);
+
+        EXPECT_EQ(testData, readData);
+    }
+}   // namespace UnitTest

+ 1 - 0
Code/Framework/AzCore/Tests/Platform/Android/platform_android_files.cmake

@@ -8,6 +8,7 @@
 
 set(FILES
     ../Common/UnixLike/Tests/Process/ProcessInfoTests_UnixLike.cpp
+    Tests/IO/SystemFileTest_Android.cpp
     Tests/UtilsTests_Android.cpp
     Tests/Memory/AllocatorBenchmarks_Android.cpp
 )

+ 60 - 0
Code/Framework/AzCore/Tests/Platform/Common/UnixLike/Tests/IO/SystemFileTest_UnixLike.cpp

@@ -0,0 +1,60 @@
+/*
+ * 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/IO/SystemFile.h>
+#include <AzCore/IO/Path/Path.h>
+#include <AzCore/UnitTest/TestTypes.h>
+#include <AzTest/Utils.h>
+
+namespace UnitTest
+{
+    class SystemFilePlatformFixture
+        : public LeakDetectionFixture
+    {
+    protected:
+        AZ::Test::ScopedAutoTempDirectory m_tempDirectory;
+    };
+
+    TEST_F(SystemFilePlatformFixture, SkipCloseOnDestructionMode_DoesNotCloseOpenHandle_Succeeds)
+    {
+        constexpr AZStd::string_view testData{ "Testing 1 2 3\n"};
+        auto testFileName = m_tempDirectory.GetDirectoryAsFixedMaxPath() / "TestFile.txt";
+        {
+            // Seed the test file with data
+            AZ::IO::SystemFile testFile(testFileName.c_str(),
+                AZ::IO::SystemFile::SF_OPEN_CREATE | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY);
+            ASSERT_TRUE(testFile.IsOpen());
+
+            testFile.Write(testData.data(), testData.size());
+        }
+
+        AZ::IO::SystemFile::FileHandleType fileHandle;
+        {
+            // Open the test file in read mode and retrieve the file handle
+            // The skip close on destruction option should skip closing the file
+            AZ::IO::SystemFile testFile(testFileName.c_str(),
+                AZ::IO::SystemFile::SF_OPEN_READ_ONLY | AZ::IO::SystemFile::SF_SKIP_CLOSE_ON_DESTRUCTION);
+            ASSERT_TRUE(testFile.IsOpen());
+
+            fileHandle = testFile.NativeHandle();
+        }
+
+        // Use the PosixInternal::Read function to read from from the open descriptor
+        AZStd::string readData;
+        auto ReadFromFile = [fileHandle](char* buffer, size_t capacity) -> size_t
+        {
+            return AZ::IO::PosixInternal::Read(fileHandle, buffer, capacity);
+        };
+        // Make sure the capacity can fit all the data written to the test file
+        readData.resize_and_overwrite(testData.size(), AZStd::move(ReadFromFile));
+
+        // Close the file descriptor
+        AZ::IO::PosixInternal::Close(fileHandle);
+
+        EXPECT_EQ(testData, readData);
+    }
+}   // namespace UnitTest

+ 67 - 0
Code/Framework/AzCore/Tests/Platform/Common/WinAPI/Tests/IO/SystemFileTest_WinAPI.cpp

@@ -0,0 +1,67 @@
+/*
+ * 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/PlatformIncl.h>
+
+#include <AzCore/IO/SystemFile.h>
+#include <AzCore/IO/Path/Path.h>
+#include <AzCore/UnitTest/TestTypes.h>
+#include <AzTest/Utils.h>
+
+namespace UnitTest
+{
+    class SystemFilePlatformFixture
+        : public LeakDetectionFixture
+    {
+    protected:
+        AZ::Test::ScopedAutoTempDirectory m_tempDirectory;
+    };
+
+    TEST_F(SystemFilePlatformFixture, SkipCloseOnDestructionMode_DoesNotCloseOpenHandle_Succeeds)
+    {
+        constexpr AZStd::string_view testData{ "Testing 1 2 3\n"};
+        auto testFileName = m_tempDirectory.GetDirectoryAsFixedMaxPath() / "TestFile.txt";
+        {
+            // Seed the test file with data
+            AZ::IO::SystemFile testFile(testFileName.c_str(),
+                AZ::IO::SystemFile::SF_OPEN_CREATE | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY);
+            ASSERT_TRUE(testFile.IsOpen());
+
+            testFile.Write(testData.data(), testData.size());
+        }
+
+        AZ::IO::SystemFile::FileHandleType fileHandle;
+        {
+            // Open the test file in read mode and retrieve the file handle
+            // The skip close on destruction option should skip closing the file
+            AZ::IO::SystemFile testFile(testFileName.c_str(),
+                AZ::IO::SystemFile::SF_OPEN_READ_ONLY | AZ::IO::SystemFile::SF_SKIP_CLOSE_ON_DESTRUCTION);
+            ASSERT_TRUE(testFile.IsOpen());
+
+            fileHandle = testFile.NativeHandle();
+        }
+
+        // Use the PosixInternal::Read function to read from from the open descriptor
+        AZStd::string readData;
+        auto ReadFromFile = [fileHandle](char* buffer, size_t capacity) -> DWORD
+        {
+            DWORD numBytesRead{};
+            if (!ReadFile(fileHandle, buffer, static_cast<DWORD>(capacity), &numBytesRead, nullptr))
+            {
+                return 0;
+            }
+            return numBytesRead;
+        };
+        // Make sure the capacity can fit all the data written to the test file
+        readData.resize_and_overwrite(testData.size(), AZStd::move(ReadFromFile));
+
+        // Close the file handle
+        CloseHandle(fileHandle);
+
+        EXPECT_EQ(testData, readData);
+    }
+}   // namespace UnitTest

+ 1 - 0
Code/Framework/AzCore/Tests/Platform/Linux/platform_linux_files.cmake

@@ -7,6 +7,7 @@
 #
 
 set(FILES
+    ../Common/UnixLike/Tests/IO/SystemFileTest_UnixLike.cpp
     ../Common/UnixLike/Tests/Process/ProcessInfoTests_UnixLike.cpp
     Tests/UtilsTests_Linux.cpp
     ../Common/UnixLike/Tests/UtilsTests_UnixLike.cpp

+ 1 - 0
Code/Framework/AzCore/Tests/Platform/Mac/platform_mac_files.cmake

@@ -9,6 +9,7 @@
 set(FILES
     ../Common/Apple/Tests/Process/ProcessInfoTests_Apple.cpp
     ../Common/Apple/Tests/UtilsTests_Apple.cpp
+    ../Common/UnixLike/Tests/IO/SystemFileTest_UnixLike.cpp
     ../Common/UnixLike/Tests/UtilsTests_UnixLike.cpp
     ../Common/Apple/Tests/Memory/AllocatorBenchmarks_Apple.cpp
 )

+ 1 - 0
Code/Framework/AzCore/Tests/Platform/Windows/platform_windows_files.cmake

@@ -7,6 +7,7 @@
 #
 
 set(FILES
+    ../Common/WinAPI/Tests/IO/SystemFileTest_WinAPI.cpp
     ../Common/WinAPI/Tests/Process/ProcessInfoTests_WinAPI.cpp
     ../Common/WinAPI/Tests/UtilsTests_WinAPI.cpp
     Tests/IO/Streamer/StorageDriveTests_Windows.cpp

+ 1 - 0
Code/Framework/AzCore/Tests/Platform/iOS/platform_ios_files.cmake

@@ -9,6 +9,7 @@
 set(FILES
     ../Common/Apple/Tests/Process/ProcessInfoTests_Apple.cpp
     ../Common/Apple/Tests/UtilsTests_Apple.cpp
+    ../Common/UnixLike/Tests/IO/SystemFileTest_UnixLike.cpp
     ../Common/UnixLike/Tests/UtilsTests_UnixLike.cpp
     ../Common/Apple/Tests/Memory/AllocatorBenchmarks_Apple.cpp
 )

+ 21 - 0
Code/Framework/AzCore/Tests/Settings/CommandLineTests.cpp

@@ -426,6 +426,27 @@ namespace UnitTest
         EXPECT_STREQ("2", commandLine.GetSwitchValue("foo").c_str());
         EXPECT_STREQ("2", commandLine.GetSwitchValue("foo", 1).c_str());
         EXPECT_STREQ("1", commandLine.GetSwitchValue("foo", 0).c_str());
+    }
+
+    TEST_F(CommandLineTests, ArgumentsParsed_AfterDoubleDash_ArePositionalArgumentsOnly)
+    {
+        AZ::CommandLine commandLine{ "-" };
+
+        constexpr AZStd::string_view argValues[] =
+        {
+            "programname.exe", "--foo=1", "--", "--foo=2", "bar", "--", "baz"
+        };
+
+        commandLine.Parse(argValues);
 
+        EXPECT_EQ(commandLine.GetNumSwitchValues("foo"), 1);
+        EXPECT_EQ("1", commandLine.GetSwitchValue("foo"));
+        ASSERT_EQ(5, commandLine.GetNumMiscValues());
+        // The first Misc entry is the executable name "programname.exe"
+        // ignore checking that entry since it is not relevant to this test
+        EXPECT_EQ("--foo=2",commandLine.GetMiscValue(1));
+        EXPECT_EQ("bar", commandLine.GetMiscValue(2));
+        EXPECT_EQ("--", commandLine.GetMiscValue(3));
+        EXPECT_EQ("baz", commandLine.GetMiscValue(4));
     }
 }   // namespace UnitTest

+ 169 - 0
Code/Framework/AzCore/Tests/Settings/TextParserTests.cpp

@@ -0,0 +1,169 @@
+/*
+ * 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/IO/ByteContainerStream.h>
+#include <AzCore/Settings/TextParser.h>
+#include <AzCore/std/string/conversions.h>
+#include <AzCore/std/containers/fixed_unordered_map.h>
+#include <AzCore/UnitTest/TestTypes.h>
+
+namespace UnitTest
+{
+    struct TextFileParams
+    {
+        AZStd::string_view m_testTextFileName;
+        AZStd::string_view m_testTextContents;
+        // The following test below will not have more than 20
+        AZStd::fixed_vector<AZStd::string_view, 20> m_expectedLines;
+    };
+
+    class TextParserTestFixture
+        : public LeakDetectionFixture
+    {
+    protected:
+        AZStd::string m_textBuffer;
+        AZ::IO::ByteContainerStream<AZStd::string> m_textStream{ &m_textBuffer };
+    };
+
+    // Parameterized test fixture for the TextFileParams
+    class TextParserParamFixture
+        : public TextParserTestFixture
+        , public ::testing::WithParamInterface<TextFileParams>
+    {
+        void SetUp() override
+        {
+            auto textFileParam = GetParam();
+
+            // Create the test text stream
+            m_textStream.Write(textFileParam.m_testTextContents.size(),
+                textFileParam.m_testTextContents.data());
+            // Seek back to the beginning of the stream for test to read written data
+            m_textStream.Seek(0, AZ::IO::GenericStream::ST_SEEK_BEGIN);
+        }
+    };
+
+    TEST_F(TextParserTestFixture, ParseTextFile_WithEmptyFunction_Fails)
+    {
+        AZ::Settings::TextParserSettings parserSettings{ AZ::Settings::TextParserSettings::ParseTextEntryFunc{} };
+
+        m_textBuffer = R"(project_path=/TestProject
+            engine_path=/TestEngine
+        )";
+        EXPECT_FALSE(AZ::Settings::ParseTextFile(m_textStream, parserSettings));
+    }
+
+    TEST_F(TextParserTestFixture, ParseTextFile_WithParseTextEntryFunctionWhichAlwaysReturnsFalse_Fails)
+    {
+        auto parseTextEntry = [](AZStd::string_view)
+        {
+            return false;
+        };
+
+        AZ::Settings::TextParserSettings parserSettings{ parseTextEntry };
+
+        m_textBuffer = R"(project_path=/TestProject
+            engine_path=/TestEngine
+        )";
+        EXPECT_FALSE(AZ::Settings::ParseTextFile(m_textStream, parserSettings));
+
+    }
+
+    TEST_F(TextParserTestFixture, ParseTextFile_WithLineLargerThan4096_Fails)
+    {
+        auto parseTextEntry = [](AZStd::string_view)
+        {
+            return true;
+        };
+
+        AZ::Settings::TextParserSettings parserSettings{ parseTextEntry };
+
+        m_textBuffer = "project_path=/TestProject\n";
+
+        // append a line longer than 4096 characters
+        m_textBuffer += "foo=";
+        m_textBuffer.append(4096, 'a');
+        m_textBuffer += '\n';
+
+        EXPECT_FALSE(AZ::Settings::ParseTextFile(m_textStream, parserSettings));
+    }
+
+    TEST_F(TextParserTestFixture, ParseTextFile_WithAllLinesSmallerThan4097_Succeeds)
+    {
+        auto parseTextEntry = [](AZStd::string_view)
+        {
+            return true;
+        };
+
+        AZ::Settings::TextParserSettings parserSettings{ parseTextEntry };
+
+        m_textBuffer = "project_path=/TestProject\n";
+
+        // append only 4000 characters
+        m_textBuffer += "foo=";
+        m_textBuffer.append(4000, 'a');
+        m_textBuffer += '\n';
+
+        EXPECT_TRUE(AZ::Settings::ParseTextFile(m_textStream, parserSettings));
+    }
+
+    TEST_P(TextParserParamFixture, ParseTextFile_ParseContents_Successfully)
+    {
+        auto textFileParam = GetParam();
+
+        // Parse Text File and write output to a vector for testing
+        using ParseSettingsVector = AZStd::fixed_vector<AZStd::string_view, 20>;
+        ParseSettingsVector parseSettingsVector;
+        auto parseTextEntry = [&parseSettingsVector](AZStd::string_view textEntry)
+        {
+            parseSettingsVector.emplace_back(textEntry);
+            return true;
+        };
+
+        AZ::Settings::TextParserSettings parserSettings{ parseTextEntry };
+
+
+        auto parseOutcome = AZ::Settings::ParseTextFile(m_textStream, parserSettings);
+        EXPECT_TRUE(parseOutcome);
+
+        // Validate that parse lines matches the expected lines
+        EXPECT_THAT(parseSettingsVector, ::testing::ContainerEq(textFileParam.m_expectedLines));
+    }
+
+INSTANTIATE_TEST_CASE_P(
+    ReadTextFile,
+    TextParserParamFixture,
+    ::testing::Values(
+        // Processes a fake text file
+        // and properly terminates the file with a newline
+        TextFileParams{ "fake_uuid.text", R"(
+engine.json
+project.json
+levels/defaultlevel.prefab
+
+)"
+        , AZStd::fixed_vector<AZStd::string_view, 20>{
+            AZStd::string_view{ "engine.json" },
+            AZStd::string_view{ "project.json" },
+            AZStd::string_view{ "levels/defaultlevel.prefab" }
+        }},
+        // Parses a fake text file
+        // and does not end with a newline
+        TextFileParams{ "fake_names.text", R"(
+shader
+material
+document property editor)"
+        , AZStd::fixed_vector<AZStd::string_view, 20>{
+            AZStd::string_view{ "shader" },
+            AZStd::string_view{ "material" },
+            AZStd::string_view{ "document property editor" }
+        }}
+        )
+    );
+
+
+}

+ 61 - 0
Code/Framework/AzCore/Tests/SystemFileTest.cpp

@@ -281,4 +281,65 @@ namespace UnitTest
         EXPECT_EQ(expectedValue, stdoutData);
     }
 
+    TEST_F(SystemFileTest, GetStdout_ReturnsHandle_ThatCanWriteToStdout_Succeeds)
+    {
+        AZStd::string stdoutData;
+        auto StoreStdout = [&stdoutData](AZStd::span<const AZStd::byte> capturedBytes)
+        {
+            AZStd::string_view capturedStrView(reinterpret_cast<const char*>(capturedBytes.data()), capturedBytes.size());
+            stdoutData += capturedStrView;
+        };
+
+        constexpr AZStd::string_view testData{ "Micro Macro Tera Zetta\n"};
+
+        // Capture stdout using descriptor of 1
+        constexpr int StdoutDescriptor = 1;
+        AZ::IO::FileDescriptorCapturer capturer(StdoutDescriptor);
+        capturer.Start(StoreStdout);
+        {
+            AZ::IO::SystemFile stdoutHandle = AZ::IO::SystemFile::GetStdout();
+            stdoutHandle.Write(testData.data(), testData.size());
+        }
+        fflush(stdout);
+        capturer.Stop();
+
+        EXPECT_EQ(testData, stdoutData);
+    }
+
+    TEST_F(SystemFileTest, GetStderr_ReturnsHandle_ThatCanWriteToStderr_Succeeds)
+    {
+        AZStd::string stderrData;
+        auto StoreStderr = [&stderrData](AZStd::span<const AZStd::byte> capturedBytes)
+        {
+            AZStd::string_view capturedStrView(reinterpret_cast<const char*>(capturedBytes.data()), capturedBytes.size());
+            stderrData += capturedStrView;
+        };
+
+        constexpr AZStd::string_view testData{ "Micro Macro Tera Zetta\n"};
+
+        // Capture stderr using descriptor of 2
+        constexpr int StderrDescriptor = 2;
+        AZ::IO::FileDescriptorCapturer capturer(StderrDescriptor);
+        capturer.Start(StoreStderr);
+        {
+            AZ::IO::SystemFile stderrHandle = AZ::IO::SystemFile::GetStderr();
+            stderrHandle.Write(testData.data(), testData.size());
+        }
+        fflush(stderr);
+        capturer.Stop();
+
+        EXPECT_EQ(testData, stderrData);
+    }
+
+    TEST_F(SystemFileTest, GetStdin_ReturnsHandle_ThatIsOpen_Succeeds)
+    {
+        char buffer[1];
+        AZ::IO::SystemFile stdinHandle = AZ::IO::SystemFile::GetStdin();
+        ASSERT_TRUE(stdinHandle.IsOpen());
+        // Read 0 bytes to validate that the stdin handle is open
+        // as well to avoid needing stdin to have data within it
+        // This call should block.
+        EXPECT_EQ(0, stdinHandle.Read(static_cast<size_t>(0), buffer));
+    }
+
 }   // namespace UnitTest

+ 1 - 0
Code/Framework/AzCore/Tests/azcoretests_files.cmake

@@ -235,6 +235,7 @@ set(FILES
     Settings/SettingsRegistryOriginTrackerTests.cpp
     Settings/SettingsRegistryScriptUtilsTests.cpp
     Settings/SettingsRegistryVisitorUtilsTests.cpp
+    Settings/TextParserTests.cpp
     Slice.cpp
     State.cpp
     Statistics.cpp

+ 31 - 4
Code/Framework/AzToolsFramework/AzToolsFramework/AssetDatabase/AssetDatabaseConnection.cpp

@@ -290,6 +290,14 @@ namespace AzToolsFramework
                     SqlParam<AZ::s64>(":sourceid"),
                     SqlParam<const char*>(":platform"));
 
+            static const char* QUERY_JOBS_BY_FAILURECAUSESOURCEID = "AzToolsFramework::AssetDatabase::QueryJobByFailureCauseSourceID";
+            static const char* QUERY_JOBS_BY_FAILURECAUSESOURCEID_STATEMENT =
+                "SELECT * FROM Jobs WHERE "
+                "FailureCauseSourcePK = :sourceid;";
+
+            static const auto s_queryJobsByFailureCauseSourceId = MakeSqlQuery(QUERY_JOBS_BY_FAILURECAUSESOURCEID, QUERY_JOBS_BY_FAILURECAUSESOURCEID_STATEMENT, LOG_NAME,
+                SqlParam<AZ::s64>(":sourceid"));
+
             // lookup by primary key
             static const char* QUERY_PRODUCT_BY_PRODUCTID = "AzToolsFramework::AssetDatabase::QueryProductByProductID";
             static const char* QUERY_PRODUCT_BY_PRODUCTID_STATEMENT =
@@ -1375,10 +1383,21 @@ namespace AzToolsFramework
 
         AZStd::string JobDatabaseEntry::ToString() const
         {
-            return AZStd::string::format("JobDatabaseEntry id:%" PRId64 " sourcepk:%" PRId64 " jobkey: %s fingerprint: %i platform: %s builderguid: %s status: %s, warnings: %u, errors %u",
-                static_cast<int64_t>(m_jobID), static_cast<int64_t>(m_sourcePK), m_jobKey.c_str(), m_fingerprint, m_platform.c_str(),
-                m_builderGuid.ToString<AZStd::string>().c_str(), AssetSystem::JobStatusString(m_status),
-                m_warningCount, m_errorCount);
+            return AZStd::string::format(
+                "JobDatabaseEntry id:%" PRId64 " sourcepk:%" PRId64
+                " jobkey: %s fingerprint: %i platform: %s builderguid: %s status: %s, failurecausesource: %" PRId64
+                ", failurecausefingerprint: %i, warnings: %u, errors %u",
+                static_cast<int64_t>(m_jobID),
+                static_cast<int64_t>(m_sourcePK),
+                m_jobKey.c_str(),
+                m_fingerprint,
+                m_platform.c_str(),
+                m_builderGuid.ToString<AZStd::string>().c_str(),
+                AssetSystem::JobStatusString(m_status),
+                static_cast<int64_t>(m_failureCauseSourcePK),
+                m_failureCauseFingerprint,
+                m_warningCount,
+                m_errorCount);
         }
 
         auto JobDatabaseEntry::GetColumns()
@@ -1392,6 +1411,8 @@ namespace AzToolsFramework
                 MakeColumn("BuilderGuid", m_builderGuid),
                 MakeColumn("Status", m_status),
                 MakeColumn("JobRunKey", m_jobRunKey),
+                MakeColumn("FailureCauseSourcePK", m_failureCauseSourcePK),
+                MakeColumn("FailureCauseFingerprint", m_failureCauseFingerprint),
                 MakeColumn("FirstFailLogTime", m_firstFailLogTime),
                 MakeColumn("FirstFailLogFile", m_firstFailLogFile),
                 MakeColumn("LastFailLogTime", m_lastFailLogTime),
@@ -1889,6 +1910,7 @@ namespace AzToolsFramework
             AddStatement(m_databaseConnection, s_queryJobByProductid);
             AddStatement(m_databaseConnection, s_queryJobBySourceid);
             AddStatement(m_databaseConnection, s_queryJobBySourceidPlatform);
+            AddStatement(m_databaseConnection, s_queryJobsByFailureCauseSourceId);
 
             AddStatement(m_databaseConnection, s_queryProductByProductid);
             AddStatement(m_databaseConnection, s_queryProductByJobid);
@@ -2250,6 +2272,11 @@ namespace AzToolsFramework
             return s_queryJobBySourceid.BindAndThen(*m_databaseConnection, handler, sourceID).Query(&GetJobResult, builderGuid, jobKey, status);
         }
 
+        bool AssetDatabaseConnection::QueryJobsByFailureCauseSourceID(AZ::s64 sourceID, jobHandler handler)
+        {
+            return s_queryJobsByFailureCauseSourceId.BindAndQuery(*m_databaseConnection, handler, &GetJobResultSimple, sourceID);
+        }
+
         bool AssetDatabaseConnection::QueryProductByProductID(AZ::s64 productid, productHandler handler)
         {
             return s_queryProductByProductid.BindAndQuery(*m_databaseConnection, handler, &GetProductResultSimple, productid);

+ 5 - 0
Code/Framework/AzToolsFramework/AzToolsFramework/AssetDatabase/AssetDatabaseConnection.h

@@ -73,6 +73,8 @@ namespace AzToolsFramework
             ChangedSourceDependencySourceColumn,
             SplitMaterialBuilderAndMaterialAssetBuilder,
             NewMaterialTypeBuildPipeline,
+            AddedJobFailureSourceColumn,
+            AddedMissingDependenciesIndex,
             //Add all new versions before this
             DatabaseVersionCount,
             LatestVersion = DatabaseVersionCount - 1
@@ -187,6 +189,8 @@ namespace AzToolsFramework
             AZ::Uuid m_builderGuid;
             AssetSystem::JobStatus m_status = AssetSystem::JobStatus::Queued;
             AZ::u64 m_jobRunKey = 0;
+            AZ::s64 m_failureCauseSourcePK = InvalidEntryId;
+            AZ::u32 m_failureCauseFingerprint = 0;
             AZ::s64 m_firstFailLogTime = 0;
             AZStd::string m_firstFailLogFile;
             AZ::s64 m_lastFailLogTime = 0;
@@ -582,6 +586,7 @@ namespace AzToolsFramework
             bool QueryJobByJobRunKey(AZ::u64 jobRunKey, jobHandler handler);
             bool QueryJobByProductID(AZ::s64 productID, jobHandler handler);
             bool QueryJobBySourceID(AZ::s64 sourceID, jobHandler handler, AZ::Uuid builderGuid = AZ::Uuid::CreateNull(), const char* jobKey = nullptr, const char* platform = nullptr, AssetSystem::JobStatus status = AssetSystem::JobStatus::Any);
+            bool QueryJobsByFailureCauseSourceID(AZ::s64 sourceID, jobHandler handler);
 
             //product
             bool QueryProductByProductID(AZ::s64 productID, productHandler handler);

+ 28 - 12
Code/Framework/AzToolsFramework/AzToolsFramework/UI/DocumentPropertyEditor/DocumentPropertyEditor.cpp

@@ -508,7 +508,6 @@ namespace AzToolsFramework
                 auto handlerInfo = DocumentPropertyEditor::GetInfoFromWidget(childWidget);
                 if (!handlerInfo.IsNull())
                 {
-                    // propertyHandlers own their widgets, so don't destroy them here. Set them free!
                     DocumentPropertyEditor::ReleaseHandler(handlerInfo);
                 }
                 else if (auto rowWidget = qobject_cast<DPERowWidget*>(childWidget))
@@ -1387,6 +1386,10 @@ namespace AzToolsFramework
     {
         // We need to append some alphabetical characters to the key or it will be treated as a very large json array index
         AZStd::string_view keyStr = AZStd::string::format("uuid%s", AZStd::to_string(key).c_str());
+        // Free the settings ptr before creating a new one. If the registry key is the same, we want
+        // the in-memory settings to be saved to disk (in settings destructor) before they're loaded
+        // from disk (in settings constructor)
+        m_dpeSettings.reset();
         m_dpeSettings = AZStd::make_unique<DocumentPropertyEditorSettings>(keyStr, propertyEditorName);
 
         if (m_dpeSettings && m_dpeSettings->WereSettingsLoaded())
@@ -1617,9 +1620,15 @@ namespace AzToolsFramework
         message.Match(AZ::DocumentPropertyEditor::Nodes::Adapter::QueryKey, showKeyQueryDialog);
     }
 
-    void DocumentPropertyEditor::RegisterHandlerPool(AZStd::shared_ptr<AZ::InstancePoolBase> handlerPool)
+    void DocumentPropertyEditor::RegisterHandlerPool(AZ::Name handlerName, AZStd::shared_ptr<AZ::InstancePoolBase> handlerPool)
     {
-        m_handlerPools.push_back(handlerPool);
+        AZ_Assert(
+            m_handlerPools.find(handlerName) == m_handlerPools.end() || m_handlerPools[handlerName] == handlerPool,
+            "Attempted to register a new handler pool to a handler name that is already in use.");
+
+        // insertion to the handler pool hash map only succeeds if the handler name is a new key
+        // so it won't overwrite any existing registered handler pool for that handler name
+        m_handlerPools.insert({ handlerName, handlerPool });
     }
 
     DocumentPropertyEditor::HandlerInfo DocumentPropertyEditor::GetInfoFromWidget(const QWidget* widget)
@@ -1634,7 +1643,11 @@ namespace AzToolsFramework
 
     AZ::Name DocumentPropertyEditor::GetNameForHandlerId(PropertyEditorToolsSystemInterface::PropertyHandlerId handlerId)
     {
-        return AZ::Name(AZStd::to_string(reinterpret_cast<uintptr_t>(handlerId)));
+        auto name = AZStd::to_string(reinterpret_cast<uintptr_t>(handlerId));
+        auto moduleId = AZ::Environment::GetModuleId();
+
+        auto nameWithModuleId = AZStd::fixed_string<256>::format("%s%p", name.c_str(), moduleId);
+        return AZ::Name(nameWithModuleId);
     }
 
     QWidget* DocumentPropertyEditor::CreateWidgetForHandler(
@@ -1644,9 +1657,12 @@ namespace AzToolsFramework
         // if we found a valid handler, grab its widget to add to the column layout
         if (handlerId)
         {
+            // first try to get the instance pool from pool manager
             auto poolManager = static_cast<AZ::InstancePoolManager*>(AZ::Interface<AZ::InstancePoolManagerInterface>::Get());
             auto handlerName = GetNameForHandlerId(handlerId);
             auto handlerPool = poolManager->GetPool<PropertyHandlerWidgetInterface>(handlerName);
+
+            // create the pool if it does not exist
             if (!handlerPool)
             {
                 AZStd::function<void(PropertyHandlerWidgetInterface&)> resetHandler = [](PropertyHandlerWidgetInterface& handler)
@@ -1665,10 +1681,12 @@ namespace AzToolsFramework
                 };
 
                 handlerPool = poolManager->CreatePool<PropertyHandlerWidgetInterface>(handlerName, resetHandler, createHandler).GetValue();
-                RegisterHandlerPool(handlerPool);
             }
 
-            // store, then reference the unique_ptr that will manage the handler's lifetime
+            // register the handler pool in DPE view to co-own the handler pool
+            // the registration is needed in case the handler pool is released by other DPE views
+            RegisterHandlerPool(handlerName, handlerPool);
+
             auto handler = handlerPool->GetInstance();
             handler->SetValueFromDom(domValue);
             createdWidget = handler->GetWidget();
@@ -1682,18 +1700,16 @@ namespace AzToolsFramework
         auto poolManager = static_cast<AZ::InstancePoolManager*>(AZ::Interface<AZ::InstancePoolManagerInterface>::Get());
         auto handlerName = GetNameForHandlerId(handler.handlerId);
         auto handlerPool = poolManager->GetPool<PropertyHandlerWidgetInterface>(handlerName);
+
         if (handlerPool)
         {
             handlerPool->RecycleInstance(handler.handlerInterface);
         }
         else
         {
-            QTimer::singleShot(
-                0,
-                [interfacePointer = handler.handlerInterface]()
-                {
-                    delete interfacePointer;
-                });
+            // if there is no handler pool, then delete the handler immediately; parent widgets won't delete it twice
+            delete handler.handlerInterface;
+            handler.handlerInterface = nullptr;
         }
     }
 } // namespace AzToolsFramework

+ 3 - 2
Code/Framework/AzToolsFramework/AzToolsFramework/UI/DocumentPropertyEditor/DocumentPropertyEditor.h

@@ -231,7 +231,7 @@ namespace AzToolsFramework
                 ->GetPool<AzQtComponents::ElidingLabel>();
         }
 
-        void RegisterHandlerPool(AZStd::shared_ptr<AZ::InstancePoolBase> handlerPool);
+        void RegisterHandlerPool(AZ::Name handlerName, AZStd::shared_ptr<AZ::InstancePoolBase> handlerPool);
 
         struct HandlerInfo
         {
@@ -280,7 +280,8 @@ namespace AzToolsFramework
         static AZ::Name GetNameForHandlerId(PropertyEditorToolsSystemInterface::PropertyHandlerId handlerId);
         static void ReleaseHandler(HandlerInfo& handler);
 
-        AZStd::vector<AZStd::shared_ptr<AZ::InstancePoolBase>> m_handlerPools;
+        // Co-owns the handler pool that is needed in DPE and the ownership would be released when the DPE is deleted
+        AZStd::unordered_map<AZ::Name, AZStd::shared_ptr<AZ::InstancePoolBase>> m_handlerPools;
     };
 } // namespace AzToolsFramework
 

+ 2 - 0
Code/Tools/AssetProcessor/assetprocessor_gui_files.cmake

@@ -47,6 +47,8 @@ set(FILES
     native/ui/BuilderInfoPatternsModel.cpp
     native/ui/BuilderInfoMetricsModel.h
     native/ui/BuilderInfoMetricsModel.cpp
+    native/ui/EnabledRelocationTypesModel.h
+    native/ui/EnabledRelocationTypesModel.cpp
     native/ui/MessageWindow.h
     native/ui/MessageWindow.cpp
     native/ui/MessageWindow.ui

+ 77 - 9
Code/Tools/AssetProcessor/native/AssetDatabase/AssetDatabase.cpp

@@ -65,6 +65,8 @@ namespace AssetProcessor
             "    BuilderGuid      BLOB NOT NULL, "
             "    Status           INTEGER NOT NULL, "
             "    JobRunKey        INTEGER NOT NULL, "
+            "    FailureCauseSourcePK INTEGER, "
+            "    FailureCauseFingerprint INTEGER, "
             "    FirstFailLogTime INTEGER NOT NULL, "
             "    FirstFailLogFile TEXT collate nocase, "
             "    LastFailLogTime  INTEGER NOT NULL, "
@@ -154,6 +156,11 @@ namespace AssetProcessor
             "    FOREIGN KEY (ProductPK) REFERENCES "
             "        Products(ProductID) ON DELETE CASCADE);";
 
+        static const char* CREATEINDEX_MISSINGPRODUCTDEPENDENCY_PRODUCTPK =
+            "AssetProcessor::CreateIndexMissingProductDependencies_ProductPK";
+        static const char* CREATEINDEX_MISSINGPRODUCTDEPENDENCY_PRODUCTPK_STATEMENT =
+            "CREATE INDEX IF NOT EXISTS MissingProductDependencies_ProductPK ON MissingProductDependencies (ProductPK);";
+
         static const char* CREATE_FILES_TABLE = "AssetProcessor::CreateFilesTable";
         static const char* CREATE_FILES_TABLE_STATEMENT =
             "CREATE TABLE IF NOT EXISTS Files( "
@@ -335,8 +342,8 @@ namespace AssetProcessor
 
         static const char* INSERT_JOB = "AssetProcessor::InsertJob";
         static const char* INSERT_JOB_STATEMENT =
-            "INSERT INTO Jobs (SourcePK, JobKey, Fingerprint, Platform, BuilderGuid, Status, JobRunKey, FirstFailLogTime, FirstFailLogFile, LastFailLogTime, LastFailLogFile, LastLogTime, LastLogFile, WarningCount, ErrorCount) "
-            "VALUES (:sourceid, :jobkey, :fingerprint, :platform, :builderguid, :status, :jobrunkey, :firstfaillogtime, :firstfaillogfile, :lastfaillogtime, :lastfaillogfile, :lastlogtime, :lastlogfile, :warningcount, :errorcount);";
+            "INSERT INTO Jobs (SourcePK, JobKey, Fingerprint, Platform, BuilderGuid, Status, JobRunKey, FailureCauseSourcePK, FailureCauseFingerprint, FirstFailLogTime, FirstFailLogFile, LastFailLogTime, LastFailLogFile, LastLogTime, LastLogFile, WarningCount, ErrorCount) "
+            "VALUES (:sourceid, :jobkey, :fingerprint, :platform, :builderguid, :status, :jobrunkey, :failurecausesourcepk, :failurecausefingerprint, :firstfaillogtime, :firstfaillogfile, :lastfaillogtime, :lastfaillogfile, :lastlogtime, :lastlogfile, :warningcount, :errorcount);";
 
         static const auto s_InsertJobQuery = MakeSqlQuery(INSERT_JOB, INSERT_JOB_STATEMENT, LOG_NAME,
             SqlParam<AZ::s64>(":sourceid"),
@@ -346,6 +353,8 @@ namespace AssetProcessor
             SqlParam<AZ::Uuid>(":builderguid"),
             SqlParam<AZ::s32>(":status"),
             SqlParam<AZ::u64>(":jobrunkey"),
+            SqlParam<AZ::s64>(":failurecausesourcepk"),
+            SqlParam<AZ::u32>(":failurecausefingerprint"),
             SqlParam<AZ::s64>(":firstfaillogtime"),
             SqlParam<const char*>(":firstfaillogfile"),
             SqlParam<AZ::s64>(":lastfaillogtime"),
@@ -366,6 +375,8 @@ namespace AssetProcessor
             "BuilderGuid = :builderguid, "
             "Status = :status, "
             "JobRunKey = :jobrunkey, "
+            "FailureCauseSourcePK = :failurecausesourcepk, "
+            "FailureCauseFingerprint = :failurecausefingerprint, "
             "FirstFailLogTime = :firstfaillogtime, "
             "FirstFailLogFile = :firstfaillogfile, "
             "LastFailLogTime = :lastfaillogtime, "
@@ -384,6 +395,8 @@ namespace AssetProcessor
             SqlParam<AZ::Uuid>(":builderguid"),
             SqlParam<AZ::s32>(":status"),
             SqlParam<AZ::u64>(":jobrunkey"),
+            SqlParam<AZ::s64>(":failurecausesourcepk"),
+            SqlParam<AZ::u32>(":failurecausefingerprint"),
             SqlParam<AZ::s64>(":firstfaillogtime"),
             SqlParam<const char*>(":firstfaillogfile"),
             SqlParam<AZ::s64>(":lastfaillogtime"),
@@ -405,7 +418,7 @@ namespace AssetProcessor
             UPDATE_JOB_FINGERPRINT_BY_SOURCE_ID_STATEMENT,
             LOG_NAME,
             SqlParam<AZ::u64>(":fingerprint"),
-            SqlParam<AZ::s64>(":sourceid"));      
+            SqlParam<AZ::s64>(":sourceid"));
 
         static const char* DELETE_JOB = "AssetProcessor::DeleteJob";
         static const char* DELETE_JOB_STATEMENT =
@@ -781,7 +794,7 @@ namespace AssetProcessor
             SqlParam<AZ::u64>(":hash"),
             SqlParam<const char*>(":filename"),
             SqlParam<AZ::s64>(":scanfolderpk"));
-        
+
         static const char* UPDATE_FILE_HASH_BY_FILENAME_SCANFOLDER_ID = "AssetProcessor::UpdateFileHashByFileNameScanFolderId";
         static const char* UPDATE_FILE_HASH_BY_FILENAME_SCANFOLDER_ID_STATEMENT =
             "UPDATE Files SET "
@@ -794,7 +807,7 @@ namespace AssetProcessor
             LOG_NAME,
             SqlParam<AZ::u64>(":hash"),
             SqlParam<const char*>(":filename"),
-            SqlParam<AZ::s64>(":scanfolderpk"));        
+            SqlParam<AZ::s64>(":scanfolderpk"));
 
         static const char* DELETE_FILE = "AssetProcessor::DeleteFile";
         static const char* DELETE_FILE_STATEMENT =
@@ -839,6 +852,16 @@ namespace AssetProcessor
         static const char* CREATEINDEX_SOURCEDEPENDENCY_SOURCEGUID = "AssetProcessor::CreateIndexSourceGuidSourceDependency";
         static const char* CREATEINDEX_SOURCEDEPENDENCY_SOURCEGUID_STATEMENT =
             "CREATE INDEX IF NOT EXISTS SourceGuid_SourceDependency ON SourceDependency (SourceGuid);";
+
+        static const char* INSERT_COLUMN_JOBS_FAILURECAUSESOURCEID = "AssetProcessor::InsertColumnJobsFailureCauseSourceId";
+        static const char* INSERT_COLUMN_JOBS_FAILURECAUSESOURCEID_STATEMENT =
+            "ALTER TABLE Jobs "
+            "ADD FailureCauseSourcePK INTEGER;";
+
+        static const char* INSERT_COLUMN_JOBS_FAILURECAUSEFINGERPRINT = "AssetProcessor::InsertColumnJobsFailureCauseFingerprint";
+        static const char* INSERT_COLUMN_JOBS_FAILURECAUSEFINGERPRINT_STATEMENT =
+            "ALTER TABLE Jobs "
+            "Add FailureCauseFingerprint INTEGER;";
     }
 
     AssetDatabaseConnection::AssetDatabaseConnection()
@@ -1171,13 +1194,37 @@ namespace AssetProcessor
             }
         }
 
-        if(foundVersion == DatabaseVersion::AddedStatsTable)
+        if (foundVersion == DatabaseVersion::AddedStatsTable)
         {
             // Version update - change SourceDependency Source to SourceGuid column
             // Do nothing so the whole database is dropped.
             // Unfortunately we have to reprocess all assets because of the way the fingerprinting algorithm works,
             // changing from storing the path to the UUID changes the fingerprint, resulting in all assets reprocessing anyway
-            AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Asset database version updated to ChangedSourceDependencySourceColumn, database will be cleared as migration is not possible for this update\n", foundVersion);
+            AZ_TracePrintf(
+                AssetProcessor::ConsoleChannel,
+                "Asset database version updated to ChangedSourceDependencySourceColumn, database will be cleared as migration is not "
+                "possible for this update\n",
+                foundVersion);
+        }
+
+        if (foundVersion == DatabaseVersion::NewMaterialTypeBuildPipeline)
+        {
+            if (m_databaseConnection->ExecuteOneOffStatement(INSERT_COLUMN_JOBS_FAILURECAUSESOURCEID)
+                && m_databaseConnection->ExecuteOneOffStatement(INSERT_COLUMN_JOBS_FAILURECAUSEFINGERPRINT))
+            {
+                foundVersion = DatabaseVersion::AddedJobFailureSourceColumn;
+                AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Upgraded Asset Database to version %i (AddedJobFailureSourceColumn)\n", foundVersion);
+            }
+        }
+
+        if (foundVersion == DatabaseVersion::AddedJobFailureSourceColumn)
+        {
+            if (m_databaseConnection->ExecuteOneOffStatement(CREATEINDEX_MISSINGPRODUCTDEPENDENCY_PRODUCTPK))
+            {
+                foundVersion = DatabaseVersion::AddedMissingDependenciesIndex;
+                AZ_TracePrintf(
+                    AssetProcessor::ConsoleChannel, "Upgraded Asset Database to version %i (AddedMissingDependenciesIndex)\n", foundVersion);
+            }
         }
 
         if (foundVersion == CurrentDatabaseVersion())
@@ -1303,6 +1350,8 @@ namespace AssetProcessor
         m_databaseConnection->AddStatement(INSERT_COLUMNS_JOB_WARNING_COUNT, INSERT_COLUMNS_JOB_WARNING_COUNT_STATEMENT);
         m_databaseConnection->AddStatement(INSERT_COLUMNS_JOB_ERROR_COUNT, INSERT_COLUMNS_JOB_ERROR_COUNT_STATEMENT);
         m_databaseConnection->AddStatement(UPDATE_JOB_FINGERPRINT_BY_SOURCE_ID, UPDATE_JOB_FINGERPRINT_BY_SOURCE_ID_STATEMENT);
+        m_databaseConnection->AddStatement(INSERT_COLUMN_JOBS_FAILURECAUSESOURCEID, INSERT_COLUMN_JOBS_FAILURECAUSESOURCEID_STATEMENT);
+        m_databaseConnection->AddStatement(INSERT_COLUMN_JOBS_FAILURECAUSEFINGERPRINT, INSERT_COLUMN_JOBS_FAILURECAUSEFINGERPRINT_STATEMENT);
         m_createStatements.push_back(CREATE_JOBS_TABLE);
 
         AddStatement(m_databaseConnection, s_GetHighestJobrunkeyQuery);
@@ -1476,6 +1525,10 @@ namespace AssetProcessor
         m_createStatements.push_back(CREATEINDEX_SOURCEDEPENDENCY_SOURCEGUID);
 
         m_databaseConnection->AddStatement(DELETE_AUTO_SUCCEED_JOBS, DELETE_AUTO_SUCCEED_JOBS_STATEMENT);
+
+        m_databaseConnection->AddStatement(
+            CREATEINDEX_MISSINGPRODUCTDEPENDENCY_PRODUCTPK, CREATEINDEX_MISSINGPRODUCTDEPENDENCY_PRODUCTPK_STATEMENT);
+        m_createStatements.push_back(CREATEINDEX_MISSINGPRODUCTDEPENDENCY_PRODUCTPK);
     }
 
     void AssetDatabaseConnection::VacuumAndAnalyze()
@@ -2040,6 +2093,21 @@ namespace AssetProcessor
         return found && succeeded;
     }
 
+    bool AssetDatabaseConnection::GetJobsByFailureCauseSourceId(AZ::s64 sourceID, AzToolsFramework::AssetDatabase::JobDatabaseEntryContainer& container)
+    {
+        bool found = false;
+        bool succeeded = QueryJobsByFailureCauseSourceID(
+            sourceID,
+            [&found, &container](JobDatabaseEntry& job)
+            {
+                found = true;
+                container.emplace_back() = AZStd::move(job);
+                return true;
+            });
+
+        return found && succeeded;
+    }
+
     bool AssetDatabaseConnection::GetJobsByProductName(QString exactProductName, JobDatabaseEntryContainer& container, AZ::Uuid builderGuid, QString jobKey, QString platform, JobStatus status)
     {
         bool found = false;
@@ -2111,7 +2179,7 @@ namespace AssetProcessor
             }
 
             if (!s_InsertJobQuery.BindAndStep(*m_databaseConnection, entry.m_sourcePK, entry.m_jobKey.c_str(), entry.m_fingerprint, entry.m_platform.c_str(),
-                entry.m_builderGuid, static_cast<int>(entry.m_status), entry.m_jobRunKey, entry.m_firstFailLogTime, entry.m_firstFailLogFile.c_str(),
+                entry.m_builderGuid, static_cast<int>(entry.m_status), entry.m_jobRunKey, entry.m_failureCauseSourcePK, entry.m_failureCauseFingerprint, entry.m_firstFailLogTime, entry.m_firstFailLogFile.c_str(),
                 entry.m_lastFailLogTime, entry.m_lastFailLogFile.c_str(), entry.m_lastLogTime, entry.m_lastLogFile.c_str(), entry.m_warningCount, entry.m_errorCount))
             {
                 return false;
@@ -2153,7 +2221,7 @@ namespace AssetProcessor
             }
 
             return s_UpdateJobQuery.BindAndStep(*m_databaseConnection, entry.m_sourcePK, entry.m_jobKey.c_str(), entry.m_fingerprint, entry.m_platform.c_str(),
-                entry.m_builderGuid, static_cast<int>(entry.m_status), entry.m_jobRunKey, entry.m_firstFailLogTime, entry.m_firstFailLogFile.c_str(),
+                entry.m_builderGuid, static_cast<int>(entry.m_status), entry.m_jobRunKey, entry.m_failureCauseSourcePK, entry.m_failureCauseFingerprint, entry.m_firstFailLogTime, entry.m_firstFailLogFile.c_str(),
                 entry.m_lastFailLogTime, entry.m_lastFailLogFile.c_str(), entry.m_lastLogTime, entry.m_lastLogFile.c_str(), entry.m_warningCount, entry.m_errorCount, entry.m_jobID);
         }
     }

+ 2 - 0
Code/Tools/AssetProcessor/native/AssetDatabase/AssetDatabase.h

@@ -114,6 +114,8 @@ namespace AssetProcessor
         bool GetJobsBySourceName(const AssetProcessor::SourceAssetReference& sourceAsset, AzToolsFramework::AssetDatabase::JobDatabaseEntryContainer& container, AZ::Uuid builderGuid = AZ::Uuid::CreateNull(), QString jobKey = QString(), QString platform = QString(), AzToolsFramework::AssetSystem::JobStatus status = AzToolsFramework::AssetSystem::JobStatus::Any);
         bool GetJobsLikeSourceName(QString likeSourceName, LikeType likeType, AzToolsFramework::AssetDatabase::JobDatabaseEntryContainer& container, AZ::Uuid builderGuid = AZ::Uuid::CreateNull(), QString jobKey = QString(), QString platform = QString(), AzToolsFramework::AssetSystem::JobStatus status = AzToolsFramework::AssetSystem::JobStatus::Any);
 
+        bool GetJobsByFailureCauseSourceId(AZ::s64 sourceID, AzToolsFramework::AssetDatabase::JobDatabaseEntryContainer& container);
+
         bool GetJobsByProductName(QString exactProductName, AzToolsFramework::AssetDatabase::JobDatabaseEntryContainer& container, AZ::Uuid builderGuid = AZ::Uuid::CreateNull(), QString jobKey = QString(), QString platform = QString(), AzToolsFramework::AssetSystem::JobStatus status = AzToolsFramework::AssetSystem::JobStatus::Any);
         bool GetJobsLikeProductName(QString likeProductName, LikeType likeType, AzToolsFramework::AssetDatabase::JobDatabaseEntryContainer& container, AZ::Uuid builderGuid = AZ::Uuid::CreateNull(), QString jobKey = QString(), QString platform = QString(), AzToolsFramework::AssetSystem::JobStatus status = AzToolsFramework::AssetSystem::JobStatus::Any);
 

+ 3 - 3
Code/Tools/AssetProcessor/native/AssetManager/SourceAssetReference.cpp

@@ -152,17 +152,17 @@ namespace AssetProcessor
 
     AZ::IO::FixedMaxPath SourceAssetReference::AbsolutePath() const
     {
-        return m_absolutePath;
+        return AZ::IO::FixedMaxPath(m_absolutePath);
     }
 
     AZ::IO::FixedMaxPath SourceAssetReference::RelativePath() const
     {
-        return m_relativePath;
+        return AZ::IO::FixedMaxPath(m_relativePath);
     }
 
     AZ::IO::FixedMaxPath SourceAssetReference::ScanFolderPath() const
     {
-        return m_scanFolderPath;
+        return AZ::IO::FixedMaxPath(m_scanFolderPath);
     }
 
     AZ::s64 SourceAssetReference::ScanFolderId() const

+ 3 - 3
Code/Tools/AssetProcessor/native/AssetManager/SourceAssetReference.h

@@ -74,9 +74,9 @@ namespace AssetProcessor
     private:
         void Normalize();
 
-        AZ::IO::FixedMaxPath m_absolutePath;
-        AZ::IO::FixedMaxPath m_relativePath;
-        AZ::IO::FixedMaxPath m_scanFolderPath;
+        AZ::IO::Path m_absolutePath;
+        AZ::IO::Path m_relativePath;
+        AZ::IO::Path m_scanFolderPath;
         AZ::s64 m_scanFolderId{};
     };
 }

+ 40 - 3
Code/Tools/AssetProcessor/native/AssetManager/assetProcessorManager.cpp

@@ -801,6 +801,9 @@ namespace AssetProcessor
         //set the random key
         job.m_jobRunKey = jobEntry.m_jobRunKey;
 
+        job.m_failureCauseSourcePK = jobEntry.m_failureCauseSourceId;
+        job.m_failureCauseFingerprint = jobEntry.m_failureCauseFingerprint;
+
         QString fullPath = jobEntry.GetAbsoluteSourcePath();
         //set the new status
         job.m_status = fullPath.length() < AP_MAX_PATH_LEN ? JobStatus::Failed : JobStatus::Failed_InvalidSourceNameExceedsMaxLimit;
@@ -1159,6 +1162,8 @@ namespace AssetProcessor
                                     m_stateData->GetProductsBySourceID(source.m_sourceID, products);
                                     DeleteProducts(products);
 
+                                    auto jobFingerprint = job.m_fingerprint;
+
                                     //set the fingerprint to failed
                                     job.m_fingerprint = FAILED_FINGERPRINT;
                                     m_stateData->SetJob(job);
@@ -1188,7 +1193,7 @@ namespace AssetProcessor
                                         fullSourcePath.c_str(),
                                         productPath.GetCachePath().c_str());
 
-                                    AutoFailJob(consoleMsg, autoFailReason, itProcessedAsset);
+                                    AutoFailJob(consoleMsg, autoFailReason, itProcessedAsset, source.m_sourceID, jobFingerprint);
 
                                     //recycle the original source
                                     AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntry scanfolder;
@@ -1487,6 +1492,35 @@ namespace AssetProcessor
                 }
             }
 
+            // Check for any jobs that previously failed due to a conflict.
+            // This allows users to fix the 'successful' job in a conflict and have the failed job reprocess automatically.
+            AzToolsFramework::AssetDatabase::JobDatabaseEntryContainer priorConflictedJobs;
+            if(m_stateData->GetJobsByFailureCauseSourceId(source.m_sourceID, priorConflictedJobs))
+            {
+                for(const auto& conflictedJob : priorConflictedJobs)
+                {
+                    // If the fingerprint has changed, try re-running the job.
+                    // The fingerprint check prevents an infinite loop because the job being queued will re-run this job if it fails again.
+                    if (conflictedJob.m_failureCauseFingerprint != job.m_fingerprint)
+                    {
+                        AzToolsFramework::AssetDatabase::SourceDatabaseEntry conflictedSource;
+                        if (m_stateData->GetSourceBySourceID(conflictedJob.m_sourcePK, conflictedSource))
+                        {
+                            SourceAssetReference conflictedSourceRef(
+                                conflictedSource.m_scanFolderPK, conflictedSource.m_sourceName.c_str());
+
+                            AZ_Info(
+                                AssetProcessor::ConsoleChannel,
+                                "Re-queuing previously conflicted source " AZ_STRING_FORMAT " - source " AZ_STRING_FORMAT
+                                " has changed and may no longer conflict\n",
+                                AZ_STRING_ARG(conflictedSource.m_sourceName),
+                                AZ_STRING_ARG(source.m_sourceName));
+                            AssessFileInternal(conflictedSourceRef.AbsolutePath().c_str(), false);
+                        }
+                    }
+                }
+            }
+
             auto* uuidInterface = AZ::Interface<AssetProcessor::IUuidRequests>::Get();
             AZ_Assert(uuidInterface, "Programmer Error - IUuidRequests interface is not available.");
 
@@ -4738,7 +4772,7 @@ namespace AssetProcessor
         // Scope the lock for just modifying the processing product info list.
         // This will allow other jobs to lock this list for emitting their own messages.
         // This speeds up asset processing time, by not having jobs holding this longer than they need to.
-        {        
+        {
             QMutexLocker locker(&m_processingJobMutex);
             m_processingProductInfoList.insert(productPath);
         }
@@ -5836,7 +5870,7 @@ namespace AssetProcessor
         Q_EMIT AssetToProcess(jobdetail); // forwarding this job to rccontroller to fail it
     }
 
-    void AssetProcessorManager::AutoFailJob(AZStd::string_view consoleMsg, AZStd::string_view autoFailReason, const AZStd::vector<AssetProcessedEntry>::iterator& assetIter)
+    void AssetProcessorManager::AutoFailJob(AZStd::string_view consoleMsg, AZStd::string_view autoFailReason, const AZStd::vector<AssetProcessedEntry>::iterator& assetIter, AZ::s64 failureCauseSourceId, AZ::u32 failureCauseFingerprint)
     {
         JobEntry jobEntry(
             assetIter->m_entry.m_sourceAssetReference,
@@ -5845,6 +5879,9 @@ namespace AssetProcessor
             assetIter->m_entry.m_jobKey, 0, GenerateNewJobRunKey(),
             assetIter->m_entry.m_sourceFileUUID);
 
+        jobEntry.m_failureCauseSourceId = failureCauseSourceId;
+        jobEntry.m_failureCauseFingerprint = failureCauseFingerprint;
+
         AutoFailJob(consoleMsg, autoFailReason, jobEntry);
     }
 

+ 9 - 1
Code/Tools/AssetProcessor/native/AssetManager/assetProcessorManager.h

@@ -249,6 +249,9 @@ namespace AssetProcessor
         //! This is used to prevent AP generating new metadata files while someone is trying to rename an existing file.
         void SetMetaCreationDelay(AZ::u32 milliseconds);
 
+        //! Gets the maximum amount of time to wait before generating a metadata file.
+        AZ::u32 GetMetaCreationDelay() const { return m_metaCreationDelayMs; }
+
         void PrepareForFileMove(AZ::IO::PathView oldPath, AZ::IO::PathView newPath) override;
 
     Q_SIGNALS:
@@ -389,7 +392,12 @@ namespace AssetProcessor
             AZStd::string_view autoFailReason,
             JobEntry jobEntry,
             AZStd::string_view jobLog = "");
-        void AutoFailJob(AZStd::string_view consoleMsg, AZStd::string_view autoFailReason, const AZStd::vector<AssetProcessedEntry>::iterator& assetIter);
+        void AutoFailJob(
+            AZStd::string_view consoleMsg,
+            AZStd::string_view autoFailReason,
+            const AZStd::vector<AssetProcessedEntry>::iterator& assetIter,
+            AZ::s64 failureCauseSourceId = AzToolsFramework::AssetDatabase::InvalidEntryId,
+            AZ::u32 failureCauseFingerprint = 0);
 
         using ProductInfoList = AZStd::vector<AZStd::pair<AzToolsFramework::AssetDatabase::ProductDatabaseEntry, const AssetBuilderSDK::JobProduct*>>;
 

+ 2 - 0
Code/Tools/AssetProcessor/native/assetprocessor.h

@@ -129,6 +129,8 @@ namespace AssetProcessor
         AZ::u32 m_computedFingerprint = 0;     // what the fingerprint was at the time of job creation.
         qint64 m_computedFingerprintTimeStamp = 0; // stores the number of milliseconds since the universal coordinated time when the fingerprint was computed.
         AZ::u64 m_jobRunKey = 0;
+        AZ::s64 m_failureCauseSourceId = AzToolsFramework::AssetDatabase::InvalidEntryId; // Id of the source that caused this job to fail (typically due to a conflict).
+        AZ::u32 m_failureCauseFingerprint = 0; // Fingerprint of the job that caused this job to fail.  Used to prevent infinite retry loops.
         bool m_checkExclusiveLock = true;      ///< indicates whether we need to check the input file for exclusive lock before we process this job
         bool m_addToDatabase = true; ///< If false, this is just a UI job, and should not affect the database.
 

+ 64 - 0
Code/Tools/AssetProcessor/native/ui/EnabledRelocationTypesModel.cpp

@@ -0,0 +1,64 @@
+/*
+ * 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 <native/ui/EnabledRelocationTypesModel.h>
+#include <native/utilities/UuidManager.h>
+
+namespace AssetProcessor
+{
+    int EnabledRelocationTypesModel::rowCount(const QModelIndex& /*parent*/) const
+    {
+        // If no types are enabled, then a message is displayed explaining that.
+        if (m_enabledTypes.empty())
+        {
+            return 1;
+        }
+        return aznumeric_caster(m_enabledTypes.size());
+    }
+
+    QVariant EnabledRelocationTypesModel::data(const QModelIndex& index, int role) const
+    {
+        AZ_Assert(index.isValid(), "EnabledRelocationTypesModel index out of bounds");
+
+        if (!index.isValid())
+        {
+            return {};
+        }
+
+        if (role == Qt::DisplayRole)
+        {
+            if (m_enabledTypes.empty())
+            {
+                return tr("No types are enabled for asset relocation.");
+            }
+            if (index.row() < m_enabledTypes.size())
+            {
+                return QString(m_enabledTypes[index.row()].c_str());
+            }
+        }
+
+        return {};
+    }
+
+    void EnabledRelocationTypesModel::Reset()
+    {
+        beginResetModel();
+
+        m_enabledTypes.clear();
+
+        AZStd::unordered_set<AZStd::string> enabledRelocationTypes = AZ::Interface<AssetProcessor::IUuidRequests>::Get()->GetEnabledTypes();
+
+        for (const auto& type : enabledRelocationTypes)
+        {
+            m_enabledTypes.insert(AZStd::upper_bound(m_enabledTypes.begin(), m_enabledTypes.end(), type), type);
+        }
+
+        endResetModel();
+    }
+
+} // namespace AssetProcessor

+ 40 - 0
Code/Tools/AssetProcessor/native/ui/EnabledRelocationTypesModel.h

@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+#pragma once
+
+#if !defined(Q_MOC_RUN)
+#include <AzCore/std/containers/vector.h>
+#include <AzCore/std/string/string.h>
+#include <QAbstractListModel>
+#endif
+
+namespace AssetProcessor
+{
+    class EnabledRelocationTypesModel : public QAbstractListModel
+    {
+        Q_OBJECT;
+
+    public:
+        EnabledRelocationTypesModel(QObject* parent = nullptr)
+            : QAbstractListModel(parent)
+        {
+        }
+
+        int rowCount(const QModelIndex& parent) const override;
+
+        QVariant data(const QModelIndex& index, int role) const override;
+
+        void Reset();
+
+    protected:
+        // Cache the enabled type list locally, for performance and to keep the sorting stable.
+        // A vector is used instead of a set to make look up in data faster.
+        AZStd::vector<AZStd::string> m_enabledTypes;
+    };
+
+} // namespace AssetProcessor

+ 9 - 0
Code/Tools/AssetProcessor/native/ui/MainWindow.cpp

@@ -19,6 +19,7 @@
 #include <native/ui/BuilderDataItem.h>
 #include <native/ui/BuilderInfoPatternsModel.h>
 #include <native/ui/BuilderInfoMetricsModel.h>
+#include <native/ui/EnabledRelocationTypesModel.h>
 #include <native/ui/SourceAssetTreeFilterModel.h>
 
 #include <AzFramework/Asset/AssetSystemBus.h>
@@ -191,6 +192,7 @@ MainWindow::MainWindow(GUIApplicationManager* guiApplicationManager, QWidget* pa
     , m_builderList(new BuilderListModel(this))
     , m_builderListSortFilterProxy(new BuilderListSortFilterProxy(this))
     , m_builderInfoPatterns(new AssetProcessor::BuilderInfoPatternsModel(this))
+    , m_enabledRelocationTypesModel(new AssetProcessor::EnabledRelocationTypesModel(this))
 {
     ui->setupUi(this);
 
@@ -243,6 +245,7 @@ void MainWindow::Activate()
     ui->buttonList->addTab(QStringLiteral("Builders"));
     ui->buttonList->addTab(QStringLiteral("Settings"));
     ui->buttonList->addTab(QStringLiteral("Shared Cache"));
+    ui->buttonList->addTab(QStringLiteral("Asset Relocation"));
 
     connect(ui->buttonList, &AzQtComponents::SegmentBar::currentChanged, ui->dialogStack, &QStackedWidget::setCurrentIndex);
     const int startIndex = static_cast<int>(DialogStackIndex::Welcome);
@@ -658,6 +661,12 @@ void MainWindow::Activate()
 
     // Shared Cache tab:
     SetupAssetServerTab();
+
+    m_enabledRelocationTypesModel->Reset();
+    ui->AssetRelocationExtensionListView->setModel(m_enabledRelocationTypesModel);
+
+    ui->MetaCreationDelayValue->setText(tr("%1 milliseconds").arg(m_guiApplicationManager->GetAssetProcessorManager()->GetMetaCreationDelay()));
+
 }
 
 void MainWindow::BuilderTabSelectionChanged(const QItemSelection& selected, const QItemSelection& /*deselected*/)

+ 5 - 1
Code/Tools/AssetProcessor/native/ui/MainWindow.h

@@ -52,6 +52,7 @@ namespace AssetProcessor
     class BuilderInfoPatternsModel;
     class BuilderInfoMetricsModel;
     class BuilderInfoMetricsSortModel;
+    class EnabledRelocationTypesModel;
 }
 
 class MainWindow
@@ -79,7 +80,8 @@ public:
         Logs,
         Connections,
         Builders,
-        Tools
+        Tools,
+        AssetRelocation
     };
 
     struct Config
@@ -172,6 +174,8 @@ private:
     AssetProcessor::BuilderInfoMetricsSortModel* m_builderInfoMetricsSort = nullptr;
     AssetProcessor::CacheServerData m_cacheServerData;
 
+    AssetProcessor::EnabledRelocationTypesModel* m_enabledRelocationTypesModel = nullptr;
+
     void SetContextLogDetails(const QMap<QString, QString>& details);
     void ClearContextLogDetails();
 

+ 262 - 191
Code/Tools/AssetProcessor/native/ui/MainWindow.ui

@@ -337,61 +337,61 @@
         <property name="currentIndex">
          <number>0</number>
         </property>
-         <widget class="QWidget" name="WelcomePage">
-           <layout class="QVBoxLayout" name="WelcomeVerticalLayout">
-             <item>
-              <widget class="QLabel" name="welcomeScreenTitle">
-               <property name="font">
-                <font>
-                 <pointsize>10</pointsize>
-                 <weight>75</weight>
-                 <bold>true</bold>
-                </font>
-               </property>
-               <property name="text">
-                <string># Welcome to the O3DE Asset Processor</string>
-               </property>
-               <property name="textFormat">
-                <enum>Qt::MarkdownText</enum>
-               </property>
-              </widget>
-             </item>
-             <item>
-               <widget class="QLabel" name="WelcomeDescriptionLabel1">
-                 <property name="text">
-                   <string>This tool converts an O3DE project's source assets to runtime ready content.</string>
-                 </property>
-               </widget>
-             </item>
-             <item>
-               <widget class="QLabel" name="WelcomeDescriptionLabel2">
-                 <property name="text">
-                   <string>You can read more about the Asset Processor &lt;a href=&quot;https://www.o3de.org/docs/user-guide/assets/asset-processor/&quot;&gt;here.&lt;/a&gt;</string>
-                 </property>
-                 <property name="openExternalLinks">
-                   <bool>true</bool>
-                 </property>
-                 <property name="textInteractionFlags">
-                   <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse</set>
-                 </property>
-               </widget>
-             </item>
-             <item>
-               <spacer name="welcomeScreenVerticalSpacer">
-                 <property name="orientation">
-                   <enum>Qt::Vertical</enum>
-                 </property>
-                 <property name="sizeHint" stdset="0">
-                   <size>
-                     <width>16</width>
-                     <height>40</height>
-                   </size>
-                 </property>
-               </spacer>
-             </item>
-           </layout>
-         </widget>
-         <widget class="QSplitter" name="jobDialogSplitter">
+        <widget class="QWidget" name="WelcomePage">
+         <layout class="QVBoxLayout" name="WelcomeVerticalLayout">
+          <item>
+           <widget class="QLabel" name="welcomeScreenTitle">
+            <property name="font">
+             <font>
+              <pointsize>10</pointsize>
+              <weight>75</weight>
+              <bold>true</bold>
+             </font>
+            </property>
+            <property name="text">
+             <string># Welcome to the O3DE Asset Processor</string>
+            </property>
+            <property name="textFormat">
+             <enum>Qt::MarkdownText</enum>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QLabel" name="WelcomeDescriptionLabel1">
+            <property name="text">
+             <string>This tool converts an O3DE project's source assets to runtime ready content.</string>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QLabel" name="WelcomeDescriptionLabel2">
+            <property name="text">
+             <string>You can read more about the Asset Processor &lt;a href=&quot;https://www.o3de.org/docs/user-guide/assets/asset-processor/&quot;&gt;here.&lt;/a&gt;</string>
+            </property>
+            <property name="openExternalLinks">
+             <bool>true</bool>
+            </property>
+            <property name="textInteractionFlags">
+             <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse</set>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <spacer name="welcomeScreenVerticalSpacer">
+            <property name="orientation">
+             <enum>Qt::Vertical</enum>
+            </property>
+            <property name="sizeHint" stdset="0">
+             <size>
+              <width>16</width>
+              <height>40</height>
+             </size>
+            </property>
+           </spacer>
+          </item>
+         </layout>
+        </widget>
+        <widget class="QSplitter" name="jobDialogSplitter">
          <property name="orientation">
           <enum>Qt::Vertical</enum>
          </property>
@@ -1378,142 +1378,141 @@
                  <attribute name="title">
                   <string>Details</string>
                  </attribute>
-                  <layout class="QGridLayout" name="builderInfoDetails" columnstretch="1,3">
-                    <item row="2" column="0">
-                      <widget class="QLabel" name="builderInfoDetailsTitleVersionNumber">
-                        <property name="font">
-                          <font>
-                            <weight>75</weight>
-                            <italic>false</italic>
-                            <bold>true</bold>
-                          </font>
-                        </property>
-                        <property name="styleSheet">
-                          <string notr="true">font: bold;</string>
-                        </property>
-                        <property name="text">
-                          <string>Version Number</string>
-                        </property>
-                        <property name="wordWrap">
-                          <bool>true</bool>
-                        </property>
-                      </widget>
-                    </item>
-                    <item row="0" column="1">
-                      <widget class="QLabel" name="builderInfoDetailsValueType">
-                        <property name="text">
-                          <string/>
-                        </property>
-                        <property name="wordWrap">
-                          <bool>true</bool>
-                        </property>
-                      </widget>
-                    </item>
-                    <item row="1" column="0">
-                      <widget class="QLabel" name="builderInfoDetailsTitleFingerprint">
-                        <property name="font">
-                          <font>
-                            <weight>75</weight>
-                            <italic>false</italic>
-                            <bold>true</bold>
-                          </font>
-                        </property>
-                        <property name="styleSheet">
-                          <string notr="true">font: bold;</string>
-                        </property>
-                        <property name="text">
-                          <string>Fingerprint</string>
-                        </property>
-                        <property name="wordWrap">
-                          <bool>true</bool>
-                        </property>
-                      </widget>
-                    </item>
-                    <item row="0" column="0">
-                      <widget class="QLabel" name="builderInfoDetailsTitleType">
-                        <property name="font">
-                          <font>
-                            <weight>75</weight>
-                            <italic>false</italic>
-                            <bold>true</bold>
-                          </font>
-                        </property>
-                        <property name="styleSheet">
-                          <string notr="true">font: bold;</string>
-                        </property>
-                        <property name="text">
-                          <string>Type</string>
-                        </property>
-                        <property name="wordWrap">
-                          <bool>true</bool>
-                        </property>
-                      </widget>
-                    </item>
-                    <item row="3" column="0">
-                      <widget class="QLabel" name="builderInfoDetailsTitleBusId">
-                        <property name="font">
-                          <font>
-                            <weight>75</weight>
-                            <italic>false</italic>
-                            <bold>true</bold>
-                          </font>
-                        </property>
-                        <property name="styleSheet">
-                          <string notr="true">font: bold;</string>
-                        </property>
-                        <property name="text">
-                          <string>BusID</string>
-                        </property>
-                        <property name="wordWrap">
-                          <bool>true</bool>
-                        </property>
-                      </widget>
-                    </item>
-                    <item row="1" column="1">
-                      <widget class="QLabel" name="builderInfoDetailsValueFingerprint">
-                        <property name="text">
-                          <string/>
-                        </property>
-                        <property name="wordWrap">
-                          <bool>true</bool>
-                        </property>
-                      </widget>
-                    </item>
-                    <item row="2" column="1">
-                      <widget class="QLabel" name="builderInfoDetailsValueVersionNumber">
-                        <property name="text">
-                          <string/>
-                        </property>
-                        <property name="wordWrap">
-                          <bool>true</bool>
-                        </property>
-                      </widget>
-                    </item>
-                    <item row="3" column="1">
-                      <widget class="QLabel" name="builderInfoDetailsValueBusId">
-                        <property name="text">
-                          <string/>
-                        </property>
-                        <property name="wordWrap">
-                          <bool>true</bool>
-                        </property>
-                      </widget>
-                    </item>
-                    <item row="4" column="0">
-                      <spacer name="builderInfoDetailsSpacer">
-                        <property name="orientation">
-                          <enum>Qt::Vertical</enum>
-                        </property>
-                        <property name="sizeHint" stdset="0">
-                          <size>
-                            <width>16</width>
-                            <height>40</height>
-                          </size>
-                        </property>
-                      </spacer>
-                    </item>
-                  </layout>
-
+                 <layout class="QGridLayout" name="builderInfoDetails" columnstretch="1,3">
+                  <item row="2" column="0">
+                   <widget class="QLabel" name="builderInfoDetailsTitleVersionNumber">
+                    <property name="font">
+                     <font>
+                      <weight>75</weight>
+                      <italic>false</italic>
+                      <bold>true</bold>
+                     </font>
+                    </property>
+                    <property name="styleSheet">
+                     <string notr="true">font: bold;</string>
+                    </property>
+                    <property name="text">
+                     <string>Version Number</string>
+                    </property>
+                    <property name="wordWrap">
+                     <bool>true</bool>
+                    </property>
+                   </widget>
+                  </item>
+                  <item row="0" column="1">
+                   <widget class="QLabel" name="builderInfoDetailsValueType">
+                    <property name="text">
+                     <string/>
+                    </property>
+                    <property name="wordWrap">
+                     <bool>true</bool>
+                    </property>
+                   </widget>
+                  </item>
+                  <item row="1" column="0">
+                   <widget class="QLabel" name="builderInfoDetailsTitleFingerprint">
+                    <property name="font">
+                     <font>
+                      <weight>75</weight>
+                      <italic>false</italic>
+                      <bold>true</bold>
+                     </font>
+                    </property>
+                    <property name="styleSheet">
+                     <string notr="true">font: bold;</string>
+                    </property>
+                    <property name="text">
+                     <string>Fingerprint</string>
+                    </property>
+                    <property name="wordWrap">
+                     <bool>true</bool>
+                    </property>
+                   </widget>
+                  </item>
+                  <item row="0" column="0">
+                   <widget class="QLabel" name="builderInfoDetailsTitleType">
+                    <property name="font">
+                     <font>
+                      <weight>75</weight>
+                      <italic>false</italic>
+                      <bold>true</bold>
+                     </font>
+                    </property>
+                    <property name="styleSheet">
+                     <string notr="true">font: bold;</string>
+                    </property>
+                    <property name="text">
+                     <string>Type</string>
+                    </property>
+                    <property name="wordWrap">
+                     <bool>true</bool>
+                    </property>
+                   </widget>
+                  </item>
+                  <item row="3" column="0">
+                   <widget class="QLabel" name="builderInfoDetailsTitleBusId">
+                    <property name="font">
+                     <font>
+                      <weight>75</weight>
+                      <italic>false</italic>
+                      <bold>true</bold>
+                     </font>
+                    </property>
+                    <property name="styleSheet">
+                     <string notr="true">font: bold;</string>
+                    </property>
+                    <property name="text">
+                     <string>BusID</string>
+                    </property>
+                    <property name="wordWrap">
+                     <bool>true</bool>
+                    </property>
+                   </widget>
+                  </item>
+                  <item row="1" column="1">
+                   <widget class="QLabel" name="builderInfoDetailsValueFingerprint">
+                    <property name="text">
+                     <string/>
+                    </property>
+                    <property name="wordWrap">
+                     <bool>true</bool>
+                    </property>
+                   </widget>
+                  </item>
+                  <item row="2" column="1">
+                   <widget class="QLabel" name="builderInfoDetailsValueVersionNumber">
+                    <property name="text">
+                     <string/>
+                    </property>
+                    <property name="wordWrap">
+                     <bool>true</bool>
+                    </property>
+                   </widget>
+                  </item>
+                  <item row="3" column="1">
+                   <widget class="QLabel" name="builderInfoDetailsValueBusId">
+                    <property name="text">
+                     <string/>
+                    </property>
+                    <property name="wordWrap">
+                     <bool>true</bool>
+                    </property>
+                   </widget>
+                  </item>
+                  <item row="4" column="0">
+                   <spacer name="builderInfoDetailsSpacer">
+                    <property name="orientation">
+                     <enum>Qt::Vertical</enum>
+                    </property>
+                    <property name="sizeHint" stdset="0">
+                     <size>
+                      <width>16</width>
+                      <height>40</height>
+                     </size>
+                    </property>
+                   </spacer>
+                  </item>
+                 </layout>
                 </widget>
                 <widget class="QWidget" name="builderInfoMetricsTab">
                  <attribute name="title">
@@ -2338,6 +2337,78 @@
           </item>
          </layout>
         </widget>
+        <widget class="QWidget" name="AssetRelocationPage">
+         <layout class="QVBoxLayout" name="AssetRelocationVerticalLayout">
+          <item>
+           <widget class="QLabel" name="AssetRelocationScreenTitle">
+            <property name="font">
+             <font>
+              <pointsize>10</pointsize>
+              <weight>75</weight>
+              <bold>true</bold>
+             </font>
+            </property>
+            <property name="text">
+             <string># Asset Relocation</string>
+            </property>
+            <property name="textFormat">
+             <enum>Qt::MarkdownText</enum>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <layout class="QHBoxLayout" name="MetaCreationDelayLayout">
+            <item>
+             <widget class="QLabel" name="MetaCreationDelayTitle">
+              <property name="text">
+               <string>Meta File Creation Delay:</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QLabel" name="MetaCreationDelayValue">
+              <property name="text">
+               <string>Delay value could not be loaded</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <spacer name="MetaCreationDelaySpacer">
+              <property name="orientation">
+               <enum>Qt::Horizontal</enum>
+              </property>
+              <property name="sizeHint" stdset="0">
+               <size>
+                <width>40</width>
+                <height>20</height>
+               </size>
+              </property>
+             </spacer>
+            </item>
+           </layout>
+          </item>
+          <item>
+           <widget class="QLabel" name="AssetRelocationDescriptionLabel">
+            <property name="text">
+             <string>This is the list of currently enabled extensions for asset relocation. You can read more about relocating assets &lt;a href=&quot;https://www.o3de.org/docs/user-guide/assets/pipeline/metadata&quot;&gt;here.&lt;/a&gt;</string>
+            </property>
+            <property name="openExternalLinks">
+             <bool>true</bool>
+            </property>
+            <property name="textInteractionFlags">
+             <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse</set>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QListView" name="AssetRelocationExtensionListView">
+            <property name="showDropIndicator" stdset="0">
+             <bool>false</bool>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </widget>
        </widget>
       </item>
      </layout>

+ 5 - 4
Code/Tools/AssetProcessor/native/ui/ProductAssetDetailsPanel.cpp

@@ -26,6 +26,7 @@
 #include <QStringLiteral>
 #include <QUrl>
 #include <native/ui/GoToButtonDelegate.h>
+#include <AzCore/Jobs/JobFunction.h>
 
 namespace AssetProcessor
 {
@@ -465,9 +466,8 @@ namespace AssetProcessor
         QString pathOnDisk = cacheRootDir.filePath(productItemData->m_databaseInfo.m_productName.c_str());
 
         AddProductIdToScanCount(productItemData->m_databaseInfo.m_productID, scanName);
-
         // Run the scan on another thread so the UI remains responsive.
-        AZStd::thread scanningThread = AZStd::thread([=]() {
+        auto* job = AZ::CreateJobFunction([=]() {
             MissingDependencyScannerRequestBus::Broadcast(&MissingDependencyScannerRequestBus::Events::ScanFile,
                 pathOnDisk.toUtf8().constData(),
                 MissingDependencyScanner::DefaultMaxScanIteration,
@@ -490,8 +490,9 @@ namespace AssetProcessor
                     }
                 }
             });
-        });
-        scanningThread.detach();
+        }, true);
+
+        job->Start();
     }
 
     void ProductAssetDetailsPanel::AddProductIdToScanCount(AZ::s64 scannedProductId, QString scanName)

+ 35 - 36
Code/Tools/AssetProcessor/native/ui/ProductAssetTreeModel.cpp

@@ -46,12 +46,12 @@ namespace AssetProcessor
             return;
         }
 
-        m_sharedDbConnection->QueryProductsTable(
-            [&](AzToolsFramework::AssetDatabase::ProductDatabaseEntry& product)
-        {
-            AddOrUpdateEntry(product, true);
-            return true; // return true to continue iterating over additional results, we are populating a container
-        });
+        m_sharedDbConnection->QueryCombined(
+            [&](AzToolsFramework::AssetDatabase::CombinedDatabaseEntry& combined)
+            {
+                AddOrUpdateEntry(combined, true);
+                return true;
+            });
     }
 
     void ProductAssetTreeModel::OnProductFileChanged(const AzToolsFramework::AssetDatabase::ProductDatabaseEntry& entry)
@@ -70,7 +70,13 @@ namespace AssetProcessor
         // Model changes need to be run on the main thread.
         AZ::SystemTickBus::QueueFunction([&, entry]()
         {
-            AddOrUpdateEntry(entry, false);
+            m_sharedDbConnection->QueryCombinedByProductID(
+                entry.m_productID,
+                [this](const AzToolsFramework::AssetDatabase::CombinedDatabaseEntry& combined)
+                {
+                    AddOrUpdateEntry(combined, false);
+                    return false; // Should only be 1 entry for 1 product
+                });
         });
     }
 
@@ -186,34 +192,25 @@ namespace AssetProcessor
     }
 
     void ProductAssetTreeModel::AddOrUpdateEntry(
-        const AzToolsFramework::AssetDatabase::ProductDatabaseEntry& product,
+        const AzToolsFramework::AssetDatabase::CombinedDatabaseEntry& combinedDatabaseEntry,
         bool modelIsResetting)
     {
-        AZStd::string platform;
-        m_sharedDbConnection->QueryJobByProductID(
-            product.m_productID,
-            [&](AzToolsFramework::AssetDatabase::JobDatabaseEntry& jobEntry)
-            {
-                platform = jobEntry.m_platform;
-                return true;
-            });
-
         // Intermediate assets are functionally source assets, output as products from other source assets.
         // Don't display them in the product assets tab.
-        if (product.m_flags.test(static_cast<int>(AssetBuilderSDK::ProductOutputFlags::IntermediateAsset)))
+        if (combinedDatabaseEntry.m_flags.test(static_cast<int>(AssetBuilderSDK::ProductOutputFlags::IntermediateAsset)))
         {
             return;
         }
 
-        const auto& existingEntry = m_productIdToTreeItem.find(product.m_productID);
+        const auto& existingEntry = m_productIdToTreeItem.find(combinedDatabaseEntry.m_productID);
         if (existingEntry != m_productIdToTreeItem.end())
         {
             AZStd::shared_ptr<ProductAssetTreeItemData> productItemData = AZStd::rtti_pointer_cast<ProductAssetTreeItemData>(existingEntry->second->GetData());
 
             // This item already exists, refresh the related data.
-            productItemData->m_databaseInfo = product;
+            productItemData->m_databaseInfo = combinedDatabaseEntry; // Intentional object slicing occuring here.  CombinedEntry -> ProductEntry
             CheckForUnresolvedIssues(productItemData);
-            
+
             QModelIndex existingIndexStart = createIndex(existingEntry->second->GetRow(), 0, existingEntry->second);
             QModelIndex existingIndexEnd = createIndex(existingEntry->second->GetRow(), existingEntry->second->GetColumnCount() - 1, existingEntry->second);
             Q_ASSERT(checkIndex(existingIndexStart));
@@ -222,11 +219,16 @@ namespace AssetProcessor
             return;
         }
 
-        AZ::IO::Path productNamePath(product.m_productName, AZ::IO::PosixPathSeparator);
+        AZ::IO::Path productNamePath(combinedDatabaseEntry.m_productName, AZ::IO::PosixPathSeparator);
 
         if (productNamePath.empty())
         {
-            AZ_Warning("AssetProcessor", false, "Product id %d has an invalid name: %s", product.m_productID, product.m_productName.c_str());
+            AZ_Warning(
+                "AssetProcessor",
+                false,
+                "Product id %d has an invalid name: %s",
+                combinedDatabaseEntry.m_productID,
+                combinedDatabaseEntry.m_productName.c_str());
             return;
         }
 
@@ -266,16 +268,8 @@ namespace AssetProcessor
             parentItem = nextParent;
         }
 
-        AZ::Uuid sourceId;
-        AZ::s64 scanFolderID;
-        m_sharedDbConnection->QuerySourceByProductID(
-            product.m_productID,
-            [&](AzToolsFramework::AssetDatabase::SourceDatabaseEntry& sourceEntry)
-        {
-            sourceId = sourceEntry.m_sourceGuid;
-            scanFolderID = sourceEntry.m_scanFolderPK;
-            return true;
-        });
+        AZ::Uuid sourceId = combinedDatabaseEntry.m_sourceGuid;
+        AZ::s64 scanFolderID = combinedDatabaseEntry.m_scanFolderPK;
 
         if (!modelIsResetting)
         {
@@ -285,10 +279,15 @@ namespace AssetProcessor
         }
 
         AZStd::shared_ptr<ProductAssetTreeItemData> productItemData = ProductAssetTreeItemData::MakeShared(
-            &product, product.m_productName, AZ::IO::FixedMaxPathString(filename.Native()).c_str(), false, sourceId, scanFolderID);
-        m_productToTreeItem[product.m_productName] =
+            &combinedDatabaseEntry,
+            combinedDatabaseEntry.m_productName,
+            AZ::IO::FixedMaxPathString(filename.Native()).c_str(),
+            false,
+            sourceId,
+            scanFolderID);
+        m_productToTreeItem[combinedDatabaseEntry.m_productName] =
             parentItem->CreateChild(productItemData);
-        m_productIdToTreeItem[product.m_productID] = m_productToTreeItem[product.m_productName];
+        m_productIdToTreeItem[combinedDatabaseEntry.m_productID] = m_productToTreeItem[combinedDatabaseEntry.m_productName];
 
         CheckForUnresolvedIssues(productItemData);
 

+ 1 - 1
Code/Tools/AssetProcessor/native/ui/ProductAssetTreeModel.h

@@ -31,7 +31,7 @@ namespace AssetProcessor
         void ResetModel() override;
 
         void AddOrUpdateEntry(
-            const AzToolsFramework::AssetDatabase::ProductDatabaseEntry& product,
+            const AzToolsFramework::AssetDatabase::CombinedDatabaseEntry& product,
             bool modelIsResetting);
 
         void RemoveAsset(AZ::s64 productId);

+ 5 - 0
Code/Tools/AssetProcessor/native/ui/style/AssetProcessor.qss

@@ -212,3 +212,8 @@ QWidget#SharedCachePage {
     background-color: rgba(34, 34, 34, 0.5);
     border: none;
 }
+
+QWidget#AssetRelocationPage {
+    background-color: rgba(34, 34, 34, 0.5);
+    border: none;
+}

+ 5 - 0
Code/Tools/AssetProcessor/native/utilities/UuidManager.cpp

@@ -214,6 +214,11 @@ namespace AssetProcessor
         return m_enabledTypes.contains(file.Extension().Native());
     }
 
+    AZStd::unordered_set<AZStd::string> UuidManager::GetEnabledTypes()
+    {
+        return m_enabledTypes;
+    }
+
     void UuidManager::EnableGenerationForTypes(AZStd::unordered_set<AZStd::string> types)
     {
         m_enabledTypes = AZStd::move(types);

+ 5 - 0
Code/Tools/AssetProcessor/native/utilities/UuidManager.h

@@ -62,6 +62,10 @@ namespace AssetProcessor
         virtual void EnableGenerationForTypes(AZStd::unordered_set<AZStd::string> types) = 0;
         //! Returns true if UUID generation is enabled for the type (based on file extension), false otherwise.
         virtual bool IsGenerationEnabledForFile(AZ::IO::PathView file) = 0;
+
+        //! Returns the list of file types that UUID generation is enabled for. This is useful over checking the registry setting
+        //! directly, because other types may be enabled via code.
+        virtual AZStd::unordered_set<AZStd::string> GetEnabledTypes() = 0;
     };
 
     //! Serialized settings type for storing the user preferences for the UUID manager
@@ -93,6 +97,7 @@ namespace AssetProcessor
         void FileRemoved(AZ::IO::PathView file) override;
         void EnableGenerationForTypes(AZStd::unordered_set<AZStd::string> types) override;
         bool IsGenerationEnabledForFile(AZ::IO::PathView file) override;
+        AZStd::unordered_set<AZStd::string> GetEnabledTypes() override;
 
     private:
         AZ::IO::Path GetCanonicalPath(AZ::IO::PathView file);

+ 122 - 62
Code/Tools/SerializeContextTools/Dumper.cpp

@@ -20,6 +20,7 @@
 #include <AzCore/Serialization/EditContext.h>
 #include <AzCore/Serialization/Json/JsonSerialization.h>
 #include <AzCore/Serialization/Json/JsonSerializationSettings.h>
+#include <AzCore/Settings/TextParser.h>
 #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
 #include <AzCore/std/algorithm.h>
 #include <AzCore/std/sort.h>
@@ -277,28 +278,32 @@ namespace AZ::SerializeContextTools
         // If the output-file parameter has been supplied open the file path using FileIOStream
         if (size_t optionCount = commandLine.GetNumSwitchValues("output-file"); optionCount > 0)
         {
-            AZ::IO::FixedMaxPath outputPath;
-            if (AZ::IO::PathView outputPathView(commandLine.GetSwitchValue("output-file", optionCount - 1));
-                outputPathView.IsRelative())
+            AZ::IO::PathView outputPathView(commandLine.GetSwitchValue("output-file", optionCount - 1));
+            // If the output file name is a single dash, use the default output stream value which writes to stdout
+            if (outputPathView != "-")
             {
-                AZ::Utils::ConvertToAbsolutePath(outputPath, outputPathView.Native());
-            }
-            else
-            {
-                outputPath = outputPathView.LexicallyNormal();
-            }
+                AZ::IO::FixedMaxPath outputPath;
+                if (outputPathView.IsRelative())
+                {
+                    AZ::Utils::ConvertToAbsolutePath(outputPath, outputPathView.Native());
+                }
+                else
+                {
+                    outputPath = outputPathView.LexicallyNormal();
+                }
 
-            constexpr AZ::IO::OpenMode openMode = AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath;
+                constexpr AZ::IO::OpenMode openMode = AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath;
 
-            if (auto& fileStream = outputStream.emplace<AZ::IO::SystemFileStream>(outputPath.c_str(), openMode);
-                !fileStream.IsOpen())
-            {
-                AZ_Printf(
-                    "dumptypes",
-                    R"(Unable to open specified output-file "%s". Object will not be dumped)"
-                    "\n",
-                    outputPath.c_str());
-                return false;
+                if (auto& fileStream = outputStream.emplace<AZ::IO::SystemFileStream>(outputPath.c_str(), openMode);
+                    !fileStream.IsOpen())
+                {
+                    AZ_Printf(
+                        "dumptypes",
+                        R"(Unable to open specified output-file "%s". Object will not be dumped)"
+                        "\n",
+                        outputPath.c_str());
+                    return false;
+                }
             }
         }
 
@@ -447,28 +452,32 @@ namespace AZ::SerializeContextTools
         // If the output-file parameter has been supplied open the file path using FileIOStream
         if (size_t optionCount = commandLine.GetNumSwitchValues("output-file"); optionCount > 0)
         {
-            AZ::IO::FixedMaxPath outputPath;
-            if (AZ::IO::PathView outputPathView(commandLine.GetSwitchValue("output-file", optionCount - 1));
-                outputPathView.IsRelative())
-            {
-                AZ::Utils::ConvertToAbsolutePath(outputPath, outputPathView.Native());
-            }
-            else
+            AZ::IO::PathView outputPathView(commandLine.GetSwitchValue("output-file", optionCount - 1));
+            // If the output file name is a single dash, use the default output stream value which writes to stdout
+            if (outputPathView != "-")
             {
-                outputPath = outputPathView.LexicallyNormal();
-            }
+                AZ::IO::FixedMaxPath outputPath;
+                if (outputPathView.IsRelative())
+                {
+                    AZ::Utils::ConvertToAbsolutePath(outputPath, outputPathView.Native());
+                }
+                else
+                {
+                    outputPath = outputPathView.LexicallyNormal();
+                }
 
-            constexpr AZ::IO::OpenMode openMode = AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath;
+                constexpr AZ::IO::OpenMode openMode = AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath;
 
-            if (auto& fileStream = outputStream.emplace<AZ::IO::SystemFileStream>(outputPath.c_str(), openMode);
-                !fileStream.IsOpen())
-            {
-                AZ_Printf(
-                    "createtype",
-                    R"(Unable to open specified output-file "%s". Object will not be dumped)"
-                    "\n",
-                    outputPath.c_str());
-                return false;
+                if (auto& fileStream = outputStream.emplace<AZ::IO::SystemFileStream>(outputPath.c_str(), openMode);
+                    !fileStream.IsOpen())
+                {
+                    AZ_Printf(
+                        "createtype",
+                        R"(Unable to open specified output-file "%s". Object will not be dumped)"
+                        "\n",
+                        outputPath.c_str());
+                    return false;
+                }
             }
         }
 
@@ -606,35 +615,40 @@ namespace AZ::SerializeContextTools
         // If the output-file parameter has been supplied open the file path using FileIOStream
         if (size_t optionCount = commandLine.GetNumSwitchValues("output-file"); optionCount > 0)
         {
-            AZ::IO::FixedMaxPath outputPath;
-            if (AZ::IO::PathView outputPathView(commandLine.GetSwitchValue("output-file", optionCount - 1));
-                outputPathView.IsRelative())
-            {
-                AZ::Utils::ConvertToAbsolutePath(outputPath, outputPathView.Native());
-            }
-            else
+            AZ::IO::PathView outputPathView(commandLine.GetSwitchValue("output-file", optionCount - 1));
+            // If the output file name is a single dash, use the default output stream value which writes to stdout
+            if (outputPathView != "-")
             {
-                outputPath = outputPathView.LexicallyNormal();
-            }
+                AZ::IO::FixedMaxPath outputPath;
+                if (outputPathView.IsRelative())
+                {
+                    AZ::Utils::ConvertToAbsolutePath(outputPath, outputPathView.Native());
+                }
+                else
+                {
+                    outputPath = outputPathView.LexicallyNormal();
+                }
 
-            constexpr AZ::IO::OpenMode openMode = AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath;
+                constexpr AZ::IO::OpenMode openMode = AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath;
 
-            if (auto& fileStream = outputStream.emplace<AZ::IO::SystemFileStream>(outputPath.c_str(), openMode);
-                !fileStream.IsOpen())
-            {
-                AZ_Printf(
-                    "createuuid",
-                    R"(Unable to open specified output-file "%s". Uuid will not be output to stream)"
-                    "\n",
-                    outputPath.c_str());
-                return false;
+                if (auto& fileStream = outputStream.emplace<AZ::IO::SystemFileStream>(outputPath.c_str(), openMode);
+                    !fileStream.IsOpen())
+                {
+                    AZ_Printf(
+                        "createuuid",
+                        R"(Unable to open specified output-file "%s". Uuid will not be output to stream)"
+                        "\n",
+                        outputPath.c_str());
+                    return false;
+                }
             }
         }
 
         size_t valuesOptionCount = commandLine.GetNumSwitchValues("values");
-        if (valuesOptionCount == 0)
+        size_t valuesFileOptionCount = commandLine.GetNumSwitchValues("values-file");
+        if (valuesOptionCount == 0 && valuesFileOptionCount == 0)
         {
-            AZ_Error("createuuid", false, "The following options must be supplied: --values ");
+            AZ_Error("createuuid", false, "One of following options must be supplied: --values or --values-file");
             return false;
         }
 
@@ -655,23 +669,69 @@ namespace AZ::SerializeContextTools
         const bool quietOutput = commandLine.HasSwitch("q") || commandLine.HasSwitch("quiet");
 
         bool result = true;
+
+        struct UuidStringPair
+        {
+            AZ::Uuid m_uuid;
+            AZStd::string m_value;
+        };
+        AZStd::vector<UuidStringPair> uuidsToWrite;
         for (size_t i = 0; i < valuesOptionCount; ++i)
         {
             AZStd::string value = commandLine.GetSwitchValue("values", i);
             auto uuidFromName = AZ::Uuid::CreateName(value);
+            uuidsToWrite.push_back({ AZStd::move(uuidFromName), AZStd::move(value) });
+        }
+
+        // Read string values from each --values-file argument
+        for (size_t i = 0; i < valuesFileOptionCount; ++i)
+        {
+            AZ::IO::FixedMaxPath inputValuePath(AZ::IO::PathView(commandLine.GetSwitchValue("values-file", i)));
+            AZ::IO::SystemFileStream valuesFileStream;
+            if (inputValuePath == "-")
+            {
+                // If the input file is dash read from stdin
+                valuesFileStream = AZ::IO::SystemFileStream(AZ::IO::SystemFile::GetStdin());
+            }
+            else
+            {
+                // Open the path from the values-file option
+                constexpr AZ::IO::OpenMode openMode = AZ::IO::OpenMode::ModeRead;
+                valuesFileStream.Open(inputValuePath.c_str(), openMode);
+            }
 
-            auto VisitStream = [&uuidFromName, &value, withCurlyBraces, withDashes, quietOutput](auto&& stream) -> bool
+            if (valuesFileStream.IsOpen())
+            {
+                // Use the text parser to parse plain text lines
+                AZ::Settings::TextParserSettings textParserSettings;
+                textParserSettings.m_parseTextEntryFunc = [&uuidsToWrite](AZStd::string_view token)
+                {
+                    // Remove leading and surrounding spaces and carriage returns
+                    token = AZ::StringFunc::StripEnds(token, " \r");
+                    auto uuidFromName = AZ::Uuid::CreateName(token);
+                    uuidsToWrite.push_back({ AZStd::move(uuidFromName), token });
+                    return true;
+                };
+
+                AZ::Settings::ParseTextFile(valuesFileStream, textParserSettings);
+            }
+        }
+
+        for (const UuidStringPair& uuidStringPair : uuidsToWrite)
+        {
+            auto VisitStream = [&uuidToWrite = uuidStringPair.m_uuid, &value = uuidStringPair.m_value,
+                withCurlyBraces, withDashes, quietOutput](auto&& stream) -> bool
             {
                 AZStd::fixed_string<256> uuidString;
                 if (quietOutput)
                 {
                     uuidString = AZStd::fixed_string<256>::format("%s\n",
-                        uuidFromName.ToFixedString(withCurlyBraces, withDashes).c_str());
+                        uuidToWrite.ToFixedString(withCurlyBraces, withDashes).c_str());
                 }
                 else
                 {
                     uuidString = AZStd::fixed_string<256>::format(R"(%s %s)" "\n",
-                        uuidFromName.ToFixedString(withCurlyBraces, withDashes).c_str(),
+                        uuidToWrite.ToFixedString(withCurlyBraces, withDashes).c_str(),
                         value.c_str());
                 }
 

+ 3 - 0
Code/Tools/SerializeContextTools/Runner.cpp

@@ -99,8 +99,11 @@ namespace SerializeContextTools
         AZ_Printf("Help", R"(        Ex. --values "engine.json" --values "project.json")" "\n");
         AZ_Printf("Help", R"(        Ex. --values engine.json,project.json)" "\n");
         AZ_Printf("Help", R"(        Ex. --values engine.json,project.json --values gem.json)" "\n");
+        AZ_Printf("Help", "    [opt] --values-file=<filepath>: Path to file containing linefeed delimited strings to convert to UUD.\n");
+        AZ_Printf("Help", "          specifying an argument of dash '-' reads input from stdin\n");
         AZ_Printf("Help", "    [opt] --output-file=<filepath>: Path to the file to output constructed uuids.\n");
         AZ_Printf("Help", "          If not supplied, output is written to stdout.\n");
+        AZ_Printf("Help", "          specifying an argument of dash '-' writes output to stdout\n");
         AZ_Printf("Help", "    [opt] --with-curly-braces=<true|false> Outputs the Uuid with curly braces. Defaults to true\n");
         AZ_Printf("Help", "         Ex. when true = {0123456789abcdef0123456789abcdef}\n");
         AZ_Printf("Help", "         Ex. when false = 0123456789abcdef0123456789abcdef\n");

+ 2 - 0
Code/Tools/TestImpactFramework/Runtime/Common/Code/Include/Static/TestEngine/Common/TestImpactTestEngine.h

@@ -9,6 +9,8 @@
 #include <Process/Scheduler/TestImpactProcessScheduler.h>
 #include <TestEngine/Common/TestImpactTestEngineBus.h>
 #include <TestEngine/Common/TestImpactTestEngineException.h>
+#include <TestImpactFramework/TestImpactPolicy.h>
+#include <TestImpactFramework/TestImpactTestSequence.h>
 
 #include <AzCore/std/containers/unordered_map.h>
 #include <AzCore/std/containers/vector.h>

+ 3 - 0
Code/Tools/TestImpactFramework/Runtime/Native/Code/Source/TestEngine/Native/TestImpactNativeTestEngine.h

@@ -17,6 +17,8 @@
 #include <TestEngine/Common/Run/TestImpactTestEngineInstrumentedRun.h>
 #include <TestEngine/Common/Run/TestImpactTestEngineRegularRun.h>
 
+#include <TestRunner/Native/Job/TestImpactNativeShardedTestJobInfoGenerator.h>
+
 #include <AzCore/std/containers/vector.h>
 #include <AzCore/std/smart_ptr/unique_ptr.h>
 
@@ -26,6 +28,7 @@ namespace TestImpact
     class NativeTestEnumerationJobInfoGenerator;
     class NativeRegularTestRunJobInfoGenerator;
     class NativeInstrumentedTestRunJobInfoGenerator;
+    struct NativeShardedArtifactDir;
     class NativeShardedRegularTestRunJobInfoGenerator;
     class NativeShardedInstrumentedTestRunJobInfoGenerator;
     class NativeTestEnumerator;

+ 0 - 4
Gems/LmbrCentral/Code/Source/Builders/LevelBuilder/LevelBuilderWorker.cpp

@@ -194,10 +194,6 @@ namespace LevelBuilder
         //  The defines exist in CryEngine code that we can't link from here.
         //  We want to minimize engine changes to make it easier for game teams to incorporate these dependency improvements.
 
-        // CCullThread::LoadLevel attempts to load the occluder mesh, if it exists.
-        //      AZ::IO::HandleType fileHandle = gEnv->pCryPak->FOpen((string(pFolderName) + "/occluder.ocm").c_str(), "rbx");
-        AddLevelRelativeSourcePathProductDependency("occluder.ocm", sourceLevelPakPath, productPathDependencies);
-
         // C3DEngine::LoadLevel attempts to load this file for the current level, if it exists.
         //      GetISystem()->LoadConfiguration(GetLevelFilePath(LEVEL_CONFIG_FILE));
         AddLevelRelativeSourcePathProductDependency("level.cfg", sourceLevelPakPath, productPathDependencies);

+ 0 - 1
Gems/LyShine/Code/Editor/Animation/UiAnimViewNodes.cpp

@@ -22,7 +22,6 @@
 
 #include "Objects/EntityObject.h"
 #include "ViewManager.h"
-#include "Export/ExportManager.h"
 #include <Editor/Util/fastlib.h>
 
 #include "QtUtilWin.h"

+ 4 - 0
Tools/LyTestTools/ly_test_tools/__init__.py

@@ -8,6 +8,7 @@ OS and devices are detected and set as constants when ly_test_tools.__init__() c
 """
 import logging
 import sys
+import platform
 
 logger = logging.getLogger(__name__)
 
@@ -46,6 +47,7 @@ if WINDOWS:
     LAUNCHERS['windows_atom_tools'] = WinAtomToolsLauncher
     LAUNCHERS['android'] = AndroidLauncher
 elif MAC:
+    ARM64 = platform.machine() == 'arm64'
     HOST_OS_PLATFORM = 'mac'
     HOST_OS_EDITOR = NotImplementedError('LyTestTools does not yet support Mac editor')
     HOST_OS_DEDICATED_SERVER = NotImplementedError('LyTestTools does not yet support Mac dedicated server')
@@ -53,6 +55,7 @@ elif MAC:
     from ly_test_tools.launchers import MacLauncher
     LAUNCHERS['mac'] = MacLauncher
 elif LINUX:
+    ARM64 = platform.machine() == 'aarch64'
     HOST_OS_PLATFORM = 'linux'
     HOST_OS_EDITOR = 'linux_editor'
     HOST_OS_DEDICATED_SERVER = 'linux_dedicated'
@@ -64,5 +67,6 @@ elif LINUX:
     LAUNCHERS['linux_dedicated'] = DedicatedLinuxLauncher
     LAUNCHERS['linux_atom_tools'] = LinuxAtomToolsLauncher
 else:
+    ARM64 = False
     logger.warning(f'WARNING: LyTestTools only supports Windows, Mac, and Linux. '
                    f'Unexpectedly detected HOST_OS_PLATFORM: "{HOST_OS_PLATFORM}".')

+ 72 - 27
scripts/o3de/o3de/engine_template.py

@@ -16,7 +16,7 @@ import sys
 import json
 import uuid
 import re
-
+from typing import Tuple
 
 from o3de import manifest, register, validation, utils
 
@@ -672,7 +672,7 @@ def create_template(source_path: pathlib.Path,
         return ext.lower() in cpp_file_ext
 
     def _transform_into_template(s_data: object,
-                                 prefer_sanitized_name: bool = False) -> (bool, str):
+                                 prefer_sanitized_name: bool = False) -> Tuple[bool, str]:
         """
         Internal function to transform any data into templated data
         :param s_data: the input data, this could be file data or file name data
@@ -706,32 +706,77 @@ def create_template(source_path: pathlib.Path,
         if not keep_license_text:
             t_data = _replace_license_text(t_data)
 
+        def find_pattern_and_add_replacement(pattern: str, replace_placeholder: str):
+            """
+            Searchs for pattern containing Uuid within the file data
+            and replaces matched Uuids with with the specified placeholder
+            :param pattern: Regular expression pattern to search for Uuid
+                            Pattern must contain a symbolic group of (?P<Uuid>...)
+                            as documented at https://docs.python.org/3/library/re.html#regular-expression-syntax
+            :replace_placeholder: text to replaced matched Uuid symbolic group pattern with
+            """
+            nonlocal t_data
+            match_result_list = re.findall(pattern, t_data)
+            if match_result_list:
+                for uuid_text in match_result_list:
+                    replacements.append((uuid_text, replace_placeholder))
+                    t_data = t_data.replace(uuid_text, replace_placeholder)
+
         # See if this file has the ModuleClassId
-        try:
-            pattern = r'.*AZ_RTTI\(\$\{SanitizedCppName\}Module, \"(?P<ModuleClassId>\{.*-.*-.*-.*-.*\})\",'
-            module_class_id = re.search(pattern, t_data).group('ModuleClassId')
-            replacements.append((module_class_id, '${ModuleClassId}'))
-            t_data = t_data.replace(module_class_id, '${ModuleClassId}')
-        except Exception as e:
-            pass
+        find_pattern_and_add_replacement( \
+            r'.*AZ_RTTI\(\$\{SanitizedCppName\}Module,\s*"(?P<Uuid>\{?[A-Fa-f0-9]{8}-?[A-Fa-f0-9]{4}-?[A-Fa-f0-9]{4}-?[A-Fa-f0-9]{4}-?[A-Fa-f0-9]{12}\}?)"', \
+            '${ModuleClassId}')
 
         # See if this file has the SysCompClassId
-        try:
-            pattern = r'.*AZ_COMPONENT\(\$\{SanitizedCppName\}SystemComponent, \"(?P<SysCompClassId>\{.*-.*-.*-.*-.*\})\"'
-            sys_comp_class_id = re.search(pattern, t_data).group('SysCompClassId')
-            replacements.append((sys_comp_class_id, '${SysCompClassId}'))
-            t_data = t_data.replace(sys_comp_class_id, '${SysCompClassId}')
-        except Exception as e:
-            pass
+        find_pattern_and_add_replacement( \
+            r'.*AZ_COMPONENT\(\$\{SanitizedCppName\}SystemComponent,\s*"(?P<Uuid>\{?[A-Fa-f0-9]{8}-?[A-Fa-f0-9]{4}-?[A-Fa-f0-9]{4}-?[A-Fa-f0-9]{4}-?[A-Fa-f0-9]{12}\}?)"', \
+            '${SysCompClassId}')
 
         # See if this file has the EditorSysCompClassId
-        try:
-            pattern = r'.*AZ_COMPONENT\(\$\{SanitizedCppName\}EditorSystemComponent, \"(?P<EditorSysCompClassId>\{.*-.*-.*-.*-.*\})\"'
-            editor_sys_comp_class_id = re.search(pattern, t_data).group('EditorSysCompClassId')
-            replacements.append((editor_sys_comp_class_id, '${EditorSysCompClassId}'))
-            t_data = t_data.replace(editor_sys_comp_class_id, '${EditorSysCompClassId}')
-        except Exception as e:
-            pass
+        find_pattern_and_add_replacement( \
+            r'.*AZ_COMPONENT\(\$\{SanitizedCppName\}EditorSystemComponent,\s*"(?P<Uuid>\{?[A-Fa-f0-9]{8}-?[A-Fa-f0-9]{4}-?[A-Fa-f0-9]{4}-?[A-Fa-f0-9]{4}-?[A-Fa-f0-9]{12}\}?)"', \
+            '${EditorSysCompClassId}')
+
+        # Replace TypeIds.h TypeIds values with placeholders
+        # Replace <Uuid> values for EditorSystemComponentTypeId with the ${EditorSysCompClassId} placeholder
+        # Users non-capturing group of (?:...) to match a separating '=', '{' or '('
+        find_pattern_and_add_replacement( \
+            r'.*\$\{SanitizedCppName\}EditorSystemComponentTypeId\s*(?:{|=|\()\s*"(?P<Uuid>\{?[A-Fa-f0-9]{8}-?[A-Fa-f0-9]{4}-?[A-Fa-f0-9]{4}-?[A-Fa-f0-9]{4}-?[A-Fa-f0-9]{12}\}?)"', \
+            '${EditorSysCompClassId}')
+
+        # Replace the <Uuid> values from a line that matches patterns such as
+        # 1. *${SanitizedCppName}SystemComponentTypeId = <Uuid>;
+        # 2. *${SanitizedCppName}SystemComponentTypeId{<Uuid>};
+        # 3. *${SanitizedCppName}SystemComponentTypeId(<Uuid>);
+        # with the ${SysCompClassId} placeholder
+        find_pattern_and_add_replacement( \
+            r'.*\$\{SanitizedCppName\}SystemComponentTypeId\s*(?:{|=|\()\s*"(?P<Uuid>\{?[A-Fa-f0-9]{8}-?[A-Fa-f0-9]{4}-?[A-Fa-f0-9]{4}-?[A-Fa-f0-9]{4}-?[A-Fa-f0-9]{12}\}?)"', \
+            '${SysCompClassId}')
+
+        # Replace <Uuid> values for ModuleTypeId with the ${EditorSysCompClassId} placeholder
+        find_pattern_and_add_replacement( \
+            r'.*\$\{SanitizedCppName\}ModuleTypeId\s*(?:{|=|\()\s*"(?P<Uuid>\{?[A-Fa-f0-9]{8}-?[A-Fa-f0-9]{4}-?[A-Fa-f0-9]{4}-?[A-Fa-f0-9]{4}-?[A-Fa-f0-9]{12}\}?)"', \
+            '${ModuleClassId}')
+
+        # Replace <Uuid> values for remaining *TypeId variables with the {${Random_Uuid}} placeholder
+        find_pattern_and_add_replacement( \
+            r'.*TypeId\s*(?:{|=|\()\s*"(?P<Uuid>\{?[A-Fa-f0-9]{8}-?[A-Fa-f0-9]{4}-?[A-Fa-f0-9]{4}-?[A-Fa-f0-9]{4}-?[A-Fa-f0-9]{12}\}?)"', \
+            '{${Random_Uuid}}')
+
+        # Finally replace any remaining AZ_TYPE_INFO*, AZ_RTTI*, AZ_COMPONENT*, and AZ_EDITOR_COMPONENT*
+        # with the {${Random_Uuid}} placehlder
+        find_pattern_and_add_replacement( \
+            r'.*AZ_TYPE_INFO.*?\(.+,\s*"(?P<Uuid>\{?[A-Fa-f0-9]{8}-?[A-Fa-f0-9]{4}-?[A-Fa-f0-9]{4}-?[A-Fa-f0-9]{4}-?[A-Fa-f0-9]{12}\}?)"', \
+            '{${Random_Uuid}}')
+        find_pattern_and_add_replacement( \
+            r'.*AZ_RTTI.*?\(.+,\s*"(?P<Uuid>\{?[A-Fa-f0-9]{8}-?[A-Fa-f0-9]{4}-?[A-Fa-f0-9]{4}-?[A-Fa-f0-9]{4}-?[A-Fa-f0-9]{12}\}?)"', \
+            '{${Random_Uuid}}')
+        find_pattern_and_add_replacement( \
+            r'.*AZ_COMPONENT.*?\(.+,\s*"(?P<Uuid>\{?[A-Fa-f0-9]{8}-?[A-Fa-f0-9]{4}-?[A-Fa-f0-9]{4}-?[A-Fa-f0-9]{4}-?[A-Fa-f0-9]{12}\}?)"', \
+            '{${Random_Uuid}}')
+        find_pattern_and_add_replacement( \
+            r'.*AZ_EDITOR_COMPONENT.*?\(.+,\s*"(?P<Uuid>\{?[A-Fa-f0-9]{8}-?[A-Fa-f0-9]{4}-?[A-Fa-f0-9]{4}-?[A-Fa-f0-9]{4}-?[A-Fa-f0-9]{12}\}?)"', \
+            '{${Random_Uuid}}')
 
         # we want to send back the transformed data and whether or not this file
         # may require transformation when instantiated. So if the input data is not the
@@ -2114,7 +2159,7 @@ def create_gem(gem_path: pathlib.Path,
     replacements.append(("${Origin}", origin if origin else ""))
     replacements.append(("${OriginURL}", origin_url if origin_url else ""))
     replacements.append(("${Version}", version if version else "1.0.0"))
-    
+
 
     tags = [gem_name]
     if user_tags:
@@ -2748,17 +2793,17 @@ def add_args(subparsers) -> None:
     create_gem_subparser.add_argument('-lu', '--license-url', type=str, required=False,
                        default='Link to the license web site i.e. https://opensource.org/licenses/Apache-2.0',
                        help='Link to the license web site i.e. https://opensource.org/licenses/Apache-2.0')
-    create_gem_subparser.add_argument('-o', '--origin', type=str, required=False, 
+    create_gem_subparser.add_argument('-o', '--origin', type=str, required=False,
                        default='The name of the originator or creator',
                        help='The name of the originator or creator i.e. XYZ Inc.')
-    create_gem_subparser.add_argument('-ou', '--origin-url', type=str, required=False, 
+    create_gem_subparser.add_argument('-ou', '--origin-url', type=str, required=False,
                        default='The website for this Gem',
                        help='The website for your Gem. i.e. http://www.mydomain.com')
     create_gem_subparser.add_argument('-ut', '--user-tags', type=str, nargs='*', required=False,
                        help='Adds tag(s) to user_tags property. Can be specified multiple times.')
     create_gem_subparser.add_argument('-pl', '--platforms', type=str, nargs='*', required=False,
                        help='Add platform(s) to platforms property. Can be specified multiple times.')
-    create_gem_subparser.add_argument('-ip', '--icon-path', type=str, required=False, 
+    create_gem_subparser.add_argument('-ip', '--icon-path', type=str, required=False,
                        default="preview.png",
                        help='Select Gem icon path')
     create_gem_subparser.add_argument('-du', '--documentation-url', type=str, required=False,