Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

URDF->SDF handle links with no inertia or small mass #1238

Merged
merged 47 commits into from
Mar 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
a7d8a24
Adding tests that catch warnings when urdf have no inertia element, o…
aaronchongth Feb 8, 2023
15845d8
Promote link inertia and mass related urdf2sdf sdfdbg to sdfwarn with…
aaronchongth Feb 9, 2023
bcd9459
Added flag for converting urdf links with small or no mass to frames
aaronchongth Feb 10, 2023
90bfe5f
Converts urdf links with small or no mass to frames, added tests
aaronchongth Feb 10, 2023
09e148e
Adding warning when conversion happens, added tests for small masses,…
aaronchongth Feb 10, 2023
053f70e
Fix cpplint errors
aaronchongth Feb 10, 2023
c5f757d
Adding integration test
aaronchongth Feb 14, 2023
9b0aca3
Fix lint
aaronchongth Feb 14, 2023
d829d50
Use camelCase
aaronchongth Feb 16, 2023
089b8a7
Added URDFMinimumAllowedLinkMass
aaronchongth Feb 16, 2023
4887eae
Fix tests expecting warnings when converting to frames
aaronchongth Feb 17, 2023
cb6d540
Adding inline contains and notContains to test_utils
aaronchongth Feb 17, 2023
d4de275
Using RedirectConsoleStream and ScopeExit
aaronchongth Feb 17, 2023
ecdad41
Refactor to use Root::LoadSdfString
aaronchongth Feb 17, 2023
2d01243
Removing debug message when converted frame is from a root link
aaronchongth Feb 17, 2023
1e5a2da
Added attached_to for frames during conversion, using < instead of ma…
aaronchongth Feb 21, 2023
27bf7b7
Update brief of URDFSetConvertLinkWithNoMassToFrame to mention case o…
aaronchongth Feb 21, 2023
d0a2770
Remove stale commentted code
aaronchongth Feb 21, 2023
2f86ca7
Update comment about the errors we are expecting
aaronchongth Feb 21, 2023
e1b488b
Convert to frame by default, remove minimal mass option, refactor imp…
aaronchongth Feb 28, 2023
a880d4f
Only convert to frame when parent joint is fixed, attaches and transf…
aaronchongth Mar 1, 2023
bfb68c8
Rephrased conversion error, modified unit tests
aaronchongth Mar 1, 2023
dff421c
prints zero mass errors as well when conversion to frame fails
aaronchongth Mar 2, 2023
dd687c3
integration tests that mimic the unit tests
aaronchongth Mar 2, 2023
3d2bf35
Added integration test with valid and invalid use of force torque sen…
aaronchongth Mar 2, 2023
c53243c
Merge branch 'sdf12' into aaron/urdf-inertia-handling
aaronchongth Mar 2, 2023
c9a7f4b
Fix lint
aaronchongth Mar 2, 2023
d29328b
Fix joint reduction logic, more specific error messages, more targete…
aaronchongth Mar 6, 2023
a575e1c
Convert joints to frames when converting links, attach them to conver…
aaronchongth Mar 6, 2023
99db618
Integration tests with child fixed links as well
aaronchongth Mar 6, 2023
0e8c8fb
Change sdferr to sdfwarn, no way to use ParserConfig::WarningsPolicy …
aaronchongth Mar 6, 2023
86ac8a6
Merge branch 'sdf12' into aaron/urdf-inertia-handling
aaronchongth Mar 6, 2023
5d2d26c
sdferr to sdfwarn for the case where conversion to frame is attempted
aaronchongth Mar 7, 2023
b495dd9
Removing case within converting to frame, where parent joint turns in…
aaronchongth Mar 7, 2023
a53f9c7
Reduced scope of implementation, more specific warning messages
aaronchongth Mar 14, 2023
0479650
Remove mention of parser config in warning, since that is not typical…
aaronchongth Mar 14, 2023
432a1cb
Integration tests revisited and modified, removed printouts
aaronchongth Mar 14, 2023
d9c9cb4
Test case showing ParserConfig::URDFSetPreserveFixedJoint(true) overr…
aaronchongth Mar 15, 2023
8976110
Merge branch 'sdf12' into aaron/urdf-inertia-handling
aaronchongth Mar 15, 2023
774d86b
Use sdf::testing::contains instead of local contains functions in tes…
aaronchongth Mar 15, 2023
b290786
Remove unused InitSDF, added TODO for warning when joints are convert…
aaronchongth Mar 16, 2023
bb9fa63
Cleaned up if else cases
aaronchongth Mar 16, 2023
509bb50
Using << operator of Errors
aaronchongth Mar 16, 2023
a0d457a
Replacing placeholder url with expected URL for the documentation fro…
aaronchongth Mar 16, 2023
ba52e6f
URL to tutorial as a constant, removing checking URL from test, just …
aaronchongth Mar 17, 2023
94382d7
Refactored fixed child joint logic as it is never reached
aaronchongth Mar 20, 2023
6aa625c
Reduce number of if statements, renaming to only check if parent join…
aaronchongth Mar 20, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 63 additions & 68 deletions src/ign_TEST.cc

Large diffs are not rendered by default.

75 changes: 38 additions & 37 deletions src/parser_TEST.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "sdf/Console.hh"
#include "sdf/Filesystem.hh"
#include "test_config.h"
#include "test_utils.hh"

/////////////////////////////////////////////////
TEST(Parser, initStringTrim)
Expand Down Expand Up @@ -277,13 +278,6 @@ TEST(Parser, NameUniqueness)
}
}

/////////////////////////////////////////////////
/// Check that _a contains _b
static bool contains(const std::string &_a, const std::string &_b)
{
return _a.find(_b) != std::string::npos;
}

/////////////////////////////////////////////////
TEST(Parser, SyntaxErrorInValues)
{
Expand All @@ -301,10 +295,11 @@ TEST(Parser, SyntaxErrorInValues)
sdf::init(sdf);

sdf::readFile(path, sdf);
EXPECT_PRED2(contains, buffer.str(),
EXPECT_PRED2(sdf::testing::contains, buffer.str(),
"Unable to set value [bad 0 0 0 0 0] for key [pose]");
EXPECT_PRED2(contains, buffer.str(), "bad_syntax_pose.sdf:L5");
EXPECT_PRED2(contains, buffer.str(),
EXPECT_PRED2(sdf::testing::contains, buffer.str(),
"bad_syntax_pose.sdf:L5");
EXPECT_PRED2(sdf::testing::contains, buffer.str(),
"/sdf/world[@name=\"default\"]/model[@name=\"robot1\"]/pose:");
}
{
Expand All @@ -315,10 +310,11 @@ TEST(Parser, SyntaxErrorInValues)
sdf::init(sdf);

sdf::readFile(path, sdf);
EXPECT_PRED2(contains, buffer.str(),
EXPECT_PRED2(sdf::testing::contains, buffer.str(),
"Unable to set value [bad ] for key[linear]");
EXPECT_PRED2(contains, buffer.str(), "bad_syntax_double.sdf:L7");
EXPECT_PRED2(contains, buffer.str(),
EXPECT_PRED2(sdf::testing::contains, buffer.str(),
"bad_syntax_double.sdf:L7");
EXPECT_PRED2(sdf::testing::contains, buffer.str(),
"/sdf/world[@name=\"default\"]/model[@name=\"robot1\"]/"
"link[@name=\"link\"]/velocity_decay/linear:");
}
Expand All @@ -330,10 +326,11 @@ TEST(Parser, SyntaxErrorInValues)
sdf::init(sdf);

sdf::readFile(path, sdf);
EXPECT_PRED2(contains, buffer.str(),
EXPECT_PRED2(sdf::testing::contains, buffer.str(),
"Unable to set value [0 1 bad ] for key[gravity]");
EXPECT_PRED2(contains, buffer.str(), "bad_syntax_vector.sdf:L4");
EXPECT_PRED2(contains, buffer.str(),
EXPECT_PRED2(sdf::testing::contains, buffer.str(),
"bad_syntax_vector.sdf:L4");
EXPECT_PRED2(sdf::testing::contains, buffer.str(),
"/sdf/world[@name=\"default\"]/gravity:");
}

Expand Down Expand Up @@ -363,15 +360,15 @@ TEST(Parser, MissingRequiredAttributesErrors)
sdf::init(sdf);

sdf::readFile(path, sdf);
EXPECT_PRED2(contains, buffer.str(),
EXPECT_PRED2(sdf::testing::contains, buffer.str(),
"Error Code " +
std::to_string(
static_cast<int>(sdf::ErrorCode::ATTRIBUTE_MISSING)));
EXPECT_PRED2(contains, buffer.str(),
EXPECT_PRED2(sdf::testing::contains, buffer.str(),
"Required attribute[name] in element[link] is not specified "
"in SDF.");
EXPECT_PRED2(contains, buffer.str(), "box_bad_test.world:L6");
EXPECT_PRED2(contains, buffer.str(),
EXPECT_PRED2(sdf::testing::contains, buffer.str(), "box_bad_test.world:L6");
EXPECT_PRED2(sdf::testing::contains, buffer.str(),
"/sdf/world[@name=\"default\"]/model[@name=\"box\"]/link:");
}

Expand Down Expand Up @@ -401,14 +398,15 @@ TEST(Parser, IncludesErrors)
sdf::init(sdf);

sdf::readFile(path, sdf);
EXPECT_PRED2(contains, buffer.str(),
EXPECT_PRED2(sdf::testing::contains, buffer.str(),
"Error Code " +
std::to_string(
static_cast<int>(sdf::ErrorCode::ATTRIBUTE_MISSING)));
EXPECT_PRED2(contains, buffer.str(),
EXPECT_PRED2(sdf::testing::contains, buffer.str(),
"<include> element missing 'uri' attribute");
EXPECT_PRED2(contains, buffer.str(), "includes_missing_uri.sdf:L5");
EXPECT_PRED2(contains, buffer.str(),
EXPECT_PRED2(sdf::testing::contains, buffer.str(),
"includes_missing_uri.sdf:L5");
EXPECT_PRED2(sdf::testing::contains, buffer.str(),
"/sdf/world[@name=\"default\"]/include[0]:");
}
{
Expand All @@ -421,14 +419,15 @@ TEST(Parser, IncludesErrors)
sdf::init(sdf);

sdf::readFile(path, sdf);
EXPECT_PRED2(contains, buffer.str(),
EXPECT_PRED2(sdf::testing::contains, buffer.str(),
"Error Code " +
std::to_string(
static_cast<int>(sdf::ErrorCode::URI_LOOKUP)));
EXPECT_PRED2(contains, buffer.str(),
EXPECT_PRED2(sdf::testing::contains, buffer.str(),
"Unable to find uri[missing_model]");
EXPECT_PRED2(contains, buffer.str(), "includes_missing_model.sdf:L6");
EXPECT_PRED2(contains, buffer.str(),
EXPECT_PRED2(sdf::testing::contains, buffer.str(),
"includes_missing_model.sdf:L6");
EXPECT_PRED2(sdf::testing::contains, buffer.str(),
"/sdf/world[@name=\"default\"]/include[0]/uri:");
}
{
Expand All @@ -446,15 +445,16 @@ TEST(Parser, IncludesErrors)
sdf::init(sdf);

sdf::readFile(path, sdf);
EXPECT_PRED2(contains, buffer.str(),
EXPECT_PRED2(sdf::testing::contains, buffer.str(),
"Error Code " +
std::to_string(static_cast<int>(sdf::ErrorCode::URI_LOOKUP)));
EXPECT_PRED2(contains, buffer.str(),
EXPECT_PRED2(sdf::testing::contains, buffer.str(),
"Unable to resolve uri[box_missing_config]");
EXPECT_PRED2(contains, buffer.str(),
EXPECT_PRED2(sdf::testing::contains, buffer.str(),
"since it does not contain a model.config");
EXPECT_PRED2(contains, buffer.str(), "includes_model_without_sdf.sdf:L6");
EXPECT_PRED2(contains, buffer.str(),
EXPECT_PRED2(sdf::testing::contains, buffer.str(),
"includes_model_without_sdf.sdf:L6");
EXPECT_PRED2(sdf::testing::contains, buffer.str(),
"/sdf/world[@name=\"default\"]/include[0]/uri:");
}
{
Expand All @@ -472,15 +472,16 @@ TEST(Parser, IncludesErrors)
sdf::init(sdf);

sdf::readFile(path, sdf);
EXPECT_PRED2(contains, buffer.str(),
EXPECT_PRED2(sdf::testing::contains, buffer.str(),
"Error Code " +
std::to_string(
static_cast<int>(sdf::ErrorCode::ELEMENT_MISSING)));
EXPECT_PRED2(contains, buffer.str(),
EXPECT_PRED2(sdf::testing::contains, buffer.str(),
"Failed to find top level <model> / <actor> / <light> for "
"<include>\n");
EXPECT_PRED2(contains, buffer.str(), "includes_without_top_level.sdf:L6");
EXPECT_PRED2(contains, buffer.str(),
EXPECT_PRED2(sdf::testing::contains, buffer.str(),
"includes_without_top_level.sdf:L6");
EXPECT_PRED2(sdf::testing::contains, buffer.str(),
"/sdf/world[@name=\"default\"]/include[0]/uri:");
}

Expand Down
135 changes: 108 additions & 27 deletions src/parser_urdf.cc
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@
#include <urdf_model/link.h>
#include <urdf_parser/urdf_parser.h>

#include "sdf/Error.hh"
#include "sdf/sdf.hh"
#include "sdf/Types.hh"

#include "XmlUtils.hh"
#include "SDFExtension.hh"
Expand Down Expand Up @@ -61,7 +63,8 @@ bool g_initialRobotPoseValid = false;
std::set<std::string> g_fixedJointsTransformedInRevoluteJoints;
std::set<std::string> g_fixedJointsTransformedInFixedJoints;
const int g_outputDecimalPrecision = 16;

const char kSdformatUrdfExtensionUrl[] =
"http://sdformat.org/tutorials?tut=sdformat_urdf_extensions";

/// \brief parser xml string into urdf::Vector3
/// \param[in] _key XML key where vector3 value might be
Expand Down Expand Up @@ -2663,48 +2666,126 @@ void URDF2SDF::ListSDFExtensions(const std::string &_reference)
void CreateSDF(tinyxml2::XMLElement *_root,
urdf::LinkConstSharedPtr _link)
{
// must have an <inertial> block and cannot have zero mass.
// allow det(I) == zero, in the case of point mass geoms.
// Links without an <inertial> block will be considered to have zero mass.
const bool linkHasZeroMass = !_link->inertial || _link->inertial->mass <= 0;

// link must have an <inertial> block and cannot have zero mass, if its parent
// joint and none of its child joints are reduced.
// allow det(I) == zero, in the case of point mass geoms.
// @todo: keyword "world" should be a constant defined somewhere else
if (_link->name != "world" &&
((!_link->inertial) || gz::math::equal(_link->inertial->mass, 0.0)))
if (_link->name != "world" && linkHasZeroMass)
{
if (!_link->child_links.empty())
Errors nonJointReductionErrors;
std::stringstream errorStream;
errorStream << "urdf2sdf: link[" << _link->name << "] has ";
if (!_link->inertial)
{
sdfdbg << "urdf2sdf: link[" << _link->name
<< "] has no inertia, ["
<< static_cast<int>(_link->child_links.size())
<< "] children links ignored.\n";
errorStream << "no <inertial> block defined. ";
}

if (!_link->child_joints.empty())
else
{
sdfdbg << "urdf2sdf: link[" << _link->name
<< "] has no inertia, ["
<< static_cast<int>(_link->child_links.size())
<< "] children joints ignored.\n";
errorStream << "a mass value of less than or equal to zero. ";
}
errorStream << "Please ensure this link has a valid mass to prevent any "
<< "missing links or joints in the resulting SDF model.";
nonJointReductionErrors.emplace_back(
ErrorCode::LINK_INERTIA_INVALID, errorStream.str());
errorStream.str(std::string());

// if the parent joint is reduced, which resolves the massless issue of this
// link, no warnings will be emitted
bool parentJointReduced = _link->parent_joint &&
FixedJointShouldBeReduced(_link->parent_joint) &&
g_reduceFixedJoints;

if (_link->parent_joint)
if (!parentJointReduced)
{
sdfdbg << "urdf2sdf: link[" << _link->name
<< "] has no inertia, "
<< "parent joint [" << _link->parent_joint->name
<< "] ignored.\n";
}
// check if joint lumping was turned off for the parent joint
if (_link->parent_joint)
{
if (_link->parent_joint->type == urdf::Joint::FIXED)
{
errorStream << "urdf2sdf: allowing joint lumping by removing any "
<< "<disableFixedJointLumping> or <preserveFixedJoint> "
<< "gazebo tag on fixed parent joint["
<< _link->parent_joint->name
<< "], as well as ensuring that "
<< "ParserConfig::URDFPreserveFixedJoint is false, could "
<< "help resolve this warning. See "
<< std::string(kSdformatUrdfExtensionUrl)
<< " for more information about this behavior.";
nonJointReductionErrors.emplace_back(
ErrorCode::LINK_INERTIA_INVALID, errorStream.str());
errorStream.str(std::string());
}

sdfdbg << "urdf2sdf: link[" << _link->name
<< "] has no inertia, not modeled in sdf\n";
return;
azeey marked this conversation as resolved.
Show resolved Hide resolved
errorStream << "urdf2sdf: parent joint["
<< _link->parent_joint->name
<< "] ignored.";
nonJointReductionErrors.emplace_back(
ErrorCode::LINK_INERTIA_INVALID, errorStream.str());
errorStream.str(std::string());
}

// check if joint lumping was turned off for child joints
if (!_link->child_joints.empty())
{
for (const auto &cj : _link->child_joints)
{
// Lumping child joints occur before CreateSDF, so if it does occur
// this link would already contain the lumped mass from the child
// link. If a child joint is still fixed at this point, it means joint
// lumping has been disabled.
if (cj->type == urdf::Joint::FIXED)
{
errorStream << "urdf2sdf: allowing joint lumping by removing any "
<< "<disableFixedJointLumping> or <preserveFixedJoint> "
<< "gazebo tag on fixed child joint["
<< cj->name
<< "], as well as ensuring that "
<< "ParserConfig::URDFPreserveFixedJoint is false, "
<< "could help resolve this warning. See "
<< std::string(kSdformatUrdfExtensionUrl)
<< " for more information about this behavior.";
nonJointReductionErrors.emplace_back(
ErrorCode::LINK_INERTIA_INVALID, errorStream.str());
errorStream.str(std::string());
}
}

errorStream << "urdf2sdf: ["
<< static_cast<int>(_link->child_joints.size())
<< "] child joints ignored.";
nonJointReductionErrors.emplace_back(
ErrorCode::LINK_INERTIA_INVALID, errorStream.str());
errorStream.str(std::string());
errorStream << "urdf2sdf: ["
<< static_cast<int>(_link->child_links.size())
<< "] child links ignored.";
nonJointReductionErrors.emplace_back(
ErrorCode::LINK_INERTIA_INVALID, errorStream.str());
errorStream.str(std::string());
}

// Emit all accumulated warnings and return
sdfwarn << nonJointReductionErrors;
errorStream << "urdf2sdf: link[" << _link->name
<< "] is not modeled in sdf.";
sdfwarn << Error(ErrorCode::LINK_INERTIA_INVALID, errorStream.str());
return;
}
}

// create <body:...> block for non fixed joint attached bodies
// create <body:...> block for non fixed joint attached bodies that have mass
if ((_link->getParent() && _link->getParent()->name == "world") ||
!g_reduceFixedJoints ||
(!_link->parent_joint ||
!FixedJointShouldBeReduced(_link->parent_joint)))
{
CreateLink(_root, _link, gz::math::Pose3d::Zero);
if (!linkHasZeroMass)
{
CreateLink(_root, _link, gz::math::Pose3d::Zero);
}
}

// recurse into children
Expand Down
Loading