RobotImporterUtils.cpp 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050
  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 "RobotImporterUtils.h"
  9. #include "TypeConversions.h"
  10. #include <AzCore/Asset/AssetManager.h>
  11. #include <AzCore/Asset/AssetManagerBus.h>
  12. #include <AzCore/IO/Path/Path.h>
  13. #include <AzCore/StringFunc/StringFunc.h>
  14. #include <AzCore/std/string/regex.h>
  15. #include <AzCore/Utils/Utils.h>
  16. #include <AzToolsFramework/API/EditorAssetSystemAPI.h>
  17. #include <RobotImporter/Utils/ErrorUtils.h>
  18. #include <string.h>
  19. namespace ROS2::Utils
  20. {
  21. inline namespace Internal
  22. {
  23. bool FileExistsCall(const AZ::IO::PathView& filePath)
  24. {
  25. AZ::IO::FixedMaxPath pathStorage(filePath);
  26. return AZ::IO::SystemFile::Exists(pathStorage.c_str());
  27. };
  28. } // namespace Internal
  29. bool IsWheelURDFHeuristics(const sdf::Model& model, const sdf::Link* link)
  30. {
  31. auto wheelMatcher = [](AZStd::string_view name)
  32. {
  33. // StringFunc matches are case-insensitive by default
  34. return AZ::StringFunc::StartsWith(name, "wheel_") || AZ::StringFunc::EndsWith(name, "_wheel");
  35. };
  36. const AZStd::string linkName(link->Name().c_str(), link->Name().size());
  37. // Check if link name is catchy for wheel
  38. if (!wheelMatcher(linkName))
  39. {
  40. return false;
  41. }
  42. // Wheels need to have collision and visuals
  43. if ((link->CollisionCount() == 0) || (link->VisualCount() == 0))
  44. {
  45. return false;
  46. }
  47. // When this link is a child, the parent link joint needs to be CONTINUOUS
  48. AZStd::vector<const sdf::Joint*> joints = GetJointsForChildLink(model, linkName, true);
  49. // URDFs only have a single parent
  50. // This is explained in the Pose frame semantics tutorial for sdformat
  51. // http://sdformat.org/tutorials?tut=pose_frame_semantics&ver=1.5#parent-frames-in-urdf
  52. // The SDF URDF parser converts continuous joints to revolute joints with a limit
  53. // of -infinity to +infinity
  54. // https://github.com/gazebosim/sdformat/blob/sdf13/src/parser_urdf.cc#L3009-L3039
  55. bool isWheel{};
  56. if (!joints.empty())
  57. {
  58. const sdf::Joint* potentialWheelJoint = joints.front();
  59. if (const sdf::JointAxis* jointAxis = potentialWheelJoint->Axis(); jointAxis != nullptr)
  60. {
  61. using LimitType = decltype(jointAxis->Lower());
  62. // There should only be 1 element for URDF, however that will not be verified
  63. // in case this function is called on link from an SDF file
  64. isWheel = potentialWheelJoint->Type() == sdf::JointType::CONTINUOUS;
  65. isWheel = isWheel ||
  66. (potentialWheelJoint->Type() == sdf::JointType::REVOLUTE &&
  67. jointAxis->Lower() == -AZStd::numeric_limits<LimitType>::infinity() &&
  68. jointAxis->Upper() == AZStd::numeric_limits<LimitType>::infinity());
  69. }
  70. }
  71. return isWheel;
  72. }
  73. AZ::Transform GetLocalTransformURDF(const sdf::Link* link, AZ::Transform t)
  74. {
  75. // Determine if the pose is relative to another link
  76. // See doxygen at
  77. // http://osrf-distributions.s3.amazonaws.com/sdformat/api/13.2.0/classsdf_1_1SDF__VERSION__NAMESPACE_1_1Link.html#a011d84b31f584938d89ac6b8c8a09eb3
  78. sdf::SemanticPose linkSemanticPos = link->SemanticPose();
  79. gz::math::Pose3d resolvedPose;
  80. if (sdf::Errors poseResolveErrors = linkSemanticPos.Resolve(resolvedPose); !poseResolveErrors.empty())
  81. {
  82. AZStd::string poseErrorMessages = Utils::JoinSdfErrorsToString(poseResolveErrors);
  83. AZ_Error(
  84. "RobotImporter",
  85. false,
  86. R"(Failed to get world transform for link %s. Errors: "%s")",
  87. link->Name().c_str(),
  88. poseErrorMessages.c_str());
  89. return {};
  90. }
  91. const AZ::Transform linkTransform = URDF::TypeConversions::ConvertPose(resolvedPose);
  92. const AZ::Transform resolvedTransform = linkTransform * t;
  93. return resolvedTransform;
  94. }
  95. void VisitLinks(const sdf::Model& sdfModel, const LinkVisitorCallback& linkVisitorCB, bool visitNestedModelLinks)
  96. {
  97. // Function object which can visit all links of a model
  98. // Optionally it supports recursing nested models to visit their links as well
  99. struct VisitLinksForNestedModels_fn
  100. {
  101. VisitModelResponse operator()(const sdf::Model& model)
  102. {
  103. m_modelStack.push_back(model);
  104. VisitModelResponse visitResponse = VisitLinksForModel(model);
  105. if (m_recurseModels && visitResponse == VisitModelResponse::VisitNestedAndSiblings)
  106. {
  107. // Nested model link are only visited if the joint visitor returns true
  108. for (uint64_t modelIndex{}; modelIndex < model.ModelCount(); ++modelIndex)
  109. {
  110. if (const sdf::Model* nestedModel = model.ModelByIndex(modelIndex); nestedModel != nullptr)
  111. {
  112. if (VisitModelResponse nestedVisitResponse = operator()(*nestedModel);
  113. nestedVisitResponse >= VisitModelResponse::Stop)
  114. {
  115. // Sibling nested model links are only visited
  116. // if the joint visitor returns true
  117. break;
  118. }
  119. }
  120. }
  121. }
  122. m_modelStack.pop_back();
  123. return visitResponse;
  124. }
  125. private:
  126. //! Returns success by default
  127. //! But an invoked visitor can return false to halt further iteration
  128. VisitModelResponse VisitLinksForModel(const sdf::Model& currentModel)
  129. {
  130. for (uint64_t linkIndex{}; linkIndex < currentModel.LinkCount(); ++linkIndex)
  131. {
  132. if (const sdf::Link* link = currentModel.LinkByIndex(linkIndex); link != nullptr)
  133. {
  134. if (!m_linkVisitorCB(*link, m_modelStack))
  135. {
  136. return VisitModelResponse::Stop;
  137. }
  138. }
  139. }
  140. return VisitModelResponse::VisitNestedAndSiblings;
  141. }
  142. public:
  143. LinkVisitorCallback m_linkVisitorCB;
  144. bool m_recurseModels{};
  145. private:
  146. // Stack storing the current composition of models visited so far
  147. ModelStack m_modelStack;
  148. };
  149. VisitLinksForNestedModels_fn VisitLinksForNestedModels{};
  150. VisitLinksForNestedModels.m_linkVisitorCB = linkVisitorCB;
  151. VisitLinksForNestedModels.m_recurseModels = visitNestedModelLinks;
  152. VisitLinksForNestedModels(sdfModel);
  153. }
  154. void VisitJoints(const sdf::Model& sdfModel, const JointVisitorCallback& jointVisitorCB, bool visitNestedModelJoints)
  155. {
  156. // Function object which can visit all joints of a model
  157. // Optionally it supports recursing nested models to visit their joints as well
  158. struct VisitJointsForNestedModels_fn
  159. {
  160. VisitModelResponse operator()(const sdf::Model& model)
  161. {
  162. m_modelStack.push_back(model);
  163. VisitModelResponse visitResponse = VisitJointsForModel(model);
  164. if (m_recurseModels && visitResponse == VisitModelResponse::VisitNestedAndSiblings)
  165. {
  166. // Nested model joints are only visited if the joint visitor returns true
  167. for (uint64_t modelIndex{}; modelIndex < model.ModelCount(); ++modelIndex)
  168. {
  169. if (const sdf::Model* nestedModel = model.ModelByIndex(modelIndex); nestedModel != nullptr)
  170. {
  171. if (VisitModelResponse nestedVisitResponse = operator()(*nestedModel);
  172. nestedVisitResponse >= VisitModelResponse::Stop)
  173. {
  174. // Sibling nested model joints are only visited
  175. // if the joint visitor returns true
  176. break;
  177. }
  178. }
  179. }
  180. }
  181. m_modelStack.pop_back();
  182. return visitResponse;
  183. }
  184. private:
  185. VisitModelResponse VisitJointsForModel(const sdf::Model& currentModel)
  186. {
  187. for (uint64_t jointIndex{}; jointIndex < currentModel.JointCount(); ++jointIndex)
  188. {
  189. const sdf::Joint* joint = currentModel.JointByIndex(jointIndex);
  190. // Skip any joints whose parent and child link references
  191. // don't have an actual sdf::link in the parsed model
  192. if (joint != nullptr && currentModel.LinkNameExists(joint->ParentName()) && currentModel.LinkByName(joint->ChildName()))
  193. {
  194. if (!m_jointVisitorCB(*joint, m_modelStack))
  195. {
  196. return VisitModelResponse::Stop;
  197. }
  198. }
  199. }
  200. return VisitModelResponse::VisitNestedAndSiblings;
  201. }
  202. public:
  203. JointVisitorCallback m_jointVisitorCB;
  204. bool m_recurseModels{};
  205. private:
  206. // Stack storing the current composition of models visited so far
  207. ModelStack m_modelStack;
  208. };
  209. VisitJointsForNestedModels_fn VisitJointsForNestedModels{};
  210. VisitJointsForNestedModels.m_jointVisitorCB = jointVisitorCB;
  211. VisitJointsForNestedModels.m_recurseModels = visitNestedModelJoints;
  212. VisitJointsForNestedModels(sdfModel);
  213. }
  214. AZStd::unordered_map<AZStd::string, const sdf::Link*> GetAllLinks(const sdf::Model& sdfModel, bool gatherNestedModelLinks)
  215. {
  216. using LinkMap = AZStd::unordered_map<AZStd::string, const sdf::Link*>;
  217. LinkMap links;
  218. auto GatherLinks = [&links](const sdf::Link& link, const ModelStack& modelStack)
  219. {
  220. std::string fullyQualifiedLinkName;
  221. // Prepend the Model names to the link name using the Name Scoping support in libsdformat
  222. // http://sdformat.org/tutorials?tut=composition_proposal#1-3-name-scoping-and-cross-referencing
  223. for (const sdf::Model& model : modelStack)
  224. {
  225. fullyQualifiedLinkName = sdf::JoinName(fullyQualifiedLinkName, model.Name());
  226. }
  227. fullyQualifiedLinkName = sdf::JoinName(fullyQualifiedLinkName, link.Name());
  228. AZStd::string azLinkName(fullyQualifiedLinkName.c_str(), fullyQualifiedLinkName.size());
  229. links.insert_or_assign(AZStd::move(azLinkName), &link);
  230. return true;
  231. };
  232. VisitLinks(sdfModel, GatherLinks, gatherNestedModelLinks);
  233. return links;
  234. }
  235. AZStd::unordered_map<AZStd::string, const sdf::Joint*> GetAllJoints(const sdf::Model& sdfModel, bool gatherNestedModelJoints)
  236. {
  237. using JointMap = AZStd::unordered_map<AZStd::string, const sdf::Joint*>;
  238. JointMap joints;
  239. auto GatherJoints = [&joints](const sdf::Joint& joint, const ModelStack& modelStack)
  240. {
  241. std::string fullyQualifiedJointName;
  242. // Prepend the Model names to the joint name using the Name Scoping support in libsdformat
  243. // http://sdformat.org/tutorials?tut=composition_proposal#1-3-name-scoping-and-cross-referencing
  244. for (const sdf::Model& model : modelStack)
  245. {
  246. fullyQualifiedJointName = sdf::JoinName(fullyQualifiedJointName, model.Name());
  247. }
  248. fullyQualifiedJointName = sdf::JoinName(fullyQualifiedJointName, joint.Name());
  249. AZStd::string azJointName(fullyQualifiedJointName.c_str(), fullyQualifiedJointName.size());
  250. joints.insert_or_assign(AZStd::move(azJointName), &joint);
  251. return true;
  252. };
  253. VisitJoints(sdfModel, GatherJoints, gatherNestedModelJoints);
  254. return joints;
  255. }
  256. AZStd::vector<const sdf::Joint*> GetJointsForChildLink(
  257. const sdf::Model& sdfModel, AZStd::string_view linkName, bool gatherNestedModelJoints)
  258. {
  259. using JointVector = AZStd::vector<const sdf::Joint*>;
  260. JointVector joints;
  261. auto GatherJointsWhereLinkIsChild = [&joints, linkName](const sdf::Joint& joint, const ModelStack& modelStack)
  262. {
  263. if (AZStd::string_view jointChildName{ joint.ChildName().c_str(), joint.ChildName().size() }; jointChildName == linkName)
  264. {
  265. joints.emplace_back(&joint);
  266. }
  267. return true;
  268. };
  269. VisitJoints(sdfModel, GatherJointsWhereLinkIsChild, gatherNestedModelJoints);
  270. return joints;
  271. }
  272. AZStd::vector<const sdf::Joint*> GetJointsForParentLink(
  273. const sdf::Model& sdfModel, AZStd::string_view linkName, bool gatherNestedModelJoints)
  274. {
  275. using JointVector = AZStd::vector<const sdf::Joint*>;
  276. JointVector joints;
  277. auto GatherJointsWhereLinkIsParent = [&joints, linkName](const sdf::Joint& joint, const ModelStack& modelStack)
  278. {
  279. if (AZStd::string_view jointParentName{ joint.ParentName().c_str(), joint.ParentName().size() }; jointParentName == linkName)
  280. {
  281. joints.emplace_back(&joint);
  282. }
  283. return true;
  284. };
  285. VisitJoints(sdfModel, GatherJointsWhereLinkIsParent, gatherNestedModelJoints);
  286. return joints;
  287. }
  288. //! Provides overloads for comparison operators for the VisitModelResponse enum
  289. AZ_DEFINE_ENUM_RELATIONAL_OPERATORS(VisitModelResponse);
  290. // Function object which can visit all models in an SDF document
  291. // Optionally it supports recursing nested models as well
  292. struct VisitModelsForNestedModels_fn
  293. {
  294. VisitModelResponse operator()(const sdf::Model& model)
  295. {
  296. // The VisitModelResponse enum value is used to filter out
  297. // less callbacks the higher the value grows.
  298. // So any values above VisitNestedAndSiblings will not visit nested models
  299. VisitModelResponse visitResponse = m_modelVisitorCB(model, m_modelStack);
  300. if (m_recurseModels && visitResponse == VisitModelResponse::VisitNestedAndSiblings)
  301. {
  302. // Nested models are only visited if the model visitor returns VisitNestedAndSiblings
  303. m_modelStack.push_back(model);
  304. for (uint64_t modelIndex{}; modelIndex < model.ModelCount(); ++modelIndex)
  305. {
  306. if (const sdf::Model* nestedModel = model.ModelByIndex(modelIndex); nestedModel != nullptr)
  307. {
  308. if (VisitModelResponse nestedVisitResponse = operator()(*nestedModel);
  309. nestedVisitResponse >= VisitModelResponse::Stop)
  310. {
  311. // Visiting of the nested model has returned Stop, so halt any sibling model visitation
  312. break;
  313. }
  314. }
  315. }
  316. m_modelStack.pop_back();
  317. }
  318. return visitResponse;
  319. }
  320. VisitModelResponse operator()(const sdf::World& world)
  321. {
  322. // Nested model are only visited if the model visitor returns true
  323. for (uint64_t modelIndex{}; modelIndex < world.ModelCount(); ++modelIndex)
  324. {
  325. if (const sdf::Model* model = world.ModelByIndex(modelIndex); model != nullptr)
  326. {
  327. // Delegate to the sdf::Model call operator overload to visit nested models
  328. // Stop visiting the world's <model> children if any children return Stop
  329. if (VisitModelResponse visitResponse = operator()(*model); visitResponse >= VisitModelResponse::Stop)
  330. {
  331. return visitResponse;
  332. }
  333. }
  334. }
  335. // By default visit any sibling worlds' models if visitation doesn't return Stop
  336. return VisitModelResponse::VisitNestedAndSiblings;
  337. }
  338. void operator()(const sdf::Root& root)
  339. {
  340. // Visit all <model> tags at the root of the SDF
  341. VisitModelResponse modelVisitResponse = VisitModelResponse::VisitNestedAndSiblings;
  342. if (const sdf::Model* model = root.Model(); model != nullptr)
  343. {
  344. modelVisitResponse = operator()(*model);
  345. }
  346. // If the root <model> indicated that visitation should stop, then return
  347. if (modelVisitResponse >= VisitModelResponse::Stop)
  348. {
  349. return;
  350. }
  351. // Next visit any <world> tags in the SDF
  352. for (uint64_t worldIndex{}; worldIndex < root.WorldCount(); ++worldIndex)
  353. {
  354. if (const sdf::World* world = root.WorldByIndex(worldIndex); world != nullptr)
  355. {
  356. // Delegate to the sdf::World call operator overload to visit any <model> tags in the World
  357. if (VisitModelResponse worldVisitResponse = operator()(*world); worldVisitResponse >= VisitModelResponse::Stop)
  358. {
  359. break;
  360. }
  361. }
  362. }
  363. }
  364. public:
  365. ModelVisitorCallback m_modelVisitorCB;
  366. bool m_recurseModels{};
  367. private:
  368. // Stack storing the current composition of models visited so far
  369. ModelStack m_modelStack;
  370. };
  371. void VisitModels(const sdf::Root& sdfRoot, const ModelVisitorCallback& modelVisitorCB, bool visitNestedModels)
  372. {
  373. VisitModelsForNestedModels_fn VisitModelsForNestedModels{};
  374. VisitModelsForNestedModels.m_modelVisitorCB = modelVisitorCB;
  375. VisitModelsForNestedModels.m_recurseModels = visitNestedModels;
  376. VisitModelsForNestedModels(sdfRoot);
  377. }
  378. void VisitModels(const sdf::World& sdfWorld, const ModelVisitorCallback& modelVisitorCB, bool visitNestedModels)
  379. {
  380. VisitModelsForNestedModels_fn VisitModelsForNestedModels{};
  381. VisitModelsForNestedModels.m_modelVisitorCB = modelVisitorCB;
  382. VisitModelsForNestedModels.m_recurseModels = visitNestedModels;
  383. VisitModelsForNestedModels(sdfWorld);
  384. }
  385. void VisitModels(const sdf::Model& sdfModel, const ModelVisitorCallback& modelVisitorCB, bool visitNestedModels)
  386. {
  387. VisitModelsForNestedModels_fn VisitModelsForNestedModels{};
  388. VisitModelsForNestedModels.m_modelVisitorCB = modelVisitorCB;
  389. VisitModelsForNestedModels.m_recurseModels = visitNestedModels;
  390. VisitModelsForNestedModels(sdfModel);
  391. }
  392. ModelMap GetAllModels(const sdf::Root& sdfRoot, bool gatherNestedModelsForModel)
  393. {
  394. ModelMap modelMap;
  395. auto GatherModels = [&modelMap](const sdf::Model& nestedModel, const ModelStack& modelStack) -> VisitModelResponse
  396. {
  397. std::string fullyQualifiedModelName;
  398. // Prepend the Model names to the joint name using the Name Scoping support in libsdformat
  399. // http://sdformat.org/tutorials?tut=composition_proposal#1-3-name-scoping-and-cross-referencing
  400. for (const sdf::Model& model : modelStack)
  401. {
  402. fullyQualifiedModelName = sdf::JoinName(fullyQualifiedModelName, model.Name());
  403. }
  404. fullyQualifiedModelName = sdf::JoinName(fullyQualifiedModelName, nestedModel.Name());
  405. AZStd::string azFullModelName(fullyQualifiedModelName.c_str(), fullyQualifiedModelName.size());
  406. modelMap.insert_or_assign(AZStd::move(azFullModelName), &nestedModel);
  407. return VisitModelResponse::VisitNestedAndSiblings;
  408. };
  409. VisitModels(sdfRoot, GatherModels, gatherNestedModelsForModel);
  410. return modelMap;
  411. }
  412. ModelMap GetAllModels(const sdf::World& sdfWorld, bool gatherNestedModelsForModel)
  413. {
  414. ModelMap modelMap;
  415. auto GatherModels = [&modelMap](const sdf::Model& nestedModel, const ModelStack& modelStack) -> VisitModelResponse
  416. {
  417. std::string fullyQualifiedModelName;
  418. // Prepend the Model names to the joint name using the Name Scoping support in libsdformat
  419. // http://sdformat.org/tutorials?tut=composition_proposal#1-3-name-scoping-and-cross-referencing
  420. for (const sdf::Model& model : modelStack)
  421. {
  422. fullyQualifiedModelName = sdf::JoinName(fullyQualifiedModelName, model.Name());
  423. }
  424. fullyQualifiedModelName = sdf::JoinName(fullyQualifiedModelName, nestedModel.Name());
  425. AZStd::string azFullModelName(fullyQualifiedModelName.c_str(), fullyQualifiedModelName.size());
  426. modelMap.insert_or_assign(AZStd::move(azFullModelName), &nestedModel);
  427. return VisitModelResponse::VisitNestedAndSiblings;
  428. };
  429. VisitModels(sdfWorld, GatherModels, gatherNestedModelsForModel);
  430. return modelMap;
  431. }
  432. ModelMap GetAllModels(const sdf::Model& sdfModel, bool gatherNestedModelsForModel)
  433. {
  434. ModelMap modelMap;
  435. auto GatherModels = [&modelMap](const sdf::Model& nestedModel, const ModelStack& modelStack) -> VisitModelResponse
  436. {
  437. std::string fullyQualifiedModelName;
  438. // Prepend the Model names to the joint name using the Name Scoping support in libsdformat
  439. // http://sdformat.org/tutorials?tut=composition_proposal#1-3-name-scoping-and-cross-referencing
  440. for (const sdf::Model& model : modelStack)
  441. {
  442. fullyQualifiedModelName = sdf::JoinName(fullyQualifiedModelName, model.Name());
  443. }
  444. fullyQualifiedModelName = sdf::JoinName(fullyQualifiedModelName, nestedModel.Name());
  445. AZStd::string azFullModelName(fullyQualifiedModelName.c_str(), fullyQualifiedModelName.size());
  446. modelMap.insert_or_assign(AZStd::move(azFullModelName), &nestedModel);
  447. return VisitModelResponse::VisitNestedAndSiblings;
  448. };
  449. VisitModels(sdfModel, GatherModels, gatherNestedModelsForModel);
  450. return modelMap;
  451. }
  452. const sdf::Model* GetModelContainingLink(const sdf::Root& root, AZStd::string_view fullyQualifiedLinkName)
  453. {
  454. const sdf::Model* resultModel{};
  455. auto IsLinkInModel = [&fullyQualifiedLinkName,
  456. &resultModel](const sdf::Model& model, const ModelStack&) -> VisitModelResponse
  457. {
  458. const std::string stdLinkName(fullyQualifiedLinkName.data(), fullyQualifiedLinkName.size());
  459. if (const sdf::Link* searchLink = model.LinkByName(stdLinkName); searchLink != nullptr)
  460. {
  461. resultModel = &model;
  462. return VisitModelResponse::Stop;
  463. }
  464. return VisitModelResponse::VisitNestedAndSiblings;
  465. };
  466. VisitModels(root, IsLinkInModel);
  467. return resultModel;
  468. }
  469. const sdf::Model* GetModelContainingLink(const sdf::Root& root, const sdf::Link& link)
  470. {
  471. const sdf::Model* resultModel{};
  472. auto IsLinkInModel = [&link, &resultModel](const sdf::Model& model, const ModelStack&) -> VisitModelResponse
  473. {
  474. if (const sdf::Link* searchLink = model.LinkByName(link.Name()); searchLink != &link)
  475. {
  476. resultModel = &model;
  477. return VisitModelResponse::Stop;
  478. }
  479. return VisitModelResponse::VisitNestedAndSiblings;
  480. };
  481. VisitModels(root, IsLinkInModel);
  482. return resultModel;
  483. }
  484. const sdf::Model* GetModelContainingJoint(const sdf::Root& root, AZStd::string_view fullyQualifiedJointName)
  485. {
  486. const sdf::Model* resultModel{};
  487. auto IsJointInModel = [&fullyQualifiedJointName, &resultModel](const sdf::Model& model, const ModelStack&) -> VisitModelResponse
  488. {
  489. const std::string stdJointName(fullyQualifiedJointName.data(), fullyQualifiedJointName.size());
  490. if (const sdf::Joint* searchJoint = model.JointByName(stdJointName); searchJoint != nullptr)
  491. {
  492. resultModel = &model;
  493. return VisitModelResponse::Stop;
  494. }
  495. return VisitModelResponse::VisitNestedAndSiblings;
  496. };
  497. VisitModels(root, IsJointInModel);
  498. return resultModel;
  499. }
  500. const sdf::Model* GetModelContainingJoint(const sdf::Root& root, const sdf::Joint& joint)
  501. {
  502. const sdf::Model* resultModel{};
  503. auto IsJointInModel = [&joint, &resultModel](const sdf::Model& model, const ModelStack&) -> VisitModelResponse
  504. {
  505. if (const sdf::Joint* searchJoint = model.JointByName(joint.Name()); searchJoint != &joint)
  506. {
  507. resultModel = &model;
  508. return VisitModelResponse::Stop;
  509. }
  510. return VisitModelResponse::VisitNestedAndSiblings;
  511. };
  512. VisitModels(root, IsJointInModel);
  513. return resultModel;
  514. }
  515. const sdf::Model* GetModelContainingModel(const sdf::Root& root, const sdf::Model& model)
  516. {
  517. const sdf::Model* resultModel{};
  518. auto IsModelInModel = [&model, &resultModel](const sdf::Model& outerModel, const ModelStack&) -> VisitModelResponse
  519. {
  520. // Validate the memory address of the model matches the outer model found searching the visited model "child models"
  521. if (const sdf::Model* searchModel = outerModel.ModelByName(model.Name()); searchModel != &model)
  522. {
  523. resultModel = &outerModel;
  524. return VisitModelResponse::Stop;
  525. }
  526. return VisitModelResponse::VisitNestedAndSiblings;
  527. };
  528. VisitModels(root, IsModelInModel);
  529. return resultModel;
  530. }
  531. AssetFilenameReferences GetReferencedAssetFilenames(const sdf::Root& root)
  532. {
  533. AssetFilenameReferences filenames;
  534. auto GetAssetsFromModel = [&filenames](const sdf::Model& model, const ModelStack&) -> VisitModelResponse
  535. {
  536. const auto addFilenameFromGeometry = [&filenames](const sdf::Geometry* geometry, ReferencedAssetType assetType)
  537. {
  538. if (geometry->Type() == sdf::GeometryType::MESH)
  539. {
  540. if (auto mesh = geometry->MeshShape(); mesh)
  541. {
  542. AZStd::string uri(mesh->Uri().c_str(), mesh->Uri().size());
  543. if (filenames.contains(uri))
  544. {
  545. filenames[uri] = filenames[uri] | assetType;
  546. }
  547. else
  548. {
  549. filenames.emplace(uri, assetType);
  550. }
  551. }
  552. }
  553. };
  554. const auto addFilenamesFromMaterial = [&filenames](const sdf::Material* material)
  555. {
  556. // Only PBR entries on a material have filenames that need to be added.
  557. if ((!material) || (!material->PbrMaterial()))
  558. {
  559. return;
  560. }
  561. if (auto pbr = material->PbrMaterial(); pbr)
  562. {
  563. auto pbrWorkflow = pbr->Workflow(sdf::PbrWorkflowType::METAL);
  564. if (!pbrWorkflow)
  565. {
  566. pbrWorkflow = pbr->Workflow(sdf::PbrWorkflowType::SPECULAR);
  567. if (!pbrWorkflow)
  568. {
  569. return;
  570. }
  571. }
  572. if (auto texture = pbrWorkflow->AlbedoMap(); !texture.empty())
  573. {
  574. filenames.emplace(AZStd::string(texture.c_str(), texture.size()), ReferencedAssetType::Texture);
  575. }
  576. if (auto texture = pbrWorkflow->NormalMap(); !texture.empty())
  577. {
  578. filenames.emplace(AZStd::string(texture.c_str(), texture.size()), ReferencedAssetType::Texture);
  579. }
  580. if (auto texture = pbrWorkflow->AmbientOcclusionMap(); !texture.empty())
  581. {
  582. filenames.emplace(AZStd::string(texture.c_str(), texture.size()), ReferencedAssetType::Texture);
  583. }
  584. if (auto texture = pbrWorkflow->EmissiveMap(); !texture.empty())
  585. {
  586. filenames.emplace(AZStd::string(texture.c_str(), texture.size()), ReferencedAssetType::Texture);
  587. }
  588. if (pbrWorkflow->Type() == sdf::PbrWorkflowType::METAL)
  589. {
  590. if (auto texture = pbrWorkflow->RoughnessMap(); !texture.empty())
  591. {
  592. filenames.emplace(AZStd::string(texture.c_str(), texture.size()), ReferencedAssetType::Texture);
  593. }
  594. if (auto texture = pbrWorkflow->MetalnessMap(); !texture.empty())
  595. {
  596. filenames.emplace(AZStd::string(texture.c_str(), texture.size()), ReferencedAssetType::Texture);
  597. }
  598. }
  599. }
  600. };
  601. const auto processLink = [&addFilenameFromGeometry, &addFilenamesFromMaterial](const sdf::Link* link)
  602. {
  603. for (uint64_t index = 0; index < link->VisualCount(); index++)
  604. {
  605. addFilenameFromGeometry(link->VisualByIndex(index)->Geom(), ReferencedAssetType::VisualMesh);
  606. addFilenamesFromMaterial(link->VisualByIndex(index)->Material());
  607. }
  608. for (uint64_t index = 0; index < link->CollisionCount(); index++)
  609. {
  610. addFilenameFromGeometry(link->CollisionByIndex(index)->Geom(), ReferencedAssetType::ColliderMesh);
  611. }
  612. };
  613. for (uint64_t index = 0; index < model.LinkCount(); index++)
  614. {
  615. processLink(model.LinkByIndex(index));
  616. }
  617. return VisitModelResponse::VisitNestedAndSiblings;
  618. };
  619. VisitModels(root, GetAssetsFromModel);
  620. return filenames;
  621. }
  622. AZ::IO::Path ResolveAmentPrefixPath(
  623. AZ::IO::Path unresolvedPath,
  624. AZStd::string_view amentPrefixPath,
  625. const FileExistsCB& fileExistsCB)
  626. {
  627. AZStd::vector<AZ::IO::Path> amentPrefixPaths;
  628. // Parse the AMENT_PREFIX_PATH environment variable into a set of distinct paths.
  629. auto AmentPrefixPathVisitor = [&amentPrefixPaths](
  630. AZStd::string_view prefixPath)
  631. {
  632. amentPrefixPaths.push_back(prefixPath);
  633. };
  634. // Note this code only works on Unix platforms
  635. // For Windows this will not work as the drive letter has a colon in it (C:\)
  636. AZ::StringFunc::TokenizeVisitor(amentPrefixPath, AmentPrefixPathVisitor, ':');
  637. AZ::IO::PathView strippedPath;
  638. // The AMENT_PREFIX_PATH is only used for lookups if the URI starts with "model://" or "package://"
  639. constexpr AZStd::string_view ValidAmentPrefixes[] = {"model://", "package://"};
  640. for (const auto& prefix : ValidAmentPrefixes)
  641. {
  642. // Perform a case-sensitive check to look for the prefix.
  643. constexpr bool prefixMatchCaseSensitive = true;
  644. if (AZ::StringFunc::StartsWith(unresolvedPath.Native(), prefix, prefixMatchCaseSensitive))
  645. {
  646. strippedPath = AZ::IO::PathView(unresolvedPath).Native().substr(prefix.size());
  647. break;
  648. }
  649. }
  650. // If no valid prefix was found, or if the URI *only* contains the prefix, return an empty result.
  651. if (strippedPath.empty())
  652. {
  653. return {};
  654. }
  655. // If the remaining path is an absolute path, it shouldn't get resolved with AMENT_PREFIX_PATH. return an empty result.
  656. if (strippedPath.IsAbsolute())
  657. {
  658. return {};
  659. }
  660. // Check to see if the relative part of the URI path refers to a location
  661. // within each <ament prefix path>/share directory
  662. for (const AZ::IO::Path& amentPrefixPath : amentPrefixPaths)
  663. {
  664. auto pathIter = strippedPath.begin();
  665. AZ::IO::PathView packageName = *pathIter;
  666. const AZ::IO::Path amentSharePath = amentPrefixPath / "share";
  667. const AZ::IO::Path packageManifestPath = amentSharePath / packageName / "package.xml";
  668. // Given a path like 'ambulance/meshes/model.stl', it will be considered a match if
  669. // <ament prefix path>/share/ambulance/package.xml exists and
  670. // <ament prefix path>/share/ambulance/meshes/model.stl exists.
  671. if (const AZ::IO::Path candidateResolvedPath = amentSharePath / strippedPath;
  672. fileExistsCB(packageManifestPath) && fileExistsCB(candidateResolvedPath))
  673. {
  674. AZ_Trace("ResolveAssetPath", R"(Resolved using AMENT_PREFIX_PATH: "%.*s" -> "%.*s")" "\n",
  675. AZ_PATH_ARG(unresolvedPath), AZ_PATH_ARG(candidateResolvedPath));
  676. return candidateResolvedPath;
  677. }
  678. }
  679. // No resolution was found, return an empty result.
  680. return {};
  681. }
  682. /// Finds global path from URDF/SDF path
  683. AZ::IO::Path ResolveAssetPath(
  684. AZ::IO::Path unresolvedPath,
  685. const AZ::IO::PathView& baseFilePath,
  686. AZStd::string_view amentPrefixPath,
  687. const SdfAssetBuilderSettings& settings,
  688. const FileExistsCB& fileExistsCB)
  689. {
  690. AZ_Printf("ResolveAssetPath", "ResolveAssetPath with %s\n", unresolvedPath.c_str());
  691. const auto& pathResolverSettings = settings.m_resolverSettings;
  692. // If the settings tell us to try the AMENT_PREFIX_PATH, use that first to try and resolve path.
  693. if (pathResolverSettings.m_useAmentPrefixPath)
  694. {
  695. if (AZ::IO::Path amentResolvedPath = ResolveAmentPrefixPath(unresolvedPath, amentPrefixPath, fileExistsCB); !amentResolvedPath.empty())
  696. {
  697. return amentResolvedPath;
  698. }
  699. }
  700. // Append all ancestor directories from the root file to the candidate replacement paths if the settings enable using
  701. // them for path resolution and the root file isn't empty.
  702. AZStd::vector<AZ::IO::Path> ancestorPaths;
  703. if (!baseFilePath.empty() && pathResolverSettings.m_useAncestorPaths)
  704. {
  705. // The first time through this loop, fileAncestorPath contains the full file name ('/a/b/c.sdf') so
  706. // ParentPath() will return the path containing the file ('/a/b'). Each iteration will walk up the path
  707. // to the root, including the root, before stopping ('/a', '/').
  708. AZ::IO::Path fileAncestorPath = baseFilePath;
  709. do
  710. {
  711. fileAncestorPath = fileAncestorPath.ParentPath();
  712. ancestorPaths.emplace_back(fileAncestorPath);
  713. } while (fileAncestorPath != fileAncestorPath.RootPath());
  714. }
  715. // Loop through each prefix in the builder settings and attempt to resolve it as either an absolute path
  716. // or a relative path that's relative in some way to the base file.
  717. for (const auto& [prefix, replacements] : pathResolverSettings.m_uriPrefixMap)
  718. {
  719. // Note this is a case-sensitive check to match the exact URI scheme
  720. // If that is not desired, then this code should be updated to read
  721. // a value from the Setting Registry indicating whether the uriPrefix matching
  722. // should be case sensitive
  723. constexpr bool uriPrefixMatchCaseSensitive = true;
  724. // If the path doesn't start with the given prefix, move on to the next prefix
  725. if (!AZ::StringFunc::StartsWith(unresolvedPath.Native(), prefix, uriPrefixMatchCaseSensitive))
  726. {
  727. continue;
  728. }
  729. // Strip the number of characters from the Uri scheme from beginning of the path
  730. AZ::IO::PathView strippedUriPath = AZ::IO::PathView(unresolvedPath).Native().substr(prefix.size());
  731. // Loop through each replacement path for this prefix, attach it to the front, and look for matches.
  732. for (const auto& replacement : replacements)
  733. {
  734. AZ::IO::Path replacedUriPath(replacement);
  735. replacedUriPath /= strippedUriPath;
  736. // If we successfully matched the prefix, and the replacement path is completely empty, we don't need to look any further.
  737. // There's no match.
  738. if (replacedUriPath.empty())
  739. {
  740. AZ_Trace("ResolveAssetPath", R"(Resolved Path is empty: "%.*s" -> "")" "\n", AZ_PATH_ARG(unresolvedPath));
  741. return {};
  742. }
  743. // If the replaced path is an absolute path, if it exists, return it.
  744. // If it doesn't exist, keep trying other replacements.
  745. if (replacedUriPath.IsAbsolute())
  746. {
  747. if (fileExistsCB(replacedUriPath))
  748. {
  749. AZ_Trace("ResolveAssetPath", R"(Resolved Absolute Path: "%.*s" -> "%.*s")" "\n",
  750. AZ_PATH_ARG(unresolvedPath), AZ_PATH_ARG(replacedUriPath));
  751. return replacedUriPath;
  752. }
  753. else
  754. {
  755. // The file didn't exist, so continue.
  756. continue;
  757. }
  758. }
  759. // The URI path is not absolute, so attempt to append it to the ancestor directories of the URDF/SDF file
  760. for (const AZ::IO::Path& ancestorPath : ancestorPaths)
  761. {
  762. if (const AZ::IO::Path candidateResolvedPath = ancestorPath / replacedUriPath;
  763. fileExistsCB(candidateResolvedPath))
  764. {
  765. AZ_Trace("ResolveAssetPath", R"(Resolved using ancestor paths: "%.*s" -> "%.*s")" "\n",
  766. AZ_PATH_ARG(unresolvedPath), AZ_PATH_ARG(candidateResolvedPath));
  767. return candidateResolvedPath;
  768. }
  769. }
  770. }
  771. }
  772. // At this point, the path has no identified URI prefix. If it's an absolute path, try to locate and return it.
  773. // Otherwise, return an empty path as an error.
  774. if (unresolvedPath.IsAbsolute())
  775. {
  776. if (fileExistsCB(unresolvedPath))
  777. {
  778. AZ_Trace("ResolveAssetPath", R"(Resolved Absolute Path: "%.*s")" "\n",
  779. AZ_PATH_ARG(unresolvedPath));
  780. return unresolvedPath;
  781. }
  782. else
  783. {
  784. AZ_Trace("ResolveAssetPath", R"(Failed to resolve Absolute Path: "%.*s")" "\n",
  785. AZ_PATH_ARG(unresolvedPath));
  786. return {};
  787. }
  788. }
  789. // The path is a relative path, so use the directory containing the base URDF/SDF file as the root path,
  790. // and if the file can be found successfully, return the path. Otherwise, return an empty path as an error.
  791. const AZ::IO::Path relativePath = AZ::IO::Path(baseFilePath.ParentPath()) / unresolvedPath;
  792. if (fileExistsCB(relativePath))
  793. {
  794. AZ_Trace("ResolveAssetPath", R"(Resolved Relative Path: "%.*s" -> "%.*s")" "\n",
  795. AZ_PATH_ARG(unresolvedPath), AZ_PATH_ARG(relativePath));
  796. return relativePath;
  797. }
  798. AZ_Trace("ResolveAssetPath", R"(Failed to resolve Relative Path: "%.*s" -> "%.*s")" "\n",
  799. AZ_PATH_ARG(unresolvedPath), AZ_PATH_ARG(relativePath));
  800. return {};
  801. }
  802. AmentPrefixString GetAmentPrefixPath()
  803. {
  804. // Support reading the AMENT_PREFIX_PATH environment variable on Unix/Windows platforms
  805. auto StoreAmentPrefixPath = [](char* buffer, size_t size) -> size_t
  806. {
  807. auto getEnvOutcome = AZ::Utils::GetEnv(AZStd::span(buffer, size), "AMENT_PREFIX_PATH");
  808. if (!getEnvOutcome.IsSuccess())
  809. {
  810. if (getEnvOutcome.GetError().m_errorCode == AZ::Utils::GetEnvErrorCode::EnvNotSet)
  811. {
  812. AZ_Error("UrdfAssetMap", false, "AMENT_PREFIX_PATH is not set in the environment.");
  813. }
  814. else if (getEnvOutcome.GetError().m_errorCode == AZ::Utils::GetEnvErrorCode::BufferTooSmall)
  815. {
  816. AZ_Error(
  817. "UrdfAssetMap",
  818. false,
  819. "AMENT_PREFIX_PATH is too long (%zu), maximum permissible size is %zu ",
  820. getEnvOutcome.GetError().m_requiredSize,
  821. size);
  822. }
  823. else
  824. {
  825. AZ_Error("UrdfAssetMap", false, "AMENT_PREFIX_PATH is not found.");
  826. }
  827. }
  828. return getEnvOutcome ? getEnvOutcome.GetValue().size() : 0;
  829. };
  830. AmentPrefixString amentPrefixPath;
  831. amentPrefixPath.resize_and_overwrite(amentPrefixPath.capacity(), StoreAmentPrefixPath);
  832. return amentPrefixPath;
  833. }
  834. } // namespace ROS2::Utils
  835. namespace ROS2::Utils::SDFormat
  836. {
  837. AZStd::string GetPluginFilename(const sdf::Plugin& plugin)
  838. {
  839. const AZ::IO::Path path{ plugin.Filename().c_str() };
  840. return path.Filename().String();
  841. }
  842. AZStd::vector<AZStd::string> GetUnsupportedParams(
  843. const sdf::ElementPtr& rootElement, const AZStd::unordered_set<AZStd::string>& supportedParams)
  844. {
  845. AZStd::vector<AZStd::string> unsupportedParams;
  846. AZStd::function<void(const sdf::ElementPtr& elementPtr, const std::string& prefix)> elementVisitor =
  847. [&](const sdf::ElementPtr& elementPtr, const std::string& prefix) -> void
  848. {
  849. auto childPtr = elementPtr->GetFirstElement();
  850. AZStd::string prefixAz(prefix.c_str(), prefix.size());
  851. if (!childPtr && !prefixAz.empty() && !supportedParams.contains(prefixAz))
  852. {
  853. unsupportedParams.push_back(prefixAz);
  854. }
  855. while (childPtr)
  856. {
  857. if (childPtr->GetName() == "plugin")
  858. {
  859. break;
  860. }
  861. std::string currentName = prefix;
  862. currentName.append(">");
  863. currentName.append(childPtr->GetName());
  864. elementVisitor(childPtr, currentName);
  865. childPtr = childPtr->GetNextElement();
  866. }
  867. };
  868. elementVisitor(rootElement, "");
  869. return unsupportedParams;
  870. }
  871. bool IsPluginSupported(const sdf::Plugin& plugin, const AZStd::unordered_set<AZStd::string>& supportedPlugins)
  872. {
  873. return supportedPlugins.contains(GetPluginFilename(plugin));
  874. }
  875. sdf::ParserConfig CreateSdfParserConfigFromSettings(const SdfAssetBuilderSettings& settings, const AZ::IO::PathView& baseFilePath)
  876. {
  877. sdf::ParserConfig sdfConfig;
  878. sdfConfig.URDFSetPreserveFixedJoint(settings.m_urdfPreserveFixedJoints);
  879. // Fill in the URI resolution with the supplied prefix mappings.
  880. for (auto& [prefix, pathList] : settings.m_resolverSettings.m_uriPrefixMap)
  881. {
  882. std::string uriPath;
  883. for(auto& path : pathList)
  884. {
  885. if (!uriPath.empty())
  886. {
  887. uriPath.append(std::string(":"));
  888. }
  889. uriPath.append(std::string(path.c_str(), path.size()));
  890. }
  891. if (!prefix.empty() && !uriPath.empty())
  892. {
  893. std::string uriPrefix(prefix.c_str(), prefix.size());
  894. sdfConfig.AddURIPath(uriPrefix, uriPath);
  895. AZ_Trace("SdfParserConfig", "Added URI mapping '%s' -> '%s'", uriPrefix.c_str(), uriPath.c_str());
  896. }
  897. }
  898. // If any files couldn't be found using our supplied prefix mappings, this callback will get called.
  899. // Attempt to use our full path resolution, and print a warning if it still couldn't be resolved.
  900. sdfConfig.SetFindCallback([settings, baseFilePath](const std::string &fileName) -> std::string
  901. {
  902. auto amentPrefixPath = Utils::GetAmentPrefixPath();
  903. auto resolved = Utils::ResolveAssetPath(AZ::IO::Path(fileName.c_str()), baseFilePath, amentPrefixPath, settings);
  904. if (!resolved.empty())
  905. {
  906. AZ_Trace("SdfParserConfig", "SDF SetFindCallback resolved '%s' -> '%s'", fileName.c_str(), resolved.c_str());
  907. return resolved.c_str();
  908. }
  909. AZ_Warning("SdfParserConfig", false, "SDF SetFindCallback failed to resolve '%s'", fileName.c_str());
  910. return fileName;
  911. });
  912. return sdfConfig;
  913. }
  914. } // namespace ROS2::Utils::SDFormat