|
35 | 35 | #include "dart/common/Resource.hpp" |
36 | 36 | #include "dart/common/ResourceRetriever.hpp" |
37 | 37 | #include "dart/common/Uri.hpp" |
| 38 | +#include "dart/config.hpp" |
38 | 39 | #include "dart/dynamics/FreeJoint.hpp" |
39 | 40 | #include "dart/dynamics/MeshShape.hpp" |
40 | 41 | #include "dart/dynamics/PlanarJoint.hpp" |
|
52 | 53 | #include <filesystem> |
53 | 54 | #include <iostream> |
54 | 55 | #include <map> |
| 56 | +#include <sstream> |
| 57 | +#include <string> |
55 | 58 |
|
56 | 59 | #include <cstring> |
57 | 60 |
|
| 61 | +#if DART_HAVE_spdlog |
| 62 | + #include <spdlog/sinks/ostream_sink.h> |
| 63 | + #include <spdlog/spdlog.h> |
| 64 | +#endif |
| 65 | + |
58 | 66 | using namespace dart; |
59 | 67 | using namespace dart::dynamics; |
60 | 68 | using namespace dart::test; |
@@ -140,6 +148,65 @@ class MemoryResourceRetriever final : public common::ResourceRetriever |
140 | 148 | std::map<std::string, std::string> mFiles; |
141 | 149 | }; |
142 | 150 |
|
| 151 | +class LogCapture |
| 152 | +{ |
| 153 | +public: |
| 154 | + LogCapture() |
| 155 | + { |
| 156 | +#if DART_HAVE_spdlog |
| 157 | + mStream = std::make_shared<std::ostringstream>(); |
| 158 | + mSink = std::make_shared<spdlog::sinks::ostream_sink_mt>(*mStream); |
| 159 | + mPreviousLogger = spdlog::default_logger(); |
| 160 | + mLogger = std::make_shared<spdlog::logger>("sdf-parser-log-capture", mSink); |
| 161 | + mLogger->set_level(spdlog::level::trace); |
| 162 | + mLogger->flush_on(spdlog::level::trace); |
| 163 | + spdlog::set_default_logger(mLogger); |
| 164 | +#else |
| 165 | + mOldCout = std::cout.rdbuf(mStream.rdbuf()); |
| 166 | + mOldCerr = std::cerr.rdbuf(mStream.rdbuf()); |
| 167 | +#endif |
| 168 | + } |
| 169 | + |
| 170 | + ~LogCapture() |
| 171 | + { |
| 172 | +#if DART_HAVE_spdlog |
| 173 | + if (mLogger) |
| 174 | + mLogger->flush(); |
| 175 | + spdlog::set_default_logger(mPreviousLogger); |
| 176 | + if (mLogger) |
| 177 | + spdlog::drop(mLogger->name()); |
| 178 | +#else |
| 179 | + std::cout.rdbuf(mOldCout); |
| 180 | + std::cerr.rdbuf(mOldCerr); |
| 181 | +#endif |
| 182 | + } |
| 183 | + |
| 184 | + std::string contents() |
| 185 | + { |
| 186 | +#if DART_HAVE_spdlog |
| 187 | + if (mLogger) |
| 188 | + mLogger->flush(); |
| 189 | + if (mStream) |
| 190 | + return mStream->str(); |
| 191 | + return {}; |
| 192 | +#else |
| 193 | + return mStream.str(); |
| 194 | +#endif |
| 195 | + } |
| 196 | + |
| 197 | +private: |
| 198 | +#if DART_HAVE_spdlog |
| 199 | + std::shared_ptr<std::ostringstream> mStream; |
| 200 | + std::shared_ptr<spdlog::sinks::ostream_sink_mt> mSink; |
| 201 | + std::shared_ptr<spdlog::logger> mPreviousLogger; |
| 202 | + std::shared_ptr<spdlog::logger> mLogger; |
| 203 | +#else |
| 204 | + std::ostringstream mStream; |
| 205 | + std::streambuf* mOldCout; |
| 206 | + std::streambuf* mOldCerr; |
| 207 | +#endif |
| 208 | +}; |
| 209 | + |
143 | 210 | } // namespace |
144 | 211 |
|
145 | 212 | //============================================================================== |
@@ -480,3 +547,91 @@ f 1 3 4 |
480 | 547 | }); |
481 | 548 | EXPECT_TRUE(foundMesh); |
482 | 549 | } |
| 550 | + |
| 551 | +//============================================================================== |
| 552 | +TEST(SdfParser, WarnsOnMissingInertialBlock) |
| 553 | +{ |
| 554 | + auto retriever = std::make_shared<MemoryResourceRetriever>(); |
| 555 | + const std::string modelUri = "memory://pkg/models/missing_inertial/model.sdf"; |
| 556 | + |
| 557 | + const std::string modelSdf = R"( |
| 558 | +<?xml version="1.0" ?> |
| 559 | +<sdf version="1.7"> |
| 560 | + <model name="no_inertial"> |
| 561 | + <link name="link_without_inertial"> |
| 562 | + <visual name="v"> |
| 563 | + <geometry> |
| 564 | + <box><size>0.1 0.1 0.1</size></box> |
| 565 | + </geometry> |
| 566 | + </visual> |
| 567 | + </link> |
| 568 | + </model> |
| 569 | +</sdf> |
| 570 | +)"; |
| 571 | + |
| 572 | + retriever->add(modelUri, modelSdf); |
| 573 | + |
| 574 | + LogCapture capture; |
| 575 | + SdfParser::Options options; |
| 576 | + options.mResourceRetriever = retriever; |
| 577 | + auto skeleton = SdfParser::readSkeleton(modelUri, options); |
| 578 | + |
| 579 | + ASSERT_TRUE(skeleton); |
| 580 | + ASSERT_EQ(skeleton->getNumBodyNodes(), 1u); |
| 581 | + const auto* body = skeleton->getBodyNode(0); |
| 582 | + EXPECT_DOUBLE_EQ(body->getMass(), 1.0); |
| 583 | + |
| 584 | + const auto logs = capture.contents(); |
| 585 | + EXPECT_NE(logs.find("missing <inertial>"), std::string::npos) |
| 586 | + << "Expected warning about missing <inertial> block in logs: " << logs; |
| 587 | +} |
| 588 | + |
| 589 | +//============================================================================== |
| 590 | +TEST(SdfParser, WarnsOnTinyMassAndDefaultsInertia) |
| 591 | +{ |
| 592 | + auto retriever = std::make_shared<MemoryResourceRetriever>(); |
| 593 | + const std::string modelUri = "memory://pkg/models/tiny_mass/model.sdf"; |
| 594 | + const double tinyMass = 1e-14; |
| 595 | + const double clampedMass = 1e-9; // matches parser clamp |
| 596 | + |
| 597 | + const std::string modelSdf = std::string(R"( |
| 598 | +<?xml version="1.0" ?> |
| 599 | +<sdf version="1.7"> |
| 600 | + <model name="tiny_mass_model"> |
| 601 | + <link name="link_with_mass_only"> |
| 602 | + <inertial> |
| 603 | + <mass>)") + std::to_string(tinyMass) |
| 604 | + + R"(</mass> |
| 605 | + </inertial> |
| 606 | + <collision name="c"> |
| 607 | + <geometry> |
| 608 | + <box><size>0.1 0.1 0.1</size></box> |
| 609 | + </geometry> |
| 610 | + </collision> |
| 611 | + </link> |
| 612 | + </model> |
| 613 | +</sdf> |
| 614 | +)"; |
| 615 | + |
| 616 | + retriever->add(modelUri, modelSdf); |
| 617 | + |
| 618 | + LogCapture capture; |
| 619 | + SdfParser::Options options; |
| 620 | + options.mResourceRetriever = retriever; |
| 621 | + auto skeleton = SdfParser::readSkeleton(modelUri, options); |
| 622 | + |
| 623 | + ASSERT_TRUE(skeleton); |
| 624 | + ASSERT_EQ(skeleton->getNumBodyNodes(), 1u); |
| 625 | + const auto* body = skeleton->getBodyNode(0); |
| 626 | + const auto inertia = body->getInertia(); |
| 627 | + EXPECT_DOUBLE_EQ(inertia.getMass(), clampedMass); |
| 628 | + const Eigen::Matrix3d expectedMoment |
| 629 | + = Eigen::Matrix3d::Identity() * clampedMass; |
| 630 | + EXPECT_TRUE(inertia.getMoment().isApprox(expectedMoment)); |
| 631 | + |
| 632 | + const auto logs = capture.contents(); |
| 633 | + EXPECT_NE(logs.find("Clamping to"), std::string::npos) |
| 634 | + << "Expected warning about tiny mass clamping in logs: " << logs; |
| 635 | + EXPECT_NE(logs.find("defines <mass> but no <inertia>"), std::string::npos) |
| 636 | + << "Expected warning about missing inertia tensor in logs: " << logs; |
| 637 | +} |
0 commit comments