Bladeren bron

Add spawner interface to allow selection of spawn points (#343)

* Add spawner interface to allow selection of spawn points

Signed-off-by: Artur Kamieniecki <[email protected]>
Artur Kamieniecki 2 jaren geleden
bovenliggende
commit
8aca946b89
29 gewijzigde bestanden met toevoegingen van 847 en 178 verwijderingen
  1. 3 1
      Gems/ROS2/Code/Include/ROS2/Spawner/SpawnerBus.h
  2. 26 0
      Gems/ROS2/Code/Include/ROS2/Spawner/SpawnerInfo.h
  3. 4 0
      Gems/ROS2/Code/Source/ROS2EditorModule.cpp
  4. 51 0
      Gems/ROS2/Code/Source/RobotImporter/Pages/PrefabMakerPage.cpp
  5. 6 0
      Gems/ROS2/Code/Source/RobotImporter/Pages/PrefabMakerPage.h
  6. 6 1
      Gems/ROS2/Code/Source/RobotImporter/RobotImporterWidget.cpp
  7. 1 1
      Gems/ROS2/Code/Source/RobotImporter/URDF/ArticulationsMaker.cpp
  8. 3 3
      Gems/ROS2/Code/Source/RobotImporter/URDF/CollidersMaker.cpp
  9. 1 1
      Gems/ROS2/Code/Source/RobotImporter/URDF/InertialsMaker.cpp
  10. 1 1
      Gems/ROS2/Code/Source/RobotImporter/URDF/PrefabMakerUtils.cpp
  11. 27 28
      Gems/ROS2/Code/Source/RobotImporter/URDF/URDFPrefabMaker.cpp
  12. 7 2
      Gems/ROS2/Code/Source/RobotImporter/URDF/URDFPrefabMaker.h
  13. 1 1
      Gems/ROS2/Code/Source/RobotImporter/URDF/VisualsMaker.cpp
  14. 1 1
      Gems/ROS2/Code/Source/RobotImporter/Utils/SourceAssetsStorage.cpp
  15. 13 25
      Gems/ROS2/Code/Source/Spawner/ROS2SpawnPointComponent.cpp
  16. 13 14
      Gems/ROS2/Code/Source/Spawner/ROS2SpawnPointComponent.h
  17. 91 0
      Gems/ROS2/Code/Source/Spawner/ROS2SpawnPointComponentController.cpp
  18. 59 0
      Gems/ROS2/Code/Source/Spawner/ROS2SpawnPointComponentController.h
  19. 56 0
      Gems/ROS2/Code/Source/Spawner/ROS2SpawnPointEditorComponent.cpp
  20. 41 0
      Gems/ROS2/Code/Source/Spawner/ROS2SpawnPointEditorComponent.h
  21. 17 50
      Gems/ROS2/Code/Source/Spawner/ROS2SpawnerComponent.cpp
  22. 7 14
      Gems/ROS2/Code/Source/Spawner/ROS2SpawnerComponent.h
  23. 141 0
      Gems/ROS2/Code/Source/Spawner/ROS2SpawnerComponentController.cpp
  24. 69 0
      Gems/ROS2/Code/Source/Spawner/ROS2SpawnerComponentController.h
  25. 104 0
      Gems/ROS2/Code/Source/Spawner/ROS2SpawnerEditorComponent.cpp
  26. 49 0
      Gems/ROS2/Code/Source/Spawner/ROS2SpawnerEditorComponent.h
  27. 4 0
      Gems/ROS2/Code/ros2_editor_files.cmake
  28. 4 0
      Gems/ROS2/Code/ros2_files.cmake
  29. 41 35
      Templates/Ros2FleetRobotTemplate/Template/Levels/Warehouse/Warehouse.prefab

+ 3 - 1
Gems/ROS2/Code/Include/ROS2/Spawner/SpawnerBus.h

@@ -11,6 +11,7 @@
 #include <AzCore/EBus/EBus.h>
 #include <AzCore/Math/Transform.h>
 #include <AzCore/RTTI/BehaviorContext.h>
+#include <ROS2/Spawner/SpawnerInfo.h>
 
 namespace ROS2
 {
@@ -28,8 +29,9 @@ namespace ROS2
         //! @return default spawn point coordinates set by user in Editor (by default: translation: {0, 0, 0}, rotation: {0, 0, 0, 1},
         //! scale: 1.0)
         virtual const AZ::Transform& GetDefaultSpawnPose() const = 0;
+
+        virtual AZStd::unordered_map<AZStd::string, SpawnPointInfo> GetAllSpawnPointInfos() const = 0;
     };
 
     using SpawnerRequestsBus = AZ::EBus<SpawnerRequests>;
-    using SpawnerInterface = AZ::Interface<SpawnerRequests>;
 } // namespace ROS2

+ 26 - 0
Gems/ROS2/Code/Include/ROS2/Spawner/SpawnerInfo.h

@@ -0,0 +1,26 @@
+/*
+ * 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/Math/Transform.h>
+#include <AzCore/Memory/Memory.h>
+#include <AzCore/Memory/Memory_fwd.h>
+#include <AzCore/Memory/SystemAllocator.h>
+#include <AzCore/std/containers/unordered_map.h>
+#include <AzCore/std/string/string.h>
+
+namespace ROS2
+{
+    struct SpawnPointInfo
+    {
+        AZStd::string info;
+        AZ::Transform pose;
+    };
+
+    using SpawnPointInfoMap = AZStd::unordered_map<AZStd::string, SpawnPointInfo>;
+} // namespace ROS2

+ 4 - 0
Gems/ROS2/Code/Source/ROS2EditorModule.cpp

@@ -5,6 +5,8 @@
  * SPDX-License-Identifier: Apache-2.0 OR MIT
  *
  */
+#include "Spawner/ROS2SpawnPointEditorComponent.h"
+#include "Spawner/ROS2SpawnerEditorComponent.h"
 #include <Camera/ROS2CameraSensorEditorComponent.h>
 #include <Lidar/LidarRegistrarEditorSystemComponent.h>
 #include <Manipulation/JointsManipulationEditorComponent.h>
@@ -43,6 +45,8 @@ namespace ROS2
                   LidarRegistrarEditorSystemComponent::CreateDescriptor(),
                   ROS2RobotImporterEditorSystemComponent::CreateDescriptor(),
                   ROS2CameraSensorEditorComponent::CreateDescriptor(),
+                  ROS2SpawnerEditorComponent::CreateDescriptor(),
+                  ROS2SpawnPointEditorComponent::CreateDescriptor(),
                   SdfAssetBuilderSystemComponent::CreateDescriptor(),
                   JointsManipulationEditorComponent::CreateDescriptor() });
         }

+ 51 - 0
Gems/ROS2/Code/Source/RobotImporter/Pages/PrefabMakerPage.cpp

@@ -7,7 +7,18 @@
  */
 
 #include "PrefabMakerPage.h"
+#include <AzCore/Component/Entity.h>
+#include <AzCore/Debug/Trace.h>
+#include <AzCore/EBus/Results.h>
+#include <AzCore/Math/Transform.h>
+#include <AzCore/std/smart_ptr/make_shared.h>
+#include <AzCore/std/string/string.h>
+#include <ROS2/Spawner/SpawnerBus.h>
+#include <ROS2/Spawner/SpawnerInfo.h>
 #include <RobotImporter/RobotImporterWidget.h>
+#include <optional>
+#include <qcombobox.h>
+#include <qvariant.h>
 
 namespace ROS2
 {
@@ -17,6 +28,20 @@ namespace ROS2
         , m_parentImporterWidget(parent)
         , m_success(false)
     {
+        AZ::EBusAggregateResults<AZStd::unordered_map<AZStd::string, SpawnPointInfo>> allActiveSpawnPoints;
+        SpawnerRequestsBus::BroadcastResult(allActiveSpawnPoints, &SpawnerRequestsBus::Events::GetAllSpawnPointInfos);
+
+        m_spawnPointsComboBox = new QComboBox(this);
+        m_spawnPointsInfos = allActiveSpawnPoints.values;
+
+        for (int i = 0; i < allActiveSpawnPoints.values.size(); i++)
+        {
+            for (const auto& element : allActiveSpawnPoints.values[i])
+            {
+                m_spawnPointsComboBox->addItem(element.first.c_str(), QVariant(i));
+            }
+        }
+
         m_prefabName = new QLineEdit(this);
         m_createButton = new QPushButton(tr("Create Prefab"), this);
         m_log = new QTextEdit(this);
@@ -28,6 +53,17 @@ namespace ROS2
         layoutInner->addWidget(m_createButton);
         layout->addLayout(layoutInner);
         layout->addWidget(m_useArticulation);
+        QLabel* spawnPointListLabel;
+        if (allActiveSpawnPoints.values.size() == 0)
+        {
+            spawnPointListLabel = new QLabel("Select spawn position (No spawn positions were detected)", this);
+        }
+        else
+        {
+            spawnPointListLabel = new QLabel("Select spawn position", this);
+        }
+        layout->addWidget(spawnPointListLabel);
+        layout->addWidget(m_spawnPointsComboBox);
         layout->addWidget(m_log);
         setLayout(layout);
         connect(m_createButton, &QPushButton::pressed, this, &PrefabMakerPage::onCreateButtonPressed);
@@ -60,5 +96,20 @@ namespace ROS2
     {
         return m_useArticulation->isChecked();
     }
+    AZStd::optional<AZ::Transform> PrefabMakerPage::getSelectedSpawnPoint() const
+    {
+        if (!m_spawnPointsInfos.empty())
+        {
+            int vectorIndex = m_spawnPointsComboBox->currentData().toInt();
+            AZStd::string mapKey(m_spawnPointsComboBox->currentText().toStdString().c_str());
+            auto& map = m_spawnPointsInfos[vectorIndex];
+            if (auto spawnInfo = map.find(mapKey);
+                spawnInfo != map.end())
+            {
+                return spawnInfo->second.pose;
+            }
+        }
+        return AZStd::nullopt;
+    }
 
 } // namespace ROS2

+ 6 - 0
Gems/ROS2/Code/Source/RobotImporter/Pages/PrefabMakerPage.h

@@ -8,6 +8,9 @@
 
 #pragma once
 
+#include "ROS2/Spawner/SpawnerInfo.h"
+#include <AzCore/Component/Entity.h>
+#include <qcombobox.h>
 #if !defined(Q_MOC_RUN)
 #include <AzCore/Math/Crc.h>
 #include <AzCore/std/string/string.h>
@@ -34,6 +37,7 @@ namespace ROS2
         void setSuccess(bool success);
         bool isComplete() const override;
         bool IsUseArticulations() const;
+        AZStd::optional<AZ::Transform> getSelectedSpawnPoint() const;
     Q_SIGNALS:
         void onCreateButtonPressed();
 
@@ -43,6 +47,8 @@ namespace ROS2
         QPushButton* m_createButton;
         QTextEdit* m_log;
         QCheckBox* m_useArticulation;
+        QComboBox* m_spawnPointsComboBox;
+        AZStd::vector<SpawnPointInfoMap> m_spawnPointsInfos;
         RobotImporterWidget* m_parentImporterWidget;
     };
 } // namespace ROS2

+ 6 - 1
Gems/ROS2/Code/Source/RobotImporter/RobotImporterWidget.cpp

@@ -371,7 +371,12 @@ namespace ROS2
         }
         const bool useArticulation = m_prefabMakerPage->IsUseArticulations();
         m_prefabMaker = AZStd::make_unique<URDFPrefabMaker>(
-            m_urdfPath.String(), m_parsedUrdf, prefabPath.String(), m_urdfAssetsMapping, useArticulation);
+            m_urdfPath.String(),
+            m_parsedUrdf,
+            prefabPath.String(),
+            m_urdfAssetsMapping,
+            useArticulation,
+            m_prefabMakerPage->getSelectedSpawnPoint());
 
         auto prefabOutcome = m_prefabMaker->CreatePrefabFromURDF();
         if (prefabOutcome.IsSuccess())

+ 1 - 1
Gems/ROS2/Code/Source/RobotImporter/URDF/ArticulationsMaker.cpp

@@ -100,7 +100,7 @@ namespace ROS2
         AZ::Entity* entity = AzToolsFramework::GetEntityById(entityId);
         AZ_Assert(entity, "No entity for id %s", entityId.ToString().c_str());
 
-        AZ_TracePrintf("ArticulationsMaker", "Processing inertial for entity id: %s\n", entityId.ToString().c_str());
+        AZ_Trace("ArticulationsMaker", "Processing inertial for entity id: %s\n", entityId.ToString().c_str());
         PhysX::EditorArticulationLinkConfiguration articulationLinkConfiguration;
 
         articulationLinkConfiguration = AddToArticulationConfig(articulationLinkConfiguration, link->inertial);

+ 3 - 3
Gems/ROS2/Code/Source/RobotImporter/URDF/CollidersMaker.cpp

@@ -58,7 +58,7 @@ namespace ROS2
         {
             m_wheelMaterial =
                 AZ::Data::Asset<Physics::MaterialAsset>(assetId, Physics::MaterialAsset::TYPEINFO_Uuid(), physicsMaterialAssetRelPath);
-            AZ_TracePrintf(Internal::CollidersMakerLoggingTag, "Waiting for asset load\n");
+            AZ_Trace(Internal::CollidersMakerLoggingTag, "Waiting for asset load\n");
             m_wheelMaterial.BlockUntilLoadComplete();
         }
         else
@@ -150,7 +150,7 @@ namespace ROS2
 
             if (result.GetResult() != AZ::SceneAPI::Events::ProcessingResult::Success)
             {
-                AZ_TracePrintf(Internal::CollidersMakerLoggingTag, "Scene updated\n");
+                AZ_Trace(Internal::CollidersMakerLoggingTag, "Scene updated\n");
                 return;
             }
 
@@ -242,7 +242,7 @@ namespace ROS2
         { // it is ok not to have collision in a link
             return;
         }
-        AZ_TracePrintf(Internal::CollidersMakerLoggingTag, "Processing collisions for entity id:%s\n", entityId.ToString().c_str());
+        AZ_Trace(Internal::CollidersMakerLoggingTag, "Processing collisions for entity id:%s\n", entityId.ToString().c_str());
 
         auto geometry = collision->geometry;
         if (!geometry)

+ 1 - 1
Gems/ROS2/Code/Source/RobotImporter/URDF/InertialsMaker.cpp

@@ -21,7 +21,7 @@ namespace ROS2
         { // it is ok not to have inertia in a link
             return;
         }
-        AZ_TracePrintf("AddInertial", "Processing inertial for entity id: %s\n", entityId.ToString().c_str());
+        AZ_Trace("AddInertial", "Processing inertial for entity id: %s\n", entityId.ToString().c_str());
 
         AZ::Entity* entity = AzToolsFramework::GetEntityById(entityId);
         PhysX::EditorRigidBodyConfiguration rigidBodyConfiguration;

+ 1 - 1
Gems/ROS2/Code/Source/RobotImporter/URDF/PrefabMakerUtils.cpp

@@ -79,7 +79,7 @@ namespace ROS2::PrefabMakerUtils
             return AZ::Failure(AZStd::string("Invalid id for created entity"));
         }
 
-        AZ_TracePrintf("CreateEntity", "Processing entity id: %s with name: %s\n", entityId.ToString().c_str(), name.c_str());
+        AZ_Trace("CreateEntity", "Processing entity id: %s with name: %s\n", entityId.ToString().c_str(), name.c_str());
         AZ::Entity* entity = AzToolsFramework::GetEntityById(entityId);
         entity->SetName(name);
         entity->Deactivate();

+ 27 - 28
Gems/ROS2/Code/Source/RobotImporter/URDF/URDFPrefabMaker.cpp

@@ -12,6 +12,7 @@
 #include <API/EditorAssetSystemAPI.h>
 #include <AzCore/Debug/Trace.h>
 #include <AzCore/IO/FileIO.h>
+#include <AzCore/Math/Transform.h>
 #include <AzToolsFramework/Entity/EditorEntityHelpers.h>
 #include <AzToolsFramework/Prefab/PrefabLoaderInterface.h>
 #include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
@@ -19,9 +20,9 @@
 #include <AzToolsFramework/ToolsComponents/TransformComponent.h>
 #include <ROS2/Frame/ROS2FrameComponent.h>
 #include <ROS2/ROS2GemUtilities.h>
-#include <ROS2/Spawner/SpawnerBus.h>
 #include <RobotControl/ROS2RobotControlComponent.h>
 #include <RobotImporter/Utils/RobotImporterUtils.h>
+#include <optional>
 
 namespace ROS2
 {
@@ -30,12 +31,14 @@ namespace ROS2
         urdf::ModelInterfaceSharedPtr model,
         AZStd::string prefabPath,
         const AZStd::shared_ptr<Utils::UrdfAssetMap> urdfAssetsMapping,
-        bool useArticulations)
+        bool useArticulations,
+        const AZStd::optional<AZ::Transform> spawnPosition)
         : m_model(model)
         , m_visualsMaker(model->materials_, urdfAssetsMapping)
         , m_collidersMaker(urdfAssetsMapping)
         , m_prefabPath(std::move(prefabPath))
         , m_urdfAssetsMapping(urdfAssetsMapping)
+        , m_spawnPosition(spawnPosition)
         , m_useArticulations(useArticulations)
     {
         AZ_Assert(!m_prefabPath.empty(), "Prefab path is empty");
@@ -92,7 +95,7 @@ namespace ROS2
 
         for (const auto& [name, result] : createdLinks)
         {
-            AZ_TracePrintf(
+            AZ_Trace(
                 "CreatePrefabFromURDF",
                 "Link with name %s was created as: %s\n",
                 name.c_str(),
@@ -121,7 +124,7 @@ namespace ROS2
                     auto* transformInterface = entity->FindComponent<AzToolsFramework::Components::TransformComponent>();
                     if (transformInterface)
                     {
-                        AZ_TracePrintf(
+                        AZ_Trace(
                             "CreatePrefabFromURDF",
                             "Setting transform %s %s to [%f %f %f] [%f %f %f %f]\n",
                             name.c_str(),
@@ -137,8 +140,7 @@ namespace ROS2
                     }
                     else
                     {
-                        AZ_TracePrintf(
-                            "CreatePrefabFromURDF", "Setting transform failed: %s does not have transform interface\n", name.c_str());
+                        AZ_Trace("CreatePrefabFromURDF", "Setting transform failed: %s does not have transform interface\n", name.c_str());
                     }
                 }
             }
@@ -150,34 +152,33 @@ namespace ROS2
             const auto thisEntry = createdLinks.at(name);
             if (!thisEntry.IsSuccess())
             {
-                AZ_TracePrintf("CreatePrefabFromURDF", "Link %s creation failed\n", name.c_str());
+                AZ_Trace("CreatePrefabFromURDF", "Link %s creation failed\n", name.c_str());
                 continue;
             }
             auto parentPtr = linkPtr->getParent();
             if (!parentPtr)
             {
-                AZ_TracePrintf("CreatePrefabFromURDF", "Link %s has no parents\n", name.c_str());
+                AZ_Trace("CreatePrefabFromURDF", "Link %s has no parents\n", name.c_str());
                 continue;
             }
             AZStd::string parentName(parentPtr->name.c_str(), parentPtr->name.size());
             const auto parentEntry = createdLinks.find(parentName);
             if (parentEntry == createdLinks.end())
             {
-                AZ_TracePrintf("CreatePrefabFromURDF", "Link %s has invalid parent name %s\n", name.c_str(), parentName.c_str());
+                AZ_Trace("CreatePrefabFromURDF", "Link %s has invalid parent name %s\n", name.c_str(), parentName.c_str());
                 continue;
             }
             if (!parentEntry->second.IsSuccess())
             {
-                AZ_TracePrintf(
-                    "CreatePrefabFromURDF", "Link %s has parent %s which has failed to create\n", name.c_str(), parentName.c_str());
+                AZ_Trace("CreatePrefabFromURDF", "Link %s has parent %s which has failed to create\n", name.c_str(), parentName.c_str());
                 continue;
             }
-            AZ_TracePrintf(
+            AZ_Trace(
                 "CreatePrefabFromURDF",
                 "Link %s setting parent to %s\n",
                 thisEntry.GetValue().ToString().c_str(),
                 parentEntry->second.GetValue().ToString().c_str());
-            AZ_TracePrintf("CreatePrefabFromURDF", "Link %s setting parent to %s\n", name.c_str(), parentName.c_str());
+            AZ_Trace("CreatePrefabFromURDF", "Link %s setting parent to %s\n", name.c_str(), parentName.c_str());
             auto* entity = AzToolsFramework::GetEntityById(thisEntry.GetValue());
             entity->Activate();
             AZ::TransformBus::Event(thisEntry.GetValue(), &AZ::TransformBus::Events::SetParent, parentEntry->second.GetValue());
@@ -189,7 +190,7 @@ namespace ROS2
         for (const auto& [name, jointPtr] : joints)
         {
             AZ_Assert(jointPtr, "joint %s is null", name.c_str());
-            AZ_TracePrintf(
+            AZ_Trace(
                 "CreatePrefabFromURDF",
                 "Creating joint %s : %s -> %s\n",
                 name.c_str(),
@@ -223,8 +224,7 @@ namespace ROS2
             }
         }
 
-
-        MoveEntityToDefaultSpawnPoint(createEntityRoot.GetValue());
+        MoveEntityToDefaultSpawnPoint(createEntityRoot.GetValue(), m_spawnPosition);
 
         auto contentEntityId = createEntityRoot.GetValue();
         AddRobotControl(contentEntityId);
@@ -241,11 +241,11 @@ namespace ROS2
         AZ::IO::Path relativeFilePath = prefabLoaderInterface->GenerateRelativePath(m_prefabPath.c_str());
 
         const auto templateId = prefabSystemComponent->GetTemplateIdFromFilePath(relativeFilePath);
-        AZ_TracePrintf("CreatePrefabFromURDF", "GetTemplateIdFromFilePath  %s -> %d \n", m_prefabPath.c_str(), templateId);
+        AZ_Trace("CreatePrefabFromURDF", "GetTemplateIdFromFilePath  %s -> %d \n", m_prefabPath.c_str(), templateId);
 
         if (templateId != AzToolsFramework::Prefab::InvalidTemplateId)
         {
-            AZ_TracePrintf("CreatePrefabFromURDF", "Prefab was already loaded\n");
+            AZ_Trace("CreatePrefabFromURDF", "Prefab was already loaded\n");
             prefabSystemComponent->RemoveTemplate(templateId);
         }
 
@@ -255,7 +255,7 @@ namespace ROS2
             AZ::EntityId prefabContainerEntityId = outcome.GetValue();
             PrefabMakerUtils::AddRequiredComponentsToEntity(prefabContainerEntityId);
         }
-        AZ_TracePrintf("CreatePrefabFromURDF", "Successfully created prefab %s\n", m_prefabPath.c_str());
+        AZ_Trace("CreatePrefabFromURDF", "Successfully created prefab %s\n", m_prefabPath.c_str());
 
         // End undo batch labeled "Robot Importer prefab creation"
         if (currentUndoBatch != nullptr)
@@ -323,13 +323,13 @@ namespace ROS2
         return m_prefabPath;
     }
 
-    void URDFPrefabMaker::MoveEntityToDefaultSpawnPoint(const AZ::EntityId& rootEntityId)
+    void URDFPrefabMaker::MoveEntityToDefaultSpawnPoint(
+        const AZ::EntityId& rootEntityId, AZStd::optional<AZ::Transform> spawnPosition = AZStd::nullopt)
     {
-        auto spawner = ROS2::SpawnerInterface::Get();
-
-        if (spawner == nullptr)
+        if (!spawnPosition.has_value())
         {
-            AZ_TracePrintf("URDF Importer", "Spawner not found - creating entity in (0,0,0)\n") return;
+            AZ_Trace("URDF Importer", "SpawnPosition is null - spawning in Editors default position\n");
+            return;
         }
 
         auto entity_ = AzToolsFramework::GetEntityById(rootEntityId);
@@ -337,12 +337,11 @@ namespace ROS2
 
         if (transformInterface_ == nullptr)
         {
-            AZ_TracePrintf("URDF Importer", "TransformComponent not found in created entity\n") return;
+            AZ_Trace("URDF Importer", "TransformComponent not found in created entity\n") return;
         }
 
-        auto pose = spawner->GetDefaultSpawnPose();
-
-        transformInterface_->SetWorldTM(pose);
+        transformInterface_->SetWorldTM(*spawnPosition);
+        AZ_Trace("URDF Importer", "Successfully set spawn position\n")
     }
 
     AZStd::string URDFPrefabMaker::GetStatus()

+ 7 - 2
Gems/ROS2/Code/Source/RobotImporter/URDF/URDFPrefabMaker.h

@@ -15,12 +15,14 @@
 #include "UrdfParser.h"
 #include "VisualsMaker.h"
 #include <AzCore/Component/EntityId.h>
+#include <AzCore/Math/Transform.h>
 #include <AzCore/std/containers/map.h>
 #include <AzCore/std/smart_ptr/make_shared.h>
 #include <AzCore/std/smart_ptr/shared_ptr.h>
 #include <AzCore/std/string/string.h>
 #include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
 #include <RobotImporter/Utils/SourceAssetsStorage.h>
+#include <optional>
 
 namespace ROS2
 {
@@ -39,7 +41,8 @@ namespace ROS2
             urdf::ModelInterfaceSharedPtr model,
             AZStd::string prefabPath,
             const AZStd::shared_ptr<Utils::UrdfAssetMap> urdfAssetsMapping,
-            bool useArticulations = false);
+            bool useArticulations = false,
+            AZStd::optional<AZ::Transform> spawnPosition = AZStd::nullopt);
 
         ~URDFPrefabMaker() = default;
 
@@ -59,7 +62,7 @@ namespace ROS2
         AzToolsFramework::Prefab::PrefabEntityResult AddEntitiesForLink(urdf::LinkSharedPtr link, AZ::EntityId parentEntityId);
         void BuildAssetsForLink(urdf::LinkSharedPtr link);
         void AddRobotControl(AZ::EntityId rootEntityId);
-        static void MoveEntityToDefaultSpawnPoint(const AZ::EntityId& rootEntityId);
+        static void MoveEntityToDefaultSpawnPoint(const AZ::EntityId& rootEntityId, AZStd::optional<AZ::Transform> spawnPosition);
 
         urdf::ModelInterfaceSharedPtr m_model;
         AZStd::string m_prefabPath;
@@ -74,5 +77,7 @@ namespace ROS2
 
         AZStd::shared_ptr<Utils::UrdfAssetMap> m_urdfAssetsMapping;
         bool m_useArticulations{ false };
+
+        const AZStd::optional<AZ::Transform> m_spawnPosition;
     };
 } // namespace ROS2

+ 1 - 1
Gems/ROS2/Code/Source/RobotImporter/URDF/VisualsMaker.cpp

@@ -67,7 +67,7 @@ namespace ROS2
             return;
         }
 
-        AZ_TracePrintf("AddVisual", "Processing visual for entity id:%s\n", entityId.ToString().c_str());
+        AZ_Trace("AddVisual", "Processing visual for entity id:%s\n", entityId.ToString().c_str());
 
         // Use a name generated from the link unless specific name is defined for this visual
         const char* subEntityName = visual->name.empty() ? generatedName.c_str() : visual->name.c_str();

+ 1 - 1
Gems/ROS2/Code/Source/RobotImporter/Utils/SourceAssetsStorage.cpp

@@ -451,7 +451,7 @@ namespace ROS2::Utils
 
         if (result.GetResult() != AZ::SceneAPI::Events::ProcessingResult::Success)
         {
-            AZ_TracePrintf("CreateSceneManifest", "Scene updated\n");
+            AZ_Trace("CreateSceneManifest", "Scene updated\n");
             return false;
         }
 

+ 13 - 25
Gems/ROS2/Code/Source/Spawner/ROS2SpawnPointComponent.cpp

@@ -7,6 +7,7 @@
  */
 
 #include "ROS2SpawnPointComponent.h"
+#include "Spawner/ROS2SpawnPointComponentController.h"
 #include <AzCore/Component/Entity.h>
 
 #include <AzCore/Serialization/EditContext.h>
@@ -17,47 +18,34 @@
 
 namespace ROS2
 {
+    ROS2SpawnPointComponent::ROS2SpawnPointComponent(const ROS2SpawnPointComponentConfig& config)
+        : ROS2SpawnPointComponentBase(config)
+    {
+    }
+
     void ROS2SpawnPointComponent::Activate()
     {
+        ROS2SpawnPointComponentBase::Activate();
     }
 
     void ROS2SpawnPointComponent::Deactivate()
     {
+        ROS2SpawnPointComponentBase::Deactivate();
     }
 
     void ROS2SpawnPointComponent::Reflect(AZ::ReflectContext* context)
     {
+        ROS2SpawnPointComponentBase::Reflect(context);
+
         if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
         {
-            serialize->Class<ROS2SpawnPointComponent, AZ::Component>()
-                ->Version(1)
-                ->Field("Name", &ROS2SpawnPointComponent::m_name)
-                ->Field("Info", &ROS2SpawnPointComponent::m_info);
-
-            if (AZ::EditContext* ec = serialize->GetEditContext())
-            {
-                ec->Class<ROS2SpawnPointComponent>("ROS2 Spawn Point", "Spawn Point")
-                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "Stores information about available spawn point")
-                    ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"))
-                    ->Attribute(AZ::Edit::Attributes::Category, "ROS2")
-                    ->DataElement(AZ::Edit::UIHandlers::EntityId, &ROS2SpawnPointComponent::m_name, "Name", "Name")
-                    ->DataElement(
-                        AZ::Edit::UIHandlers::EntityId, &ROS2SpawnPointComponent::m_info, "Info", "Spawn point detailed description");
-            }
+            serialize->Class<ROS2SpawnPointComponent, ROS2SpawnPointComponentBase>()->Version(1);
         }
     }
 
     AZStd::pair<AZStd::string, SpawnPointInfo> ROS2SpawnPointComponent::GetInfo() const
     {
-        auto transform_component = GetEntity()->FindComponent<AzFramework::TransformComponent>();
-
-        // if SpawnPointComponent entity for some reason does not include TransformComponent - this default pose will be returned
-        AZ::Transform transform = { AZ::Vector3{ 0, 0, 0 }, AZ::Quaternion{ 0, 0, 0, 1.0 }, 1.0 };
-
-        if (transform_component != nullptr)
-        {
-            transform = transform_component->GetWorldTM();
-        }
-        return { m_name, SpawnPointInfo{ m_info, transform } };
+        return m_controller.GetInfo();
     }
+
 } // namespace ROS2

+ 13 - 14
Gems/ROS2/Code/Source/Spawner/ROS2SpawnPointComponent.h

@@ -7,37 +7,36 @@
  */
 #pragma once
 
+#include "Spawner/ROS2SpawnPointComponentController.h"
 #include <AzCore/Component/Component.h>
 #include <AzCore/Math/Transform.h>
+#include <AzFramework/Components/ComponentAdapter.h>
+#include <ROS2/Spawner/SpawnerInfo.h>
 
 namespace ROS2
 {
-    struct SpawnPointInfo
-    {
-        AZStd::string info;
-        AZ::Transform pose;
-    };
+
+    using ROS2SpawnPointComponentBase =
+        AzFramework::Components::ComponentAdapter<ROS2SpawnPointComponentController, ROS2SpawnPointComponentConfig>;
 
     //! SpawnPoint indicates a place which is suitable to spawn a robot.
-    class ROS2SpawnPointComponent : public AZ::Component
+    class ROS2SpawnPointComponent : public ROS2SpawnPointComponentBase
     {
     public:
-        AZ_COMPONENT(ROS2SpawnPointComponent, "{2AE1CAAE-B300-49FD-8F6D-F7AAABED1EC3}", AZ::Component);
+        AZ_COMPONENT(ROS2SpawnPointComponent, "{422c0495-5bbf-4207-ac17-8e607c6d3b30}", AZ::Component);
 
         ROS2SpawnPointComponent() = default;
-
+        ROS2SpawnPointComponent(const ROS2SpawnPointComponentConfig& config);
         ~ROS2SpawnPointComponent() = default;
+
+        static void Reflect(AZ::ReflectContext* context);
+
         //////////////////////////////////////////////////////////////////////////
-        // Component overrides
+        // ROS2SpawnPointComponentBase overrides
         void Activate() override;
         void Deactivate() override;
         //////////////////////////////////////////////////////////////////////////
-        static void Reflect(AZ::ReflectContext* context);
 
         AZStd::pair<AZStd::string, SpawnPointInfo> GetInfo() const;
-
-    private:
-        AZStd::string m_name;
-        AZStd::string m_info;
     };
 } // namespace ROS2

+ 91 - 0
Gems/ROS2/Code/Source/Spawner/ROS2SpawnPointComponentController.cpp

@@ -0,0 +1,91 @@
+/*
+ * 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 "Spawner/ROS2SpawnPointComponentController.h"
+#include "Spawner/ROS2SpawnerComponentController.h"
+#include <AzCore/Component/TransformBus.h>
+#include <AzCore/Serialization/EditContext.h>
+#include <AzCore/Serialization/SerializeContext.h>
+
+namespace ROS2
+{
+    void ROS2SpawnPointComponentConfig::Reflect(AZ::ReflectContext* context)
+    {
+        if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serializeContext->Class<ROS2SpawnPointComponentConfig, AZ::ComponentConfig>()
+                ->Version(1)
+                ->Field("Name", &ROS2SpawnPointComponentConfig::m_name)
+                ->Field("Info", &ROS2SpawnPointComponentConfig::m_info);
+
+            if (auto editContext = serializeContext->GetEditContext())
+            {
+                editContext
+                    ->Class<ROS2SpawnPointComponentConfig>("ROS2SpawnPointComponentConfig", "Config for the ROS2 Spawn Point Component")
+                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "Stores information about available spawn point")
+                    ->DataElement(AZ::Edit::UIHandlers::EntityId, &ROS2SpawnPointComponentConfig::m_name, "Name", "Name")
+                    ->DataElement(
+                        AZ::Edit::UIHandlers::EntityId, &ROS2SpawnPointComponentConfig::m_info, "Info", "Spawn point detailed description");
+            }
+        }
+    }
+
+    void ROS2SpawnPointComponentController::Reflect(AZ::ReflectContext* context)
+    {
+        ROS2SpawnPointComponentConfig::Reflect(context);
+
+        if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serializeContext->Class<ROS2SpawnPointComponentController>()->Version(1)->Field(
+                "Configuration", &ROS2SpawnPointComponentController::m_config);
+
+            AZ::EditContext* editContext = serializeContext->GetEditContext();
+            if (editContext)
+            {
+                editContext
+                    ->Class<ROS2SpawnPointComponentController>("ROS2SpawnPointController", "Controller for the ROS2 Spawn Point Component")
+                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
+                    ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
+                    ->DataElement(AZ::Edit::UIHandlers::Default, &ROS2SpawnPointComponentController::m_config);
+            }
+        }
+    }
+
+    ROS2SpawnPointComponentController::ROS2SpawnPointComponentController(const ROS2SpawnPointComponentConfig& config)
+    {
+        SetConfiguration(config);
+    }
+
+    void ROS2SpawnPointComponentController::SetConfiguration(const ROS2SpawnPointComponentConfig& config)
+    {
+        m_config = config;
+    }
+
+    const ROS2SpawnPointComponentConfig& ROS2SpawnPointComponentController::GetConfiguration() const
+    {
+        return m_config;
+    }
+
+    void ROS2SpawnPointComponentController::Activate(AZ::EntityId entityId)
+    {
+        m_config.m_editorEntityId = entityId;
+    }
+
+    void ROS2SpawnPointComponentController::Deactivate()
+    {
+    }
+
+    AZStd::pair<AZStd::string, SpawnPointInfo> ROS2SpawnPointComponentController::GetInfo() const
+    {
+        AZ::Transform transform = { AZ::Vector3{ 0, 0, 0 }, AZ::Quaternion{ 0, 0, 0, 1.0 }, 1.0 };
+        AZ::TransformBus::EventResult(transform, m_config.m_editorEntityId, &AZ::TransformBus::Events::GetWorldTM);
+
+        return { m_config.m_name, SpawnPointInfo{ m_config.m_info, transform } };
+    }
+
+} // namespace ROS2

+ 59 - 0
Gems/ROS2/Code/Source/Spawner/ROS2SpawnPointComponentController.h

@@ -0,0 +1,59 @@
+/*
+ * 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/Component/Component.h>
+#include <AzCore/Component/ComponentBus.h>
+#include <AzCore/Math/Transform.h>
+#include <ROS2/Spawner/SpawnerInfo.h>
+
+namespace ROS2
+{
+
+    class ROS2SpawnPointComponentConfig final : public AZ::ComponentConfig
+    {
+    public:
+        AZ_RTTI(ROS2SpawnPointComponentConfig, "{eb3e6937-0d1d-4a31-87d7-6d6663e3cf35}");
+
+        ROS2SpawnPointComponentConfig() = default;
+        ~ROS2SpawnPointComponentConfig() = default;
+
+        static void Reflect(AZ::ReflectContext* context);
+
+        AZStd::string m_name;
+        AZStd::string m_info;
+
+        AZ::EntityId m_editorEntityId;
+    };
+
+    //! SpawnPoint indicates a place which is suitable to spawn a robot.
+    class ROS2SpawnPointComponentController
+    {
+    public:
+        AZ_TYPE_INFO(ROS2SpawnPointComponentController, "{cd29d626-0205-4ca0-ac0f-5377e4fd84dd}");
+
+        ROS2SpawnPointComponentController() = default;
+        explicit ROS2SpawnPointComponentController(const ROS2SpawnPointComponentConfig& config);
+        ~ROS2SpawnPointComponentController() = default;
+
+        static void Reflect(AZ::ReflectContext* context);
+
+        //////////////////////////////////////////////////////////////////////////
+        // Controller component
+        void Activate(AZ::EntityId entityId);
+        void Deactivate();
+        void SetConfiguration(const ROS2SpawnPointComponentConfig& config);
+        const ROS2SpawnPointComponentConfig& GetConfiguration() const;
+        //////////////////////////////////////////////////////////////////////////
+
+        AZStd::pair<AZStd::string, SpawnPointInfo> GetInfo() const;
+
+    private:
+        ROS2SpawnPointComponentConfig m_config;
+    };
+} // namespace ROS2

+ 56 - 0
Gems/ROS2/Code/Source/Spawner/ROS2SpawnPointEditorComponent.cpp

@@ -0,0 +1,56 @@
+/*
+ * 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 "ROS2SpawnPointEditorComponent.h"
+#include "Spawner/ROS2SpawnPointComponentController.h"
+#include "Spawner/ROS2SpawnerEditorComponent.h"
+
+namespace ROS2
+{
+    ROS2SpawnPointEditorComponent::ROS2SpawnPointEditorComponent(const ROS2SpawnPointComponentConfig& configuration)
+        : ROS2SpawnPointEditorComponentBase(configuration)
+    {
+    }
+
+    void ROS2SpawnPointEditorComponent::Reflect(AZ::ReflectContext* context)
+    {
+        ROS2SpawnPointEditorComponentBase::Reflect(context);
+
+        AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
+
+        if (serializeContext)
+        {
+            serializeContext->Class<ROS2SpawnPointEditorComponent, ROS2SpawnPointEditorComponentBase>()->Version(1);
+
+            AZ::EditContext* editContext = serializeContext->GetEditContext();
+            if (editContext)
+            {
+                editContext->Class<ROS2SpawnPointEditorComponent>("ROS2 Spawn Point", "Spawn point for robots")
+                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
+                    ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"))
+                    ->Attribute(AZ::Edit::Attributes::Category, "ROS2")
+                    ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly);
+            }
+        }
+    }
+
+    void ROS2SpawnPointEditorComponent::Activate()
+    {
+        ROS2SpawnPointEditorComponentBase::Activate();
+    }
+
+    void ROS2SpawnPointEditorComponent::Deactivate()
+    {
+        ROS2SpawnPointEditorComponentBase::Deactivate();
+    }
+
+    AZStd::pair<AZStd::string, SpawnPointInfo> ROS2SpawnPointEditorComponent::GetInfo() const
+    {
+        return m_controller.GetInfo();
+    }
+} // namespace ROS2

+ 41 - 0
Gems/ROS2/Code/Source/Spawner/ROS2SpawnPointEditorComponent.h

@@ -0,0 +1,41 @@
+/*
+ * 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 "Spawner/ROS2SpawnPointComponent.h"
+#include "Spawner/ROS2SpawnPointComponentController.h"
+#include "Spawner/ROS2SpawnerEditorComponent.h"
+#include <AzToolsFramework/ToolsComponents/EditorComponentAdapter.h>
+#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
+
+namespace ROS2
+{
+    using ROS2SpawnPointEditorComponentBase = AzToolsFramework::Components::
+        EditorComponentAdapter<ROS2SpawnPointComponentController, ROS2SpawnPointComponent, ROS2SpawnPointComponentConfig>;
+
+    class ROS2SpawnPointEditorComponent : public ROS2SpawnPointEditorComponentBase
+    {
+    public:
+        AZ_EDITOR_COMPONENT(
+            ROS2SpawnPointEditorComponent, "{2AE1CAAE-B300-49FD-8F6D-F7AAABED1EC3}", AzToolsFramework::Components::EditorComponentBase);
+
+        ROS2SpawnPointEditorComponent() = default;
+        ROS2SpawnPointEditorComponent(const ROS2SpawnPointComponentConfig& config);
+        ~ROS2SpawnPointEditorComponent() = default;
+
+        static void Reflect(AZ::ReflectContext* context);
+
+        //////////////////////////////////////////////////////////////////////////
+        // ROS2SpawnPointEditorComponentBase overrides
+        void Activate() override;
+        void Deactivate() override;
+        //////////////////////////////////////////////////////////////////////////
+
+        AZStd::pair<AZStd::string, SpawnPointInfo> GetInfo() const;
+    };
+} // namespace ROS2

+ 17 - 50
Gems/ROS2/Code/Source/Spawner/ROS2SpawnerComponent.cpp

@@ -7,6 +7,7 @@
  */
 
 #include "ROS2SpawnerComponent.h"
+#include "Spawner/ROS2SpawnerComponentController.h"
 #include <AzCore/Serialization/EditContext.h>
 #include <AzCore/Serialization/SerializeContext.h>
 #include <AzFramework/Spawnable/Spawnable.h>
@@ -18,8 +19,15 @@
 namespace ROS2
 {
 
+    ROS2SpawnerComponent::ROS2SpawnerComponent(const ROS2SpawnerComponentConfig& properties)
+        : ROS2SpawnerComponentBase(properties)
+    {
+    }
+
     void ROS2SpawnerComponent::Activate()
     {
+        ROS2SpawnerComponentBase::Activate();
+
         auto ros2Node = ROS2Interface::Get()->GetNode();
 
         m_getSpawnablesNamesService = ros2Node->create_service<gazebo_msgs::srv::GetWorldProperties>(
@@ -53,6 +61,8 @@ namespace ROS2
 
     void ROS2SpawnerComponent::Deactivate()
     {
+        ROS2SpawnerComponentBase::Deactivate();
+
         m_getSpawnablesNamesService.reset();
         m_spawnService.reset();
         m_getSpawnPointInfoService.reset();
@@ -61,33 +71,18 @@ namespace ROS2
 
     void ROS2SpawnerComponent::Reflect(AZ::ReflectContext* context)
     {
+        ROS2SpawnerComponentBase::Reflect(context);
+
         if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
         {
-            serialize->Class<ROS2SpawnerComponent, AZ::Component>()
-                ->Version(1)
-                ->Field("Spawnables", &ROS2SpawnerComponent::m_spawnables)
-                ->Field("Default spawn point", &ROS2SpawnerComponent::m_defaultSpawnPose);
-
-            if (AZ::EditContext* ec = serialize->GetEditContext())
-            {
-                ec->Class<ROS2SpawnerComponent>("ROS2 Spawner", "Spawner component")
-                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "Manages spawning of robots in configurable locations")
-                    ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"))
-                    ->Attribute(AZ::Edit::Attributes::Category, "ROS2")
-                    ->DataElement(AZ::Edit::UIHandlers::EntityId, &ROS2SpawnerComponent::m_spawnables, "Spawnables", "Spawnables")
-                    ->DataElement(
-                        AZ::Edit::UIHandlers::EntityId,
-                        &ROS2SpawnerComponent::m_defaultSpawnPose,
-                        "Default spawn pose",
-                        "Default spawn pose");
-            }
+            serialize->Class<ROS2SpawnerComponent, ROS2SpawnerComponentBase>()->Version(1);
         }
     }
 
     void ROS2SpawnerComponent::GetAvailableSpawnableNames(
         const GetAvailableSpawnableNamesRequest request, GetAvailableSpawnableNamesResponse response)
     {
-        for (const auto& spawnable : m_spawnables)
+        for (const auto& spawnable : m_controller.GetSpawnables())
         {
             response->model_names.emplace_back(spawnable.first.c_str());
         }
@@ -100,7 +95,7 @@ namespace ROS2
 
         auto spawnPoints = GetSpawnPoints();
 
-        if (!m_spawnables.contains(spawnableName))
+        if (!m_controller.GetSpawnables().contains(spawnableName))
         {
             response->success = false;
             response->status_message = "Could not find spawnable with given name: " + request->name;
@@ -111,7 +106,7 @@ namespace ROS2
         {
             // if a ticket for this spawnable was not created but the spawnable name is correct, create the ticket and then use it to
             // spawn an entity
-            auto spawnable = m_spawnables.find(spawnableName);
+            auto spawnable = m_controller.GetSpawnables().find(spawnableName);
             m_tickets.emplace(spawnable->first, AzFramework::EntitySpawnTicket(spawnable->second));
         }
 
@@ -173,11 +168,6 @@ namespace ROS2
         }
     }
 
-    const AZ::Transform& ROS2SpawnerComponent::GetDefaultSpawnPose() const
-    {
-        return m_defaultSpawnPose;
-    }
-
     void ROS2SpawnerComponent::GetSpawnPointsNames(
         const ROS2::GetSpawnPointsNamesRequest request, ROS2::GetSpawnPointsNamesResponse response)
     {
@@ -206,29 +196,6 @@ namespace ROS2
 
     AZStd::unordered_map<AZStd::string, SpawnPointInfo> ROS2SpawnerComponent::GetSpawnPoints()
     {
-        AZStd::vector<AZ::EntityId> children;
-        AZ::TransformBus::EventResult(children, GetEntityId(), &AZ::TransformBus::Events::GetChildren);
-
-        AZStd::unordered_map<AZStd::string, SpawnPointInfo> result;
-
-        for (const AZ::EntityId& child : children)
-        {
-            AZ::Entity* childEntity = nullptr;
-            AZ::ComponentApplicationBus::BroadcastResult(childEntity, &AZ::ComponentApplicationRequests::FindEntity, child);
-            AZ_Assert(childEntity, "No child entity %s", child.ToString().c_str());
-            const auto* spawnPoint = childEntity->FindComponent<ROS2SpawnPointComponent>();
-
-            if (spawnPoint == nullptr)
-            {
-                continue;
-            }
-
-            result.insert(spawnPoint->GetInfo());
-        }
-
-        // setting name of spawn point component "default" in a child entity will have no effect since it is overwritten here with the
-        // default spawn pose of spawner
-        result["default"] = SpawnPointInfo{ "Default spawn pose defined in the Editor", m_defaultSpawnPose };
-        return result;
+        return m_controller.GetSpawnPoints();
     }
 } // namespace ROS2

+ 7 - 14
Gems/ROS2/Code/Source/Spawner/ROS2SpawnerComponent.h

@@ -8,13 +8,14 @@
 #pragma once
 
 #include "ROS2SpawnPointComponent.h"
+#include "Spawner/ROS2SpawnerComponentController.h"
 #include <AzCore/Asset/AssetCommon.h>
 #include <AzCore/Asset/AssetSerializer.h>
 #include <AzCore/Component/Component.h>
 #include <AzCore/std/containers/unordered_map.h>
+#include <AzFramework/Components/ComponentAdapter.h>
 #include <AzFramework/Spawnable/Spawnable.h>
 #include <AzFramework/Spawnable/SpawnableEntitiesInterface.h>
-#include <ROS2/Spawner/SpawnerBus.h>
 #include <gazebo_msgs/srv/get_model_state.hpp>
 #include <gazebo_msgs/srv/get_world_properties.hpp>
 #include <gazebo_msgs/srv/spawn_entity.hpp>
@@ -31,17 +32,17 @@ namespace ROS2
     using GetSpawnPointsNamesRequest = std::shared_ptr<gazebo_msgs::srv::GetWorldProperties::Request>;
     using GetSpawnPointsNamesResponse = std::shared_ptr<gazebo_msgs::srv::GetWorldProperties::Response>;
 
+    using ROS2SpawnerComponentBase = AzFramework::Components::ComponentAdapter<ROS2SpawnerComponentController, ROS2SpawnerComponentConfig>;
     //! Manages robots spawning.
     //! Allows user to set spawnable prefabs in the Editor and spawn them using ROS2 service during the simulation.
-    class ROS2SpawnerComponent
-        : public AZ::Component
-        , public SpawnerRequestsBus::Handler
+    class ROS2SpawnerComponent : public ROS2SpawnerComponentBase
     {
     public:
-        AZ_COMPONENT(ROS2SpawnerComponent, "{5950AC6B-75F3-4E0F-BA5C-17C877013710}", AZ::Component, SpawnerRequestsBus::Handler);
+        AZ_COMPONENT(ROS2SpawnerComponent, "{8ea91880-0067-11ee-be56-0242ac120002}", AZ::Component);
 
-        // AZ::Component interface implementation.
+        // ROS2SpawnerComponentBase interface implementation.
         ROS2SpawnerComponent() = default;
+        ROS2SpawnerComponent(const ROS2SpawnerComponentConfig& properties);
         ~ROS2SpawnerComponent() = default;
         //////////////////////////////////////////////////////////////////////////
         // Component overrides
@@ -50,14 +51,8 @@ namespace ROS2
         //////////////////////////////////////////////////////////////////////////
         static void Reflect(AZ::ReflectContext* context);
 
-        //////////////////////////////////////////////////////////////////////////
-        // SpawnerRequestsBus::Handler overrides
-        const AZ::Transform& GetDefaultSpawnPose() const override;
-        //////////////////////////////////////////////////////////////////////////
-
     private:
         int m_counter = 1;
-        AZStd::unordered_map<AZStd::string, AZ::Data::Asset<AzFramework::Spawnable>> m_spawnables;
         AZStd::unordered_map<AZStd::string, AzFramework::EntitySpawnTicket> m_tickets;
 
         rclcpp::Service<gazebo_msgs::srv::GetWorldProperties>::SharedPtr m_getSpawnablesNamesService;
@@ -65,8 +60,6 @@ namespace ROS2
         rclcpp::Service<gazebo_msgs::srv::SpawnEntity>::SharedPtr m_spawnService;
         rclcpp::Service<gazebo_msgs::srv::GetModelState>::SharedPtr m_getSpawnPointInfoService;
 
-        AZ::Transform m_defaultSpawnPose = { AZ::Vector3{ 0, 0, 0 }, AZ::Quaternion{ 0, 0, 0, 1 }, 1.0 };
-
         void GetAvailableSpawnableNames(const GetAvailableSpawnableNamesRequest request, GetAvailableSpawnableNamesResponse response);
         void SpawnEntity(const SpawnEntityRequest request, SpawnEntityResponse response);
         void PreSpawn(

+ 141 - 0
Gems/ROS2/Code/Source/Spawner/ROS2SpawnerComponentController.cpp

@@ -0,0 +1,141 @@
+/*
+ * 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 "ROS2SpawnerComponentController.h"
+#include "Spawner/ROS2SpawnPointComponent.h"
+#include "Spawner/ROS2SpawnerComponent.h"
+#include <AzCore/Component/ComponentBus.h>
+#include <AzCore/Component/TransformBus.h>
+#include <AzCore/RTTI/RTTIMacros.h>
+#include <AzCore/Serialization/EditContext.h>
+#include <AzCore/Serialization/EditContextConstants.inl>
+#include <AzCore/Serialization/SerializeContext.h>
+#include <ROS2/Spawner/SpawnerInfo.h>
+
+namespace ROS2
+{
+    void ROS2SpawnerComponentConfig::Reflect(AZ::ReflectContext* context)
+    {
+        if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serializeContext->Class<ROS2SpawnerComponentConfig, AZ::ComponentConfig>()
+                ->Version(1)
+                ->Field("Editor entity id", &ROS2SpawnerComponentConfig::m_editorEntityId)
+                ->Field("Spawnables", &ROS2SpawnerComponentConfig::m_spawnables)
+                ->Field("Default spawn pose", &ROS2SpawnerComponentConfig::m_defaultSpawnPose);
+
+            if (auto editContext = serializeContext->GetEditContext())
+            {
+                editContext->Class<ROS2SpawnerComponentConfig>("ROS2SpawnerComponentConfig", "Config for ROS2SpawnerComponent")
+                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
+                    ->DataElement(AZ::Edit::UIHandlers::Default, &ROS2SpawnerComponentConfig::m_spawnables, "Spawnables", "Spawnables")
+                    ->DataElement(
+                        AZ::Edit::UIHandlers::Default,
+                        &ROS2SpawnerComponentConfig::m_defaultSpawnPose,
+                        "Default spawn pose",
+                        "Default spawn pose");
+            }
+        }
+    }
+
+    AZ::EntityId ROS2SpawnerComponentController::GetEditorEntityId() const
+    {
+        return m_config.m_editorEntityId;
+    }
+
+    AZStd::unordered_map<AZStd::string, AZ::Data::Asset<AzFramework::Spawnable>> ROS2SpawnerComponentController::GetSpawnables() const
+    {
+        return m_config.m_spawnables;
+    }
+
+    const AZ::Transform& ROS2SpawnerComponentController::GetDefaultSpawnPose() const
+    {
+        return m_config.m_defaultSpawnPose;
+    }
+
+    AZStd::unordered_map<AZStd::string, SpawnPointInfo> ROS2SpawnerComponentController::GetAllSpawnPointInfos() const
+    {
+        return GetSpawnPoints();
+    }
+
+    void ROS2SpawnerComponentController::Reflect(AZ::ReflectContext* context)
+    {
+        ROS2SpawnerComponentConfig::Reflect(context);
+
+        if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serializeContext->Class<ROS2SpawnerComponentController>()->Version(1)->Field(
+                "Configuration", &ROS2SpawnerComponentController::m_config);
+
+            AZ::EditContext* editContext = serializeContext->GetEditContext();
+            if (editContext)
+            {
+                editContext->Class<ROS2SpawnerComponentController>("ROS2SpawnerComponentController", "Controller for ROS2SpawnerComponent")
+                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "Manages spawning of robots in configurable locations")
+                    ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
+                    ->DataElement(AZ::Edit::UIHandlers::Default, &ROS2SpawnerComponentController::m_config);
+            }
+        }
+    }
+
+    AZStd::unordered_map<AZStd::string, SpawnPointInfo> ROS2SpawnerComponentController::GetSpawnPoints() const
+    {
+        AZStd::vector<AZ::EntityId> children;
+        AZ::TransformBus::EventResult(children, m_config.m_editorEntityId, &AZ::TransformBus::Events::GetChildren);
+
+        AZStd::unordered_map<AZStd::string, SpawnPointInfo> result;
+
+        for (const AZ::EntityId& child : children)
+        {
+            AZ::Entity* childEntity = nullptr;
+            AZ::ComponentApplicationBus::BroadcastResult(childEntity, &AZ::ComponentApplicationRequests::FindEntity, child);
+            AZ_Assert(childEntity, "No child entity found for entity %s", child.ToString().c_str());
+
+            if (const auto* spawnPoint = childEntity->FindComponent<ROS2SpawnPointComponent>(); spawnPoint != nullptr)
+            {
+                result.insert(spawnPoint->GetInfo());
+            }
+        }
+
+        // setting name of spawn point component "default" in a child entity will have no effect since it is overwritten here with the
+        // default spawn pose of spawner
+        result["default"] = SpawnPointInfo{ "Default spawn pose defined in the Editor", m_config.m_defaultSpawnPose };
+        return result;
+    }
+
+    void ROS2SpawnerComponentController::Init()
+    {
+    }
+
+    void ROS2SpawnerComponentController::Activate(AZ::EntityId entityId)
+    {
+        m_config.m_editorEntityId = entityId;
+        SpawnerRequestsBus::Handler::BusConnect(entityId);
+    }
+
+    void ROS2SpawnerComponentController::Deactivate()
+    {
+        SpawnerRequestsBus::Handler::BusDisconnect();
+    }
+
+    ROS2SpawnerComponentController::ROS2SpawnerComponentController(const ROS2SpawnerComponentConfig& config)
+    {
+        SetConfiguration(config);
+    }
+
+    void ROS2SpawnerComponentController::SetConfiguration(const ROS2SpawnerComponentConfig& config)
+    {
+        m_config = config;
+    }
+
+    const ROS2SpawnerComponentConfig& ROS2SpawnerComponentController::GetConfiguration() const
+    {
+        return m_config;
+    }
+
+} // namespace ROS2

+ 69 - 0
Gems/ROS2/Code/Source/Spawner/ROS2SpawnerComponentController.h

@@ -0,0 +1,69 @@
+/*
+ * 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 "ROS2/Spawner/SpawnerBus.h"
+#include "ROS2SpawnPointComponent.h"
+#include <AzCore/Component/ComponentBus.h>
+#include <AzCore/Component/EntityId.h>
+#include <AzCore/Memory/Memory_fwd.h>
+#include <AzCore/Memory/SystemAllocator.h>
+#include <AzCore/base.h>
+#include <AzFramework/Spawnable/Spawnable.h>
+
+namespace ROS2
+{
+    class ROS2SpawnerComponentConfig final : public AZ::ComponentConfig
+    {
+    public:
+        AZ_RTTI(ROS2SpawnerComponentConfig, "{ee71f892-006a-11ee-be56-0242ac120002}");
+
+        ROS2SpawnerComponentConfig() = default;
+        ~ROS2SpawnerComponentConfig() = default;
+
+        static void Reflect(AZ::ReflectContext* context);
+
+        AZ::EntityId m_editorEntityId;
+        AZ::Transform m_defaultSpawnPose = { AZ::Vector3{ 0, 0, 0 }, AZ::Quaternion{ 0, 0, 0, 1 }, 1.0 };
+
+        AZStd::unordered_map<AZStd::string, AZ::Data::Asset<AzFramework::Spawnable>> m_spawnables;
+    };
+
+    class ROS2SpawnerComponentController : public SpawnerRequestsBus::Handler
+    {
+    public:
+        AZ_TYPE_INFO(ROS2SpawnerComponentController, "{1e9e040c-006b-11ee-be56-0242ac120002}");
+        ROS2SpawnerComponentController() = default;
+        explicit ROS2SpawnerComponentController(const ROS2SpawnerComponentConfig& config);
+
+        static void Reflect(AZ::ReflectContext* context);
+
+        //////////////////////////////////////////////////////////////////////////
+        // Controller component
+        void Init();
+        void Activate(AZ::EntityId entityId);
+        void Deactivate();
+        void SetConfiguration(const ROS2SpawnerComponentConfig& config);
+        const ROS2SpawnerComponentConfig& GetConfiguration() const;
+        //////////////////////////////////////////////////////////////////////////
+
+        //////////////////////////////////////////////////////////////////////////
+        // SpawnerRequestsBus::Handler overrides
+        const AZ::Transform& GetDefaultSpawnPose() const override;
+        SpawnPointInfoMap GetAllSpawnPointInfos() const override;
+        //////////////////////////////////////////////////////////////////////////
+
+        SpawnPointInfoMap GetSpawnPoints() const;
+        AZ::EntityId GetEditorEntityId() const;
+        AZStd::unordered_map<AZStd::string, AZ::Data::Asset<AzFramework::Spawnable>> GetSpawnables() const;
+
+    private:
+        ROS2SpawnerComponentConfig m_config;
+    };
+} // namespace ROS2

+ 104 - 0
Gems/ROS2/Code/Source/Spawner/ROS2SpawnerEditorComponent.cpp

@@ -0,0 +1,104 @@
+/*
+ * 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 "ROS2SpawnerEditorComponent.h"
+#include "AzCore/Debug/Trace.h"
+#include "ROS2SpawnPointEditorComponent.h"
+#include "Spawner/ROS2SpawnerComponentController.h"
+#include <AzCore/Component/TransformBus.h>
+#include <AzCore/Serialization/EditContext.h>
+#include <AzCore/Serialization/EditContextConstants.inl>
+#include <ROS2/Spawner/SpawnerBus.h>
+
+namespace ROS2
+{
+    ROS2SpawnerEditorComponent::ROS2SpawnerEditorComponent(const ROS2SpawnerComponentConfig& configuration)
+        : ROS2SpawnerEditorComponentBase(configuration)
+    {
+    }
+
+    void ROS2SpawnerEditorComponent::Reflect(AZ::ReflectContext* context)
+    {
+        ROS2SpawnerEditorComponentBase::Reflect(context);
+
+        AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
+        if (serializeContext)
+        {
+            serializeContext->Class<ROS2SpawnerEditorComponent, ROS2SpawnerEditorComponentBase>()->Version(1);
+
+            AZ::EditContext* editContext = serializeContext->GetEditContext();
+            if (editContext)
+            {
+                editContext->Class<ROS2SpawnerEditorComponent>("ROS2 Spawner", "Spawner component")
+                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "Manages spawning of robots in configurable locations")
+                    ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"))
+                    ->Attribute(AZ::Edit::Attributes::Category, "ROS2")
+                    ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly);
+            }
+        }
+    }
+
+    AZStd::unordered_map<AZStd::string, SpawnPointInfo> ROS2SpawnerEditorComponent::GetSpawnPoints() const
+    {
+        AZStd::vector<AZ::EntityId> children;
+        AZ::TransformBus::EventResult(children, m_controller.GetEditorEntityId(), &AZ::TransformBus::Events::GetChildren);
+
+        AZStd::unordered_map<AZStd::string, SpawnPointInfo> result;
+
+        for (const AZ::EntityId& child : children)
+        {
+            AZ::Entity* childEntity = nullptr;
+            AZ::ComponentApplicationBus::BroadcastResult(childEntity, &AZ::ComponentApplicationRequests::FindEntity, child);
+            AZ_Assert(childEntity, "No child entity found for entity %s", child.ToString().c_str());
+
+            const auto* editorSpawnPoint = childEntity->FindComponent<ROS2SpawnPointEditorComponent>();
+
+            if (editorSpawnPoint != nullptr)
+            {
+                result.insert(editorSpawnPoint->GetInfo());
+            }
+        }
+
+        // setting name of spawn point component "default" in a child entity will have no effect since it is overwritten here with the
+        // default spawn pose of spawner
+        result["default"] = SpawnPointInfo{ "Default spawn pose defined in the Editor", m_controller.GetDefaultSpawnPose() };
+        return result;
+    }
+
+    const AZ::Transform& ROS2SpawnerEditorComponent::GetDefaultSpawnPose() const
+    {
+        return m_controller.GetDefaultSpawnPose();
+    }
+
+    AZStd::unordered_map<AZStd::string, SpawnPointInfo> ROS2SpawnerEditorComponent::GetAllSpawnPointInfos() const
+    {
+        return GetSpawnPoints();
+    }
+
+    bool ROS2SpawnerEditorComponent::ShouldActivateController() const
+    {
+        return false;
+    }
+
+    void ROS2SpawnerEditorComponent::Activate()
+    {
+        ROS2SpawnerEditorComponentBase::Activate();
+        ROS2SpawnerComponentConfig config = m_controller.GetConfiguration();
+        config.m_editorEntityId = GetEntityId();
+        AZ_Assert(config.m_editorEntityId.IsValid(), "Spawner component got an invalid entity id");
+        m_controller.SetConfiguration(config);
+        SpawnerRequestsBus::Handler::BusConnect(config.m_editorEntityId);
+    }
+
+    void ROS2SpawnerEditorComponent::Deactivate()
+    {
+        SpawnerRequestsBus::Handler::BusDisconnect();
+        ROS2SpawnerEditorComponentBase::Deactivate();
+    }
+
+} // namespace ROS2

+ 49 - 0
Gems/ROS2/Code/Source/Spawner/ROS2SpawnerEditorComponent.h

@@ -0,0 +1,49 @@
+/*
+ * 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 "Spawner/ROS2SpawnerComponent.h"
+#include "Spawner/ROS2SpawnerComponentController.h"
+#include <AzToolsFramework/ToolsComponents/EditorComponentAdapter.h>
+#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
+#include <ROS2/Spawner/SpawnerBus.h>
+
+namespace ROS2
+{
+    using ROS2SpawnerEditorComponentBase = AzToolsFramework::Components::
+        EditorComponentAdapter<ROS2SpawnerComponentController, ROS2SpawnerComponent, ROS2SpawnerComponentConfig>;
+
+    class ROS2SpawnerEditorComponent
+        : public ROS2SpawnerEditorComponentBase
+        , public SpawnerRequestsBus::Handler
+    {
+    public:
+        AZ_EDITOR_COMPONENT(
+            ROS2SpawnerEditorComponent, "{5950AC6B-75F3-4E0F-BA5C-17C877013710}", AzToolsFramework::Components::EditorComponentBase);
+        ROS2SpawnerEditorComponent() = default;
+        explicit ROS2SpawnerEditorComponent(const ROS2SpawnerComponentConfig& configuration);
+        ~ROS2SpawnerEditorComponent() = default;
+
+        static void Reflect(AZ::ReflectContext* context);
+
+        //////////////////////////////////////////////////////////////////////////
+        // ROS2SpawnerEditorComponentBase interface overrides.
+        void Activate() override;
+        void Deactivate() override;
+        bool ShouldActivateController() const override;
+        //////////////////////////////////////////////////////////////////////////
+
+        //////////////////////////////////////////////////////////////////////////
+        // SpawnerRequestsBus::Handler overrides.
+        const AZ::Transform& GetDefaultSpawnPose() const override;
+        AZStd::unordered_map<AZStd::string, SpawnPointInfo> GetAllSpawnPointInfos() const override;
+        //////////////////////////////////////////////////////////////////////////
+
+        AZStd::unordered_map<AZStd::string, SpawnPointInfo> GetSpawnPoints() const;
+    };
+} // namespace ROS2

+ 4 - 0
Gems/ROS2/Code/ros2_editor_files.cmake

@@ -58,6 +58,10 @@ set(FILES
     Source/ROS2EditorSystemComponent.cpp
     Source/ROS2EditorSystemComponent.h
     Source/ROS2GemUtilities.cpp
+    Source/Spawner/ROS2SpawnerEditorComponent.cpp
+    Source/Spawner/ROS2SpawnerEditorComponent.h
+    Source/Spawner/ROS2SpawnPointEditorComponent.cpp
+    Source/Spawner/ROS2SpawnPointEditorComponent.h
     Source/SdfAssetBuilder/SdfAssetBuilder.cpp
     Source/SdfAssetBuilder/SdfAssetBuilder.h
     Source/SdfAssetBuilder/SdfAssetBuilderSystemComponent.cpp

+ 4 - 0
Gems/ROS2/Code/ros2_files.cmake

@@ -108,6 +108,10 @@ set(FILES
         Source/Spawner/ROS2SpawnerComponent.h
         Source/Spawner/ROS2SpawnPointComponent.cpp
         Source/Spawner/ROS2SpawnPointComponent.h
+        Source/Spawner/ROS2SpawnerComponentController.cpp
+        Source/Spawner/ROS2SpawnerComponentController.h
+        Source/Spawner/ROS2SpawnPointComponentController.cpp
+        Source/Spawner/ROS2SpawnPointComponentController.h
         Source/Utilities/Controllers/PidConfiguration.cpp
         Source/Utilities/PhysicsCallbackHandler.cpp
         Source/Utilities/PhysicsCallbackHandler.h

+ 41 - 35
Templates/Ros2FleetRobotTemplate/Template/Levels/Warehouse/Warehouse.prefab

@@ -88,17 +88,19 @@
                     "Id": 4373493893052679413
                 },
                 "Component_[4933737581982406853]": {
-                    "$type": "GenericComponentWrapper",
-                    "Id": 4933737581982406853,
-                    "m_template": {
-                        "$type": "ROS2SpawnerComponent",
-                        "Spawnables": {
-                            "proteus": {
-                                "assetId": {
-                                    "guid": "{80419AAD-14CF-528D-A477-8B202D4C9B6D}",
-                                    "subId": 1966583575
-                                },
-                                "assetHint": "proteus.spawnable"
+                    "$type": "ROS2SpawnerEditorComponent",
+                    "Id": 11259578117255245776,
+                    "Controller": {
+                        "Configuration": {
+                            "Editor entity id": "",
+                            "Spawnables": {
+                                "proteus": {
+                                    "assetId": {
+                                        "guid": "{80419AAD-14CF-528D-A477-8B202D4C9B6D}",
+                                        "subId": 1966583575
+                                    },
+                                    "assetHint": "proteus.spawnable"
+                                }
                             }
                         }
                     }
@@ -156,12 +158,13 @@
                     }
                 },
                 "Component_[2115342656585992410]": {
-                    "$type": "GenericComponentWrapper",
-                    "Id": 2115342656585992410,
-                    "m_template": {
-                        "$type": "ROS2SpawnPointComponent",
-                        "Name": "spawnPoint2",
-                        "Info": "spawnPoint2"
+                    "$type": "ROS2SpawnPointEditorComponent",
+                    "Id": 11018288690574626473,
+                    "Controller": {
+                        "Configuration": {
+                            "Name": "spawnPoint2",
+                            "Info": "spawnPoint2"
+                        }
                     }
                 },
                 "Component_[4848442694260332496]": {
@@ -220,12 +223,13 @@
                     }
                 },
                 "Component_[2115342656585992410]": {
-                    "$type": "GenericComponentWrapper",
-                    "Id": 2115342656585992410,
-                    "m_template": {
-                        "$type": "ROS2SpawnPointComponent",
-                        "Name": "spawnPoint3",
-                        "Info": "spawnPoint3"
+                    "$type": "ROS2SpawnPointEditorComponent",
+                    "Id": 11018288690574626473,
+                    "Controller": {
+                        "Configuration": {
+                            "Name": "spawnPoint3",
+                            "Info": "spawnPoint3"
+                        }
                     }
                 },
                 "Component_[4848442694260332496]": {
@@ -284,12 +288,13 @@
                     }
                 },
                 "Component_[2115342656585992410]": {
-                    "$type": "GenericComponentWrapper",
-                    "Id": 2115342656585992410,
-                    "m_template": {
-                        "$type": "ROS2SpawnPointComponent",
-                        "Name": "spawnPoint1",
-                        "Info": "spawnPoint1"
+                    "$type": "ROS2SpawnPointEditorComponent",
+                    "Id": 11018288690574626473,
+                    "Controller": {
+                        "Configuration": {
+                            "Name": "spawnPoint1",
+                            "Info": "spawnPoint1"
+                        }
                     }
                 },
                 "Component_[4848442694260332496]": {
@@ -348,12 +353,13 @@
                     }
                 },
                 "Component_[2115342656585992410]": {
-                    "$type": "GenericComponentWrapper",
-                    "Id": 2115342656585992410,
-                    "m_template": {
-                        "$type": "ROS2SpawnPointComponent",
-                        "Name": "spawnPoint4",
-                        "Info": "spawnPoint4"
+                    "$type": "ROS2SpawnPointEditorComponent",
+                    "Id": 11018288690574626473,
+                    "Controller": {
+                        "Configuration": {
+                            "Name": "spawnPoint4",
+                            "Info": "spawnPoint4"
+                        }
                     }
                 },
                 "Component_[4848442694260332496]": {