URDFPrefabMaker.cpp 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776
  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 "URDFPrefabMaker.h"
  9. #include "CollidersMaker.h"
  10. #include "PrefabMakerUtils.h"
  11. #include <API/EditorAssetSystemAPI.h>
  12. #include <AzCore/Debug/Trace.h>
  13. #include <AzCore/IO/FileIO.h>
  14. #include <AzCore/Math/Transform.h>
  15. #include <AzCore/Serialization/Json/JsonUtils.h>
  16. #include <AzFramework/Scene/SceneSystemInterface.h>
  17. #include <AzToolsFramework/Entity/EditorEntityHelpers.h>
  18. #include <AzToolsFramework/Prefab/PrefabLoaderInterface.h>
  19. #include <AzToolsFramework/Prefab/PrefabLoaderScriptingBus.h>
  20. #include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
  21. #include <AzToolsFramework/Prefab/PrefabSystemScriptingBus.h>
  22. #include <AzToolsFramework/Prefab/Procedural/ProceduralPrefabAsset.h>
  23. #include <AzToolsFramework/ToolsComponents/GenericComponentWrapper.h>
  24. #include <AzToolsFramework/ToolsComponents/TransformComponent.h>
  25. #include <ROS2/Frame/ROS2FrameEditorComponent.h>
  26. #include <RobotImporter/Utils/ErrorUtils.h>
  27. #include <RobotImporter/Utils/RobotImporterUtils.h>
  28. #include <RobotImporter/Utils/TypeConversions.h>
  29. namespace ROS2RobotImporter
  30. {
  31. URDFPrefabMaker::URDFPrefabMaker(
  32. const AZStd::string& modelFilePath,
  33. const sdf::Root* root,
  34. AZStd::string prefabPath,
  35. const AZStd::shared_ptr<Utils::UrdfAssetMap> urdfAssetsMapping,
  36. bool useArticulations,
  37. const AZStd::optional<AZ::Transform> spawnPosition)
  38. : m_root(root)
  39. , m_visualsMaker(urdfAssetsMapping)
  40. , m_collidersMaker(urdfAssetsMapping)
  41. , m_prefabPath(AZStd::move(prefabPath))
  42. , m_urdfAssetsMapping(urdfAssetsMapping)
  43. , m_spawnPosition(spawnPosition)
  44. , m_useArticulations(useArticulations)
  45. {
  46. AZ_Assert(!m_prefabPath.empty(), "Prefab path is empty");
  47. AZ_Assert(m_root, "SDF Root is nullptr");
  48. }
  49. URDFPrefabMaker::CreatePrefabTemplateResult URDFPrefabMaker::CreatePrefabTemplateFromUrdfOrSdf()
  50. {
  51. {
  52. AZStd::lock_guard<AZStd::mutex> lck(m_statusLock);
  53. m_status.clear();
  54. m_articulationsCounter = 0u;
  55. }
  56. if (!ContainsModel())
  57. {
  58. return AZ::Failure(AZStd::string("URDF/SDF doesn't contain any models."));
  59. }
  60. // Visit any nested models in the SDF as well
  61. constexpr bool visitNestedModels = true;
  62. // Maintains references to all joints and a mapping from the joints to their parent and child links
  63. struct JointsMapper
  64. {
  65. struct JointToAttachedModel
  66. {
  67. AZStd::string m_fullyQualifiedName;
  68. const sdf::Joint* m_joint{};
  69. const sdf::Model* m_attachedModel{};
  70. };
  71. // this is a unique ordered vector
  72. AZStd::vector<JointToAttachedModel> m_joints;
  73. AZStd::unordered_map<const sdf::Joint*, const sdf::Link*> m_jointToParentLinks;
  74. AZStd::unordered_map<const sdf::Joint*, const sdf::Link*> m_jointToChildLinks;
  75. };
  76. JointsMapper jointsMapper;
  77. auto GetAllJointsFromModel = [&jointsMapper](const sdf::Model& model, const Utils::ModelStack&) -> Utils::VisitModelResponse
  78. {
  79. // As the VisitModels function visits nested models by default, gatherNestedModelJoints is set to false
  80. constexpr bool gatherNestedModelJoints = false;
  81. auto jointsForModel = Utils::GetAllJoints(model, gatherNestedModelJoints);
  82. for (const auto& [fullyQualifiedName, joint] : jointsForModel)
  83. {
  84. JointsMapper::JointToAttachedModel jointToAttachedModel{
  85. AZStd::string(fullyQualifiedName.c_str(), fullyQualifiedName.size()), joint, &model
  86. };
  87. jointsMapper.m_joints.push_back(AZStd::move(jointToAttachedModel));
  88. // add mapping from joint to child link
  89. std::string childName = joint->ChildName();
  90. if (const sdf::Link* link = model.LinkByName(childName); link != nullptr)
  91. {
  92. // Add a mapping of joint to child link
  93. jointsMapper.m_jointToChildLinks[joint] = link;
  94. }
  95. // add mapping from joint to parent link
  96. std::string parentName = joint->ParentName();
  97. if (const sdf::Link* link = model.LinkByName(parentName); link != nullptr)
  98. {
  99. jointsMapper.m_jointToParentLinks[joint] = link;
  100. }
  101. }
  102. return Utils::VisitModelResponse::VisitNestedAndSiblings;
  103. };
  104. // Gather all Joints in SDF including in nested models
  105. Utils::VisitModels(*m_root, GetAllJointsFromModel, visitNestedModels);
  106. // Maintains references to all Links in the SDF and a mapping of links to the model it is attached to
  107. struct LinksMapper
  108. {
  109. struct LinkToAttachedModel
  110. {
  111. AZStd::string m_fullyQualifiedName;
  112. const sdf::Link* m_link{};
  113. const sdf::Model* m_attachedModel{};
  114. };
  115. // this is a unique ordered vector
  116. AZStd::vector<LinkToAttachedModel> m_links;
  117. };
  118. LinksMapper linksMapper;
  119. struct ModelMapper
  120. {
  121. struct NestedModelToAttachedModel
  122. {
  123. AZStd::string m_fullyQualifiedName;
  124. const sdf::Model* m_nestedModel{};
  125. const sdf::Model* m_attachedModel{};
  126. };
  127. // this is a unique ordered vector
  128. AZStd::vector<NestedModelToAttachedModel> m_models;
  129. };
  130. ModelMapper modelMapper;
  131. auto GetAllLinksAndSetModelHierarchy =
  132. [&linksMapper, &modelMapper](const sdf::Model& model, const Utils::ModelStack& modelStack) -> Utils::VisitModelResponse
  133. {
  134. // As the VisitModels function visits nested models by default, gatherNestedModelLinks is set to false
  135. constexpr bool gatherNestedModelLinks = false;
  136. auto linksForModel = Utils::GetAllLinks(model, gatherNestedModelLinks);
  137. for (const auto& [fullyQualifiedName, link] : linksForModel)
  138. {
  139. // Push back the mapping of link to attached model into the ordered vector
  140. AZStd::string fullLinkName(fullyQualifiedName.c_str(), fullyQualifiedName.size());
  141. LinksMapper::LinkToAttachedModel linkToAttachedModel{ AZStd::move(fullLinkName), link, &model };
  142. linksMapper.m_links.push_back(AZStd::move(linkToAttachedModel));
  143. }
  144. // Use the model stack to create a mapping from the current model
  145. // to the parent model it is attached to.
  146. // If the current model has no parent model the attached model is set to nullptr
  147. std::string stdFullModelName;
  148. for (const sdf::Model& ancestorModel : modelStack)
  149. {
  150. stdFullModelName = sdf::JoinName(stdFullModelName, ancestorModel.Name());
  151. }
  152. stdFullModelName = sdf::JoinName(stdFullModelName, model.Name());
  153. AZStd::string fullModelName(stdFullModelName.c_str(), stdFullModelName.size());
  154. ModelMapper::NestedModelToAttachedModel nestedModelToAttachedModel;
  155. nestedModelToAttachedModel.m_fullyQualifiedName = AZStd::move(fullModelName);
  156. nestedModelToAttachedModel.m_nestedModel = &model;
  157. nestedModelToAttachedModel.m_attachedModel = !modelStack.empty() ? &modelStack.back().get() : nullptr;
  158. modelMapper.m_models.push_back(AZStd::move(nestedModelToAttachedModel));
  159. return Utils::VisitModelResponse::VisitNestedAndSiblings;
  160. };
  161. // Gather all links and add a mapping of nested model -> parent model for each model in the SDF
  162. Utils::VisitModels(*m_root, GetAllLinksAndSetModelHierarchy, visitNestedModels);
  163. // Build up a list of all entities created as a part of processing the file.
  164. AZStd::vector<AZ::EntityId> createdEntities;
  165. AZStd::unordered_map<const sdf::Model*, AzToolsFramework::Prefab::PrefabEntityResult> createdModels;
  166. AZStd::unordered_map<const sdf::Link*, AzToolsFramework::Prefab::PrefabEntityResult> createdLinks;
  167. AZStd::unordered_map<AZStd::string, const sdf::Link*> links;
  168. // Create an entity for each model
  169. for ([[maybe_unused]] const auto& [fullModelName, modelPtr, _] : modelMapper.m_models)
  170. {
  171. // Create entities for each model in the SDF
  172. const std::string modelName = modelPtr->Name();
  173. const AZStd::string azModelName(modelName.c_str(), modelName.size());
  174. if (AzToolsFramework::Prefab::PrefabEntityResult createModelEntityResult = CreateEntityForModel(*modelPtr);
  175. createModelEntityResult)
  176. {
  177. AZ::EntityId createdModelEntityId = createModelEntityResult.GetValue();
  178. // Add the model entity to the created entity list so that it gets added to the prefab
  179. createdEntities.emplace_back(createdModelEntityId);
  180. createdModels.emplace(modelPtr, createModelEntityResult);
  181. AZStd::lock_guard<AZStd::mutex> lck(m_statusLock);
  182. m_status.emplace(
  183. StatusMessageType::Model,
  184. AZStd::string::format("%s created as: %s", azModelName.c_str(), createdModelEntityId.ToString().c_str()));
  185. }
  186. else
  187. {
  188. AZStd::lock_guard<AZStd::mutex> lck(m_statusLock);
  189. m_status.emplace(
  190. StatusMessageType::Model,
  191. AZStd::string::format("%s failed: %s", azModelName.c_str(), createModelEntityResult.GetError().c_str()));
  192. }
  193. }
  194. //! Setup the parent hierarchy for the nested models
  195. for ([[maybe_unused]] const auto& [_, modelPtr, parentModelPtr] : modelMapper.m_models)
  196. {
  197. // If there is no parent model, then the model would be at the top level of the hierarchy
  198. if (parentModelPtr == nullptr || modelPtr == nullptr)
  199. {
  200. continue;
  201. }
  202. // Create entities for each model in the SDF
  203. AZ::EntityId modelEntityId;
  204. if (auto modelIt = createdModels.find(modelPtr); modelIt != createdModels.end() && modelIt->second)
  205. {
  206. modelEntityId = modelIt->second.GetValue();
  207. }
  208. AZ::EntityId parentModelEntityId;
  209. if (auto parentModelIt = createdModels.find(parentModelPtr); parentModelIt != createdModels.end() && parentModelIt->second)
  210. {
  211. parentModelEntityId = parentModelIt->second.GetValue();
  212. }
  213. // If the both the parent model and current model entity exist
  214. // set the current model transform component parent to the parent model
  215. if (parentModelEntityId.IsValid() && modelEntityId.IsValid())
  216. {
  217. // The model entity local transform should be used
  218. // to allow it to move, rotate, scale relative to the parent
  219. PrefabMakerUtils::SetEntityParentRelative(modelEntityId, parentModelEntityId);
  220. }
  221. }
  222. // Create an entity for each link and set the parent to be the model entity where the link is attached
  223. for ([[maybe_unused]] const auto& [_, linkPtr, attachedModel] : linksMapper.m_links)
  224. {
  225. AZ::EntityId modelEntityId;
  226. if (attachedModel != nullptr)
  227. {
  228. if (auto modelIt = createdModels.find(attachedModel); modelIt != createdModels.end())
  229. {
  230. if (modelIt->second.IsSuccess())
  231. {
  232. modelEntityId = modelIt->second.GetValue();
  233. }
  234. }
  235. }
  236. // Add all link as children of their attached model entity by default
  237. createdLinks[linkPtr] = AddEntitiesForLink(*linkPtr, attachedModel, modelEntityId, createdEntities);
  238. }
  239. for (const auto& [linkPtr, result] : createdLinks)
  240. {
  241. std::string linkName = linkPtr->Name();
  242. AZStd::string azLinkName(linkName.c_str(), linkName.size());
  243. AZ_Trace(
  244. "CreatePrefabFromUrdfOrSdf",
  245. "Link with name %s was created as: %s\n",
  246. linkName.c_str(),
  247. result.IsSuccess() ? (result.GetValue().ToString().c_str()) : ("[Failed]"));
  248. AZStd::lock_guard<AZStd::mutex> lck(m_statusLock);
  249. if (result.IsSuccess())
  250. {
  251. m_status.emplace(
  252. StatusMessageType::Link,
  253. AZStd::string::format("%s created as: %s", azLinkName.c_str(), result.GetValue().ToString().c_str()));
  254. }
  255. else
  256. {
  257. m_status.emplace(
  258. StatusMessageType::Link, AZStd::string::format("%s failed : %s", azLinkName.c_str(), result.GetError().c_str()));
  259. }
  260. }
  261. // Set the transforms of links
  262. for ([[maybe_unused]] const auto& [fullLinkName, linkPtr, _] : linksMapper.m_links)
  263. {
  264. if (const auto createLinkEntityResult = createdLinks.at(linkPtr); createLinkEntityResult.IsSuccess())
  265. {
  266. AZ::EntityId createdEntityId = createLinkEntityResult.GetValue();
  267. std::string linkName = linkPtr->Name();
  268. const auto linkSemanticPose = linkPtr->SemanticPose();
  269. AZ::Transform tf = Utils::GetLocalTransformURDF(linkSemanticPose);
  270. auto* entity = AzToolsFramework::GetEntityById(createdEntityId);
  271. if (entity)
  272. {
  273. auto* transformInterface = entity->FindComponent<AzToolsFramework::Components::TransformComponent>();
  274. if (transformInterface)
  275. {
  276. AZ_Trace(
  277. "CreatePrefabFromUrdfOrSdf",
  278. "Setting transform %s %s to [%f %f %f] [%f %f %f %f]\n",
  279. linkName.c_str(),
  280. createdEntityId.ToString().c_str(),
  281. tf.GetTranslation().GetX(),
  282. tf.GetTranslation().GetY(),
  283. tf.GetTranslation().GetZ(),
  284. tf.GetRotation().GetX(),
  285. tf.GetRotation().GetY(),
  286. tf.GetRotation().GetZ(),
  287. tf.GetRotation().GetW());
  288. transformInterface->SetLocalTM(tf);
  289. }
  290. else
  291. {
  292. AZ_Trace(
  293. "CreatePrefabFromUrdfOrSdf",
  294. "Setting transform failed: %s does not have transform interface\n",
  295. linkName.c_str());
  296. }
  297. }
  298. }
  299. }
  300. // Set the hierarchy
  301. AZStd::vector<AZStd::pair<AZ::EntityId, const sdf::Model*>> linkEntityIdsWithoutParent;
  302. for (const auto& [fullLinkName, linkPtr, attachedModel] : linksMapper.m_links)
  303. {
  304. std::string linkName = linkPtr->Name();
  305. AZStd::string azLinkName(linkName.c_str(), linkName.size());
  306. const auto linkPrefabResult = createdLinks.at(linkPtr);
  307. if (!linkPrefabResult.IsSuccess())
  308. {
  309. AZ_Trace("CreatePrefabFromUrdfOrSdf", "Link %s creation failed\n", fullLinkName.c_str());
  310. continue;
  311. }
  312. AZStd::vector<const sdf::Joint*> jointsWhereLinkIsChild;
  313. bool gatherNestedModelJoints = true;
  314. jointsWhereLinkIsChild = Utils::GetJointsForChildLink(*attachedModel, azLinkName, gatherNestedModelJoints);
  315. if (jointsWhereLinkIsChild.empty())
  316. {
  317. // emplace unique entry to the container of links that don't have a parent link associated with it
  318. auto linkPrefabTest = [&linkPrefabResult](const AZStd::pair<AZ::EntityId, const sdf::Model*>& query)
  319. {
  320. return (query.first == linkPrefabResult.GetValue());
  321. };
  322. if (auto existingLinkIt =
  323. AZStd::find_if(linkEntityIdsWithoutParent.begin(), linkEntityIdsWithoutParent.end(), linkPrefabTest);
  324. existingLinkIt == linkEntityIdsWithoutParent.end())
  325. {
  326. linkEntityIdsWithoutParent.emplace_back(AZStd::make_pair(linkPrefabResult.GetValue(), attachedModel));
  327. }
  328. AZ_Trace("CreatePrefabFromUrdfOrSdf", "Link %s has no parents\n", linkName.c_str());
  329. continue;
  330. }
  331. // For URDF, a link can only be child in a single joint
  332. // a link can't be a child of two other links as URDF models a tree structure and not a graph
  333. /*
  334. Here is a snippet from the Pose Frame Semantics documentation for SDFormat that explains the differences
  335. between URDF and SDF coordinate frame
  336. http://sdformat.org/tutorials?tut=pose_frame_semantics&ver=1.5#parent-frames-in-urdf
  337. > The most significant difference between URDF and SDFormat coordinate frames is related to links and joints.
  338. > While SDFormat allows kinematic loops with the topology of a directed graph,
  339. > URDF kinematics must have the topology of a directed tree, with each link being the child of at most one joint.
  340. > URDF coordinate frames are defined recursively based on this tree structure, with each joint's <origin/> tag
  341. > defining the coordinate transformation from the parent link frame to the child link frame.
  342. */
  343. // Use the first joint where this link is a child to locate the parent link pointer.
  344. const sdf::Joint* joint = jointsWhereLinkIsChild.front();
  345. std::string parentLinkName = joint->ParentName();
  346. AZStd::string parentName(parentLinkName.c_str(), parentLinkName.size());
  347. // Lookup the entity created from the parent link using the JointMapper to locate the parent SDF link.
  348. // followed by using SDF link address to lookup the O3DE created entity ID
  349. auto parentLinkIter = jointsMapper.m_jointToParentLinks.find(joint);
  350. auto parentEntityIter =
  351. parentLinkIter != jointsMapper.m_jointToParentLinks.end() ? createdLinks.find(parentLinkIter->second) : createdLinks.end();
  352. if (parentEntityIter == createdLinks.end())
  353. {
  354. AZ_Trace("CreatePrefabFromUrdfOrSdf", "Link %s has invalid parent name %s\n", linkName.c_str(), parentName.c_str());
  355. continue;
  356. }
  357. if (!parentEntityIter->second.IsSuccess())
  358. {
  359. AZ_Trace(
  360. "CreatePrefabFromUrdfOrSdf",
  361. "Link %s has parent %s which has failed to create\n",
  362. linkName.c_str(),
  363. parentName.c_str());
  364. continue;
  365. }
  366. AZ_Trace(
  367. "CreatePrefabFromUrdfOrSdf",
  368. "Link %s setting parent to %s\n",
  369. linkPrefabResult.GetValue().ToString().c_str(),
  370. parentEntityIter->second.GetValue().ToString().c_str());
  371. AZ_Trace("CreatePrefabFromUrdfOrSdf", "Link %s setting parent to %s\n", linkName.c_str(), parentName.c_str());
  372. // The joint hierarchy which specifies how a parent and child link hierarchy is represented in an SDF document
  373. // is used to establish the entity parent child hierarchy, but does not modify the world location of the link entities
  374. // therefore SetEntityParent is used to maintain the world transform of the child link
  375. PrefabMakerUtils::SetEntityParent(linkPrefabResult.GetValue(), parentEntityIter->second.GetValue());
  376. }
  377. // Iterate over all the joints and locate the entity associated with the link
  378. for ([[maybe_unused]] const auto& [fullJointName, jointPtr, _] : jointsMapper.m_joints)
  379. {
  380. std::string jointName = jointPtr->Name();
  381. AZStd::string azJointName(jointName.c_str(), jointName.size());
  382. std::string childLinkName = jointPtr->ChildName();
  383. std::string parentLinkName = jointPtr->ParentName();
  384. // Look up the O3DE created entity by first locating the parent SDF link associated with the current joint
  385. // and then using that SDF link to lookup the created entity
  386. auto parentLinkIter = jointsMapper.m_jointToParentLinks.find(jointPtr);
  387. auto parentEntityIter =
  388. parentLinkIter != jointsMapper.m_jointToParentLinks.end() ? createdLinks.find(parentLinkIter->second) : createdLinks.end();
  389. if (parentEntityIter == createdLinks.end())
  390. {
  391. AZ_Warning(
  392. "CreatePrefabFromUrdfOrSdf",
  393. false,
  394. "Joint %s has no parent link %s. Cannot create",
  395. azJointName.c_str(),
  396. parentLinkName.c_str());
  397. continue;
  398. }
  399. auto leadEntity = parentEntityIter->second;
  400. // Use the joint to lookup the child SDF link which is used to look up the O3DE entity
  401. auto childLinkIter = jointsMapper.m_jointToChildLinks.find(jointPtr);
  402. auto childEntityIter =
  403. childLinkIter != jointsMapper.m_jointToChildLinks.end() ? createdLinks.find(childLinkIter->second) : createdLinks.end();
  404. if (childEntityIter == createdLinks.end())
  405. {
  406. AZ_Warning(
  407. "CreatePrefabFromUrdfOrSdf",
  408. false,
  409. "Joint %s has no child link %s. Cannot create",
  410. azJointName.c_str(),
  411. childLinkName.c_str());
  412. continue;
  413. }
  414. auto childEntity = childEntityIter->second;
  415. AZ_Trace(
  416. "CreatePrefabFromUrdfOrSdf",
  417. "Creating joint %s : %s -> %s\n",
  418. azJointName.c_str(),
  419. parentLinkName.c_str(),
  420. childLinkName.c_str());
  421. AZ::Entity* childEntityPtr = AzToolsFramework::GetEntityById(childEntity.GetValue());
  422. if (childEntityPtr)
  423. {
  424. auto* component = childEntityPtr->FindComponent<ROS2::ROS2FrameEditorComponent>();
  425. if (component)
  426. {
  427. component->SetJointName(azJointName);
  428. }
  429. }
  430. // check if both has RigidBody and we are not creating articulation
  431. if (!m_useArticulations)
  432. {
  433. if (leadEntity.IsSuccess() && childEntity.IsSuccess())
  434. {
  435. AZStd::lock_guard<AZStd::mutex> lck(m_statusLock);
  436. auto result = m_jointsMaker.AddJointComponent(jointPtr, childEntity.GetValue(), leadEntity.GetValue());
  437. if (result.IsSuccess())
  438. {
  439. m_status.emplace(
  440. StatusMessageType::Joint, AZStd::string::format("%s created as: %llu", azJointName.c_str(), result.GetValue()));
  441. }
  442. else
  443. {
  444. m_status.emplace(
  445. StatusMessageType::Joint,
  446. AZStd::string::format("%s failed : %s", azJointName.c_str(), result.GetError().c_str()));
  447. }
  448. }
  449. else
  450. {
  451. AZ_Warning("CreatePrefabFromUrdfOrSdf", false, "cannot create joint %s", azJointName.c_str());
  452. }
  453. }
  454. }
  455. // Add control components to links that are not parented to any other link (first link of each model) based on SDFormat data.
  456. if (!linkEntityIdsWithoutParent.empty())
  457. {
  458. for (const auto& [contentEntityId, modelPtr] : linkEntityIdsWithoutParent)
  459. {
  460. if (contentEntityId.IsValid())
  461. {
  462. m_controlMaker.AddControlPlugins(*modelPtr, contentEntityId, createdLinks);
  463. }
  464. }
  465. }
  466. // Get the remaining log information (sensors, plugins)
  467. {
  468. AZStd::lock_guard<AZStd::mutex> lck(m_statusLock);
  469. const auto& modelPluginStatus = m_controlMaker.GetStatusMessages();
  470. for (const auto& mps : modelPluginStatus)
  471. {
  472. m_status.emplace(StatusMessageType::ModelPlugin, mps);
  473. }
  474. const auto& sensorStatus = m_sensorsMaker.GetStatusMessages();
  475. for (const auto& ss : sensorStatus)
  476. {
  477. m_status.emplace(StatusMessageType::Sensor, ss);
  478. }
  479. }
  480. // Create prefab, save it to disk immediately
  481. // Remove prefab, if it was already created.
  482. // clear out any previously created prefab template for this path
  483. auto* prefabSystemComponentInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabSystemComponentInterface>::Get();
  484. auto prefabLoaderInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabLoaderInterface>::Get();
  485. auto relativePath = prefabLoaderInterface->GenerateRelativePath(m_prefabPath.c_str());
  486. AzToolsFramework::Prefab::TemplateId prefabTemplateId =
  487. prefabSystemComponentInterface->GetTemplateIdFromFilePath({ relativePath.c_str() });
  488. if (prefabTemplateId != AzToolsFramework::Prefab::InvalidTemplateId)
  489. {
  490. prefabSystemComponentInterface->RemoveTemplate(prefabTemplateId);
  491. prefabTemplateId = AzToolsFramework::Prefab::InvalidTemplateId;
  492. }
  493. prefabTemplateId = AzToolsFramework::Prefab::InvalidTemplateId;
  494. // create prefab from the "set" of entities (currently just the single default entity)
  495. AzToolsFramework::Prefab::PrefabSystemScriptingBus::BroadcastResult(
  496. prefabTemplateId,
  497. &AzToolsFramework::Prefab::PrefabSystemScriptingBus::Events::CreatePrefabTemplate,
  498. createdEntities,
  499. relativePath.String());
  500. if (prefabTemplateId == AzToolsFramework::Prefab::InvalidTemplateId)
  501. {
  502. AZ_Error("CreatePrefabFromUrdfOrSdf", false, "Could not create a prefab template for entities.");
  503. return AZ::Failure("Could not create a prefab template for entities.");
  504. }
  505. AZ_Info("CreatePrefabFromUrdfOrSdf", "Successfully created prefab %s\n", m_prefabPath.c_str());
  506. return AZ::Success(prefabTemplateId);
  507. }
  508. URDFPrefabMaker::CreatePrefabTemplateResult URDFPrefabMaker::CreatePrefabFromUrdfOrSdf()
  509. {
  510. // Begin an undo batch for prefab creation process
  511. AzToolsFramework::UndoSystem::URSequencePoint* currentUndoBatch = nullptr;
  512. AzToolsFramework::ToolsApplicationRequests::Bus::BroadcastResult(
  513. currentUndoBatch, &AzToolsFramework::ToolsApplicationRequests::BeginUndoBatch, "Robot Importer prefab creation");
  514. if (currentUndoBatch == nullptr)
  515. {
  516. AZ_Warning("URDF Prefab Maker", false, "Unable to start undobatch, EBus might not be listening");
  517. }
  518. auto result = CreatePrefabTemplateFromUrdfOrSdf();
  519. if (!result.IsSuccess())
  520. {
  521. // End undo batch labeled "Robot Importer prefab creation" preemptively if an error occurs
  522. if (currentUndoBatch != nullptr)
  523. {
  524. AzToolsFramework::ToolsApplicationRequests::Bus::Broadcast(
  525. &AzToolsFramework::ToolsApplicationRequests::Bus::Events::EndUndoBatch);
  526. }
  527. return result;
  528. }
  529. auto prefabLoaderInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabLoaderInterface>::Get();
  530. // Save Template to file
  531. auto relativePath = prefabLoaderInterface->GenerateRelativePath(m_prefabPath.c_str());
  532. bool saveResult = prefabLoaderInterface->SaveTemplateToFile(result.GetValue(), m_prefabPath.c_str());
  533. if (saveResult)
  534. {
  535. // If the template saved successfully, also instantiate it into the level.
  536. auto prefabInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabPublicInterface>::Get();
  537. [[maybe_unused]] auto createPrefabOutcome =
  538. prefabInterface->InstantiatePrefab(relativePath.String(), AZ::EntityId(), AZ::Vector3::CreateZero());
  539. if (createPrefabOutcome.IsSuccess())
  540. {
  541. MoveEntityToDefaultSpawnPoint(createPrefabOutcome.GetValue(), m_spawnPosition);
  542. }
  543. }
  544. else
  545. {
  546. result = AZ::Failure(AZStd::string::format("Could not save the newly created prefab to '%s'", m_prefabPath.c_str()));
  547. }
  548. // End undo batch labeled "Robot Importer prefab creation"
  549. if (currentUndoBatch != nullptr)
  550. {
  551. AzToolsFramework::ToolsApplicationRequests::Bus::Broadcast(
  552. &AzToolsFramework::ToolsApplicationRequests::Bus::Events::EndUndoBatch);
  553. }
  554. return result;
  555. }
  556. AzToolsFramework::Prefab::PrefabEntityResult URDFPrefabMaker::CreateEntityForModel(const sdf::Model& model)
  557. {
  558. auto createEntityResult = PrefabMakerUtils::CreateEntity(AZ::EntityId{}, model.Name().c_str());
  559. if (!createEntityResult.IsSuccess())
  560. {
  561. return createEntityResult;
  562. }
  563. // Get the model entity and update its transform component with the pose information
  564. AZ::EntityId entityId = createEntityResult.GetValue();
  565. AZStd::unique_ptr<AZ::Entity> entity(AzToolsFramework::GetEntityById(entityId));
  566. if (auto* transformComponent = entity->FindComponent<AzToolsFramework::Components::TransformComponent>();
  567. transformComponent != nullptr)
  568. {
  569. gz::math::Pose3d modelPose = model.RawPose();
  570. AZ::Transform modelTransform = URDF::TypeConversions::ConvertPose(modelPose);
  571. // Set the local transform for each model to have it be translated in relation
  572. // to its parent
  573. transformComponent->SetLocalTM(AZStd::move(modelTransform));
  574. }
  575. // Allow the created model entity to persist if there are no errors at ths point
  576. entity.release();
  577. return entityId;
  578. }
  579. AzToolsFramework::Prefab::PrefabEntityResult URDFPrefabMaker::AddEntitiesForLink(
  580. const sdf::Link& link, const sdf::Model* attachedModel, AZ::EntityId parentEntityId, AZStd::vector<AZ::EntityId>& createdEntities)
  581. {
  582. auto createEntityResult = PrefabMakerUtils::CreateEntity(parentEntityId, link.Name().c_str());
  583. if (!createEntityResult.IsSuccess())
  584. {
  585. return createEntityResult;
  586. }
  587. AZ::EntityId entityId = createEntityResult.GetValue();
  588. AZ::Entity* entity = AzToolsFramework::GetEntityById(entityId);
  589. createdEntities.emplace_back(entityId);
  590. [[maybe_unused]] const auto frameComponentId =
  591. entity->CreateComponent<ROS2::ROS2FrameEditorComponent>(AZStd::string(link.Name().c_str()));
  592. AZ_Assert(frameComponentId, "ROS2 Frame Component does not exist for %s", entityId.ToString().c_str());
  593. auto createdVisualEntities = m_visualsMaker.AddVisuals(&link, entityId);
  594. createdEntities.insert(createdEntities.end(), createdVisualEntities.begin(), createdVisualEntities.end());
  595. if (!m_useArticulations)
  596. {
  597. m_inertialsMaker.AddInertial(link.Inertial(), entityId);
  598. }
  599. else
  600. {
  601. if (attachedModel != nullptr)
  602. {
  603. const auto linkResult = m_articulationsMaker.AddArticulationLink(*attachedModel, &link, entityId);
  604. std::string linkName = link.Name();
  605. AZStd::string azLinkName(linkName.c_str(), linkName.size());
  606. if (linkResult.IsSuccess())
  607. {
  608. AZStd::lock_guard<AZStd::mutex> lck(m_statusLock);
  609. m_status.emplace(
  610. StatusMessageType::Joint,
  611. AZStd::string::format("%s created as articulation link: %llu", azLinkName.c_str(), linkResult.GetValue()));
  612. m_articulationsCounter++;
  613. }
  614. else
  615. {
  616. AZStd::lock_guard<AZStd::mutex> lck(m_statusLock);
  617. m_status.emplace(
  618. StatusMessageType::Joint,
  619. AZStd::string::format("%s as articulation link failed: %s", azLinkName.c_str(), linkResult.GetError().c_str()));
  620. }
  621. }
  622. }
  623. if (attachedModel != nullptr)
  624. {
  625. m_collidersMaker.AddColliders(*attachedModel, &link, entityId);
  626. auto createdSensorEntities = m_sensorsMaker.AddSensors(*attachedModel, &link, entityId);
  627. createdEntities.insert(createdEntities.end(), createdSensorEntities.begin(), createdSensorEntities.end());
  628. }
  629. return AZ::Success(entityId);
  630. }
  631. const AZStd::string& URDFPrefabMaker::GetPrefabPath() const
  632. {
  633. return m_prefabPath;
  634. }
  635. void URDFPrefabMaker::MoveEntityToDefaultSpawnPoint(
  636. const AZ::EntityId& rootEntityId, AZStd::optional<AZ::Transform> spawnPosition = AZStd::nullopt)
  637. {
  638. if (!spawnPosition.has_value())
  639. {
  640. AZ_Trace("URDF Importer", "SpawnPosition is null - spawning in Editors default position\n");
  641. return;
  642. }
  643. if (auto entity_ = AzToolsFramework::GetEntityById(rootEntityId); entity_ != nullptr)
  644. {
  645. auto* transformInterface_ = entity_->FindComponent<AzToolsFramework::Components::TransformComponent>();
  646. if (transformInterface_ == nullptr)
  647. {
  648. AZ_Trace("URDF Importer", "TransformComponent not found in created entity\n") return;
  649. }
  650. transformInterface_->SetWorldTM(*spawnPosition);
  651. AZ_Trace("URDF Importer", "Successfully set spawn position\n")
  652. }
  653. }
  654. AZStd::string URDFPrefabMaker::GetStatus()
  655. {
  656. AZStd::string report;
  657. AZStd::lock_guard<AZStd::mutex> lck(m_statusLock);
  658. // Print warnings first
  659. constexpr unsigned int articulationsLimit = 64;
  660. if (m_articulationsCounter >= articulationsLimit)
  661. {
  662. report += "\n## 💡 Note: the number of articulations (" + AZStd::to_string(m_articulationsCounter) +
  663. ") might not be supported by the physics engine.\n";
  664. }
  665. report += "# The following components were found and parsed:\n";
  666. const AZStd::unordered_map<StatusMessageType, AZStd::string> names = { { StatusMessageType::Model, "Models" },
  667. { StatusMessageType::Link, "Links" },
  668. { StatusMessageType::Joint, "Joints" },
  669. { StatusMessageType::Sensor, "Sensors" },
  670. { StatusMessageType::SensorPlugin, "Sensor plugins" },
  671. { StatusMessageType::ModelPlugin, "Model plugins" } };
  672. auto it = m_status.begin();
  673. auto end = m_status.end();
  674. while (it != end)
  675. {
  676. const auto key = it->first;
  677. report += "\n## " + names.at(key) + ":\n";
  678. do
  679. {
  680. report += "- " + it->second + "\n";
  681. if (++it == end)
  682. {
  683. break;
  684. }
  685. } while (it->first == key);
  686. }
  687. return report;
  688. }
  689. bool URDFPrefabMaker::ContainsModel() const
  690. {
  691. const sdf::Model* sdfModel{};
  692. auto GetModelAndStopIteration = [&sdfModel](const sdf::Model& model, const Utils::ModelStack&) -> Utils::VisitModelResponse
  693. {
  694. sdfModel = &model;
  695. // Return stop to prevent further visitation of additional models
  696. return Utils::VisitModelResponse::Stop;
  697. };
  698. Utils::VisitModels(*m_root, GetModelAndStopIteration);
  699. return sdfModel != nullptr;
  700. }
  701. } // namespace ROS2RobotImporter