Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 19 additions & 8 deletions doc/XMLreference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6151,14 +6151,16 @@ applying the adhesive force. In the video above, such inactive contacts are blue
An adhesion actuator's length is always 0. :at:`ctrlrange` is required and must also be nonnegative (no repulsive forces
are allowed). The underlying :el:`general` attributes are set as follows:

=========== ======= =========== ========
Attribute Setting Attribute Setting
=========== ======= =========== ========
dyntype none dynprm 1 0 0
gaintype fixed gainprm gain 0 0
biastype none biasprm 0 0 0
trntype body ctrllimited true
=========== ======= =========== ========
============ ============= =========== ========
Attribute Setting Attribute Setting
============ ============= =========== ========
dyntype none/filter dynprm 1 0 0
gaintype fixed gainprm gain 0 0
biastype none biasprm 0 0 0
trntype body ctrllimited true
============ ============= =========== ========

If :at:`timeconst` is nonzero, :at:`dyntype` is set to ``filter`` and :at:`dynprm` to ``timeconst 0 0``.

This element has a subset of the common attributes and two custom attributes.

Expand Down Expand Up @@ -6200,6 +6202,13 @@ This element has a subset of the common attributes and two custom attributes.
value multiplied by the gain. This force is distributed equally between all the contacts involving geoms belonging
to the target body.

.. _actuator-adhesion-timeconst:

:at:`timeconst`: :at-val:`real, "0"`
Time constant for a first-order filter dynamics on the actuator activation. If positive, the actuator's
:at:`dyntype` is set to ``filter`` and the control signal is low-pass filtered with this time constant. If 0
(default), no filter dynamics are applied.


.. _actuator-plugin:

Expand Down Expand Up @@ -9713,6 +9722,8 @@ refsite, tendon, slidersite, cranksite.

.. _default-adhesion-gain:

.. _default-adhesion-timeconst:

.. _default-adhesion-user:

.. _default-adhesion-group:
Expand Down
3 changes: 2 additions & 1 deletion doc/includes/references.h
Original file line number Diff line number Diff line change
Expand Up @@ -3634,7 +3634,8 @@ const char* mjs_setToCylinder(mjsActuator* actuator, double timeconst,
const char* mjs_setToMuscle(mjsActuator* actuator, double timeconst[2], double tausmooth,
double range[2], double force, double scale, double lmin,
double lmax, double vmax, double fpmax, double fvmax);
const char* mjs_setToAdhesion(mjsActuator* actuator, double gain);
const char* mjs_setToAdhesion(mjsActuator* actuator, double gain,
double timeconst = 0);
mjsMesh* mjs_addMesh(mjSpec* s, const mjsDefault* def);
mjsHField* mjs_addHField(mjSpec* s);
mjsSkin* mjs_addSkin(mjSpec* s);
Expand Down
3 changes: 2 additions & 1 deletion include/mujoco/mujoco.h
Original file line number Diff line number Diff line change
Expand Up @@ -1704,7 +1704,8 @@ MJAPI const char* mjs_setToMuscle(mjsActuator* actuator, double timeconst[2], do
double lmax, double vmax, double fpmax, double fvmax);

// Set actuator to active adhesion; return error if any.
MJAPI const char* mjs_setToAdhesion(mjsActuator* actuator, double gain);
MJAPI const char* mjs_setToAdhesion(mjsActuator* actuator, double gain,
double timeconst);


//---------------------------------- Assets --------------------------------------------------------
Expand Down
4 changes: 4 additions & 0 deletions python/mujoco/introspect/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -10682,6 +10682,10 @@
name='gain',
type=ValueType(name='double'),
),
FunctionParameterDecl(
name='timeconst',
type=ValueType(name='double'),
),
),
doc='Set actuator to active adhesion; return error if any.',
)),
Expand Down
6 changes: 3 additions & 3 deletions python/mujoco/specs.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1296,13 +1296,13 @@ PYBIND11_MODULE(_specs, m) {
py::arg("vmax") = -1, py::arg("fpmax") = -1, py::arg("fvmax") = -1);
mjsActuator.def(
"set_to_adhesion",
[](raw::MjsActuator* self, double gain) {
std::string err = mjs_setToAdhesion(self, gain);
[](raw::MjsActuator* self, double gain, double timeconst) {
std::string err = mjs_setToAdhesion(self, gain, timeconst);
if (!err.empty()) {
throw pybind11::value_error(err);
}
},
py::arg("gain"));
py::arg("gain"), py::arg("timeconst") = 0);

// ============================= MJSTENDONPATH ===============================
// helper struct for tendon path indexing
Expand Down
10 changes: 9 additions & 1 deletion src/user/user_api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1052,14 +1052,22 @@ const char* mjs_setToMuscle(mjsActuator* actuator, double timeconst[2], double t


// Set to adhesion actuator.
const char* mjs_setToAdhesion(mjsActuator* actuator, double gain) {
const char* mjs_setToAdhesion(mjsActuator* actuator, double gain,
double timeconst) {
actuator->gainprm[0] = gain;
actuator->ctrllimited = 1;
actuator->gaintype = mjGAIN_FIXED;
actuator->biastype = mjBIAS_NONE;

if (timeconst > 0) {
actuator->dynprm[0] = timeconst;
actuator->dyntype = mjDYN_FILTER;
}

if (gain < 0)
return "adhesion gain cannot be negative";
if (timeconst < 0)
return "adhesion timeconst cannot be negative";
if (actuator->ctrlrange[0] < 0 || actuator->ctrlrange[1] < 0)
return "adhesion control range cannot be negative";
return "";
Expand Down
3 changes: 2 additions & 1 deletion src/user/user_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,8 @@ MJAPI const char* mjs_setToMuscle(mjsActuator* actuator, double timeconst[2], do
double lmax, double vmax, double fpmax, double fvmax);

// Set actuator to adhesion, return error on failure.
MJAPI const char* mjs_setToAdhesion(mjsActuator* actuator, double gain);
MJAPI const char* mjs_setToAdhesion(mjsActuator* actuator, double gain,
double timeconst);

//---------------------------------- Add assets ----------------------------------------------------

Expand Down
9 changes: 6 additions & 3 deletions src/xml/xml_native_reader.cc
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ std::vector<const char*> MJCF[nMJCF] = {
"timeconst", "range", "force", "scale",
"lmin", "lmax", "vmax", "fpmax", "fvmax"},
{"adhesion", "?", "forcelimited", "ctrlrange", "forcerange",
"gain", "user", "group", "nsample", "interp", "delay"},
"gain", "timeconst", "user", "group", "nsample", "interp", "delay"},
{">"},

{"extension", "*"},
Expand Down Expand Up @@ -432,7 +432,8 @@ std::vector<const char*> MJCF[nMJCF] = {
"timeconst", "tausmooth", "range", "force", "scale",
"lmin", "lmax", "vmax", "fpmax", "fvmax"},
{"adhesion", "*", "name", "class", "group", "nsample", "interp", "delay",
"forcelimited", "ctrlrange", "forcerange", "user", "body", "gain"},
"forcelimited", "ctrlrange", "forcerange", "user", "body", "gain",
"timeconst"},
{"plugin", "*", "name", "class", "plugin", "instance", "group", "nsample", "interp", "delay",
"ctrllimited", "forcelimited", "actlimited", "ctrlrange", "forcerange", "actrange",
"lengthrange", "gear", "cranklength", "joint", "jointinparent",
Expand Down Expand Up @@ -2481,9 +2482,11 @@ void mjXReader::OneActuator(XMLElement* elem, mjsActuator* actuator) {
// adhesion
else if (type == "adhesion") {
double gain = actuator->gainprm[0];
double timeconst = 0;
ReadAttr(elem, "gain", 1, &gain, text);
ReadAttr(elem, "ctrlrange", 2, actuator->ctrlrange, text);
err = mjs_setToAdhesion(actuator, gain);
ReadAttr(elem, "timeconst", 1, &timeconst, text);
err = mjs_setToAdhesion(actuator, gain, timeconst);
}

else if (type == "plugin") {
Expand Down
76 changes: 76 additions & 0 deletions test/xml/xml_native_reader_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3002,6 +3002,82 @@ TEST_F(ActuatorParseTest, AdhesionInheritsFromGeneral) {
mj_deleteModel(model);
}

// adhesion actuator with timeconst sets dyntype to filter
TEST_F(ActuatorParseTest, AdhesionTimeconst) {
static constexpr char xml[] = R"(
<mujoco>
<worldbody>
<body name="sphere">
<geom name="sphere" size="1"/>
</body>
</worldbody>
<actuator>
<adhesion name="adhere" body="sphere" gain="100" ctrlrange="0 1"
timeconst="0.03"/>
</actuator>
</mujoco>
)";

std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(model, NotNull()) << error.data();

// expect dyntype to be filter
EXPECT_EQ(model->actuator_dyntype[0], mjDYN_FILTER);
// expect dynprm[0] to be the timeconst value
EXPECT_EQ(model->actuator_dynprm[0], 0.03);
// expect gain was set correctly
EXPECT_EQ(model->actuator_gainprm[0], 100);
mj_deleteModel(model);
}

// adhesion actuator without timeconst has no dynamics
TEST_F(ActuatorParseTest, AdhesionNoTimeconst) {
static constexpr char xml[] = R"(
<mujoco>
<worldbody>
<body name="sphere">
<geom name="sphere" size="1"/>
</body>
</worldbody>
<actuator>
<adhesion name="adhere" body="sphere" gain="50" ctrlrange="0 1"/>
</actuator>
</mujoco>
)";

std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(model, NotNull()) << error.data();

// expect dyntype to remain none
EXPECT_EQ(model->actuator_dyntype[0], mjDYN_NONE);
// expect gain was set correctly
EXPECT_EQ(model->actuator_gainprm[0], 50);
mj_deleteModel(model);
}

// adhesion actuator rejects negative timeconst
TEST_F(ActuatorParseTest, AdhesionNegativeTimeconst) {
static constexpr char xml[] = R"(
<mujoco>
<worldbody>
<body name="sphere">
<geom name="sphere" size="1"/>
</body>
</worldbody>
<actuator>
<adhesion name="adhere" body="sphere" gain="100" ctrlrange="0 1"
timeconst="-1"/>
</actuator>
</mujoco>
)";

std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model, IsNull());
}

TEST_F(ActuatorParseTest, ActdimDefaultsPropagate) {
static constexpr char xml[] = R"(
<mujoco>
Expand Down
4 changes: 2 additions & 2 deletions wasm/codegen/generated/bindings.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9808,8 +9808,8 @@ int mjs_setName_wrapper(MjsElement& element, const String& name) {
return mjs_setName(element.get(), name.as<const std::string>().data());
}

std::string mjs_setToAdhesion_wrapper(MjsActuator& actuator, double gain) {
return std::string(mjs_setToAdhesion(actuator.get(), gain));
std::string mjs_setToAdhesion_wrapper(MjsActuator& actuator, double gain, double timeconst) {
return std::string(mjs_setToAdhesion(actuator.get(), gain, timeconst));
}

std::string mjs_setToCylinder_wrapper(MjsActuator& actuator, double timeconst, double bias, double area, double diameter) {
Expand Down
Loading