Add dof="2d" option to flexcomp for in-plane deformations.

PiperOrigin-RevId: 902620719
Change-Id: Ib06d3f7b9439e1d90a8373a4289ade0c327e72e4
This commit is contained in:
Alessio Quaglino
2026-04-20 07:13:17 -07:00
committed by Copybara-Service
parent cad734ae70
commit 2d12dee025
6 changed files with 513 additions and 2 deletions
+5 -1
View File
@@ -3593,7 +3593,7 @@ saving the XML:
.. _body-flexcomp-dof:
:at:`dof`: :at-val:`[full, radial, trilinear, quadratic], "full"`
:at:`dof`: :at-val:`[full, radial, trilinear, quadratic, 2d], "full"`
The parametrization of the flex's degrees of freedom (dofs). See the video on the right illustrating the
different parametrizations with deformable spheres. The three models in the video are respectively
`sphere_full <https://github.com/google-deepmind/mujoco/blob/main/model/flex/sphere_full.xml>`__,
@@ -3608,6 +3608,10 @@ saving the XML:
requires a free joint at the flex's parent in order for free body motion to be possible. This type of
parametrization is appropriate for shapes that are relatively spherical.
**2d**
Two orthogonal translational dofs (X and Y) per vertex. This restricts the motion of the vertices to planes
parallel to the parent body's X-Y plane.
**trilinear**
Three translational dofs at each corner of the bounding box of the flex, for a total of 24 dofs for the entire
flex, independent of the number of vertices. The positions of the vertices are updated using trilinear
+429
View File
@@ -0,0 +1,429 @@
<!-- Copyright 2026 DeepMind Technologies Limited
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<mujoco model="Gripper 2D Fin Ray">
<include file="scene.xml"/>
<option cone="elliptic" impratio="10" integrator="implicitfast"/>
<worldbody>
<body name="hand" pos="0 0 .37">
<joint name="lift" type="slide" range="-.13 .05"/>
<geom type="box" size=".12 .06 .03" rgba=".2 .2 .2 1"/>
<body name="right_arm">
<joint name="right" type="slide" axis="-1 0 0"/>
<geom type="box" size=".015 .06 .06" pos=".10 0 -.09"
rgba=".2 .2 .2 1"/>
<body name="right_finger" pos=".1 0 -.15" euler="90 0 0">
<!--
Fin Ray skeleton mesh (right finger).
The mesh is the OUTLINE of the fin ray structure:
two tapered vertical walls connected by horizontal ribs.
Each strut is a thin strip of 2 triangles; the interior
between ribs is empty. The radius property inflates the
visual geometry to give the skeleton thickness.
Vertex layout (XY plane, 7 rib levels, 4 verts per level):
Level i: L_front, L_back, R_front, R_back
Each pair offset by h=0.0015 along Y to create in-plane thickness.
Out-of-plane thickness set with radius parameter.
Side view of skeleton:
|===================| level 0 (base, pinned)
| |
|=================| level 1
| |
|===============| level 2
| |
|============| level 3
| |
|==========| level 4
| |
|========| level 5
| |
|=====| level 6
-->
<flexcomp name="right_fin" type="direct" dim="2"
radius=".004" rgba="0 .7 .7 1" mass=".3" dof="2d"
point="
-0.0210 0.0015 0.0000
-0.0170 0.0015 0.0000
-0.020538 -0.0015 0.0000
-0.016538 -0.0015 0.0000
0.0170 0.0015 0.0000
0.0210 0.0015 0.0000
0.016538 -0.0015 0.0000
0.020538 -0.0015 0.0000
-0.0190 -0.0115 0.0000
-0.0150 -0.0115 0.0000
-0.018538 -0.0145 0.0000
-0.014538 -0.0145 0.0000
0.0150 -0.0115 0.0000
0.0190 -0.0115 0.0000
0.014538 -0.0145 0.0000
0.018538 -0.0145 0.0000
-0.0170 -0.0245 0.0000
-0.0130 -0.0245 0.0000
-0.016308 -0.0275 0.0000
-0.012308 -0.0275 0.0000
0.0130 -0.0245 0.0000
0.0170 -0.0245 0.0000
0.012308 -0.0275 0.0000
0.016308 -0.0275 0.0000
-0.0140 -0.0375 0.0000
-0.0100 -0.0375 0.0000
-0.013308 -0.0405 0.0000
-0.009308 -0.0405 0.0000
0.0100 -0.0375 0.0000
0.0140 -0.0375 0.0000
0.009308 -0.0405 0.0000
0.013308 -0.0405 0.0000
-0.0110 -0.0505 0.0000
-0.0070 -0.0505 0.0000
-0.010308 -0.0535 0.0000
-0.006308 -0.0535 0.0000
0.0070 -0.0505 0.0000
0.0110 -0.0505 0.0000
0.006308 -0.0535 0.0000
0.010308 -0.0535 0.0000
-0.0080 -0.0635 0.0000
-0.0040 -0.0635 0.0000
-0.007200 -0.0665 0.0000
-0.003200 -0.0665 0.0000
0.0040 -0.0635 0.0000
0.0080 -0.0635 0.0000
0.003200 -0.0665 0.0000
0.007200 -0.0665 0.0000
-0.0040 -0.0785 0.0000
-0.0001 -0.0785 0.0000
-0.003200 -0.0815 0.0000
0.000700 -0.0815 0.0000
0.0001 -0.0785 0.0000
0.0040 -0.0785 0.0000
-0.000700 -0.0815 0.0000
0.003200 -0.0815 0.0000
"
element="
0 2 3
0 3 1
4 6 7
4 7 5
1 3 6
1 6 4
8 10 11
8 11 9
12 14 15
12 15 13
9 11 14
9 14 12
16 18 19
16 19 17
20 22 23
20 23 21
17 19 22
17 22 20
24 26 27
24 27 25
28 30 31
28 31 29
25 27 30
25 30 28
32 34 35
32 35 33
36 38 39
36 39 37
33 35 38
33 38 36
40 42 43
40 43 41
44 46 47
44 47 45
41 43 46
41 46 44
48 50 51
48 51 49
52 54 55
52 55 53
49 51 54
49 54 52
2 8 9
2 9 3
6 12 13
6 13 7
10 16 17
10 17 11
14 20 21
14 21 15
18 24 25
18 25 19
22 28 29
22 29 23
26 32 33
26 33 27
30 36 37
30 37 31
34 40 41
34 41 35
38 44 45
38 45 39
42 48 49
42 49 43
46 52 53
46 53 47
">
<edge equality="true" damping="0.02"
solimp="0.99 0.999 0.0001 0.5 2" solref="0.001 1"/>
<contact selfcollide="none" internal="false"
contype="2" conaffinity="2"/>
<pin id="0 1 2 3 4 5 6 7"/>
</flexcomp>
</body>
</body>
<body name="left_arm">
<joint name="left" type="slide" axis="1 0 0"/>
<geom type="box" size=".015 .06 .06" pos="-.10 0 -.09"
rgba=".2 .2 .2 1"/>
<body name="left_finger" pos="-.1 0 -.15" euler="90 0 0">
<flexcomp name="left_fin" type="direct" dim="2"
radius=".004" rgba="0 .7 .7 1" mass=".3" dof="2d"
point="
-0.0210 0.0015 0.0000
-0.0170 0.0015 0.0000
-0.020538 -0.0015 0.0000
-0.016538 -0.0015 0.0000
0.0170 0.0015 0.0000
0.0210 0.0015 0.0000
0.016538 -0.0015 0.0000
0.020538 -0.0015 0.0000
-0.0190 -0.0115 0.0000
-0.0150 -0.0115 0.0000
-0.018538 -0.0145 0.0000
-0.014538 -0.0145 0.0000
0.0150 -0.0115 0.0000
0.0190 -0.0115 0.0000
0.014538 -0.0145 0.0000
0.018538 -0.0145 0.0000
-0.0170 -0.0245 0.0000
-0.0130 -0.0245 0.0000
-0.016308 -0.0275 0.0000
-0.012308 -0.0275 0.0000
0.0130 -0.0245 0.0000
0.0170 -0.0245 0.0000
0.012308 -0.0275 0.0000
0.016308 -0.0275 0.0000
-0.0140 -0.0375 0.0000
-0.0100 -0.0375 0.0000
-0.013308 -0.0405 0.0000
-0.009308 -0.0405 0.0000
0.0100 -0.0375 0.0000
0.0140 -0.0375 0.0000
0.009308 -0.0405 0.0000
0.013308 -0.0405 0.0000
-0.0110 -0.0505 0.0000
-0.0070 -0.0505 0.0000
-0.010308 -0.0535 0.0000
-0.006308 -0.0535 0.0000
0.0070 -0.0505 0.0000
0.0110 -0.0505 0.0000
0.006308 -0.0535 0.0000
0.010308 -0.0535 0.0000
-0.0080 -0.0635 0.0000
-0.0040 -0.0635 0.0000
-0.007200 -0.0665 0.0000
-0.003200 -0.0665 0.0000
0.0040 -0.0635 0.0000
0.0080 -0.0635 0.0000
0.003200 -0.0665 0.0000
0.007200 -0.0665 0.0000
-0.0040 -0.0785 0.0000
-0.0001 -0.0785 0.0000
-0.003200 -0.0815 0.0000
0.000700 -0.0815 0.0000
0.0001 -0.0785 0.0000
0.0040 -0.0785 0.0000
-0.000700 -0.0815 0.0000
0.003200 -0.0815 0.0000
"
element="
0 2 3
0 3 1
4 6 7
4 7 5
1 3 6
1 6 4
8 10 11
8 11 9
12 14 15
12 15 13
9 11 14
9 14 12
16 18 19
16 19 17
20 22 23
20 23 21
17 19 22
17 22 20
24 26 27
24 27 25
28 30 31
28 31 29
25 27 30
25 30 28
32 34 35
32 35 33
36 38 39
36 39 37
33 35 38
33 38 36
40 42 43
40 43 41
44 46 47
44 47 45
41 43 46
41 46 44
48 50 51
48 51 49
52 54 55
52 55 53
49 51 54
49 54 52
2 8 9
2 9 3
6 12 13
6 13 7
10 16 17
10 17 11
14 20 21
14 21 15
18 24 25
18 25 19
22 28 29
22 29 23
26 32 33
26 33 27
30 36 37
30 37 31
34 40 41
34 41 35
38 44 45
38 45 39
42 48 49
42 49 43
46 52 53
46 53 47
">
<edge equality="true" damping="0.02"
solimp="0.99 0.999 0.0001 0.5 2" solref="0.001 1"/>
<contact selfcollide="none" internal="false"
contype="2" conaffinity="2"/>
<pin id="0 1 2 3 4 5 6 7"/>
</flexcomp>
</body>
</body>
</body>
<body>
<freejoint/>
<geom type="cylinder" size=".025 .05" pos="0 0 .1" rgba=".5 .5 0 1" euler="90 0 0" mass=".01"
priority="1" contype="2" condim="6" friction="2" solref="0.001 1" solimp="0.99 0.999 0.0001 0.5 2"/>
</body>
</worldbody>
<equality>
<joint joint1="right" joint2="left"/>
</equality>
<tendon>
<fixed name="grasp">
<joint joint="right" coef="1"/>
<joint joint="left" coef="1"/>
</fixed>
</tendon>
<actuator>
<position name="lift" joint="lift" kp="600" dampratio="1"
ctrlrange="-1 1"/>
<position name="grasp" tendon="grasp" kp="200" dampratio="1"
ctrlrange="0 1"/>
</actuator>
</mujoco>
+11
View File
@@ -613,6 +613,17 @@ bool mjCFlexcomp::Make(mjsBody* body, char* error, int error_sz, const mjVFS* vf
}
}
// add two orthogonal sliders (x and y only)
else if (doftype == mjFCOMPDOF_2D) {
for (int j=0; j < 2; j++) {
mjsJoint* jnt = mjs_addJoint(pb, 0);
jnt->type = mjJNT_SLIDE;
mjuu_setvec(jnt->pos, 0, 0, 0);
mjuu_setvec(jnt->axis, 0, 0, 0);
jnt->axis[j] = 1;
}
}
// construct body name, add to vertbody
char txt[100];
mju::sprintf_arr(txt, "%s_%d", name.c_str(), i);
+1
View File
@@ -45,6 +45,7 @@ typedef enum _mjtDof {
mjFCOMPDOF_RADIAL,
mjFCOMPDOF_TRILINEAR,
mjFCOMPDOF_QUADRATIC,
mjFCOMPDOF_2D,
mjNFCOMPDOFS
} mjtDof;
+2 -1
View File
@@ -932,7 +932,8 @@ const mjMap fdof_map[mjNFCOMPDOFS] = {
{"full", mjFCOMPDOF_FULL},
{"radial", mjFCOMPDOF_RADIAL},
{"trilinear", mjFCOMPDOF_TRILINEAR},
{"quadratic", mjFCOMPDOF_QUADRATIC}
{"quadratic", mjFCOMPDOF_QUADRATIC},
{"2d", mjFCOMPDOF_2D}
};
+65
View File
@@ -1227,6 +1227,71 @@ TEST_F(UserFlexTest, TotalMassQuadratic) {
mj_deleteModel(m);
}
TEST_F(UserFlexTest, Dof2d) {
// 3x3 grid with dof="2d": 9 vertices, 2 DOFs each -> nv = 18
static constexpr char xml_2d[] = R"(
<mujoco>
<worldbody>
<flexcomp name="test" type="grid" count="3 3 1" spacing=".1 .1 .1"
dim="2" radius=".01" dof="2d">
<edge equality="true"/>
</flexcomp>
</worldbody>
</mujoco>
)";
// same model with dof="full" for comparison: 9 vertices, 3 DOFs each -> nv = 27
static constexpr char xml_full[] = R"(
<mujoco>
<worldbody>
<flexcomp name="test" type="grid" count="3 3 1" spacing=".1 .1 .1"
dim="2" radius=".01">
<edge equality="true"/>
</flexcomp>
</worldbody>
</mujoco>
)";
std::array<char, 1024> error;
// load 2d model
mjModel* m_2d = LoadModelFromString(xml_2d, error.data(), error.size());
ASSERT_THAT(m_2d, NotNull()) << error.data();
mjData* d_2d = mj_makeData(m_2d);
// load full model
mjModel* m_full = LoadModelFromString(xml_full, error.data(), error.size());
ASSERT_THAT(m_full, NotNull()) << error.data();
mjData* d_full = mj_makeData(m_full);
// verify DOF counts
EXPECT_EQ(m_2d->nv, 18); // 9 vertices * 2 DOFs
EXPECT_EQ(m_full->nv, 27); // 9 vertices * 3 DOFs
// same number of vertices and elements
EXPECT_EQ(m_2d->nflexvert, m_full->nflexvert);
EXPECT_EQ(m_2d->nflexelem, m_full->nflexelem);
// each body has 2 DOFs in 2d mode, 3 in full mode
for (int i = 1; i < m_2d->nbody; i++) {
EXPECT_EQ(m_2d->body_dofnum[i], 2) << "body " << i;
}
for (int i = 1; i < m_full->nbody; i++) {
EXPECT_EQ(m_full->body_dofnum[i], 3) << "body " << i;
}
// simulate a few steps to make sure nothing crashes
for (int i = 0; i < 10; i++) {
mj_step(m_2d, d_2d);
mj_step(m_full, d_full);
}
mj_deleteModel(m_2d);
mj_deleteModel(m_full);
mj_deleteData(d_2d);
mj_deleteData(d_full);
}
} // namespace
} // namespace mujoco