Skip to content

Commit 7443e68

Browse files
yuvaltassacopybara-github
authored andcommitted
Allow contact sensor subtree1/subtree2 to be any body.
PiperOrigin-RevId: 799632590 Change-Id: If9cd6c5a3a5f83d89ae024a8f64232ab1f1d603a
1 parent 9eaa31a commit 7443e68

File tree

7 files changed

+64
-32
lines changed

7 files changed

+64
-32
lines changed

doc/XMLreference.rst

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7502,9 +7502,7 @@ Extraction
75027502
.. _sensor-contact-subtree2:
75037503

75047504
:at:`subtree1`, :at:`subtree2`: :at-val:`string, optional`
7505-
Name of a body whose subtree is participating in a contact. See **matching** :ref:`above <sensor-contact>`. Note
7506-
currently only "entire" subtrees are supported, in the sense that the specified body must be a direct child of the
7507-
world. General subtrees could be added in the future.
7505+
Name of a body whose subtree is participating in a contact. See **matching** :ref:`above <sensor-contact>`.
75087506

75097507
.. _sensor-contact-site:
75107508

doc/changelog.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ General
1010
- Constraint island discovery and construction, previously an experimental feature, is now :ref:`documented<soIsland>`
1111
and promoted to default; disable it with :ref:`option/flag/island <option-flag-island>`. We expect islanding to be
1212
a strict improvement over the monolithic constraint solver, please let us know if you experience any issues.
13+
- :ref:`Contact sensor<sensor-contact>` :at-val:`subtree1/subtree2` specification is now available for any body, not
14+
just direct children of the world.
1315

1416
.. admonition:: Breaking API changes
1517
:class: attention

python/mujoco/specs_test.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1428,22 +1428,6 @@ def test_bad_contact_sensor(self):
14281428
refname='cam',
14291429
),
14301430
),
1431-
dict(
1432-
expected_error='subtree1 must be a child of the world',
1433-
sensor_params=dict(
1434-
type=mujoco.mjtSensor.mjSENS_CONTACT,
1435-
objtype=mujoco.mjtObj.mjOBJ_XBODY,
1436-
objname='non_root',
1437-
),
1438-
),
1439-
dict(
1440-
expected_error='subtree2 must be a child of the world',
1441-
sensor_params=dict(
1442-
type=mujoco.mjtSensor.mjSENS_CONTACT,
1443-
reftype=mujoco.mjtObj.mjOBJ_XBODY,
1444-
refname='non_root',
1445-
),
1446-
),
14471431
]
14481432

14491433
for params in test_cases:

src/engine/engine_sensor.c

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,14 @@ static int checkMatch(const mjModel* m, int body, int geom, mjtObj type, int id)
247247
if (type == mjOBJ_SITE) return 1; // already passed site filter test
248248
if (type == mjOBJ_GEOM) return id == geom;
249249
if (type == mjOBJ_BODY) return id == body;
250-
if (type == mjOBJ_XBODY) return body >= 0 && m->body_rootid[id] == m->body_rootid[body];
250+
if (type == mjOBJ_XBODY) {
251+
// traverse up the tree from body, return true if we land on id
252+
while (body > id) {
253+
body = m->body_parentid[body];
254+
}
255+
return body == id;
256+
}
257+
251258
return 0;
252259
}
253260

src/user/user_objects.cc

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7152,11 +7152,6 @@ void mjCSensor::Compile(void) {
71527152
throw mjCError(this, "first matching criterion: if set, must be (x)body, geom or site");
71537153
}
71547154

7155-
// check that subtree1 is a full tree
7156-
if (objtype == mjOBJ_XBODY && static_cast<mjCBody*>(obj)->GetParent()->id != 0) {
7157-
throw mjCError(this, "subtree1 must be a child of the world");
7158-
}
7159-
71607155
// check second matching criterion
71617156
if (reftype != mjOBJ_BODY &&
71627157
reftype != mjOBJ_XBODY &&
@@ -7165,11 +7160,6 @@ void mjCSensor::Compile(void) {
71657160
throw mjCError(this, "second matching criterion: if set, must be (x)body or geom");
71667161
}
71677162

7168-
// check that subtree2 is a full tree
7169-
if (reftype == mjOBJ_XBODY && static_cast<mjCBody*>(ref)->GetParent()->id != 0) {
7170-
throw mjCError(this, "subtree2 must be a child of the world");
7171-
}
7172-
71737163
// check for dataspec correctness
71747164
int dataspec = intprm[0];
71757165
if (dataspec <= 0) {

test/engine/engine_sensor_test.cc

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -708,8 +708,6 @@ TEST_F(SensorTest, BadContact) {
708708
"at most one of (geom1, body1, subtree1, site) can be specified"},
709709
{"geom2='sphere1' body2='body'",
710710
"at most one of (geom2, body2, subtree2) can be specified"},
711-
{"subtree1='non_root'",
712-
"must be a child of the world"}
713711
};
714712

715713
for (const auto& test : test_cases) {
@@ -870,6 +868,31 @@ TEST_F(SensorTest, ContactSubtree) {
870868
mj_deleteModel(model);
871869
}
872870

871+
TEST_F(SensorTest, ContactSubtreePartial) {
872+
const string xml_path =
873+
GetTestDataFilePath("engine/testdata/sensor/contact_subtree_partial.xml");
874+
char error[1024];
875+
mjModel* model = mj_loadXML(xml_path.c_str(), nullptr, error, sizeof(error));
876+
ASSERT_THAT(model, NotNull()) << error;
877+
878+
mjData* data = mj_makeData(model);
879+
880+
while (data->time < 0.6) {
881+
mj_step(model, data);
882+
}
883+
884+
EXPECT_EQ(GetSensor(model, data, "all")[0], 4);
885+
EXPECT_EQ(GetSensor(model, data, "world")[0], 4);
886+
EXPECT_EQ(GetSensor(model, data, "thigh")[0], 4);
887+
EXPECT_EQ(GetSensor(model, data, "shin")[0], 2);
888+
EXPECT_EQ(GetSensor(model, data, "foot")[0], 1);
889+
EXPECT_EQ(GetSensor(model, data, "foot_w")[0], 0);
890+
EXPECT_EQ(GetSensor(model, data, "foot_w2")[0], 1);
891+
892+
mj_deleteData(data);
893+
mj_deleteModel(model);
894+
}
895+
873896
TEST_F(SensorTest, ContactNet) {
874897
const string xml_path =
875898
GetTestDataFilePath("engine/testdata/sensor/contact_net.xml");
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<mujoco model="contact subtree">
2+
<worldbody>
3+
<light pos="0 0 5"/>
4+
<geom name="floor" type="plane" size="2 2 .01"/>
5+
<body name="thigh" pos="-1 0 .1">
6+
<joint type="slide"/>
7+
<geom type="capsule" size=".1" fromto="0 0 0 .5 0 0"/>
8+
<body name="shin" pos=".7 0 0">
9+
<joint axis="0 1 0"/>
10+
<geom type="capsule" size=".1" fromto="0 0 0 0 0 .5"/>
11+
<body name="foot" pos="0 0 .7">
12+
<joint axis="0 1 0"/>
13+
<geom type="capsule" size=".1" fromto="0 0 0 -.7 0 0"/>
14+
</body>
15+
</body>
16+
</body>
17+
</worldbody>
18+
19+
<sensor>
20+
<contact name="all"/>
21+
<contact name="world" subtree1="world"/>
22+
<contact name="thigh" subtree1="thigh"/>
23+
<contact name="shin" subtree1="shin"/>
24+
<contact name="foot" subtree1="foot"/>
25+
<contact name="foot_w" subtree1="foot" body2="world"/>
26+
<contact name="foot_w2" subtree1="foot" subtree2="world"/>
27+
</sensor>
28+
</mujoco>

0 commit comments

Comments
 (0)