Skip to content

Commit d08c4cf

Browse files
iche033mergify[bot]
authored andcommitted
Resolve auto inertia based on input mass (#1513)
Support auto-inertia computation using mass and density. Implemented based on the suggestions in #1482. Inertia is first auto resolved from all collisions as usual. If mass is specified, we normalize the inertia to get unit inertia, then scaling is applied to match the desired mass. --------- Signed-off-by: Steve Peters <[email protected]> Signed-off-by: Ian Chen <[email protected]> Co-authored-by: Steve Peters <[email protected]> (cherry picked from commit aaadeea)
1 parent a29007d commit d08c4cf

File tree

9 files changed

+713
-26
lines changed

9 files changed

+713
-26
lines changed

include/sdf/Collision.hh

+4-1
Original file line numberDiff line numberDiff line change
@@ -170,12 +170,15 @@ namespace sdf
170170
/// \param[in] _autoInertiaParams An ElementPtr to the auto_inertia_params
171171
/// element to be used if the auto_inertia_params have not been set in this
172172
/// collision.
173+
/// \param[in] _warnIfDensityIsUnset True to generate a warning that
174+
/// the default density value will be used if it is not explicitly set.
173175
public: void CalculateInertial(
174176
sdf::Errors &_errors,
175177
gz::math::Inertiald &_inertial,
176178
const ParserConfig &_config,
177179
const std::optional<double> &_density,
178-
sdf::ElementPtr _autoInertiaParams);
180+
sdf::ElementPtr _autoInertiaParams,
181+
bool _warnIfDensityIsUnset = true);
179182

180183
/// \brief Get a pointer to the SDF element that was used during
181184
/// load.

include/sdf/Link.hh

+7-3
Original file line numberDiff line numberDiff line change
@@ -351,9 +351,13 @@ namespace sdf
351351
const std::string &_resolveTo = "") const;
352352

353353
/// \brief Calculate & set inertial values(mass, mass matrix
354-
/// & inertial pose) for the link. Inertial values can be provided
355-
/// by the user through the SDF or can be calculated automatically
356-
/// by setting the auto attribute to true.
354+
/// & inertial pose) for the link. This function will calculate
355+
/// the inertial values if the auto attribute is set to true.
356+
/// If mass is not provided by the user, the inertial values will be
357+
/// determined based on either link density or collision density if
358+
/// explicitly set. Otherwise, if mass is specified, the inertia matrix
359+
/// will be scaled to match the desired mass, while respecting
360+
/// the ratio of density values between collisions.
357361
/// \param[out] _errors A vector of Errors object. Each object
358362
/// would contain an error code and an error message.
359363
/// \param[in] _config Custom parser configuration

python/test/pyLink_TEST.py

+162-2
Original file line numberDiff line numberDiff line change
@@ -479,12 +479,14 @@ def test_resolveauto_inertialsWithMultipleCollisions(self):
479479
link.inertial().mass_matrix().diagonal_moments())
480480

481481
def test_inertial_values_given_with_auto_set_to_true(self):
482+
# The inertia matrix is specified but should be ignored.
483+
# <mass> is not speicifed so the inertial values should be computed
484+
# based on the collision density value.
482485
sdf = "<?xml version=\"1.0\"?>" + \
483486
"<sdf version=\"1.11\">" + \
484487
" <model name='compound_model'>" + \
485488
" <link name='compound_link'>" + \
486489
" <inertial auto='True'>" + \
487-
" <mass>4.0</mass>" + \
488490
" <pose>1 1 1 2 2 2</pose>" + \
489491
" <inertia>" + \
490492
" <ixx>1</ixx>" + \
@@ -515,11 +517,169 @@ def test_inertial_values_given_with_auto_set_to_true(self):
515517
root.resolve_auto_inertials(errors, sdfParserConfig)
516518
self.assertEqual(len(errors), 0)
517519

518-
self.assertNotEqual(4.0, link.inertial().mass_matrix().mass())
520+
self.assertEqual(2.0, link.inertial().mass_matrix().mass())
519521
self.assertEqual(Pose3d.ZERO, link.inertial().pose())
520522
self.assertEqual(Vector3d(0.33333, 0.33333, 0.33333),
521523
link.inertial().mass_matrix().diagonal_moments())
522524

525+
def test_resolveauto_inertialsWithMass(self):
526+
# The inertia matrix is specified but should be ignored.
527+
# <mass> is speicifed - the auto computed inertial values should
528+
# be scaled based on the desired mass.
529+
sdf = "<?xml version=\"1.0\"?>" + \
530+
"<sdf version=\"1.11\">" + \
531+
" <model name='compound_model'>" + \
532+
" <link name='compound_link'>" + \
533+
" <inertial auto='True'>" + \
534+
" <mass>4.0</mass>" + \
535+
" <pose>1 1 1 2 2 2</pose>" + \
536+
" <inertia>" + \
537+
" <ixx>1</ixx>" + \
538+
" <iyy>1</iyy>" + \
539+
" <izz>1</izz>" + \
540+
" </inertia>" + \
541+
" </inertial>" + \
542+
" <collision name='box_collision'>" + \
543+
" <density>2.0</density>" + \
544+
" <geometry>" + \
545+
" <box>" + \
546+
" <size>1 1 1</size>" + \
547+
" </box>" + \
548+
" </geometry>" + \
549+
" </collision>" + \
550+
" </link>" + \
551+
" </model>" + \
552+
" </sdf>"
553+
554+
root = Root()
555+
sdfParserConfig = ParserConfig()
556+
errors = root.load_sdf_string(sdf, sdfParserConfig)
557+
self.assertEqual(errors, None)
558+
559+
model = root.model()
560+
link = model.link_by_index(0)
561+
errors = []
562+
root.resolve_auto_inertials(errors, sdfParserConfig)
563+
self.assertEqual(len(errors), 0)
564+
565+
self.assertEqual(4.0, link.inertial().mass_matrix().mass())
566+
self.assertEqual(Pose3d.ZERO, link.inertial().pose())
567+
self.assertEqual(Vector3d(0.66666, 0.66666, 0.66666),
568+
link.inertial().mass_matrix().diagonal_moments())
569+
570+
def test_resolveauto_inertialsWithMassAndMultipleCollisions(self):
571+
# The inertia matrix is specified but should be ignored.
572+
# <mass> is speicifed - the auto computed inertial values should
573+
# be scaled based on the desired mass.
574+
sdf = "<?xml version=\"1.0\"?>" + \
575+
"<sdf version=\"1.11\">" + \
576+
" <model name='compound_model'>" + \
577+
" <link name='compound_link'>" + \
578+
" <inertial auto='True'>" + \
579+
" <mass>12.0</mass>" + \
580+
" <pose>1 1 1 2 2 2</pose>" + \
581+
" <inertia>" + \
582+
" <ixx>1</ixx>" + \
583+
" <iyy>1</iyy>" + \
584+
" <izz>1</izz>" + \
585+
" </inertia>" + \
586+
" </inertial>" + \
587+
" <collision name='cube_collision'>" + \
588+
" <pose>0.0 0.0 0.5 0 0 0</pose>" + \
589+
" <density>4.0</density>" + \
590+
" <geometry>" + \
591+
" <box>" + \
592+
" <size>1 1 1</size>" + \
593+
" </box>" + \
594+
" </geometry>" + \
595+
" </collision>" + \
596+
" <collision name='box_collision'>" + \
597+
" <pose>0.0 0.0 -1.0 0 0 0</pose>" + \
598+
" <density>1.0</density>" + \
599+
" <geometry>" + \
600+
" <box>" + \
601+
" <size>1 1 2</size>" + \
602+
" </box>" + \
603+
" </geometry>" + \
604+
" </collision>" + \
605+
" </link>" + \
606+
" </model>" + \
607+
"</sdf>"
608+
609+
root = Root()
610+
sdfParserConfig = ParserConfig()
611+
errors = root.load_sdf_string(sdf, sdfParserConfig)
612+
self.assertEqual(errors, None)
613+
614+
model = root.model()
615+
link = model.link_by_index(0)
616+
errors = []
617+
root.resolve_auto_inertials(errors, sdfParserConfig)
618+
self.assertEqual(len(errors), 0)
619+
620+
self.assertEqual(12.0, link.inertial().mass_matrix().mass())
621+
self.assertEqual(Pose3d.ZERO,
622+
link.inertial().pose())
623+
self.assertEqual(Vector3d(9.0, 9.0, 2.0),
624+
link.inertial().mass_matrix().diagonal_moments())
625+
626+
def test_resolveauto_inertialsWithMassAndDefaultDensity(self):
627+
# The inertia matrix is specified but should be ignored.
628+
# <mass> is speicifed - the auto computed inertial values should
629+
# be scaled based on the desired mass.
630+
# Density is not specified for the bottom collision - it should
631+
# use the default value
632+
sdf = "<?xml version=\"1.0\"?>" + \
633+
"<sdf version=\"1.11\">" + \
634+
" <model name='compound_model'>" + \
635+
" <link name='compound_link'>" + \
636+
" <inertial auto='True'>" + \
637+
" <mass>12000.0</mass>" + \
638+
" <pose>1 1 1 2 2 2</pose>" + \
639+
" <inertia>" + \
640+
" <ixx>1</ixx>" + \
641+
" <iyy>1</iyy>" + \
642+
" <izz>1</izz>" + \
643+
" </inertia>" + \
644+
" </inertial>" + \
645+
" <collision name='cube_collision'>" + \
646+
" <pose>0.0 0.0 0.5 0 0 0</pose>" + \
647+
" <density>4000.0</density>" + \
648+
" <geometry>" + \
649+
" <box>" + \
650+
" <size>1 1 1</size>" + \
651+
" </box>" + \
652+
" </geometry>" + \
653+
" </collision>" + \
654+
" <collision name='box_collision'>" + \
655+
" <pose>0.0 0.0 -1.0 0 0 0</pose>" + \
656+
" <geometry>" + \
657+
" <box>" + \
658+
" <size>1 1 2</size>" + \
659+
" </box>" + \
660+
" </geometry>" + \
661+
" </collision>" + \
662+
" </link>" + \
663+
" </model>" + \
664+
"</sdf>"
665+
666+
root = Root()
667+
sdfParserConfig = ParserConfig()
668+
errors = root.load_sdf_string(sdf, sdfParserConfig)
669+
self.assertEqual(errors, None)
670+
671+
model = root.model()
672+
link = model.link_by_index(0)
673+
errors = []
674+
root.resolve_auto_inertials(errors, sdfParserConfig)
675+
self.assertEqual(len(errors), 0)
676+
677+
self.assertEqual(12000.0, link.inertial().mass_matrix().mass())
678+
self.assertEqual(Pose3d.ZERO,
679+
link.inertial().pose())
680+
self.assertEqual(Vector3d(9000.0, 9000.0, 2000.0),
681+
link.inertial().mass_matrix().diagonal_moments())
682+
523683
def test_resolveauto_inertialsCalledWithAutoFalse(self):
524684
sdf = "<?xml version=\"1.0\"?>" + \
525685
" <sdf version=\"1.11\">" + \

src/Collision.cc

+16-12
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,8 @@ void Collision::CalculateInertial(
272272
gz::math::Inertiald &_inertial,
273273
const ParserConfig &_config,
274274
const std::optional<double> &_density,
275-
sdf::ElementPtr _autoInertiaParams)
275+
sdf::ElementPtr _autoInertiaParams,
276+
bool _warnIfDensityIsUnset)
276277
{
277278
// Order of precedence for density:
278279
double density;
@@ -291,18 +292,21 @@ void Collision::CalculateInertial(
291292
// 3. DensityDefault value.
292293
else
293294
{
294-
// If density was not explicitly set, send a warning
295-
// about the default value being used
295+
// If density was not explicitly set, default value is used
296+
// Send a warning about the default value being used if needed
296297
density = DensityDefault();
297-
Error densityMissingErr(
298-
ErrorCode::ELEMENT_MISSING,
299-
"Collision is missing a <density> child element. "
300-
"Using a default density value of " +
301-
std::to_string(DensityDefault()) + " kg/m^3. "
302-
);
303-
enforceConfigurablePolicyCondition(
304-
_config.WarningsPolicy(), densityMissingErr, _errors
305-
);
298+
if (_warnIfDensityIsUnset)
299+
{
300+
Error densityMissingErr(
301+
ErrorCode::ELEMENT_MISSING,
302+
"Collision is missing a <density> child element. "
303+
"Using a default density value of " +
304+
std::to_string(DensityDefault()) + " kg/m^3. "
305+
);
306+
enforceConfigurablePolicyCondition(
307+
_config.WarningsPolicy(), densityMissingErr, _errors
308+
);
309+
}
306310
}
307311

308312
// If this Collision's auto inertia params have not been set, then use the

src/Link.cc

+27-4
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,6 @@ Errors Link::Load(ElementPtr _sdf, const ParserConfig &_config)
208208
// If auto is to true but user has still provided
209209
// inertial values
210210
if (inertialElem->HasElement("pose") ||
211-
inertialElem->HasElement("mass") ||
212211
inertialElem->HasElement("inertia"))
213212
{
214213
Error err(
@@ -664,18 +663,43 @@ void Link::ResolveAutoInertials(sdf::Errors &_errors,
664663
return;
665664
}
666665

666+
auto inertialElem = this->dataPtr->sdf->GetElement("inertial");
667+
bool massSpecified = inertialElem->HasElement("mass");
668+
// Warn about using default collision density value if mass is not specified
669+
bool warnUseDefaultDensity = !massSpecified;
670+
667671
gz::math::Inertiald totalInertia;
668672

669673
for (sdf::Collision &collision : this->dataPtr->collisions)
670674
{
671675
gz::math::Inertiald collisionInertia;
672676
collision.CalculateInertial(_errors, collisionInertia, _config,
673677
this->dataPtr->density,
674-
this->dataPtr->autoInertiaParams);
678+
this->dataPtr->autoInertiaParams,
679+
warnUseDefaultDensity);
675680
totalInertia = totalInertia + collisionInertia;
676681
}
677682

678-
this->dataPtr->inertial = totalInertia;
683+
// If mass is specified, scale inertia to match desired mass
684+
if (massSpecified)
685+
{
686+
double mass = inertialElem->Get<double>("mass");
687+
const gz::math::MassMatrix3d &totalMassMatrix = totalInertia.MassMatrix();
688+
// normalize to get the unit mass matrix
689+
gz::math::MassMatrix3d unitMassMatrix(1.0,
690+
totalMassMatrix.DiagonalMoments() / totalMassMatrix.Mass(),
691+
totalMassMatrix.OffDiagonalMoments() / totalMassMatrix.Mass());
692+
// scale the final inertia to match specified mass
693+
this->dataPtr->inertial = gz::math::Inertiald(
694+
gz::math::MassMatrix3d(mass,
695+
mass * unitMassMatrix.DiagonalMoments(),
696+
mass * unitMassMatrix.OffDiagonalMoments()),
697+
totalInertia.Pose());
698+
}
699+
else
700+
{
701+
this->dataPtr->inertial = totalInertia;
702+
}
679703

680704
// If CalculateInertial() was called with SAVE_CALCULATION
681705
// configuration then set autoInertiaSaved to true
@@ -689,7 +713,6 @@ void Link::ResolveAutoInertials(sdf::Errors &_errors,
689713
{
690714
this->dataPtr->autoInertiaSaved = true;
691715
// Write calculated inertia values to //link/inertial element
692-
auto inertialElem = this->dataPtr->sdf->GetElement("inertial");
693716
inertialElem->GetElement("pose")->GetValue()->Set<gz::math::Pose3d>(
694717
totalInertia.Pose());
695718
inertialElem->GetElement("mass")->GetValue()->Set<double>(

0 commit comments

Comments
 (0)