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:
:el-prefix:`sensor/` **clock** (*)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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`
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
actual object.
:at:`objname`: :at-val:`string, required`
actual object. If unspecified, will be :ref:`mjOBJ_UNKNOWN<mjtObj>`.
:at:`objname`: :at-val:`string, optional`
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.
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.
@@ -3952,7 +3952,7 @@ bodies whose center of mass is of interest.
.. _keyframe:
**keyframe** (*)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~
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
+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
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.
- 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
^^^^^^^^
+2 -2
View File
@@ -3697,7 +3697,7 @@ void mjCSensor::Compile(void) {
// get sensorized object 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);
}
@@ -3779,7 +3779,7 @@ void mjCSensor::Compile(void) {
"joint must be slide or hinge in sensor '%s' (id = %d)", name.c_str(), id);
}
//set
// set
dim = 1;
datatype = mjDATATYPE_REAL;
if (type==mjSENS_JOINTPOS) {
+12 -5
View File
@@ -3147,16 +3147,23 @@ void mjXReader::Sensor(XMLElement* section) {
// user-defined sensor
else if (type=="user") {
psen->type = mjSENS_USER;
ReadAttrTxt(elem, "objtype", text, true);
psen->objtype = (mjtObj)mju_str2Type(text.c_str());
ReadAttrTxt(elem, "objname", psen->objname, true);
bool objname_given = ReadAttrTxt(elem, "objname", psen->objname);
if (ReadAttrTxt(elem, "objtype", text)) {
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);
// keywords
MapValue(elem, "needstage", &n, stage_map, stage_sz, true);
psen->needstage = (mjtStage)n;
MapValue(elem, "datatype", &n, datatype_map, datatype_sz, true);
psen->datatype = (mjtDataType)n;
if (MapValue(elem, "datatype", &n, datatype_map, datatype_sz)) {
psen->datatype = (mjtDataType)n;
}
}
else if (type=="plugin") {
+1 -1
View File
@@ -1768,7 +1768,7 @@ void mjXWriter::Sensor(XMLElement* root) {
// user-defined sensor
case mjSENS_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);
WriteAttrInt(elem, "dim", psen->dim);
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"));
}
// ------------- 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 -----------------------------------------
static const char* const kCapsuleInertiaPath =
+32
View File
@@ -935,6 +935,38 @@ TEST_F(ActuatorParseTest, ActdimDefaultsPropagate) {
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_F(XMLReaderTest, ZnearZeroNotAllowed) {
+18
View File
@@ -129,6 +129,24 @@ TEST_F(XMLWriterTest, SavesDisableSensor) {
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) {
static constexpr char xml[] = R"(
<mujoco>