diff --git a/src/xml/xml_native_writer.cc b/src/xml/xml_native_writer.cc
index 3dbe6530..31c3a1e4 100644
--- a/src/xml/xml_native_writer.cc
+++ b/src/xml/xml_native_writer.cc
@@ -768,6 +768,12 @@ void mjXWriter::Compiler(XMLElement* root) {
if (model->exactmeshinertia) {
WriteAttrTxt(section, "exactmeshinertia", "true");
}
+ if (model->boundmass) {
+ WriteAttr(section, "boundmass", 1, &model->boundmass);
+ }
+ if (model->boundinertia) {
+ WriteAttr(section, "boundinertia", 1, &model->boundinertia);
+ }
// always enable autolimits. limited attributes will be written appropriately
// TODO(b/245077553): Remove this when the default is true.
WriteAttrTxt(section, "autolimits", "true");
diff --git a/test/xml/xml_native_writer_test.cc b/test/xml/xml_native_writer_test.cc
index 6b12e83e..7d5b9ad2 100644
--- a/test/xml/xml_native_writer_test.cc
+++ b/test/xml/xml_native_writer_test.cc
@@ -208,6 +208,34 @@ TEST_F(XMLWriterTest, NotAddsInertial) {
mj_deleteModel(model);
}
+TEST_F(XMLWriterTest, KeepsBoundMassInertia) {
+ static constexpr char xml[] = R"(
+
+
+
+ )";
+ mjModel* model = LoadModelFromString(xml);
+ ASSERT_THAT(model, NotNull());
+ std::string saved_xml = SaveAndReadXml(model);
+ EXPECT_THAT(saved_xml, HasSubstr("boundmass=\"0.1\""));
+ EXPECT_THAT(saved_xml, HasSubstr("boundinertia=\"0.2\""));
+ mj_deleteModel(model);
+}
+
+TEST_F(XMLWriterTest, DropsZeroBoundMassInertia) {
+ static constexpr char xml[] = R"(
+
+
+
+ )";
+ mjModel* model = LoadModelFromString(xml);
+ ASSERT_THAT(model, NotNull());
+ std::string saved_xml = SaveAndReadXml(model);
+ EXPECT_THAT(saved_xml, Not(HasSubstr("boundmass")));
+ EXPECT_THAT(saved_xml, Not(HasSubstr("boundinertia")));
+ mj_deleteModel(model);
+}
+
TEST_F(XMLWriterTest, DropsInertialIfFromGeom) {
static constexpr char xml[] = R"(