ROS2RobotImporterEditorSystemComponent.cpp 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  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 <RobotImporter/URDF/UrdfParser.h>
  10. #include <RobotImporter/Utils/FilePath.h>
  11. #include <RobotImporter/Utils/ErrorUtils.h>
  12. #include "RobotImporterWidget.h"
  13. #include <SdfAssetBuilder/SdfAssetBuilderSettings.h>
  14. #include <AzCore/Serialization/SerializeContext.h>
  15. #include <AzCore/Utils/Utils.h>
  16. #include <AzCore/std/chrono/chrono.h>
  17. #include <AzCore/std/string/string.h>
  18. #include <AzCore/StringFunc/StringFunc.h>
  19. #include <AzToolsFramework/API/ViewPaneOptions.h>
  20. #include <sdf/sdf.hh>
  21. #if !defined(Q_MOC_RUN)
  22. #include <QWindow>
  23. #endif
  24. namespace ROS2
  25. {
  26. void ROS2RobotImporterEditorSystemComponent::Reflect(AZ::ReflectContext* context)
  27. {
  28. if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
  29. {
  30. serializeContext->Class<ROS2RobotImporterEditorSystemComponent, ROS2RobotImporterSystemComponent>()->Version(0);
  31. }
  32. if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
  33. {
  34. behaviorContext->EBus<RobotImporterRequestBus>("RobotImporterBus")
  35. ->Attribute(AZ::Script::Attributes::Category, "Robotics")
  36. ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
  37. ->Attribute(AZ::Script::Attributes::Module, "ROS2")
  38. ->Event("ImportURDF", &RobotImporterRequestBus::Events::GeneratePrefabFromFile);
  39. }
  40. }
  41. void ROS2RobotImporterEditorSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
  42. {
  43. ROS2RobotImporterSystemComponent::GetProvidedServices(provided);
  44. provided.push_back(AZ_CRC_CE("ROS2RobotImporterEditorService"));
  45. }
  46. void ROS2RobotImporterEditorSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
  47. {
  48. ROS2RobotImporterSystemComponent::GetIncompatibleServices(incompatible);
  49. incompatible.push_back(AZ_CRC_CE("ROS2RobotImporterEditorService"));
  50. }
  51. void ROS2RobotImporterEditorSystemComponent::Activate()
  52. {
  53. ROS2RobotImporterSystemComponent::Activate();
  54. AzToolsFramework::EditorEvents::Bus::Handler::BusConnect();
  55. RobotImporterRequestBus::Handler::BusConnect();
  56. }
  57. void ROS2RobotImporterEditorSystemComponent::Deactivate()
  58. {
  59. RobotImporterRequestBus::Handler::BusDisconnect();
  60. AzToolsFramework::EditorEvents::Bus::Handler::BusDisconnect();
  61. ROS2RobotImporterSystemComponent::Deactivate();
  62. }
  63. void ROS2RobotImporterEditorSystemComponent::NotifyRegisterViews()
  64. {
  65. AzToolsFramework::ViewPaneOptions options;
  66. options.showOnToolsToolbar = true;
  67. options.isDockable = false;
  68. options.detachedWindow = true;
  69. options.canHaveMultipleInstances = false;
  70. options.isDisabledInSimMode = true;
  71. options.isDeletable = true;
  72. options.toolbarIcon = ":/ROS2/ROS_import_icon.svg";
  73. AzToolsFramework::RegisterViewPane<RobotImporterWidget>("Robot Importer", "ROS2", options);
  74. }
  75. bool ROS2RobotImporterEditorSystemComponent::GeneratePrefabFromFile(
  76. const AZStd::string_view filePath, bool importAssetWithUrdf, bool useArticulation)
  77. {
  78. if (filePath.empty())
  79. {
  80. AZ_Warning("ROS2RobotImporterEditorSystemComponent", false, "Path provided for prefab is empty");
  81. return false;
  82. }
  83. if (Utils::IsFileXacro(filePath))
  84. {
  85. AZ_Warning("ROS2RobotImporterEditorSystemComponent", false, "XACRO formatted files are not supported");
  86. return false;
  87. }
  88. // Read the SDF Settings from the Settings Registry into a local struct
  89. SdfAssetBuilderSettings sdfBuilderSettings;
  90. sdfBuilderSettings.LoadSettings();
  91. // Set the parser config settings for URDF content
  92. sdf::ParserConfig parserConfig;
  93. parserConfig.URDFSetPreserveFixedJoint(sdfBuilderSettings.m_urdfPreserveFixedJoints);
  94. auto parsedUrdfOutcome = UrdfParser::ParseFromFile(filePath, parserConfig);
  95. if (!parsedUrdfOutcome)
  96. {
  97. const AZStd::string log = Utils::JoinSdfErrorsToString(parsedUrdfOutcome.error());
  98. AZ_Warning("ROS2RobotImporterEditorSystemComponent", false, "URDF parsing failed with errors:\nRefer to %s",
  99. log.c_str());
  100. return false;
  101. }
  102. // Urdf Root has been parsed successfully retrieve it from the Outcome
  103. const sdf::Root& parsedUrdfRoot = parsedUrdfOutcome.value();
  104. auto collidersNames = Utils::GetMeshesFilenames(&parsedUrdfRoot, false, true);
  105. auto visualNames = Utils::GetMeshesFilenames(&parsedUrdfRoot, true, false);
  106. auto meshNames = Utils::GetMeshesFilenames(&parsedUrdfRoot, true, true);
  107. AZStd::shared_ptr<Utils::UrdfAssetMap> urdfAssetsMapping = AZStd::make_shared<Utils::UrdfAssetMap>();
  108. if (importAssetWithUrdf)
  109. {
  110. urdfAssetsMapping = AZStd::make_shared<Utils::UrdfAssetMap>(
  111. Utils::CopyAssetForURDFAndCreateAssetMap(meshNames, filePath, collidersNames, visualNames));
  112. }
  113. bool allAssetProcessed = false;
  114. bool assetProcessorFailed = false;
  115. auto loopStartTime = AZStd::chrono::system_clock::now();
  116. /* This loop waits until all of the assets are processed.
  117. The urdf prefab cannot be created before all assets are processed.
  118. There are three stop conditions: allAssetProcessed, assetProcessorFailed and a timeout.
  119. After all asset are processed the allAssetProcessed will be set to true.
  120. assetProcessorFailed will be set to true if the asset processor does not respond.
  121. The time out will break the loop if assetLoopTimeout is exceed. */
  122. while (!allAssetProcessed && !assetProcessorFailed)
  123. {
  124. auto loopTime = AZStd::chrono::system_clock::now();
  125. if (loopTime - loopStartTime > assetLoopTimeout)
  126. {
  127. AZ_Warning("ROS2RobotImporterEditorSystemComponent", false, "Loop waiting for assets timed out");
  128. break;
  129. }
  130. allAssetProcessed = true;
  131. for (const auto& [name, asset] : *urdfAssetsMapping)
  132. {
  133. auto sourceAssetFullPath = asset.m_availableAssetInfo.m_sourceAssetGlobalPath;
  134. if (sourceAssetFullPath.empty())
  135. {
  136. AZ_Warning("ROS2RobotImporterEditorSystemComponent", false, "Asset %s missing `sourceAssetFullPath`", name.c_str());
  137. continue;
  138. }
  139. using namespace AzToolsFramework;
  140. using namespace AzToolsFramework::AssetSystem;
  141. AZ::Outcome<AssetSystem::JobInfoContainer> result = AZ::Failure();
  142. AssetSystemJobRequestBus::BroadcastResult(
  143. result, &AssetSystemJobRequestBus::Events::GetAssetJobsInfo, sourceAssetFullPath.Native(), true);
  144. if (!result.IsSuccess())
  145. {
  146. assetProcessorFailed = true;
  147. AZ_Error("ROS2RobotImporterEditorSystemComponent", false, "Asset System failed to reply with jobs infos");
  148. break;
  149. }
  150. JobInfoContainer& allJobs = result.GetValue();
  151. for (const JobInfo& job : allJobs)
  152. {
  153. if (job.m_status == JobStatus::Queued || job.m_status == JobStatus::InProgress)
  154. {
  155. AZ_Printf("ROS2RobotImporterEditorSystemComponent", "asset %s is being processed", sourceAssetFullPath.c_str());
  156. allAssetProcessed = false;
  157. }
  158. else
  159. {
  160. AZ_Printf("ROS2RobotImporterEditorSystemComponent", "asset %s is done", sourceAssetFullPath.c_str());
  161. }
  162. }
  163. }
  164. if (allAssetProcessed && !assetProcessorFailed)
  165. {
  166. AZ_Printf("ROS2RobotImporterEditorSystemComponent", "All assets processed");
  167. }
  168. };
  169. // Use the name of the first model tag in the SDF for the prefab
  170. // Otherwise use the name of the first world tag in the SDF
  171. AZStd::string prefabName;
  172. if (const sdf::Model* model = parsedUrdfRoot.Model();
  173. model != nullptr)
  174. {
  175. prefabName = AZStd::string(model->Name().c_str(), model->Name().size()) + ".prefab";
  176. }
  177. if (uint64_t urdfWorldCount = parsedUrdfRoot.WorldCount();
  178. prefabName.empty() && urdfWorldCount > 0)
  179. {
  180. const sdf::World* parsedUrdfWorld = parsedUrdfRoot.WorldByIndex(0);
  181. prefabName = AZStd::string(parsedUrdfWorld->Name().c_str(), parsedUrdfWorld->Name().size()) + ".prefab";
  182. }
  183. if (prefabName.empty())
  184. {
  185. AZ_Error("ROS2RobotImporterEditorSystemComponent", false, "URDF file converted to SDF %.*s contains no worlds."
  186. " O3DE Prefab cannot be created", AZ_STRING_ARG(filePath));
  187. return false;
  188. }
  189. const AZ::IO::Path prefabPathRelative(AZ::IO::Path("Assets") / "Importer" / prefabName);
  190. const AZ::IO::Path prefabPath(AZ::IO::Path(AZ::Utils::GetProjectPath()) / prefabPathRelative);
  191. AZStd::unique_ptr<URDFPrefabMaker> prefabMaker = AZStd::make_unique<URDFPrefabMaker>(filePath, &parsedUrdfRoot, prefabPath.String(), urdfAssetsMapping, useArticulation);
  192. auto prefabOutcome = prefabMaker->CreatePrefabFromURDF();
  193. if (!prefabOutcome.IsSuccess())
  194. {
  195. AZ_Error("ROS2RobotImporterEditorSystemComponent", false, "Unable to create Prefab from URDF file %s", filePath.data());
  196. return false;
  197. }
  198. return true;
  199. }
  200. } // namespace ROS2