Fix attach for spatial tendon. Fixes #3119.
PiperOrigin-RevId: 874067521 Change-Id: If8d415278dde2bccf269cc90e8c176e3d7edefa6
This commit is contained in:
committed by
Copybara-Service
parent
a043df6bdf
commit
6ec808e2ce
@@ -10,6 +10,11 @@ MJX
|
|||||||
|
|
||||||
- Add batch rendering support for MJX-Warp. See the :ref:`MJX-Warp batch rendering<MjxWarpBatchRendering>` section for details.
|
- Add batch rendering support for MJX-Warp. See the :ref:`MJX-Warp batch rendering<MjxWarpBatchRendering>` section for details.
|
||||||
|
|
||||||
|
Bug fixes
|
||||||
|
^^^^^^^^^
|
||||||
|
|
||||||
|
- Fixed a bug where :ref:`mjs_attach` silently dropped spatial tendons with wrapping geometries that had no
|
||||||
|
``sidesite`` attribute (:issue:`3119`, reported by :github:user:`tomstewart89`).
|
||||||
|
|
||||||
Version 3.5.0 (February 12, 2026)
|
Version 3.5.0 (February 12, 2026)
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
|||||||
@@ -6267,12 +6267,16 @@ void mjCTendon::ResolveReferences(const mjCModel* m) {
|
|||||||
try {
|
try {
|
||||||
// look for wrapped element with namespace
|
// look for wrapped element with namespace
|
||||||
path[i]->name = prefix + pname + suffix;
|
path[i]->name = prefix + pname + suffix;
|
||||||
path[i]->sidesite = prefix + psidesite + suffix;
|
if (!psidesite.empty()) {
|
||||||
|
path[i]->sidesite = prefix + psidesite + suffix;
|
||||||
|
}
|
||||||
path[i]->ResolveReferences(m);
|
path[i]->ResolveReferences(m);
|
||||||
} catch(mjCError) {
|
} catch(mjCError) {
|
||||||
// remove namespace from wrap names
|
// remove namespace from wrap names
|
||||||
path[i]->name = pname;
|
path[i]->name = pname;
|
||||||
path[i]->sidesite = psidesite;
|
if (!psidesite.empty()) {
|
||||||
|
path[i]->sidesite = psidesite;
|
||||||
|
}
|
||||||
path[i]->ResolveReferences(m);
|
path[i]->ResolveReferences(m);
|
||||||
nfailure++;
|
nfailure++;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1079,6 +1079,133 @@ TEST_F(MujocoTest, AttachSame) {
|
|||||||
mj_deleteModel(m_expected);
|
mj_deleteModel(m_expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(MujocoTest, AttachSpatialTendonWithoutSidesite) {
|
||||||
|
static constexpr char xml_parent[] = R"(
|
||||||
|
<mujoco>
|
||||||
|
<worldbody>
|
||||||
|
<body name="parent_body">
|
||||||
|
<geom size="0.1" type="sphere"/>
|
||||||
|
</body>
|
||||||
|
</worldbody>
|
||||||
|
</mujoco>)";
|
||||||
|
|
||||||
|
static constexpr char xml_child[] = R"(
|
||||||
|
<mujoco>
|
||||||
|
<worldbody>
|
||||||
|
<body name="child_body">
|
||||||
|
<geom name="wrap_geom" size="0.05" type="sphere"/>
|
||||||
|
<site name="site_A" pos="0 0 0.1"/>
|
||||||
|
<site name="site_B" pos="0 0 -0.1"/>
|
||||||
|
<site name="side_site" pos="0.05 0 0"/>
|
||||||
|
</body>
|
||||||
|
</worldbody>
|
||||||
|
<tendon>
|
||||||
|
<spatial name="tendon_with_sidesite">
|
||||||
|
<site site="site_A"/>
|
||||||
|
<geom geom="wrap_geom" sidesite="side_site"/>
|
||||||
|
<site site="site_B"/>
|
||||||
|
</spatial>
|
||||||
|
<spatial name="tendon_without_sidesite">
|
||||||
|
<site site="site_A"/>
|
||||||
|
<geom geom="wrap_geom"/>
|
||||||
|
<site site="site_B"/>
|
||||||
|
</spatial>
|
||||||
|
</tendon>
|
||||||
|
</mujoco>)";
|
||||||
|
|
||||||
|
std::array<char, 1000> er;
|
||||||
|
mjSpec* parent = mj_parseXMLString(xml_parent, 0, er.data(), er.size());
|
||||||
|
ASSERT_THAT(parent, NotNull()) << er.data();
|
||||||
|
mjSpec* child = mj_parseXMLString(xml_child, 0, er.data(), er.size());
|
||||||
|
ASSERT_THAT(child, NotNull()) << er.data();
|
||||||
|
|
||||||
|
mjsBody* parent_body = mjs_findBody(parent, "parent_body");
|
||||||
|
ASSERT_THAT(parent_body, NotNull());
|
||||||
|
mjsSite* attach_site = mjs_addSite(parent_body, 0);
|
||||||
|
mjs_setName(attach_site->element, "attach_site");
|
||||||
|
|
||||||
|
mjs_attach(attach_site->element,
|
||||||
|
mjs_findBody(child, "child_body")->element, "", "_child");
|
||||||
|
|
||||||
|
EXPECT_THAT(mjs_findElement(parent, mjOBJ_TENDON,
|
||||||
|
"tendon_with_sidesite_child"), NotNull());
|
||||||
|
EXPECT_THAT(mjs_findElement(parent, mjOBJ_TENDON,
|
||||||
|
"tendon_without_sidesite_child"), NotNull());
|
||||||
|
|
||||||
|
mjModel* model = mj_compile(parent, nullptr);
|
||||||
|
ASSERT_THAT(model, NotNull()) << mjs_getError(parent);
|
||||||
|
EXPECT_EQ(model->ntendon, 2);
|
||||||
|
|
||||||
|
mj_deleteModel(model);
|
||||||
|
mj_deleteSpec(parent);
|
||||||
|
mj_deleteSpec(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(MujocoTest, AttachSpatialTendonGitHubIssue3119) {
|
||||||
|
static constexpr char parent_xml[] = R"(
|
||||||
|
<mujoco>
|
||||||
|
<worldbody>
|
||||||
|
<body name="parent_body">
|
||||||
|
<geom size="0.1" type="sphere"/>
|
||||||
|
</body>
|
||||||
|
</worldbody>
|
||||||
|
</mujoco>)";
|
||||||
|
|
||||||
|
static constexpr char child_xml[] = R"(
|
||||||
|
<mujoco>
|
||||||
|
<worldbody>
|
||||||
|
<body name="child_body">
|
||||||
|
<geom name="wrap_geom" size="0.05" type="sphere"/>
|
||||||
|
<site name="site_A" pos="0 0 0.1"/>
|
||||||
|
<site name="site_B" pos="0 0 -0.1"/>
|
||||||
|
<site name="side_site" pos="0.05 0 0"/>
|
||||||
|
</body>
|
||||||
|
</worldbody>
|
||||||
|
<tendon>
|
||||||
|
<spatial name="tendon_with_sidesite">
|
||||||
|
<site site="site_A"/>
|
||||||
|
<geom geom="wrap_geom" sidesite="side_site"/>
|
||||||
|
<site site="site_B"/>
|
||||||
|
</spatial>
|
||||||
|
<spatial name="tendon_without_sidesite">
|
||||||
|
<site site="site_A"/>
|
||||||
|
<geom geom="wrap_geom"/>
|
||||||
|
<site site="site_B"/>
|
||||||
|
</spatial>
|
||||||
|
</tendon>
|
||||||
|
</mujoco>)";
|
||||||
|
|
||||||
|
std::array<char, 1000> er;
|
||||||
|
mjSpec* parent_spec =
|
||||||
|
mj_parseXMLString(parent_xml, 0, er.data(), er.size());
|
||||||
|
ASSERT_THAT(parent_spec, NotNull()) << er.data();
|
||||||
|
mjSpec* child_spec =
|
||||||
|
mj_parseXMLString(child_xml, 0, er.data(), er.size());
|
||||||
|
ASSERT_THAT(child_spec, NotNull()) << er.data();
|
||||||
|
|
||||||
|
mjsBody* parent_body = mjs_findBody(parent_spec, "parent_body");
|
||||||
|
ASSERT_THAT(parent_body, NotNull());
|
||||||
|
mjsSite* attach_site = mjs_addSite(parent_body, 0);
|
||||||
|
mjs_setName(attach_site->element, "attach_site");
|
||||||
|
|
||||||
|
mjs_attach(attach_site->element,
|
||||||
|
mjs_findBody(child_spec, "child_body")->element,
|
||||||
|
"", "_child");
|
||||||
|
|
||||||
|
EXPECT_THAT(mjs_findElement(parent_spec, mjOBJ_TENDON,
|
||||||
|
"tendon_with_sidesite_child"), NotNull());
|
||||||
|
EXPECT_THAT(mjs_findElement(parent_spec, mjOBJ_TENDON,
|
||||||
|
"tendon_without_sidesite_child"), NotNull());
|
||||||
|
|
||||||
|
mjModel* model = mj_compile(parent_spec, nullptr);
|
||||||
|
ASSERT_THAT(model, NotNull()) << mjs_getError(parent_spec);
|
||||||
|
EXPECT_EQ(model->ntendon, 2);
|
||||||
|
|
||||||
|
mj_deleteModel(model);
|
||||||
|
mj_deleteSpec(parent_spec);
|
||||||
|
mj_deleteSpec(child_spec);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(MujocoTest, AttachDifferent) {
|
TEST_F(MujocoTest, AttachDifferent) {
|
||||||
std::array<char, 1000> er;
|
std::array<char, 1000> er;
|
||||||
mjtNum tol = 0;
|
mjtNum tol = 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user