Sensors of type user no longer require objtype and objname. If unspecified, the objtype will be mjOBJ_UNKNOWN. Also, `user sensors datatype default is now "real"`.

PiperOrigin-RevId: 489652972
Change-Id: I0a8f21ee1a2f5292e6bbcfe3785536cfd1498b85
This commit is contained in:
Yuval Tassa
2022-11-19 02:04:53 -08:00
committed by Copybara-Service
parent 6f2092bef2
commit 221c63d744
8 changed files with 96 additions and 14 deletions
+6 -6
View File
@@ -3913,7 +3913,7 @@ at a specified body, in global coordinates.
.. _sensor-clock: .. _sensor-clock:
:el-prefix:`sensor/` **clock** (*) :el-prefix:`sensor/` **clock** (*)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This element creates sensor that returns the simulation time. This element creates sensor that returns the simulation time.
@@ -3934,12 +3934,12 @@ bodies whose center of mass is of interest.
:at:`name`, :at:`noise`, :at:`cutoff`, :at:`user` :at:`name`, :at:`noise`, :at:`cutoff`, :at:`user`
See :ref:`CSensor`. See :ref:`CSensor`.
:at:`objtype`: :at-val:`(any element type that can be named), required` :at:`objtype`: :at-val:`(any element type that can be named), optional`
Type of the MuJoCo object to which the sensor is attached. This together with the objname attribute determines the Type of the MuJoCo object to which the sensor is attached. This together with the objname attribute determines the
actual object. actual object. If unspecified, will be :ref:`mjOBJ_UNKNOWN<mjtObj>`.
:at:`objname`: :at-val:`string, required` :at:`objname`: :at-val:`string, optional`
Name of the MuJoCo object to which the sensor is attached. Name of the MuJoCo object to which the sensor is attached.
:at:`datatype`: :at-val:`[real, positive, axis, quaternion], required` :at:`datatype`: :at-val:`[real, positive, axis, quaternion], "real"`
The type of output generated by this sensor. "axis" means a unit-length 3D vector. "quat" means a unit quaternion. The type of output generated by this sensor. "axis" means a unit-length 3D vector. "quat" means a unit quaternion.
These need to be declared because when MuJoCo adds noise, it must respect the vector normalization. "real" means a These need to be declared because when MuJoCo adds noise, it must respect the vector normalization. "real" means a
generic array (or scalar) of real values to which noise can be added independently. generic array (or scalar) of real values to which noise can be added independently.
@@ -3952,7 +3952,7 @@ bodies whose center of mass is of interest.
.. _keyframe: .. _keyframe:
**keyframe** (*) **keyframe** (*)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~
This is a grouping element for keyframe definitions. It does not have attributes. Keyframes can be used to create a This is a grouping element for keyframe definitions. It does not have attributes. Keyframes can be used to create a
library of states that are of interest to the user, and to initialize the simulation state to one of the states in the library of states that are of interest to the user, and to initialize the simulation state to one of the states in the
+2
View File
@@ -54,6 +54,8 @@ General
``dlopen`` on a POSIX system, and to ``LoadLibraryA`` on Windows. The second function scans a specified directory for ``dlopen`` on a POSIX system, and to ``LoadLibraryA`` on Windows. The second function scans a specified directory for
all dynamic libraries file and loads each library found. Dynamic libraries opened by these functions are assumed to all dynamic libraries file and loads each library found. Dynamic libraries opened by these functions are assumed to
register one or more MuJoCo plugins on load. register one or more MuJoCo plugins on load.
- Sensors of type :ref:`user<sensor-user>` no longer require :at:`objtype` and :at:`objname`. If unspecified, the
objtype will be :ref:`mjOBJ_UNKNOWN<mjtObj>`. ``user`` sensors :at:`datatype` default is now :at-val:`"real"`.
Simulate Simulate
^^^^^^^^ ^^^^^^^^
+2 -2
View File
@@ -3697,7 +3697,7 @@ void mjCSensor::Compile(void) {
// get sensorized object id // get sensorized object id
objid = pobj->id; objid = pobj->id;
} else if (type != mjSENS_CLOCK && type != mjSENS_PLUGIN) { } else if (type != mjSENS_CLOCK && type != mjSENS_PLUGIN && type != mjSENS_USER) {
throw mjCError(this, "invalid type in sensor '%s' (id = %d)", name.c_str(), id); throw mjCError(this, "invalid type in sensor '%s' (id = %d)", name.c_str(), id);
} }
@@ -3779,7 +3779,7 @@ void mjCSensor::Compile(void) {
"joint must be slide or hinge in sensor '%s' (id = %d)", name.c_str(), id); "joint must be slide or hinge in sensor '%s' (id = %d)", name.c_str(), id);
} }
//set // set
dim = 1; dim = 1;
datatype = mjDATATYPE_REAL; datatype = mjDATATYPE_REAL;
if (type==mjSENS_JOINTPOS) { if (type==mjSENS_JOINTPOS) {
+12 -5
View File
@@ -3147,16 +3147,23 @@ void mjXReader::Sensor(XMLElement* section) {
// user-defined sensor // user-defined sensor
else if (type=="user") { else if (type=="user") {
psen->type = mjSENS_USER; psen->type = mjSENS_USER;
ReadAttrTxt(elem, "objtype", text, true); bool objname_given = ReadAttrTxt(elem, "objname", psen->objname);
psen->objtype = (mjtObj)mju_str2Type(text.c_str()); if (ReadAttrTxt(elem, "objtype", text)) {
ReadAttrTxt(elem, "objname", psen->objname, true); if (!objname_given) {
throw mjXError(elem, "objtype '%s' given but objname is missing", text.c_str());
}
psen->objtype = (mjtObj)mju_str2Type(text.c_str());
} else if (objname_given) {
throw mjXError(elem, "objname '%s' given but objtype is missing", psen->objname.c_str());
}
ReadAttrInt(elem, "dim", &psen->dim, true); ReadAttrInt(elem, "dim", &psen->dim, true);
// keywords // keywords
MapValue(elem, "needstage", &n, stage_map, stage_sz, true); MapValue(elem, "needstage", &n, stage_map, stage_sz, true);
psen->needstage = (mjtStage)n; psen->needstage = (mjtStage)n;
MapValue(elem, "datatype", &n, datatype_map, datatype_sz, true); if (MapValue(elem, "datatype", &n, datatype_map, datatype_sz)) {
psen->datatype = (mjtDataType)n; psen->datatype = (mjtDataType)n;
}
} }
else if (type=="plugin") { else if (type=="plugin") {
+1 -1
View File
@@ -1768,7 +1768,7 @@ void mjXWriter::Sensor(XMLElement* root) {
// user-defined sensor // user-defined sensor
case mjSENS_USER: case mjSENS_USER:
elem = InsertEnd(section, "user"); elem = InsertEnd(section, "user");
WriteAttrTxt(elem, "objtype", mju_type2Str(psen->objtype)); if (mju_type2Str(psen->objtype)) WriteAttrTxt(elem, "objtype", mju_type2Str(psen->objtype));
WriteAttrTxt(elem, "objname", psen->objname); WriteAttrTxt(elem, "objname", psen->objname);
WriteAttrInt(elem, "dim", psen->dim); WriteAttrInt(elem, "dim", psen->dim);
WriteAttrKey(elem, "needstage", stage_map, stage_sz, (int)psen->needstage); WriteAttrKey(elem, "needstage", stage_map, stage_sz, (int)psen->needstage);
+23
View File
@@ -202,6 +202,29 @@ TEST_F(RelativeFrameSensorParsingTest, BadRefType) {
EXPECT_THAT(error.data(), HasSubstr("reference frame object must be")); EXPECT_THAT(error.data(), HasSubstr("reference frame object must be"));
} }
// ------------- sensor compilation --------------------------------------------
using SensorTest = MujocoTest;
TEST_F(SensorTest, OjbtypeParsedButNotRequired) {
static constexpr char xml[] = R"(
<mujoco>
<sensor>
<user dim="3" needstage="vel" datatype="axis"/>
<user dim="2" needstage="pos" objtype="body" objname="world"/>
</sensor>
</mujoco>
)";
mjModel* model = LoadModelFromString(xml, 0, 0);
ASSERT_THAT(model, NotNull());
EXPECT_EQ(model->sensor_datatype[0], mjDATATYPE_AXIS);
EXPECT_EQ(model->sensor_objtype[0], mjOBJ_UNKNOWN);
EXPECT_EQ(model->sensor_dim[0], 3);
EXPECT_EQ(model->sensor_datatype[1], mjDATATYPE_REAL);
EXPECT_EQ(model->sensor_objtype[1], mjOBJ_BODY);
EXPECT_EQ(model->sensor_objid[1], 0);
mj_deleteModel(model);
}
// ------------- test capsule inertias ----------------------------------------- // ------------- test capsule inertias -----------------------------------------
static const char* const kCapsuleInertiaPath = static const char* const kCapsuleInertiaPath =
+32
View File
@@ -935,6 +935,38 @@ TEST_F(ActuatorParseTest, ActdimDefaultsPropagate) {
mj_deleteModel(model); mj_deleteModel(model);
} }
// ------------- test sensor parsing -------------------------------------------
using SensorParseTest = MujocoTest;
TEST_F(SensorParseTest, UserObjTypeNoName) {
static constexpr char xml[] = R"(
<mujoco>
<sensor>
<user dim="1" needstage="vel" objtype="site"/>
</sensor>
</mujoco>
)";
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(model, IsNull());
EXPECT_THAT(error.data(), HasSubstr("objtype 'site' given but"));
}
TEST_F(SensorParseTest, UserObjNameNoType) {
static constexpr char xml[] = R"(
<mujoco>
<sensor>
<user dim="1" needstage="vel" objname="kevin"/>
</sensor>
</mujoco>
)";
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(model, IsNull());
EXPECT_THAT(error.data(), HasSubstr("objname 'kevin' given but"));
}
// ------------- test general parsing ------------------------------------------ // ------------- test general parsing ------------------------------------------
TEST_F(XMLReaderTest, ZnearZeroNotAllowed) { TEST_F(XMLReaderTest, ZnearZeroNotAllowed) {
+18
View File
@@ -129,6 +129,24 @@ TEST_F(XMLWriterTest, SavesDisableSensor) {
mj_deleteModel(model); mj_deleteModel(model);
} }
TEST_F(XMLWriterTest, EmptyUserSensor) {
static constexpr char xml[] = R"(
<mujoco>
<sensor>
<user dim="2" needstage="vel"/>
</sensor>
</mujoco>
)";
mjModel* model = LoadModelFromString(xml);
std::string saved_xml = SaveAndReadXml(model);
EXPECT_THAT(saved_xml, HasSubstr("dim=\"2\""));
EXPECT_THAT(saved_xml, HasSubstr("needstage=\"vel\""));
EXPECT_THAT(saved_xml, HasSubstr("datatype=\"real\""));
EXPECT_THAT(saved_xml, Not(HasSubstr("objtype")));
EXPECT_THAT(saved_xml, Not(HasSubstr("objname")));
mj_deleteModel(model);
}
TEST_F(XMLWriterTest, KeepsEmptyClasses) { TEST_F(XMLWriterTest, KeepsEmptyClasses) {
static constexpr char xml[] = R"( static constexpr char xml[] = R"(
<mujoco> <mujoco>