Cartesian end-effector control using site transmission.

- Added `refsite` attribute to actuators with `site` transmission.
- Such actuators now have a well defined length and can be used for e.g., Cartesian end-effector control.
- Added example models and documentation describing the new feature.

PiperOrigin-RevId: 471561218
Change-Id: I538f09af9600d5c53992c4a9fed325648aab4fa3
This commit is contained in:
Yuval Tassa
2022-09-01 10:06:11 -07:00
committed by Copybara-Service
parent 834e8dd506
commit 46da1285af
10 changed files with 283 additions and 55 deletions
+30 -13
View File
@@ -4302,25 +4302,42 @@ specify them independently.
:at:`joint`: :at-val:`string, optional`
This and the next four attributes determine the type of actuator transmission. All of them are optional, and exactly
one of them must be specified. If this attribute is specified, the actuator acts on the given joint. For **hinge**
and **slide** joints, the actuator length equals the joint position/angle times the first element of gear. For
and **slide** joints, the actuator length equals the joint position/angle times the first element of :at:`gear`. For
**ball** joints, the first three elements of gear define a 3d rotation axis in the child frame around which the
actuator produces torque. The actuator length is defined as the dot-product between this gear axis and the angle-axis
representation of the joint quaternion position. For **free** joints, gear defines a 3d translation axis in the world
frame followed by a 3d rotation axis in the child frame. The actuator generates force and torque relative to the
specified axes. The actuator length for free joints is defined as zero (so it should not be used with position
servos).
representation of the joint quaternion, and is in units of radian if :at:`gear` is normalized (generally scaled by
by the norm of :at:`gear`). Note that after total rotation of more than :math:`\pi`, the length will wrap to :math:`-
\pi`, and vice-versa. Therefore :el:`position` servos for ball joints should generally use tighter limits which
prevent this wrapping. For **free** joints, gear defines a 3d translation axis in the world frame followed by a 3d
rotation axis in the child frame. The actuator generates force and torque relative to the specified axes. The
actuator length for free joints is defined as zero (so it should not be used with position servos).
:at:`jointinparent`: :at-val:`string, optional`
Identical to joint, except that for ball and free joints, the 3d rotation axis given by gear is defined in the parent
frame (which is the world frame for free joints) rather than the child frame.
:at:`site`: :at-val:`string, optional`
This transmission can apply force and torque at a site. The gear vector defines a 3d translation axis followed by a 3d
rotation axis. Both are defined in the site's frame. This can be used to model jets and propellers. The effect is
similar to actuating a free joint, and the actuator length is again defined as zero. One difference from the joint
and jointinparent transmissions above is that here the actuator operates on a site rather than a joint, but this
difference disappears when the site is defined at the frame origin of the free-floating body. The other difference is
that for site transmissions both the translation and rotation axes are defined in local coordinates. In contrast,
translation is global and rotation is local for joint, and both translation and rotation are global for
jointinparent.
This transmission can apply force and torque at a site. The gear vector defines a 3d translation axis followed by a
3d rotation axis. Both are defined in the site's frame. This can be used to model jets and propellers. The effect is
similar to actuating a free joint, and the actuator length is defined as zero unless a :at:`refsite` is defined (see
below). One difference from the :at:`joint` and :at:`jointinparent` transmissions above is that here the actuator
operates on a site rather than a joint, but this difference disappears when the site is defined at the frame origin
of the free-floating body. The other difference is that for site transmissions both the translation and rotation axes
are defined in local coordinates. In contrast, translation is global and rotation is local for :at:`joint`, and both
translation and rotation are global for :at:`jointinparent`.
.. youtube:: s-0JHanqV1A
:align: right
:height: 150px
:at:`refsite`: :at-val:`string, optional`
When using a :at:`site` transmission, measure the translation and rotation w.r.t the frame of the :at:`refsite`. In
this case the actuator *does* have length and :el:`position` actuators can be used to directly control an end
effector, see `refsite.xml <https://github.com/deepmind/mujoco/tree/main/test/engine/testdata/refsite.xml>`_ example
model. As above, the length is the dot product of the :at:`gear` vector and the frame difference. So ``gear="0 1 0 0
0 0"`` means "Y-offset of :at:`site` in the :at:`refsite` frame", while ``gear="0 0 0 0 0 1"`` means rotation "Z-
rotation of :at:`site` in the :at:`refsite` frame". It is recommended to use a normalized :at:`gear` vector with
nonzeros in only the first 3 *or* the last 3 elements of :at:`gear`, so the actuator length will be in either length
units or radians, respectively. As with ball joints (see :at:`joint` above), for rotations which exceed a total angle
of :math:`\pi` will wrap around, so tighter limits are recommended.
:at:`body`: :at-val:`string, optional`
This transmission can apply linear forces at contact points in the direction of the contact normal. The set of
contacts is all those belonging to the specified :at:`body`. This can be used to model natural active adhesion
+10 -2
View File
@@ -21,8 +21,16 @@ General
- Increased ``mjNEQDATA``, the row length of equality constraint parameters in ``mjModel.eq_data``, from 7 to 11.
- Added visualisation of anchor points for both :el:`connect` and :el:`weld` constraints (activated by the 'N' key in
``simulate``).
- Added `example model <https://github.com/deepmind/mujoco/tree/main/test/engine/testdata/weld.xml>`_ showing different
- Added `weld.xml <https://github.com/deepmind/mujoco/tree/main/test/engine/testdata/weld.xml>`_ showing different
uses of new weld attributes.
.. youtube:: s-0JHanqV1A
:align: right
:height: 150px
- Cartesian 6D end-effector control is now possible by adding a reference site to actuators with :at:`site`
transmission. See description of new :at:`refsite` attribute in the :ref:`actuator<general>` documentation and
`refsite.xml <https://github.com/deepmind/mujoco/tree/main/test/engine/testdata/refsite.xml>`_ example model.
- Joint and tendon ``limited`` attribute and actuator ``ctrllimited``, ``forcelimited`` and ``actlimited`` attributes
now default to ``auto`` rather than ``false``. Limits are automatically set to ``true`` if the corresponding range *is
defined* and ``false`` otherwise.
@@ -49,7 +57,7 @@ General
- Added ``mjv_defaultFreeCamera`` which sets the default free camera, respecting the above attributes.
- ``simulate`` now supports taking a screenshot via a button in the File section or via ``Ctrl-P``.
- Improvements to time synchronisation in `simulate`, in particular report actual real-time factor if different from
requested factor.
requested factor (if e.g., the timestep is so small that simulation cannot keep up with real-time).
- Added a disable flag for sensors.
- :ref:`mju_mulQuat` and :ref:`mju_mulQuatAxis` support in place computation. For example
|br| ``mju_mulQuat(a, a, b);`` sets the quaternion ``a`` equal to the product of ``a`` and ``b``.
+25 -12
View File
@@ -255,14 +255,20 @@ These three components of an actuator - transmission, activation dynamics, and f
actuator works. The user can set them independently for maximum flexibility, or use :ref:`Actuator shortcuts
<CActuator>` which instantiate common actuator types.
Transmission
.. _geTransmission:
Each actuator has a scalar length :math:`l_i(q)` defined by the type of transmission and its parameters. The gradient
:math:`\nabla l_i` is an :math:`n_V`-dimensional vector of moment arms. It determines the mapping from scalar
actuator force to joint force. The transmission properties are determined by the MuJoCo object to which the actuator
is attached; the possible attachment object types are :at:`joint`, :at:`tendon`, :at:`jointinparent`,
:at:`slider-crank`, :at:`site`, and :at:`body`. The :at:`joint` and :at:`tendon` transmission types act as expected
mechanically and correspond to the actuator applying forces or torques to the target object.
Transmission
~~~~~~~~~~~~
Each actuator has a scalar length :math:`l_i(q)` defined by the type of transmission and its parameters. The gradient
:math:`\nabla l_i` is an :math:`n_V`-dimensional vector of moment arms. It determines the mapping from scalar
actuator force to joint force. The transmission properties are determined by the MuJoCo object to which the actuator
is attached; the possible attachment object types are :at:`joint`, :at:`tendon`, :at:`jointinparent`,
:at:`slider-crank`, :at:`site`, and :at:`body`.
The :at:`joint` and :at:`tendon` transmission types act as expected and correspond to the actuator applying forces or
torques to the target object. Ball joints are special, see the :at:`joint` documentation in :ref:`actuator<general>`
reference for more details.
The :at:`jointinparent` transmission is unique to ball and free joint and asserts that rotation should be measured
in the parent rather than child frame.
@@ -273,11 +279,18 @@ Transmission
also be modeled explicitly by creating MuJoCo bodies and coupling them with equality constraints to the rest of the
system, but that would be less efficient.
:at:`site` and :at:`body` are degenerate transmission targets, as their length :math:`l_i(q)` is always 0.
They can therefore not be used to maintain a desired length value, as with a position actuator. Site
transmissions correspond to applying a Cartsian force/torque at the site, while :el:`body` transmissions correspond
to applying forces at contact points belonging to a body. For more information about adhesion, see the
:ref:`adhesion<adhesion>` shorcut documentation.
:at:`site` transmission (without a :at:`refsite`, see below) and :at:`body` transmission targets have a fixed zero
length :math:`l_i(q) = 0`. They can therefore not be used to maintain a desired length, but can be used to apply
forces. Site transmissions correspond to applying a Cartsian force/torque at the site, and are useful for modeling
jets and propellors. :el:`body` transmissions correspond to applying forces at contact points belonging to a body, in
order to model vacuum grippers and biomechanical adhesive appendages. For more information about adhesion, see the
:ref:`adhesion<adhesion>` actuator documentation.
If a :at:`site` transmission target is defined with the optional :at:`refsite` attribute, forces and torques are
applied in the frame of the reference site rather than the the site's own frame. If a reference site is defined then
the length of the actuator is nonzero and corresponds to the pose difference of the two sites. This length can then
be controlled with a :el:`position` actuator, enabling Cartesian end-effector control. See the :at:`refsite`
documentation in :ref:`actuator<general>` reference for more details.
Activation dynamics
Some actuators such as pneumatic and hydraulic cylinders as well as biological muscles have an internal state called
+77 -12
View File
@@ -614,6 +614,7 @@ void mj_transmission(const mjModel* m, mjData* d) {
mjtNum wrench[6], gearAxis[3];
mjtNum *jac, *jacA, *jacS;
mjtNum *length = d->actuator_length, *moment = d->actuator_moment, *gear;
mjtNum *jacref = NULL, *moment_tmp = NULL; // required for site actuators
mjMARKSTACK;
if (!nu) {
@@ -763,7 +764,7 @@ void mj_transmission(const mjModel* m, mjData* d) {
case mjTRN_TENDON: // tendon
length[i] = d->ten_length[id]*gear[0];
// moment: dense or sparse
// moment: sparse or dense
if (mj_isSparse(m)) {
int end = d->ten_J_rowadr[id] + d->ten_J_rownnz[id];
for (int j=d->ten_J_rowadr[id]; j<end; j++) {
@@ -775,20 +776,84 @@ void mj_transmission(const mjModel* m, mjData* d) {
break;
case mjTRN_SITE: // site
// cannot compute meaningful length, set to 0
length[i] = 0;
// get site translation and rotation global Jacobians
// get site translation (jac) and rotation (jacS) Jacobians in global frame
mj_jacSite(m, d, jac, jacS, id);
// wrench: site gear vector in global coordinates
mju_mulMatVec(wrench, d->site_xmat+9*id, gear, 3, 3); // translation
mju_mulMatVec(wrench+3, d->site_xmat+9*id, gear+3, 3, 3); // rotation
// reference site undefined
if (m->actuator_trnid[2*i+1] == -1) {
// cannot compute meaningful length, set to 0
length[i] = 0;
// wrench: gear expressed in global frame
mju_rotVecMat(wrench, gear, d->site_xmat+9*id); // translation
mju_rotVecMat(wrench+3, gear+3, d->site_xmat+9*id); // rotation
// moment: global Jacobian projected on wrench
mju_mulMatTVec(moment+i*nv, jac, wrench, 3, nv); // translation
mju_mulMatTVec(jac, jacS, wrench+3, 3, nv); // rotation
mju_addTo(moment+i*nv, jac, nv); // add the two
}
// reference site defined
else {
int refid = m->actuator_trnid[2*i+1];
if (!jacref) jacref = mj_stackAlloc(d, 3*nv);
// clear length
length[i] = 0;
// translational transmission
if (!mju_isZero(gear, 3)) {
// vec: site position in reference site frame
mju_sub3(vec, d->site_xpos+3*id, d->site_xpos+3*refid);
mju_rotVecMatT(vec, vec, d->site_xmat+9*refid);
// length: dot product with gear
length[i] += mju_dot3(vec, gear);
// jacref: global Jacobian of reference site
mj_jacSite(m, d, jacref, NULL, refid);
// subtract jacref from jac
mju_subFrom(jac, jacref, 3*nv);
// wrench: translational gear expressed in global frame
mju_rotVecMat(wrench, gear, d->site_xmat+9*refid);
// moment: global Jacobian projected on wrench
mju_mulMatTVec(moment+i*nv, jac, wrench, 3, nv);
}
// rotational transmission
if (!mju_isZero(gear+3, 3)) {
mjtNum refquat[4];
// get site and refsite quats from parent bodies (avoiding mju_mat2Quat)
mju_mulQuat(quat, m->site_quat+4*id, d->xquat+4*m->site_bodyid[id]);
mju_mulQuat(refquat, m->site_quat+4*refid, d->xquat+4*m->site_bodyid[refid]);
// convert difference to expmap (axis-angle)
mju_subQuat(vec, quat, refquat);
// add length: dot product with gear
length[i] += mju_dot3(vec, gear+3);
// jacref: global rotational Jacobian of reference site
mj_jacSite(m, d, NULL, jacref, refid);
// subtract jacref from jacS
mju_subFrom(jacS, jacref, 3*nv);
// wrench: rotational gear expressed in global frame
mju_rotVecMat(wrench, gear+3, d->site_xmat+9*refid);
// moment_tmp: global Jacobian projected on wrench, add to moment
if (!moment_tmp) moment_tmp = mj_stackAlloc(d, nv);
mju_mulMatTVec(moment_tmp, jacS, wrench, 3, nv);
mju_addTo(moment+i*nv, moment_tmp, nv);
}
}
// moment: global Jacobian projected on wrench
mju_mulMatTVec(moment+i*nv, jac, wrench, 3, nv); // translation
mju_mulMatTVec(jac, jacS, wrench+3, 3, nv); // rotation
mju_addTo(moment+i*nv, jac, nv); // add the two
break;
case mjTRN_BODY: // body (adhesive contacts)
+11 -1
View File
@@ -3358,6 +3358,7 @@ mjCActuator::mjCActuator(mjCModel* _model, mjCDef* _def) {
cranklength = 0;
target.clear();
slidersite.clear();
refsite.clear();
userdata.clear();
// clear private variables
@@ -3499,7 +3500,16 @@ void mjCActuator::Compile(void) {
break;
case mjTRN_SITE:
// get site
// get refsite, copy into trnid[1]
if (!refsite.empty()) {
ptarget = model->FindObject(mjOBJ_SITE, refsite);
if (!ptarget) {
throw mjCError(this, "reference site '%s' not found for actuator %d", refsite.c_str(), id);
}
trnid[1] = ptarget->id;
}
// proceed with regular site target
ptarget = model->FindObject(mjOBJ_SITE, target);
break;
+1
View File
@@ -863,6 +863,7 @@ class mjCActuator : public mjCBase {
std::vector<double> userdata; // user data
std::string target; // transmission target name
std::string slidersite; // site defining cylinder, for slider-crank only
std::string refsite; // reference site, for site transmission only
private:
mjCActuator(mjCModel* = 0, mjCDef* = 0);// constructor
+21 -15
View File
@@ -265,40 +265,40 @@ static const char* MJCF[nMJCF][mjXATTRNUM] = {
{"actuator", "*", "0"},
{"<"},
{"general", "*", "26", "name", "class", "group",
{"general", "*", "27", "name", "class", "group",
"ctrllimited", "forcelimited", "actlimited", "ctrlrange", "forcerange", "actrange",
"lengthrange", "gear", "cranklength", "user",
"joint", "jointinparent", "tendon", "slidersite", "cranksite", "site", "body",
"dyntype", "gaintype", "biastype", "dynprm", "gainprm", "biasprm"},
{"motor", "*", "17", "name", "class", "group",
"joint", "jointinparent", "tendon", "slidersite", "cranksite", "site", "refsite",
"body", "dyntype", "gaintype", "biastype", "dynprm", "gainprm", "biasprm"},
{"motor", "*", "18", "name", "class", "group",
"ctrllimited", "forcelimited", "ctrlrange", "forcerange",
"lengthrange", "gear", "cranklength", "user",
"joint", "jointinparent", "tendon", "slidersite", "cranksite", "site"},
{"position", "*", "18", "name", "class", "group",
"joint", "jointinparent", "tendon", "slidersite", "cranksite", "site", "refsite"},
{"position", "*", "19", "name", "class", "group",
"ctrllimited", "forcelimited", "ctrlrange", "forcerange",
"lengthrange", "gear", "cranklength", "user",
"joint", "jointinparent", "tendon", "slidersite", "cranksite", "site",
"joint", "jointinparent", "tendon", "slidersite", "cranksite", "site", "refsite",
"kp"},
{"velocity", "*", "18", "name", "class", "group",
{"velocity", "*", "19", "name", "class", "group",
"ctrllimited", "forcelimited", "ctrlrange", "forcerange",
"lengthrange", "gear", "cranklength", "user",
"joint", "jointinparent", "tendon", "slidersite", "cranksite", "site",
"joint", "jointinparent", "tendon", "slidersite", "cranksite", "site", "refsite",
"kv"},
{"intvelocity", "*", "19", "name", "class", "group",
{"intvelocity", "*", "20", "name", "class", "group",
"ctrllimited", "forcelimited",
"ctrlrange", "forcerange", "actrange", "lengthrange",
"gear", "cranklength", "user",
"joint", "jointinparent", "tendon", "slidersite", "cranksite", "site",
"joint", "jointinparent", "tendon", "slidersite", "cranksite", "site", "refsite",
"kp"},
{"damper", "*", "17", "name", "class", "group",
{"damper", "*", "18", "name", "class", "group",
"forcelimited", "ctrlrange", "forcerange",
"lengthrange", "gear", "cranklength", "user",
"joint", "jointinparent", "tendon", "slidersite", "cranksite", "site",
"joint", "jointinparent", "tendon", "slidersite", "cranksite", "site", "refsite",
"kv"},
{"cylinder", "*", "21", "name", "class", "group",
{"cylinder", "*", "22", "name", "class", "group",
"ctrllimited", "forcelimited", "ctrlrange", "forcerange",
"lengthrange", "gear", "cranklength", "user",
"joint", "jointinparent", "tendon", "slidersite", "cranksite", "site",
"joint", "jointinparent", "tendon", "slidersite", "cranksite", "site", "refsite",
"timeconst", "area", "diameter", "bias"},
{"muscle", "*", "25", "name", "class", "group",
"ctrllimited", "forcelimited", "ctrlrange", "forcerange",
@@ -1439,6 +1439,12 @@ void mjXReader::OneActuator(XMLElement* elem, mjCActuator* pact) {
throw mjXError(elem, "cranklength and slidersite can only be used in slidercrank transmission");
}
// site-specific parameters (refsite)
int r3 = ReadAttrTxt(elem, "refsite", pact->refsite);
if (r3 && pact->trntype!=mjTRN_SITE && pact->trntype!=mjTRN_UNDEFINED) {
throw mjXError(elem, "refsite can only be used with site transmission");
}
// get predefined type
type = elem->Value();
+1
View File
@@ -566,6 +566,7 @@ void mjXWriter::OneActuator(XMLElement* elem, mjCActuator* pact, mjCDef* def) {
case mjTRN_SITE:
WriteAttrTxt(elem, "site", pact->target);
WriteAttrTxt(elem, "refsite", pact->refsite);
break;
case mjTRN_BODY:
+46
View File
@@ -21,12 +21,16 @@
#include <gtest/gtest.h>
#include <mujoco/mjmodel.h>
#include <mujoco/mujoco.h>
#include "src/engine/engine_util_blas.h"
#include "src/engine/engine_util_spatial.h"
#include "test/fixture.h"
namespace mujoco {
namespace {
using ::testing::ElementsAre;
using ::testing::Pointwise;
using ::testing::DoubleNear;
using CoreSmoothTest = MujocoTest;
static std::vector<mjtNum> GetVector(const mjtNum* array, int length) {
@@ -195,6 +199,48 @@ TEST_F(CoreSmoothTest, WeldRatioMultipleConstraints) {
TestConnect(kModelFilePath);
}
// --------------------------- site actuators ----------------------------------
// Test Cartesian position control using site transmission with refsite
TEST_F(CoreSmoothTest, RefsiteBringsToPose) {
constexpr char kRefsitePath[] = "engine/testdata/refsite.xml";
const std::string xml_path = GetTestDataFilePath(kRefsitePath);
mjModel* model = mj_loadXML(xml_path.c_str(), nullptr, 0, 0);
mjData* data = mj_makeData(model);
// set pose target in ctrl (3 positions, 3 rotations)
mjtNum targetpos[] = {.01, .02, .03};
mjtNum targetrot[] = {.1, .2, .3};
mju_copy3(data->ctrl, targetpos);
mju_copy3(data->ctrl+3, targetrot);
// step for 5 seconds
while (data->time < 5) {
mj_step(model, data);
}
// get site IDs
int refsite_id = mj_name2id(model, mjOBJ_SITE, "reference");
int site_id = mj_name2id(model, mjOBJ_SITE, "end_effector");
// check that position matches target to within 1e-5 length units
double tol_pos = 1e-5;
mjtNum relpos[3];
mju_sub3(relpos, data->site_xpos+3*site_id, data->site_xpos+3*refsite_id);
EXPECT_THAT(relpos, Pointwise(DoubleNear(tol_pos), targetpos));
// check that orientation matches target to within 1e-3 radians
double tol_rot = 1e-3;
mjtNum site_xquat[4], refsite_xquat[4], relrot[3];
mju_mat2Quat(refsite_xquat, data->site_xmat+9*refsite_id);
mju_mat2Quat(site_xquat, data->site_xmat+9*site_id);
mju_subQuat(relrot, site_xquat, refsite_xquat);
EXPECT_THAT(relrot, Pointwise(DoubleNear(tol_rot), targetrot));
mj_deleteData(data);
mj_deleteModel(model);
}
// ------------------------ ellipsoid fluid model ------------------------------
+61
View File
@@ -0,0 +1,61 @@
<mujoco>
<!--
Adding a high fluid viscosity and using implicit integration for extra stabillity.
Extra stabillity is required because the abstract "arm" model is not very realistic.
(e.g. 4 consecutive ball joints is not a realistic kinematic design)
-->
<option viscosity="10" integrator="implicit">
<flag gravity="disable" contact="disable"/>
</option>
<statistic meansize=".05"/>
<default>
<default class="translation">
<position kp="100" ctrlrange="-.25 .25"/>
</default>
<default class="rotation">
<!--
Note that the rotational control range is purposefully limited to (-pi/2, pi/2) to avoid the
documented instabillity near pi, which is due to taking quaternion differences.
Increase this range to pi or bigger in order to see the instabillity.
See here for more details https://mujoco.readthedocs.io/en/latest/XMLreference.html#actuator
-->
<position kp=".2" ctrlrange="-1.571 1.571"/>
</default>
<joint damping=".01" stiffness=".0001"/>
<site type="box" size=".012 .012 .012" rgba=".7 .7 .8 1"/>
</default>
<worldbody>
<light pos="0 0 2"/>
<geom type="box" size=".25 .25 .01" pos="0 0 -.01"/>
<site name="reference" pos="0 0 .25"/>
<body name="arm" pos="-.25 .25 0">
<joint type="ball"/>
<geom type="box" size=".01" fromto="0 0 0 0 0 .25"/>
<body pos="0 0 .25">
<joint type="ball"/>
<geom type="box" size=".01" fromto="0 0 0 .25 0 0"/>
<body pos=".25 0 0">
<joint type="ball"/>
<geom type="box" size=".01" fromto="0 0 0 0 -.2 0"/>
<body pos="0 -.2 0">
<joint type="ball"/>
<geom type="box" size=".01" fromto="0 0 0 0 -.05 0"/>
<site name="end_effector" pos="0 -.05 0"/>
</body>
</body>
</body>
</body>
</worldbody>
<actuator>
<position name="x" site="end_effector" refsite="reference" gear="1 0 0 0 0 0" class="translation"/>
<position name="y" site="end_effector" refsite="reference" gear="0 1 0 0 0 0" class="translation"/>
<position name="z" site="end_effector" refsite="reference" gear="0 0 1 0 0 0" class="translation"/>
<position name="rx" site="end_effector" refsite="reference" gear="0 0 0 1 0 0" class="rotation"/>
<position name="ry" site="end_effector" refsite="reference" gear="0 0 0 0 1 0" class="rotation"/>
<position name="rz" site="end_effector" refsite="reference" gear="0 0 0 0 0 1" class="rotation"/>
</actuator>
</mujoco>