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:
committed by
Copybara-Service
parent
6f2092bef2
commit
221c63d744
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
^^^^^^^^
|
^^^^^^^^
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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") {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 =
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user