From 6aabbc189253c3e62a452d16768dc7f3441c8464 Mon Sep 17 00:00:00 2001 From: Alessio Quaglino Date: Fri, 21 Feb 2025 04:53:24 -0800 Subject: [PATCH] Fixes an issue when self-attaching a model with pulley in tendons. The decision to attach a tendon is taken by looking at whether any of the elements in its path exist when the namespace is added to them. This does not work when pulleys are present so we need to ignore them in the count of unreferenced element. PiperOrigin-RevId: 729484365 Change-Id: I561a3a2eaa975a17421c8abe3ebfec8d4dd88951 --- src/user/user_objects.cc | 6 ++- test/xml/xml_native_reader_test.cc | 64 ++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/src/user/user_objects.cc b/src/user/user_objects.cc index 0e8c0044..4c5fc228 100644 --- a/src/user/user_objects.cc +++ b/src/user/user_objects.cc @@ -5413,9 +5413,13 @@ const mjCWrap* mjCTendon::GetWrap(int i) const { void mjCTendon::ResolveReferences(const mjCModel* m) { int nfailure = 0; + int npulley = 0; for (int i=0; iname; std::string psidesite = path[i]->sidesite; + if (path[i]->type == mjWRAP_PULLEY) { + npulley++; + } try { // look for wrapped element with namespace path[i]->name = prefix + pname + suffix; @@ -5429,7 +5433,7 @@ void mjCTendon::ResolveReferences(const mjCModel* m) { nfailure++; } } - if (nfailure==path.size()) { + if (nfailure==path.size()-npulley) { throw mjCError(this, "tendon '%s' (id = %d): no attached reference found", name.c_str(), id); } prefix.clear(); diff --git a/test/xml/xml_native_reader_test.cc b/test/xml/xml_native_reader_test.cc index 873d9294..2747af2a 100644 --- a/test/xml/xml_native_reader_test.cc +++ b/test/xml/xml_native_reader_test.cc @@ -1441,6 +1441,70 @@ TEST_F(XMLReaderTest, ParseReplicateRepeatedName) { EXPECT_THAT(error.data(), HasSubstr("Element 'replicate'")); } +TEST_F(XMLReaderTest, ParseReplicateTendon) { + static constexpr char xml[] = R"( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )"; + std::array error; + mjSpec* spec = mj_parseXMLString(xml, 0, error.data(), error.size()); + EXPECT_THAT(spec, NotNull()) << error.data(); + mjModel* m = mj_compile(spec, 0); + EXPECT_THAT(m, NotNull()); + EXPECT_THAT(m->nbody, 67); + EXPECT_THAT(m->ngeom, 66); + EXPECT_THAT(m->nsite, 6); + EXPECT_THAT(m->nu, 1); + EXPECT_THAT(m->ntendon, 1); + mj_deleteModel(m); + mj_deleteSpec(spec); +} + // ---------------------- test spec assets parsing ----------------------------- TEST_F(XMLReaderTest, ParseSpecAssets) {