SdfParserTest.cpp 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674
  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 <AzCore/UnitTest/TestTypes.h>
  9. #include <AzCore/std/smart_ptr/make_shared.h>
  10. #include <AzTest/AzTest.h>
  11. #include <AzTest/Utils.h>
  12. #include <RobotImporter/SDFormat/ROS2SensorHooks.h>
  13. #include <RobotImporter/URDF/UrdfParser.h>
  14. #include <RobotImporter/Utils/ErrorUtils.h>
  15. #include <RobotImporter/Utils/RobotImporterUtils.h>
  16. namespace UnitTest
  17. {
  18. class SdfParserTest : public LeakDetectionFixture
  19. {
  20. public:
  21. std::string GetSdfWithTwoSensors()
  22. {
  23. return R"(<?xml version="1.0"?>
  24. <sdf version="1.6">
  25. <model name="test_two_sensors">
  26. <link name="link1">
  27. <sensor name="camera" type="camera">
  28. <pose>0 0 0 0 0 0</pose>
  29. <camera>
  30. <horizontal_fov>2.0</horizontal_fov>
  31. <image>
  32. <width>640</width>
  33. <height>480</height>
  34. </image>
  35. <clip>
  36. <near>0.01</near>
  37. <far>1000</far>
  38. </clip>
  39. </camera>
  40. <update_rate>10</update_rate>
  41. <plugin name="camera_plug" filename="libgazebo_ros_camera.so">
  42. <camera_name>custom_camera</camera_name>
  43. </plugin>
  44. </sensor>
  45. </link>
  46. <link name="link2">
  47. <sensor name="laser" type="ray">
  48. <always_on>1</always_on>
  49. <visualize>1</visualize>
  50. <update_rate>20.0</update_rate>
  51. <pose>0 0 0 0 0 0</pose>
  52. <ray>
  53. <scan>
  54. <horizontal>
  55. <samples>640</samples>
  56. <resolution>1.0</resolution>
  57. <min_angle>-2.0</min_angle>
  58. <max_angle>2.5</max_angle>
  59. </horizontal>
  60. </scan>
  61. <range>
  62. <min>0.02</min>
  63. <max>10</max>
  64. <resolution>0.01</resolution>
  65. </range>
  66. </ray>
  67. <plugin name="laser_plug" filename="librayplugin.so"/>
  68. </sensor>
  69. </link>
  70. </model>
  71. </sdf>)";
  72. }
  73. static std::string GetSdfWithImuSensor()
  74. {
  75. return R"(<?xml version="1.0"?>
  76. <sdf version="1.6">
  77. <model name="test_imu_sensor">
  78. <link name="link1">
  79. <sensor name="link1_imu" type="imu">
  80. <always_on>true</always_on>
  81. <update_rate>200</update_rate>
  82. <imu>
  83. <angular_velocity>
  84. <x>
  85. <noise type="gaussian">
  86. <mean>0.0</mean>
  87. <stddev>2e-4</stddev>
  88. </noise>
  89. </x>
  90. <y>
  91. <noise type="gaussian">
  92. <mean>0.0</mean>
  93. <stddev>3e-4</stddev>
  94. </noise>
  95. </y>
  96. <z>
  97. <noise type="gaussian">
  98. <mean>0.0</mean>
  99. <stddev>4e-4</stddev>
  100. </noise>
  101. </z>
  102. </angular_velocity>
  103. <linear_acceleration>
  104. <x>
  105. <noise type="gaussian">
  106. <mean>0.0</mean>
  107. <stddev>1.7e-2</stddev>
  108. </noise>
  109. </x>
  110. <y>
  111. <noise type="gaussian">
  112. <mean>0.0</mean>
  113. <stddev>1.8e-2</stddev>
  114. </noise>
  115. </y>
  116. <z>
  117. <noise type="gaussian">
  118. <mean>0.0</mean>
  119. <stddev>1.9e-2</stddev>
  120. </noise>
  121. </z>
  122. </linear_acceleration>
  123. </imu>
  124. <plugin name="imu_plugin" filename="libgazebo_ros_imu_sensor.so">
  125. <ros>
  126. <argument>~/out:=imu</argument>
  127. </ros>
  128. </plugin>
  129. </sensor>
  130. </link>
  131. </model>
  132. </sdf>)";
  133. }
  134. static std::string GetSdfWithDuplicateModelName()
  135. {
  136. return R"(<?xml version="1.0"?>
  137. <sdf version="1.7">
  138. <model name="root_model">
  139. <link name="root_link"/>
  140. </model>
  141. <world name="default">
  142. <model name="my_model">
  143. <link name="link1"/>
  144. </model>
  145. <model name="your_model">
  146. <link name="link2"/>
  147. </model>
  148. <model name="my_model">
  149. <link name="link3"/>
  150. </model>
  151. </world>
  152. </sdf>)";
  153. }
  154. static std::string GetSdfWithWorldThatHasMultipleModels()
  155. {
  156. return R"(<?xml version="1.0"?>
  157. <sdf version="1.8">
  158. <model name="root_model">
  159. <link name="root_link"/>
  160. </model>
  161. <world name="default">
  162. <model name="my_model">
  163. <link name="link1"/>
  164. </model>
  165. <model name="your_model">
  166. <link name="link2"/>
  167. </model>
  168. </world>
  169. <world name="second">
  170. <model name="every_model">
  171. <link name="link3"/>
  172. </model>
  173. </world>
  174. </sdf>)";
  175. }
  176. static std::string GetSdfWithMultipleModelsThatHaveLinksWithTheSameName()
  177. {
  178. return R"(<?xml version="1.0"?>
  179. <sdf version="1.10">
  180. <world name="default">
  181. <model name="my_model">
  182. <link name="same_link_name"/>
  183. </model>
  184. <model name="your_model">
  185. <link name="same_link_name"/>
  186. </model>
  187. </world>
  188. </sdf>)";
  189. }
  190. struct SdfXmlStack
  191. {
  192. AZStd::deque<std::string> m_stack;
  193. };
  194. static SdfXmlStack GetSdfWorldWithNestedModelWithPose()
  195. {
  196. SdfXmlStack sdfXmlStack;
  197. sdfXmlStack.m_stack.emplace_back(R"(<?xml version="1.0"?>
  198. <sdf version="1.10">
  199. <world name="default">
  200. <model name="top_model">
  201. <include>
  202. <uri>model://nested_test.sdf</uri>
  203. </include>
  204. <pose>8 2 4 0 -0 -1.564217</pose>
  205. </model>
  206. </world>
  207. </sdf>)");
  208. sdfXmlStack.m_stack.emplace_back(R"(<?xml version="1.0"?>
  209. <sdf version="1.10">
  210. <model name="nested_model">
  211. <link name="link">
  212. <inertial>
  213. <mass>50</mass>
  214. </inertial>
  215. </link>
  216. </model>
  217. </sdf>)");
  218. return sdfXmlStack;
  219. }
  220. };
  221. TEST_F(SdfParserTest, SdfWithDuplicateModelNames_ResultsInError)
  222. {
  223. const auto xmlStr = GetSdfWithDuplicateModelName();
  224. const auto sdfRootOutcome = ROS2::UrdfParser::Parse(xmlStr, {});
  225. ASSERT_FALSE(sdfRootOutcome);
  226. const auto& sdfErrors = sdfRootOutcome.GetSdfErrors();
  227. EXPECT_FALSE(sdfErrors.empty());
  228. AZStd::string errorString = ROS2::Utils::JoinSdfErrorsToString(sdfRootOutcome.GetSdfErrors());
  229. printf("SDF with duplicate model names failed to parse with errors: %s\n", errorString.c_str());
  230. }
  231. TEST_F(SdfParserTest, SdfWithModelsOnRootAndWorld_ParsesSuccessfully)
  232. {
  233. const auto xmlStr = GetSdfWithWorldThatHasMultipleModels();
  234. const auto sdfRootOutcome = ROS2::UrdfParser::Parse(xmlStr, {});
  235. ASSERT_TRUE(sdfRootOutcome);
  236. const auto& sdfRoot = sdfRootOutcome.GetRoot();
  237. // This SDF should have a model on the root that points to root model
  238. const auto* rootSdfModel = sdfRoot.Model();
  239. ASSERT_NE(nullptr, rootSdfModel);
  240. EXPECT_EQ("root_model", rootSdfModel->Name());
  241. EXPECT_TRUE(rootSdfModel->LinkNameExists("root_link"));
  242. // The SDF should also have a world on the root as well
  243. ASSERT_EQ(2, sdfRoot.WorldCount());
  244. const auto* sdfWorld = sdfRoot.WorldByIndex(0);
  245. ASSERT_NE(nullptr, sdfWorld);
  246. // Also validate that the world can be looked up by name
  247. sdfWorld = sdfRoot.WorldByName("default");
  248. ASSERT_NE(nullptr, sdfWorld);
  249. EXPECT_EQ(2, sdfWorld->ModelCount());
  250. const auto* myModel = sdfWorld->ModelByName("my_model");
  251. ASSERT_NE(nullptr, myModel);
  252. EXPECT_TRUE(myModel->LinkNameExists("link1"));
  253. const auto* yourModel = sdfWorld->ModelByName("your_model");
  254. ASSERT_NE(nullptr, yourModel);
  255. EXPECT_TRUE(yourModel->LinkNameExists("link2"));
  256. sdfWorld = sdfRoot.WorldByName("second");
  257. ASSERT_NE(nullptr, sdfWorld);
  258. const auto* everyModel = sdfWorld->ModelByName("every_model");
  259. ASSERT_NE(nullptr, everyModel);
  260. EXPECT_TRUE(everyModel->LinkNameExists("link3"));
  261. AZStd::vector<const sdf::Model*> models;
  262. // Test visitation return results. All model siblings and nested models are visited
  263. auto StoreModelAndVisitNestedModelsAndSiblings =
  264. [&models](const sdf::Model& model, const ROS2::Utils::ModelStack&) -> ROS2::Utils::VisitModelResponse
  265. {
  266. models.push_back(&model);
  267. return ROS2::Utils::VisitModelResponse::VisitNestedAndSiblings;
  268. };
  269. ROS2::Utils::VisitModels(sdfRoot, StoreModelAndVisitNestedModelsAndSiblings);
  270. ASSERT_EQ(4, models.size());
  271. EXPECT_EQ("root_model", models[0]->Name());
  272. EXPECT_EQ("my_model", models[1]->Name());
  273. EXPECT_EQ("your_model", models[2]->Name());
  274. EXPECT_EQ("every_model", models[3]->Name());
  275. // Test visiting models where siblings visited
  276. // In this case only models directly on the SDF root object
  277. // or directory child of the sdf world has there models visited
  278. models.clear();
  279. auto StoreModelAndVisitSiblings =
  280. [&models](const sdf::Model& model, const ROS2::Utils::ModelStack&) -> ROS2::Utils::VisitModelResponse
  281. {
  282. models.push_back(&model);
  283. return ROS2::Utils::VisitModelResponse::VisitSiblings;
  284. };
  285. ROS2::Utils::VisitModels(sdfRoot, StoreModelAndVisitSiblings);
  286. ASSERT_EQ(4, models.size());
  287. EXPECT_EQ("root_model", models[0]->Name());
  288. EXPECT_EQ("my_model", models[1]->Name());
  289. EXPECT_EQ("your_model", models[2]->Name());
  290. EXPECT_EQ("every_model", models[3]->Name());
  291. // Visit only the first model and stop any futher visitation
  292. models.clear();
  293. auto StoreModelAndStop = [&models](const sdf::Model& model, const ROS2::Utils::ModelStack&) -> ROS2::Utils::VisitModelResponse
  294. {
  295. models.push_back(&model);
  296. return ROS2::Utils::VisitModelResponse::Stop;
  297. };
  298. ROS2::Utils::VisitModels(sdfRoot, StoreModelAndStop);
  299. ASSERT_EQ(1, models.size());
  300. EXPECT_EQ("root_model", models[0]->Name());
  301. }
  302. TEST_F(SdfParserTest, VisitingSdfWithMultipleModelsWithSameLinkName_VisitsAllLinks)
  303. {
  304. const auto xmlStr = GetSdfWithMultipleModelsThatHaveLinksWithTheSameName();
  305. const auto sdfRootOutcome = ROS2::UrdfParser::Parse(xmlStr, {});
  306. ASSERT_TRUE(sdfRootOutcome);
  307. const auto& sdfRoot = sdfRootOutcome.GetRoot();
  308. // The SDF should also have a single world
  309. ASSERT_EQ(1, sdfRoot.WorldCount());
  310. const auto* sdfWorld = sdfRoot.WorldByIndex(0);
  311. ASSERT_NE(nullptr, sdfWorld);
  312. // There should be two models of "my_model" and "your_model"
  313. EXPECT_EQ(2, sdfWorld->ModelCount());
  314. const auto* myModel = sdfWorld->ModelByName("my_model");
  315. ASSERT_NE(nullptr, myModel);
  316. EXPECT_TRUE(myModel->LinkNameExists("same_link_name"));
  317. const auto* yourModel = sdfWorld->ModelByName("your_model");
  318. ASSERT_NE(nullptr, yourModel);
  319. EXPECT_TRUE(yourModel->LinkNameExists("same_link_name"));
  320. // Make sure that all links are gathered
  321. AZStd::unordered_map<AZStd::string, const sdf::Link*> links = ROS2::Utils::GetAllLinks(*myModel, true);
  322. auto otherLinks = ROS2::Utils::GetAllLinks(*yourModel, true);
  323. links.insert(AZStd::move(otherLinks.begin()), AZStd::move(otherLinks.end()));
  324. ASSERT_EQ(2, links.size());
  325. EXPECT_TRUE(links.contains("my_model::same_link_name"));
  326. EXPECT_TRUE(links.contains("your_model::same_link_name"));
  327. }
  328. TEST_F(SdfParserTest, NestedModel_CanBeIncludedFromURI_Succeeds)
  329. {
  330. const SdfXmlStack sdfStack = GetSdfWorldWithNestedModelWithPose();
  331. ASSERT_EQ(2, sdfStack.m_stack.size());
  332. // First create the model file in a temporary directory and setup
  333. // the model URI prefixes
  334. const std::string& nestedModelXml = sdfStack.m_stack.back();
  335. constexpr AZ::IO::PathView nestedModelFilePath = "nested_test.sdf";
  336. AZ::Test::ScopedAutoTempDirectory tempDirectory;
  337. AZStd::optional<AZ::IO::FixedMaxPath> nestedModelFullPath =
  338. AZ::Test::CreateTestFile(tempDirectory, nestedModelFilePath, AZStd::string_view(nestedModelXml.data(), nestedModelXml.size()));
  339. // Verify the nested test SDF file has been created
  340. ASSERT_TRUE(nestedModelFullPath.has_value());
  341. // Now grab the world sdf content and use the Parse command to parse the contents
  342. const auto xmlStr = sdfStack.m_stack.front();
  343. sdf::ParserConfig sdfConfig;
  344. // Add the temporary directory as a URI prefix
  345. sdfConfig.AddURIPath("model://", tempDirectory.GetDirectory());
  346. auto SdfFindCallback = [](const std::string& fileName) -> std::string
  347. {
  348. ADD_FAILURE() << "File " << fileName << " was not found in UnitTest.\n";
  349. return std::string{};
  350. };
  351. sdfConfig.SetFindCallback(AZStd::move(SdfFindCallback));
  352. const auto sdfRootOutcome = ROS2::UrdfParser::Parse(xmlStr, sdfConfig);
  353. ASSERT_TRUE(sdfRootOutcome);
  354. const auto& sdfRoot = sdfRootOutcome.GetRoot();
  355. // The SDF should also have a single world
  356. ASSERT_EQ(1, sdfRoot.WorldCount());
  357. const auto* sdfWorld = sdfRoot.WorldByIndex(0);
  358. ASSERT_NE(nullptr, sdfWorld);
  359. // There should be only one model on the world that is the "top_model"
  360. EXPECT_EQ(1, sdfWorld->ModelCount());
  361. // The nested model contains the name from within
  362. const auto* topModel = sdfWorld->ModelByName("top_model");
  363. ASSERT_NE(nullptr, topModel);
  364. gz::math::Pose3d modelPose = topModel->RawPose();
  365. EXPECT_DOUBLE_EQ(8.0, modelPose.X());
  366. EXPECT_DOUBLE_EQ(2.0, modelPose.Y());
  367. EXPECT_DOUBLE_EQ(4.0, modelPose.Z());
  368. EXPECT_DOUBLE_EQ(-1.564217, modelPose.Yaw());
  369. ASSERT_EQ(1, topModel->ModelCount());
  370. const auto* nestedModel = topModel->ModelByName("nested_model");
  371. ASSERT_NE(nullptr, nestedModel);
  372. // The nested model should have a link on it
  373. EXPECT_TRUE(nestedModel->LinkNameExists("link"));
  374. // The link name can also be looked up using the relative scoped name as well from the parent top model
  375. EXPECT_TRUE(topModel->LinkNameExists("nested_model::link"));
  376. }
  377. TEST_F(SdfParserTest, CheckModelCorrectness)
  378. {
  379. {
  380. const auto xmlStr = GetSdfWithTwoSensors();
  381. const auto sdfRootOutcome = ROS2::UrdfParser::Parse(xmlStr, {});
  382. ASSERT_TRUE(sdfRootOutcome);
  383. const auto& sdfRoot = sdfRootOutcome.GetRoot();
  384. const auto* sdfModel = sdfRoot.Model();
  385. ASSERT_NE(nullptr, sdfModel);
  386. EXPECT_EQ(sdfModel->Name(), "test_two_sensors");
  387. EXPECT_EQ(sdfModel->LinkCount(), 2U);
  388. const auto* link1 = sdfModel->LinkByName("link1");
  389. ASSERT_TRUE(link1);
  390. EXPECT_EQ(link1->SensorCount(), 1U);
  391. const auto* sensor1 = link1->SensorByIndex(0U);
  392. ASSERT_TRUE(sensor1);
  393. EXPECT_EQ(sensor1->Type(), sdf::SensorType::CAMERA);
  394. EXPECT_EQ(sensor1->UpdateRate(), 10);
  395. auto* cameraSensor = sensor1->CameraSensor();
  396. ASSERT_TRUE(cameraSensor);
  397. EXPECT_EQ(cameraSensor->ImageWidth(), 640);
  398. EXPECT_EQ(cameraSensor->ImageHeight(), 480);
  399. EXPECT_NEAR(cameraSensor->HorizontalFov().Radian(), 2.0, 1e-5);
  400. EXPECT_NEAR(cameraSensor->NearClip(), 0.01, 1e-5);
  401. EXPECT_NEAR(cameraSensor->FarClip(), 1000, 1e-5);
  402. EXPECT_EQ(sensor1->Plugins().size(), 1U);
  403. EXPECT_EQ(sensor1->Plugins().at(0).Name(), "camera_plug");
  404. EXPECT_EQ(sensor1->Plugins().at(0).Filename(), "libgazebo_ros_camera.so");
  405. const auto* link2 = sdfModel->LinkByName("link2");
  406. ASSERT_TRUE(link2);
  407. EXPECT_EQ(link2->SensorCount(), 1U);
  408. const auto* sensor2 = link2->SensorByIndex(0U);
  409. ASSERT_TRUE(sensor2);
  410. EXPECT_EQ(sensor2->Type(), sdf::SensorType::LIDAR);
  411. EXPECT_EQ(sensor2->UpdateRate(), 20);
  412. auto* lidarSensor = sensor2->LidarSensor();
  413. ASSERT_TRUE(lidarSensor);
  414. EXPECT_EQ(lidarSensor->HorizontalScanSamples(), 640);
  415. EXPECT_NEAR(lidarSensor->HorizontalScanResolution(), 1.0, 1e-5);
  416. EXPECT_NEAR(lidarSensor->HorizontalScanMinAngle().Radian(), -2.0, 1e-5);
  417. EXPECT_NEAR(lidarSensor->HorizontalScanMaxAngle().Radian(), 2.5, 1e-5);
  418. EXPECT_NEAR(lidarSensor->RangeResolution(), 0.01, 1e-5);
  419. EXPECT_NEAR(lidarSensor->RangeMin(), 0.02, 1e-5);
  420. EXPECT_NEAR(lidarSensor->RangeMax(), 10.0, 1e-5);
  421. EXPECT_EQ(sensor2->Plugins().size(), 1U);
  422. EXPECT_EQ(sensor2->Plugins().at(0).Name(), "laser_plug");
  423. EXPECT_EQ(sensor2->Plugins().at(0).Filename(), "librayplugin.so");
  424. }
  425. {
  426. const auto xmlStr = GetSdfWithImuSensor();
  427. const auto sdfRootOutcome = ROS2::UrdfParser::Parse(xmlStr, {});
  428. ASSERT_TRUE(sdfRootOutcome);
  429. const auto& sdfRoot = sdfRootOutcome.GetRoot();
  430. const auto* sdfModel = sdfRoot.Model();
  431. ASSERT_NE(nullptr, sdfModel);
  432. EXPECT_EQ(sdfModel->Name(), "test_imu_sensor");
  433. EXPECT_EQ(sdfModel->LinkCount(), 1U);
  434. const auto* link1 = sdfModel->LinkByName("link1");
  435. ASSERT_TRUE(link1);
  436. EXPECT_EQ(link1->SensorCount(), 1U);
  437. const auto* sensor1 = link1->SensorByIndex(0U);
  438. ASSERT_TRUE(sensor1);
  439. EXPECT_EQ(sensor1->Type(), sdf::SensorType::IMU);
  440. EXPECT_EQ(sensor1->Name(), "link1_imu");
  441. EXPECT_EQ(sensor1->UpdateRate(), 200);
  442. auto* imuSensor = sensor1->ImuSensor();
  443. ASSERT_TRUE(imuSensor);
  444. EXPECT_EQ(imuSensor->AngularVelocityXNoise().Type(), sdf::NoiseType::GAUSSIAN);
  445. EXPECT_EQ(imuSensor->AngularVelocityYNoise().Type(), sdf::NoiseType::GAUSSIAN);
  446. EXPECT_EQ(imuSensor->AngularVelocityZNoise().Type(), sdf::NoiseType::GAUSSIAN);
  447. EXPECT_EQ(imuSensor->AngularVelocityXNoise().Mean(), 0.0);
  448. EXPECT_EQ(imuSensor->AngularVelocityYNoise().Mean(), 0.0);
  449. EXPECT_EQ(imuSensor->AngularVelocityZNoise().Mean(), 0.0);
  450. EXPECT_NEAR(imuSensor->AngularVelocityXNoise().StdDev(), 2e-4, 1e-5);
  451. EXPECT_NEAR(imuSensor->AngularVelocityYNoise().StdDev(), 3e-4, 1e-5);
  452. EXPECT_NEAR(imuSensor->AngularVelocityZNoise().StdDev(), 4e-4, 1e-5);
  453. EXPECT_EQ(imuSensor->LinearAccelerationXNoise().Type(), sdf::NoiseType::GAUSSIAN);
  454. EXPECT_EQ(imuSensor->LinearAccelerationYNoise().Type(), sdf::NoiseType::GAUSSIAN);
  455. EXPECT_EQ(imuSensor->LinearAccelerationZNoise().Type(), sdf::NoiseType::GAUSSIAN);
  456. EXPECT_EQ(imuSensor->LinearAccelerationXNoise().Mean(), 0.0);
  457. EXPECT_EQ(imuSensor->LinearAccelerationYNoise().Mean(), 0.0);
  458. EXPECT_EQ(imuSensor->LinearAccelerationZNoise().Mean(), 0.0);
  459. EXPECT_NEAR(imuSensor->LinearAccelerationXNoise().StdDev(), 1.7e-2, 1e-5);
  460. EXPECT_NEAR(imuSensor->LinearAccelerationYNoise().StdDev(), 1.8e-2, 1e-5);
  461. EXPECT_NEAR(imuSensor->LinearAccelerationZNoise().StdDev(), 1.9e-2, 1e-5);
  462. EXPECT_EQ(sensor1->Plugins().size(), 1U);
  463. EXPECT_EQ(sensor1->Plugins().at(0).Name(), "imu_plugin");
  464. EXPECT_EQ(sensor1->Plugins().at(0).Filename(), "libgazebo_ros_imu_sensor.so");
  465. }
  466. }
  467. TEST_F(SdfParserTest, RobotImporterUtils)
  468. {
  469. AZStd::unordered_set<AZStd::string> supportedPlugins{ "libgazebo_ros_camera.so", "libgazebo_ros_openni_kinect.so" };
  470. sdf::Plugin plug;
  471. plug.SetName("test_camera");
  472. plug.SetFilename("libgazebo_ros_camera.so");
  473. EXPECT_EQ("libgazebo_ros_camera.so", ROS2::Utils::SDFormat::GetPluginFilename(plug));
  474. EXPECT_TRUE(ROS2::Utils::SDFormat::IsPluginSupported(plug, supportedPlugins));
  475. plug.SetFilename("/usr/lib/libgazebo_ros_camera.so");
  476. EXPECT_EQ("libgazebo_ros_camera.so", ROS2::Utils::SDFormat::GetPluginFilename(plug));
  477. EXPECT_TRUE(ROS2::Utils::SDFormat::IsPluginSupported(plug, supportedPlugins));
  478. plug.SetFilename("~/dev/libgazebo_ros_camera.so");
  479. EXPECT_EQ("libgazebo_ros_camera.so", ROS2::Utils::SDFormat::GetPluginFilename(plug));
  480. EXPECT_TRUE(ROS2::Utils::SDFormat::IsPluginSupported(plug, supportedPlugins));
  481. plug.SetFilename("fun.so");
  482. EXPECT_EQ("fun.so", ROS2::Utils::SDFormat::GetPluginFilename(plug));
  483. EXPECT_FALSE(ROS2::Utils::SDFormat::IsPluginSupported(plug, supportedPlugins));
  484. plug.SetFilename("fun");
  485. EXPECT_EQ("fun", ROS2::Utils::SDFormat::GetPluginFilename(plug));
  486. EXPECT_FALSE(ROS2::Utils::SDFormat::IsPluginSupported(plug, supportedPlugins));
  487. AZStd::unordered_set<AZStd::string> cameraSupportedParams{
  488. ">update_rate", ">camera>horizontal_fov", ">camera>image>width", ">camera>image>height"
  489. };
  490. const auto xmlStr = GetSdfWithTwoSensors();
  491. const auto sdfRootOutcome = ROS2::UrdfParser::Parse(xmlStr, {});
  492. ASSERT_TRUE(sdfRootOutcome);
  493. const auto& sdfRoot = sdfRootOutcome.GetRoot();
  494. const auto* sdfModel = sdfRoot.Model();
  495. ASSERT_NE(nullptr, sdfModel);
  496. const sdf::ElementPtr cameraElement = sdfModel->LinkByName("link1")->SensorByIndex(0U)->Element();
  497. const sdf::ElementPtr laserElement = sdfModel->LinkByName("link2")->SensorByIndex(0U)->Element();
  498. {
  499. const auto& unsupportedCameraParams = ROS2::Utils::SDFormat::GetUnsupportedParams(cameraElement, cameraSupportedParams);
  500. EXPECT_EQ(unsupportedCameraParams.size(), 4U);
  501. EXPECT_EQ(unsupportedCameraParams[0U], ">pose");
  502. EXPECT_EQ(unsupportedCameraParams[1U], ">camera>clip>near");
  503. EXPECT_EQ(unsupportedCameraParams[2U], ">camera>clip>far");
  504. EXPECT_EQ(unsupportedCameraParams[3U], "plugin \"libgazebo_ros_camera.so\"");
  505. }
  506. cameraSupportedParams.emplace(">pose");
  507. {
  508. const auto& unsupportedCameraParams = ROS2::Utils::SDFormat::GetUnsupportedParams(cameraElement, cameraSupportedParams);
  509. EXPECT_EQ(unsupportedCameraParams.size(), 3U);
  510. EXPECT_EQ(unsupportedCameraParams[0U], ">camera>clip>near");
  511. EXPECT_EQ(unsupportedCameraParams[1U], ">camera>clip>far");
  512. EXPECT_EQ(unsupportedCameraParams[2U], "plugin \"libgazebo_ros_camera.so\"");
  513. }
  514. cameraSupportedParams.emplace(">camera>clip>near");
  515. cameraSupportedParams.emplace(">camera>clip>far");
  516. {
  517. const auto& unsupportedCameraParams = ROS2::Utils::SDFormat::GetUnsupportedParams(cameraElement, cameraSupportedParams);
  518. EXPECT_EQ(unsupportedCameraParams.size(), 1U);
  519. EXPECT_EQ(unsupportedCameraParams[0U], "plugin \"libgazebo_ros_camera.so\"");
  520. }
  521. {
  522. const auto& unsupportedCameraParams =
  523. ROS2::Utils::SDFormat::GetUnsupportedParams(cameraElement, cameraSupportedParams, supportedPlugins);
  524. EXPECT_EQ(unsupportedCameraParams.size(), 1U);
  525. EXPECT_EQ(unsupportedCameraParams[0U], "plugin \"libgazebo_ros_camera.so\": >camera_name");
  526. }
  527. AZStd::unordered_set<AZStd::string> supportedCameraPluginParams = { ">camera_name", ">ros>remapping" };
  528. {
  529. const auto& unsupportedCameraParams = ROS2::Utils::SDFormat::GetUnsupportedParams(
  530. cameraElement, cameraSupportedParams, supportedPlugins, supportedCameraPluginParams);
  531. EXPECT_EQ(unsupportedCameraParams.size(), 0U);
  532. }
  533. const AZStd::unordered_set<AZStd::string> laserSupportedParams{ ">pose",
  534. ">update_rate",
  535. ">ray>scan>horizontal>samples",
  536. ">ray>scan>horizontal>resolution",
  537. ">ray>scan>horizontal>min_angle",
  538. ">ray>scan>horizontal>max_angle",
  539. ">ray>range>min",
  540. ">ray>range>max",
  541. ">ray>range>resolution",
  542. ">always_on",
  543. ">visualize" };
  544. const auto& unsupportedLaserParams =
  545. ROS2::Utils::SDFormat::GetUnsupportedParams(laserElement, laserSupportedParams, supportedPlugins);
  546. EXPECT_EQ(unsupportedLaserParams.size(), 1U);
  547. EXPECT_EQ(unsupportedLaserParams[0U], "plugin \"librayplugin.so\"");
  548. }
  549. // temporarily disable import hooks for sensors and models for https://github.com/o3de/sig-simulation/pull/96
  550. // TEST_F(SdfParserTest, SensorPluginImporterHookCheck)
  551. // {
  552. // {
  553. // const auto xmlStr = GetSdfWithTwoSensors();
  554. // const auto sdfRootOutcome = ROS2::UrdfParser::Parse(xmlStr, {});
  555. // ASSERT_TRUE(sdfRootOutcome);
  556. // const auto& sdfRoot = sdfRootOutcome.GetRoot();
  557. // const auto* sdfModel = sdfRoot.Model();
  558. // ASSERT_NE(nullptr, sdfModel);
  559. // const sdf::ElementPtr cameraElement = sdfModel->LinkByName("link1")->SensorByIndex(0U)->Element();
  560. // const auto& cameraImporterHook = ROS2::SDFormat::ROS2SensorHooks::ROS2CameraSensor();
  561. // const auto& unsupportedCameraParams = ROS2::Utils::SDFormat::GetUnsupportedParams(
  562. // cameraElement, cameraImporterHook.m_supportedSensorParams, cameraImporterHook.m_pluginNames,
  563. // cameraImporterHook.m_supportedPluginParams);
  564. // EXPECT_EQ(unsupportedCameraParams.size(), 1U);
  565. // EXPECT_EQ(unsupportedCameraParams[0U], "plugin \"libgazebo_ros_camera.so\": >camera_name");
  566. // sdf::Plugin plug;
  567. // plug.SetName("test_camera");
  568. // plug.SetFilename("libgazebo_ros_camera.so");
  569. // EXPECT_TRUE(ROS2::Utils::SDFormat::IsPluginSupported(plug, cameraImporterHook.m_pluginNames));
  570. // plug.SetFilename("/usr/lib/libgazebo_ros_openni_kinect.so");
  571. // EXPECT_TRUE(ROS2::Utils::SDFormat::IsPluginSupported(plug, cameraImporterHook.m_pluginNames));
  572. // plug.SetFilename("~/dev/libgazebo_ros_imu.so");
  573. // EXPECT_FALSE(ROS2::Utils::SDFormat::IsPluginSupported(plug, cameraImporterHook.m_pluginNames));
  574. // plug.SetFilename("libgazebo_ros_camera");
  575. // EXPECT_FALSE(ROS2::Utils::SDFormat::IsPluginSupported(plug, cameraImporterHook.m_pluginNames));
  576. // EXPECT_TRUE(cameraImporterHook.m_sensorTypes.contains(sdf::SensorType::CAMERA));
  577. // EXPECT_TRUE(cameraImporterHook.m_sensorTypes.contains(sdf::SensorType::DEPTH_CAMERA));
  578. // EXPECT_FALSE(cameraImporterHook.m_sensorTypes.contains(sdf::SensorType::GPS));
  579. // const sdf::ElementPtr lidarElement = sdfModel->LinkByName("link2")->SensorByIndex(0U)->Element();
  580. // const auto& lidarImporterHook = ROS2::SDFormat::ROS2SensorHooks::ROS2LidarSensor();
  581. // const auto& unsupportedLidarParams = ROS2::Utils::SDFormat::GetUnsupportedParams(
  582. // lidarElement, lidarImporterHook.m_supportedSensorParams, lidarImporterHook.m_pluginNames,
  583. // lidarImporterHook.m_supportedPluginParams);
  584. // EXPECT_EQ(unsupportedLidarParams.size(), 3U);
  585. // EXPECT_EQ(unsupportedLidarParams[0U], ">always_on");
  586. // EXPECT_EQ(unsupportedLidarParams[1U], ">ray>range>resolution");
  587. // EXPECT_EQ(unsupportedLidarParams[2U], "plugin \"librayplugin.so\"");
  588. // }
  589. // {
  590. // const auto xmlStr = GetSdfWithImuSensor();
  591. // const auto sdfRootOutcome = ROS2::UrdfParser::Parse(xmlStr, {});
  592. // ASSERT_TRUE(sdfRootOutcome);
  593. // const auto& sdfRoot = sdfRootOutcome.GetRoot();
  594. // const auto* sdfModel = sdfRoot.Model();
  595. // ASSERT_NE(nullptr, sdfModel);
  596. // const sdf::ElementPtr imuElement = sdfModel->LinkByName("link1")->SensorByIndex(0U)->Element();
  597. // const auto& importerHook = ROS2::SDFormat::ROS2SensorHooks::ROS2ImuSensor();
  598. // const auto& unsupportedImuParams = ROS2::Utils::SDFormat::GetUnsupportedParams(
  599. // imuElement, importerHook.m_supportedSensorParams, importerHook.m_pluginNames, importerHook.m_supportedPluginParams);
  600. // EXPECT_EQ(unsupportedImuParams.size(), 1U);
  601. // EXPECT_EQ(unsupportedImuParams[0U], ">always_on");
  602. // }
  603. // }
  604. } // namespace UnitTest