ROS2RobotImporterEditorSystemComponent.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include "ROS2RobotImporterEditorSystemComponent.h"
  9. #include "RobotImporterWidget.h"
  10. #include <AzCore/Serialization/SerializeContext.h>
  11. #include <AzCore/StringFunc/StringFunc.h>
  12. #include <AzCore/Utils/Utils.h>
  13. #include <AzCore/std/chrono/chrono.h>
  14. #include <AzCore/std/string/string.h>
  15. #include <AzCore/std/utility/move.h>
  16. #include <AzToolsFramework/API/ViewPaneOptions.h>
  17. #include <AzToolsFramework/Entity/EditorEntityHelpers.h>
  18. #include <RobotImporter/URDF/UrdfParser.h>
  19. #include <RobotImporter/Utils/ErrorUtils.h>
  20. #include <RobotImporter/Utils/FilePath.h>
  21. #include <SDFormat/ROS2ModelPluginHooks.h>
  22. #include <SDFormat/ROS2SensorHooks.h>
  23. #include <SdfAssetBuilder/SdfAssetBuilderSettings.h>
  24. #include <sdf/sdf.hh>
  25. #if !defined(Q_MOC_RUN)
  26. #include <QWindow>
  27. #endif
  28. namespace ROS2
  29. {
  30. void ROS2RobotImporterEditorSystemComponent::Reflect(AZ::ReflectContext* context)
  31. {
  32. if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
  33. {
  34. serializeContext->Class<ROS2RobotImporterEditorSystemComponent, ROS2RobotImporterSystemComponent>()->Version(1);
  35. }
  36. if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
  37. {
  38. behaviorContext->EBus<RobotImporterRequestBus>("RobotImporterBus")
  39. ->Attribute(AZ::Script::Attributes::Category, "Robotics")
  40. ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
  41. ->Attribute(AZ::Script::Attributes::Module, "ROS2")
  42. ->Event("ImportURDF", &RobotImporterRequestBus::Events::GeneratePrefabFromFile);
  43. }
  44. }
  45. void ROS2RobotImporterEditorSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
  46. {
  47. ROS2RobotImporterSystemComponent::GetProvidedServices(provided);
  48. provided.push_back(AZ_CRC_CE("ROS2RobotImporterEditorService"));
  49. }
  50. void ROS2RobotImporterEditorSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
  51. {
  52. ROS2RobotImporterSystemComponent::GetIncompatibleServices(incompatible);
  53. incompatible.push_back(AZ_CRC_CE("ROS2RobotImporterEditorService"));
  54. }
  55. void ROS2RobotImporterEditorSystemComponent::Activate()
  56. {
  57. ROS2RobotImporterSystemComponent::Activate();
  58. AzToolsFramework::EditorEvents::Bus::Handler::BusConnect();
  59. RobotImporterRequestBus::Handler::BusConnect();
  60. // Register default sensor and plugin hooks
  61. // temporarily disable import hooks for sensors and models for https://github.com/o3de/sig-simulation/pull/96
  62. // m_sensorHooks.emplace_back(SDFormat::ROS2SensorHooks::ROS2CameraSensor());
  63. // m_sensorHooks.emplace_back(SDFormat::ROS2SensorHooks::ROS2GNSSSensor());
  64. // m_sensorHooks.emplace_back(SDFormat::ROS2SensorHooks::ROS2ImuSensor());
  65. // m_sensorHooks.emplace_back(SDFormat::ROS2SensorHooks::ROS2LidarSensor());
  66. // m_modelPluginHooks.emplace_back(SDFormat::ROS2ModelPluginHooks::ROS2AckermannModel());
  67. // m_modelPluginHooks.emplace_back(SDFormat::ROS2ModelPluginHooks::ROS2SkidSteeringModel());
  68. // m_modelPluginHooks.emplace_back(SDFormat::ROS2ModelPluginHooks::ROS2JointStatePublisherModel());
  69. // m_modelPluginHooks.emplace_back(SDFormat::ROS2ModelPluginHooks::ROS2JointPoseTrajectoryModel());
  70. // Query user-defined sensor and plugin hooks
  71. auto serializeContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetSerializeContext();
  72. serializeContext->EnumerateAll(
  73. [&](const AZ::SerializeContext::ClassData* classData, const AZ::Uuid& typeId) -> bool
  74. {
  75. return CopyHooksCallback<SDFormat::SensorImporterHooksStorage>(m_sensorHooks, classData, "SensorImporterHooks");
  76. });
  77. serializeContext->EnumerateAll(
  78. [&](const AZ::SerializeContext::ClassData* classData, const AZ::Uuid& typeId) -> bool
  79. {
  80. return CopyHooksCallback<SDFormat::ModelPluginImporterHooksStorage>(
  81. m_modelPluginHooks, classData, "ModelPluginImporterHooks");
  82. });
  83. }
  84. void ROS2RobotImporterEditorSystemComponent::Deactivate()
  85. {
  86. RobotImporterRequestBus::Handler::BusDisconnect();
  87. AzToolsFramework::EditorEvents::Bus::Handler::BusDisconnect();
  88. ROS2RobotImporterSystemComponent::Deactivate();
  89. }
  90. void ROS2RobotImporterEditorSystemComponent::NotifyRegisterViews()
  91. {
  92. AzToolsFramework::ViewPaneOptions options;
  93. options.showOnToolsToolbar = true;
  94. options.isDockable = false;
  95. options.detachedWindow = true;
  96. options.canHaveMultipleInstances = false;
  97. options.isDisabledInSimMode = true;
  98. options.isDeletable = true;
  99. options.toolbarIcon = ":/ROS2/ROS_import_icon.svg";
  100. AzToolsFramework::RegisterViewPane<RobotImporterWidget>("Robot Importer", "ROS2", options);
  101. }
  102. bool ROS2RobotImporterEditorSystemComponent::GeneratePrefabFromFile(
  103. const AZStd::string_view filePath, bool importAssetWithUrdf, bool useArticulation)
  104. {
  105. if (filePath.empty())
  106. {
  107. AZ_Warning("ROS2RobotImporterEditorSystemComponent", false, "Path provided for prefab is empty");
  108. return false;
  109. }
  110. if (Utils::IsFileXacro(filePath))
  111. {
  112. AZ_Warning("ROS2RobotImporterEditorSystemComponent", false, "XACRO formatted files are not supported");
  113. return false;
  114. }
  115. // Read the SDF Settings from the Settings Registry into a local struct
  116. SdfAssetBuilderSettings sdfBuilderSettings;
  117. sdfBuilderSettings.LoadSettings();
  118. // Set the parser config settings for URDF content
  119. sdf::ParserConfig parserConfig = Utils::SDFormat::CreateSdfParserConfigFromSettings(sdfBuilderSettings, filePath);
  120. auto parsedSdfOutcome = UrdfParser::ParseFromFile(filePath, parserConfig, sdfBuilderSettings);
  121. if (!parsedSdfOutcome)
  122. {
  123. const AZStd::string log = Utils::JoinSdfErrorsToString(parsedSdfOutcome.GetSdfErrors());
  124. AZ_Warning("ROS2RobotImporterEditorSystemComponent", false, "URDF/SDF parsing failed with errors:\nRefer to %s", log.c_str());
  125. return false;
  126. }
  127. // Urdf Root has been parsed successfully retrieve it from the Outcome
  128. const sdf::Root& parsedSdfRoot = parsedSdfOutcome.GetRoot();
  129. auto assetNames = Utils::GetReferencedAssetFilenames(parsedSdfRoot);
  130. AZStd::shared_ptr<Utils::UrdfAssetMap> urdfAssetsMapping = AZStd::make_shared<Utils::UrdfAssetMap>();
  131. if (importAssetWithUrdf)
  132. {
  133. urdfAssetsMapping = AZStd::make_shared<Utils::UrdfAssetMap>(
  134. Utils::CopyReferencedAssetsAndCreateAssetMap(assetNames, filePath, sdfBuilderSettings));
  135. }
  136. bool allAssetProcessed = false;
  137. bool assetProcessorFailed = false;
  138. auto loopStartTime = AZStd::chrono::system_clock::now();
  139. /* This loop waits until all of the assets are processed.
  140. The urdf prefab cannot be created before all assets are processed.
  141. There are three stop conditions: allAssetProcessed, assetProcessorFailed and a timeout.
  142. After all asset are processed the allAssetProcessed will be set to true.
  143. assetProcessorFailed will be set to true if the asset processor does not respond.
  144. The time out will break the loop if assetLoopTimeout is exceed. */
  145. while (!allAssetProcessed && !assetProcessorFailed)
  146. {
  147. auto loopTime = AZStd::chrono::system_clock::now();
  148. if (loopTime - loopStartTime > assetLoopTimeout)
  149. {
  150. AZ_Warning("ROS2RobotImporterEditorSystemComponent", false, "Loop waiting for assets timed out");
  151. break;
  152. }
  153. allAssetProcessed = true;
  154. for (const auto& [name, asset] : *urdfAssetsMapping)
  155. {
  156. auto sourceAssetFullPath = asset.m_availableAssetInfo.m_sourceAssetGlobalPath;
  157. if (sourceAssetFullPath.empty())
  158. {
  159. AZ_Warning("ROS2RobotImporterEditorSystemComponent", false, "Asset %s missing `sourceAssetFullPath`", name.c_str());
  160. continue;
  161. }
  162. using namespace AzToolsFramework;
  163. using namespace AzToolsFramework::AssetSystem;
  164. AZ::Outcome<AssetSystem::JobInfoContainer> result = AZ::Failure();
  165. AssetSystemJobRequestBus::BroadcastResult(
  166. result, &AssetSystemJobRequestBus::Events::GetAssetJobsInfo, sourceAssetFullPath.Native(), true);
  167. if (!result.IsSuccess())
  168. {
  169. assetProcessorFailed = true;
  170. AZ_Error("ROS2RobotImporterEditorSystemComponent", false, "Asset System failed to reply with jobs infos");
  171. break;
  172. }
  173. JobInfoContainer& allJobs = result.GetValue();
  174. for (const JobInfo& job : allJobs)
  175. {
  176. if (job.m_status == JobStatus::Queued || job.m_status == JobStatus::InProgress)
  177. {
  178. AZ_Printf("ROS2RobotImporterEditorSystemComponent", "asset %s is being processed\n", sourceAssetFullPath.c_str());
  179. allAssetProcessed = false;
  180. }
  181. else
  182. {
  183. AZ_Printf("ROS2RobotImporterEditorSystemComponent", "asset %s is done\n", sourceAssetFullPath.c_str());
  184. }
  185. }
  186. }
  187. if (allAssetProcessed && !assetProcessorFailed)
  188. {
  189. AZ_Printf("ROS2RobotImporterEditorSystemComponent", "All assets processed\n");
  190. }
  191. };
  192. // Use the URDF/SDF file name stem the prefab name
  193. auto fileStem = AZ::IO::PathView(filePath).Stem();
  194. AZStd::string prefabName = AZStd::string::format("%.*s.prefab", AZ_PATH_ARG(fileStem));
  195. if (prefabName.empty())
  196. {
  197. AZ_Error(
  198. "ROS2RobotImporterEditorSystemComponent",
  199. false,
  200. R"(URDF/SDF doesn't filename doesn't contain a stem "%.*s".)"
  201. " O3DE Prefab cannot be created",
  202. AZ_STRING_ARG(filePath));
  203. return false;
  204. }
  205. const AZ::IO::Path prefabPathRelative(AZ::IO::Path("Assets") / "Importer" / prefabName);
  206. const AZ::IO::Path prefabPath(AZ::IO::Path(AZ::Utils::GetProjectPath()) / prefabPathRelative);
  207. AZStd::unique_ptr<URDFPrefabMaker> prefabMaker =
  208. AZStd::make_unique<URDFPrefabMaker>(filePath, &parsedSdfRoot, prefabPath.String(), urdfAssetsMapping, useArticulation);
  209. auto prefabOutcome = prefabMaker->CreatePrefabFromUrdfOrSdf();
  210. if (!prefabOutcome.IsSuccess())
  211. {
  212. AZ_Error(
  213. "ROS2RobotImporterEditorSystemComponent",
  214. false,
  215. "Unable to create Prefab from URDF/SDF file %.*s",
  216. AZ_STRING_ARG(filePath));
  217. return false;
  218. }
  219. return true;
  220. }
  221. const SDFormat::SensorImporterHooksStorage& ROS2RobotImporterEditorSystemComponent::GetSensorHooks() const
  222. {
  223. return m_sensorHooks;
  224. }
  225. const SDFormat::ModelPluginImporterHooksStorage& ROS2RobotImporterEditorSystemComponent::GetModelPluginHooks() const
  226. {
  227. return m_modelPluginHooks;
  228. }
  229. } // namespace ROS2