diff --git a/src/xml/xml_native_writer.cc b/src/xml/xml_native_writer.cc index add60d87..fa813b48 100644 --- a/src/xml/xml_native_writer.cc +++ b/src/xml/xml_native_writer.cc @@ -296,10 +296,12 @@ void mjXWriter::OneJoint(XMLElement* elem, mjCJoint* pjoint, mjCDef* def) { WriteAttrInt(elem, "group", pjoint->group, def->joint.group); WriteAttr(elem, "ref", 1, &pjoint->ref, &zero); WriteAttr(elem, "springref", 1, &pjoint->springref, &zero); - WriteAttr(elem, "solreflimit", mjNREF, pjoint->solref_limit, def->joint.solref_limit); - WriteAttr(elem, "solimplimit", mjNIMP, pjoint->solimp_limit, def->joint.solimp_limit); - WriteAttr(elem, "solreffriction", mjNREF, pjoint->solref_friction, def->joint.solref_friction); - WriteAttr(elem, "solimpfriction", mjNIMP, pjoint->solimp_friction, def->joint.solimp_friction); + WriteAttr(elem, "solreflimit", mjNREF, pjoint->solref_limit, def->joint.solref_limit, true); + WriteAttr(elem, "solimplimit", mjNIMP, pjoint->solimp_limit, def->joint.solimp_limit, true); + WriteAttr(elem, "solreffriction", mjNREF, pjoint->solref_friction, def->joint.solref_friction, + true); + WriteAttr(elem, "solimpfriction", mjNIMP, pjoint->solimp_friction, def->joint.solimp_friction, + true); WriteAttr(elem, "stiffness", 1, &pjoint->stiffness, &def->joint.stiffness); WriteAttrKey(elem, "limited", TFAuto_map, 3, pjoint->limited, def->joint.limited); WriteAttr(elem, "range", 2, pjoint->range, def->joint.range); @@ -373,10 +375,10 @@ void mjXWriter::OneGeom(XMLElement* elem, mjCGeom* pgeom, mjCDef* def) { WriteAttrInt(elem, "condim", pgeom->condim, def->geom.condim); WriteAttrInt(elem, "group", pgeom->group, def->geom.group); WriteAttrInt(elem, "priority", pgeom->priority, def->geom.priority); - WriteAttr(elem, "friction", 3, pgeom->friction, def->geom.friction); + WriteAttr(elem, "friction", 3, pgeom->friction, def->geom.friction, true); WriteAttr(elem, "solmix", 1, &pgeom->solmix, &def->geom.solmix); - WriteAttr(elem, "solref", mjNREF, pgeom->solref, def->geom.solref); - WriteAttr(elem, "solimp", mjNIMP, pgeom->solimp, def->geom.solimp); + WriteAttr(elem, "solref", mjNREF, pgeom->solref, def->geom.solref, true); + WriteAttr(elem, "solimp", mjNIMP, pgeom->solimp, def->geom.solimp, true); WriteAttr(elem, "margin", 1, &pgeom->margin, &def->geom.margin); WriteAttr(elem, "gap", 1, &pgeom->gap, &def->geom.gap); WriteAttr(elem, "gap", 1, &pgeom->gap, &def->geom.gap); @@ -533,10 +535,11 @@ void mjXWriter::OnePair(XMLElement* elem, mjCPair* ppair, mjCDef* def) { WriteAttrInt(elem, "condim", ppair->condim, def->pair.spec.condim); WriteAttr(elem, "margin", 1, &ppair->margin, &def->pair.spec.margin); WriteAttr(elem, "gap", 1, &ppair->gap, &def->pair.spec.gap); - WriteAttr(elem, "solref", mjNREF, ppair->solref, def->pair.spec.solref); - WriteAttr(elem, "solreffriction", mjNREF, ppair->solreffriction, def->pair.spec.solreffriction); - WriteAttr(elem, "solimp", mjNIMP, ppair->solimp, def->pair.spec.solimp); - WriteAttr(elem, "friction", 5, ppair->friction, def->pair.spec.friction); + WriteAttr(elem, "solref", mjNREF, ppair->solref, def->pair.spec.solref, true); + WriteAttr(elem, "solreffriction", mjNREF, ppair->solreffriction, def->pair.spec.solreffriction, + true); + WriteAttr(elem, "solimp", mjNIMP, ppair->solimp, def->pair.spec.solimp, true); + WriteAttr(elem, "friction", 5, ppair->friction, def->pair.spec.friction); // all 5 values } @@ -586,8 +589,8 @@ void mjXWriter::OneEquality(XMLElement* elem, mjCEquality* peq, mjCDef* def) { // defaults and regular WriteAttrKey(elem, "active", bool_map, 2, peq->active, def->equality.active); - WriteAttr(elem, "solref", mjNREF, peq->solref, def->equality.solref); - WriteAttr(elem, "solimp", mjNIMP, peq->solimp, def->equality.solimp); + WriteAttr(elem, "solref", mjNREF, peq->solref, def->equality.solref, true); + WriteAttr(elem, "solimp", mjNIMP, peq->solimp, def->equality.solimp, true); } @@ -604,10 +607,12 @@ void mjXWriter::OneTendon(XMLElement* elem, mjCTendon* pten, mjCDef* def) { // defaults and regular WriteAttrInt(elem, "group", pten->group, def->tendon.group); - WriteAttr(elem, "solreflimit", mjNREF, pten->solref_limit, def->tendon.solref_limit); - WriteAttr(elem, "solimplimit", mjNIMP, pten->solimp_limit, def->tendon.solimp_limit); - WriteAttr(elem, "solreffriction", mjNREF, pten->solref_friction, def->tendon.solref_friction); - WriteAttr(elem, "solimpfriction", mjNIMP, pten->solimp_friction, def->tendon.solimp_friction); + WriteAttr(elem, "solreflimit", mjNREF, pten->solref_limit, def->tendon.solref_limit, true); + WriteAttr(elem, "solimplimit", mjNIMP, pten->solimp_limit, def->tendon.solimp_limit, true); + WriteAttr(elem, "solreffriction", mjNREF, pten->solref_friction, def->tendon.solref_friction, + true); + WriteAttr(elem, "solimpfriction", mjNIMP, pten->solimp_friction, def->tendon.solimp_friction, + true); WriteAttrKey(elem, "limited", TFAuto_map, 3, pten->limited, def->tendon.limited); WriteAttr(elem, "range", 2, pten->range, def->tendon.range); WriteAttr(elem, "margin", 1, &pten->margin, &def->tendon.margin); @@ -711,8 +716,8 @@ void mjXWriter::OneActuator(XMLElement* elem, mjCActuator* pact, mjCDef* def) { } WriteAttrKey(elem, "gaintype", gain_map, gain_sz, pact->gaintype, def->actuator.gaintype); WriteAttrKey(elem, "biastype", bias_map, bias_sz, pact->biastype, def->actuator.biastype); - WriteAttr(elem, "gainprm", mjNGAIN, pact->gainprm, def->actuator.gainprm); - WriteAttr(elem, "biasprm", mjNBIAS, pact->biasprm, def->actuator.biasprm); + WriteAttr(elem, "gainprm", mjNGAIN, pact->gainprm, def->actuator.gainprm, true); + WriteAttr(elem, "biasprm", mjNBIAS, pact->biasprm, def->actuator.biasprm, true); } // userdata diff --git a/src/xml/xml_util.cc b/src/xml/xml_util.cc index df80e533..91ab9858 100644 --- a/src/xml/xml_util.cc +++ b/src/xml/xml_util.cc @@ -904,7 +904,8 @@ static int Round(double x) { // write attribute template -void mjXUtil::WriteAttr(XMLElement* elem, string name, int n, const T* data, const T* def) { +void mjXUtil::WriteAttr(XMLElement* elem, string name, int n, const T* data, const T* def, + bool trim) { // make sure all are defined if constexpr (std::is_floating_point_v) { for (int i=0; i 0 && data[n-1] == def[n-1]) { + n--; + } + } + // increase precision for testing stringstream stream; stream.precision(mujoco::_mjPRIVATE__get_xml_precision()); @@ -945,17 +953,17 @@ void mjXUtil::WriteAttr(XMLElement* elem, string name, int n, const T* data, con template void mjXUtil::WriteAttr(XMLElement* elem, string name, int n, - const double* data, const double* def); + const double* data, const double* def, bool trim); template void mjXUtil::WriteAttr(XMLElement* elem, string name, int n, - const float* data, const float* def); + const float* data, const float* def, bool trim); template void mjXUtil::WriteAttr(XMLElement* elem, string name, int n, - const int* data, const int* def); + const int* data, const int* def, bool trim); template void mjXUtil::WriteAttr(XMLElement* elem, string name, int n, const unsigned char* data, - const unsigned char* def); + const unsigned char* def, bool trim); // write vector attribute, default = zero array diff --git a/src/xml/xml_util.h b/src/xml/xml_util.h index 875eb5a4..c38d3890 100644 --- a/src/xml/xml_util.h +++ b/src/xml/xml_util.h @@ -195,7 +195,7 @@ class mjXUtil { // write attribute- any type template static void WriteAttr(tinyxml2::XMLElement* elem, std::string name, int n, const T* data, - const T* def = 0); + const T* def = 0, bool trim = false); // write vector attribute, with and without default static void WriteVector(tinyxml2::XMLElement* elem, std::string name, diff --git a/test/xml/xml_native_writer_test.cc b/test/xml/xml_native_writer_test.cc index 53147f0f..1d2bf472 100644 --- a/test/xml/xml_native_writer_test.cc +++ b/test/xml/xml_native_writer_test.cc @@ -722,7 +722,7 @@ TEST_F(XMLWriterTest, WritesActuatorDefaults) { std::string saved_xml = SaveAndReadXml(model); EXPECT_THAT(saved_xml, Not(HasSubstr("mass"))); EXPECT_THAT(saved_xml, HasSubstr( - " + + + + + + + + + + + + )"; + mjModel* model = LoadModelFromString(xml); + ASSERT_THAT(model, NotNull()); + std::string saved_xml = SaveAndReadXml(model); + EXPECT_THAT(saved_xml, HasSubstr("friction=\"0.9\"")); + EXPECT_THAT(saved_xml, HasSubstr("solref=\"0.1\"")); + EXPECT_THAT(saved_xml, HasSubstr("solimp=\"0.1 0.2\"")); + EXPECT_THAT(saved_xml, HasSubstr("gainprm=\"3\" biasprm=\"0 -3 -4\"")); + EXPECT_THAT(saved_xml, HasSubstr("gainprm=\"0 0 -10\"")); + mj_deleteModel(model); +} + // check that no precision is lost when saving XMLs with FullFloatPrecision TEST_F(XMLWriterTest, SetPrecision) { static constexpr char xml[] = R"(