Automatically infer *limited field from *range field for joints, tendons and actuators.

PiperOrigin-RevId: 467980594
Change-Id: If635c55a90cf949796b99a41395a6c2c4f7ea094
This commit is contained in:
Kevin Zakka
2022-08-16 11:34:41 -07:00
committed by Copybara-Service
parent 29535a8eca
commit 4bfc2c0311
9 changed files with 474 additions and 58 deletions
+18 -14
View File
@@ -3154,9 +3154,10 @@ unit quaternions.
the desired time constant (first value) and damping ratio (second value). This is done by taking into account the
joint inertia in the model reference configuration. Note that the format is the same as the solref parameter of the
constraint solver.
:at:`limited`: :at-val:`[false, true], "false"`
This attribute specifies if the joint has limits. It interacts with the range attribute below. Both must be set to
enable joint limits. If this attribute is "false", any joint range data will be ignored.
:at:`limited`: :at-val:`[false, true, auto], "auto"`
This attribute specifies if the joint has limits. It interacts with the range attribute below. If this attribute
is "false", joint limits are disabled. If this attribute is "true", joint limits are enabled. If this
attribute is "auto", joint limits will be enabled if range is defined and disabled otherwise.
:at:`solreflimit`, :at:`solimplimit`
Constraint solver parameters for simulating joint limits. See :ref:`CSolver`.
:at:`solreffriction`, :at:`solimpfriction`
@@ -3167,7 +3168,7 @@ unit quaternions.
:at:`range`: :at-val:`real(2), "0 0"`
The joint limits. Limits can be imposed on all joint types except for free joints. For hinge and ball joints, the
range is specified in degrees or radians depending on the angle attribute of :ref:`compiler <compiler>`. For ball
joints, the limit is imposed on the angle of rotation (relative to the the reference configuration) regardless of the
joints, the limit is imposed on the angle of rotation (relative to the reference configuration) regardless of the
axis of rotation. Only the second range parameter is used for ball joints; the first range parameter should be set to
0. See the :ref:`Limit <coLimit>` section in the Computation chapter for more information.
:at:`margin`: :at-val:`real, "0"`
@@ -4103,9 +4104,9 @@ the obstacle geom.
:at:`group`: :at-val:`int, "0"`
Integer group to which the tendon belongs. This attribute can be used for custom tags. It is also used by the
visualizer to enable and disable the rendering of entire groups of tendons.
:at:`limited`: :at-val:`[false, true], "false"`
:at:`limited`: :at-val:`[false, true, auto], "auto"`
If this attribute is "true", the length limits defined by the range attribute below are imposed by the constraint
solver.
solver. If this attribute is "auto", length limits will be enabled if range is defined and disabled otherwise.
:at:`range`: :at-val:`real(2), "0 0"`
Range of allowed tendon lengths. To enable length limits, set the limited attribute to "true" in addition to defining
the present value.
@@ -4250,17 +4251,20 @@ specify them independently.
:at:`group`: :at-val:`int, "0"`
Integer group to which the actuator belongs. This attribute can be used for custom tags. It is also used by the
visualizer to enable and disable the rendering of entire groups of actuators.
:at:`ctrllimited`: :at-val:`[false, true], "false"`
:at:`ctrllimited`: :at-val:`[false, true, auto], "auto"`
If true, the control input to this actuator is automatically clamped to :at:`ctrlrange` at runtime. If false, control
input clamping is disabled. Note that control input clamping can also be globally disabled with the :at:`clampctrl`
attribute of :ref:`option/flag <option-flag>`.
:at:`forcelimited`: :at-val:`[false, true], "false"`
input clamping is disabled. If auto, control clamping will automatically be set to true if :at:`ctrlrange` is
defined without explicitly setting this attribute to "true". Note that control input clamping can also be globally
disabled with the :at:`clampctrl` attribute of :ref:`option/flag <option-flag>`.
:at:`forcelimited`: :at-val:`[false, true, auto], "auto"`
If true, the force output of this actuator is automatically clamped to :at:`forcerange` at runtime. If false, force
clamping is disabled.
:at:`actlimited`: :at-val:`[false, true], "false"`
clamping is disabled. If auto, force clamping will automatically be set to true if :at:`forcerange` is
defined without explicitly setting this attribute to "true".
:at:`actlimited`: :at-val:`[false, true, auto], "auto"`
If true, the internal state (activation) associated with this actuator is automatically clamped to :at:`actrange` at
runtime. If false, activation clamping is disabled. See the :ref:`Activation clamping <CActRange>` section for more
details.
runtime. If false, activation clamping is disabled. If auto, activation clamping will automatically be set to true
if :at:`actrange` is defined without explicitly setting this attribute to "true". See the :ref:`Activation clamping <CActRange>`
section for more details.
:at:`ctrlrange`: :at-val:`real(2), "0 0"`
Range for clamping the control input. The compiler expects the first value to be smaller than the second value.
:at:`forcerange`: :at-val:`real(2), "0 0"`
+7
View File
@@ -20,6 +20,13 @@ General
the free camera at model load time.
- 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``.
- Joint and tendon ``limited`` attribute and actuator ``ctrllimited``, ``forcelimited`` and ``actlimited`` attributes
now default to ``"auto"``, which means they are automatically set to ``true`` if the corresponding range is
defined and disabled otherwise.
.. attention::
This is a minor breaking change. Code where a range was defined and limited was unspecified will now
be limited. Please explicitly set limited to ``false`` to revert to the previous behavior.
Deleted/deprecated features
^^^^^^^^^^^^^^^^^^^^^^^^^^^
+31 -6
View File
@@ -797,7 +797,7 @@ mjCJoint::mjCJoint(mjCModel* _model, mjCDef* _def) {
group = 0;
mjuu_setvec(pos, 0, 0, 0);
mjuu_setvec(axis, 0, 0, 1);
limited = false;
limited = 2;
stiffness = 0;
range[0] = 0;
range[1] = 0;
@@ -851,6 +851,15 @@ int mjCJoint::Compile(void) {
}
}
// free joints cannot be limited
if (type==mjJNT_FREE) {
limited = 0;
}
// otherwise if limited is auto, set according to whether range is specified
else if (limited==2) {
limited = (range[0]==0 && range[1]==0) ? 0 : 1;
}
// resolve limits
if (limited) {
// check data
@@ -2975,7 +2984,7 @@ mjCTendon::mjCTendon(mjCModel* _model, mjCDef* _def) {
group = 0;
material.clear();
width = 0.003;
limited = false;
limited = 2;
range[0] = 0;
range[1] = 0;
mj_defaultSolRefImp(solref_limit, solimp_limit);
@@ -3205,6 +3214,11 @@ void mjCTendon::Compile(void) {
}
}
// if limited is auto, set to 1 if range is specified, otherwise unlimited
if (limited==2) {
limited = (range[0]==0 && range[1]==0) ? 0 : 1;
}
// check limits
if (range[0]>=range[1] && limited) {
throw mjCError(this, "invalid limits in tendon '%s (id = %d)'", name.c_str(), id);
@@ -3317,9 +3331,9 @@ void mjCWrap::Compile(void) {
mjCActuator::mjCActuator(mjCModel* _model, mjCDef* _def) {
// actuator defaults
group = 0;
ctrllimited = false;
forcelimited = false;
actlimited = false;
ctrllimited = 2;
forcelimited = 2;
actlimited = 2;
trntype = mjTRN_UNDEFINED;
dyntype = mjDYN_NONE;
gaintype = mjGAIN_FIXED;
@@ -3366,6 +3380,17 @@ void mjCActuator::Compile(void) {
}
userdata.resize(model->nuser_actuator);
// if limited is auto, set to 1 if range is specified, otherwise unlimited
if (forcelimited==2) {
forcelimited = (forcerange[0]==0 && forcerange[1]==0) ? 0 : 1;
}
if (ctrllimited==2) {
ctrllimited = (ctrlrange[0]==0 && ctrlrange[1]==0) ? 0 : 1;
}
if (actlimited==2) {
actlimited = (actrange[0]==0 && actrange[1]==0) ? 0 : 1;
}
// check limits
if (forcerange[0]>=forcerange[1] && forcelimited) {
throw mjCError(this, "invalid force range for actuator '%s' (id = %d)", name.c_str(), id);
@@ -3437,7 +3462,7 @@ void mjCActuator::Compile(void) {
if (pjnt->urdfeffort>0) {
forcerange[0] = -pjnt->urdfeffort;
forcerange[1] = pjnt->urdfeffort;
forcelimited = true;
forcelimited = 1;
}
break;
+5 -5
View File
@@ -228,7 +228,7 @@ class mjCJoint : public mjCBase {
// variables set by user: joint properties
mjtJoint type; // type of Joint
int group; // used for rendering
bool limited; // does joint have limits
int limited; // does joint have limits: 0 false, 1 true, 2 auto
double pos[3]; // anchor position
double axis[3]; // joint axis
double stiffness; // stiffness coefficient
@@ -783,7 +783,7 @@ class mjCTendon : public mjCBase {
// variables set by user
int group; // group for visualization
std::string material; // name of material for rendering
bool limited; // does tendon have limits
int limited; // does tendon have limits: 0 false, 1 true, 2 auto
double width; // width for rendering
mjtNum solref_limit[mjNREF]; // solver reference: tendon limits
mjtNum solimp_limit[mjNIMP]; // solver impedance: tendon limits
@@ -843,9 +843,9 @@ class mjCActuator : public mjCBase {
public:
// variables set by user or API
int group; // group for visualization
bool ctrllimited; // are control limits defined
bool forcelimited; // are force limits defined
bool actlimited; // are activation limits defined
int ctrllimited; // are control limits defined: 0 false, 1 true, 2 auto
int forcelimited; // are force limits defined: 0 false, 1 true, 2 auto
int actlimited; // are activation limits defined: 0 false, 1 true, 2 auto
mjtDyn dyntype; // dynamics type
mjtTrn trntype; // transmission type
mjtGain gaintype; // gain type
+10 -25
View File
@@ -1092,9 +1092,7 @@ void mjXReader::OneJoint(XMLElement* elem, mjCJoint* pjoint) {
if (MapValue(elem, "type", &n, joint_map, joint_sz)) {
pjoint->type = (mjtJoint)n;
}
if (MapValue(elem, "limited", &n, bool_map, 2)) {
pjoint->limited = (n==1);
}
MapValue(elem, "limited", &pjoint->limited, TFAuto_map, 3);
ReadAttrInt(elem, "group", &pjoint->group);
ReadAttr(elem, "solreflimit", mjNREF, pjoint->solref_limit, text, false, false);
ReadAttr(elem, "solimplimit", mjNIMP, pjoint->solimp_limit, text, false, false);
@@ -1350,7 +1348,6 @@ void mjXReader::OneEquality(XMLElement* elem, mjCEquality* pequality) {
// tendon element parser
void mjXReader::OneTendon(XMLElement* elem, mjCTendon* pten) {
int n;
string text;
// read attributes
@@ -1358,9 +1355,7 @@ void mjXReader::OneTendon(XMLElement* elem, mjCTendon* pten) {
ReadAttrTxt(elem, "class", pten->classname);
ReadAttrInt(elem, "group", &pten->group);
ReadAttrTxt(elem, "material", pten->material);
if (MapValue(elem, "limited", &n, bool_map, 2)) {
pten->limited = (n==1);
}
MapValue(elem, "limited", &pten->limited, TFAuto_map, 3);
ReadAttr(elem, "width", 1, &pten->width, text);
ReadAttr(elem, "solreflimit", mjNREF, pten->solref_limit, text, false, false);
ReadAttr(elem, "solimplimit", mjNIMP, pten->solimp_limit, text, false, false);
@@ -1392,15 +1387,9 @@ void mjXReader::OneActuator(XMLElement* elem, mjCActuator* pact) {
ReadAttrTxt(elem, "name", pact->name);
ReadAttrTxt(elem, "class", pact->classname);
ReadAttrInt(elem, "group", &pact->group);
if (MapValue(elem, "ctrllimited", &n, bool_map, 2)) {
pact->ctrllimited = (n==1);
}
if (MapValue(elem, "forcelimited", &n, bool_map, 2)) {
pact->forcelimited = (n==1);
}
if (MapValue(elem, "actlimited", &n, bool_map, 2)) {
pact->actlimited = (n==1);
}
MapValue(elem, "ctrllimited", &pact->ctrllimited, TFAuto_map, 3);
MapValue(elem, "forcelimited", &pact->forcelimited, TFAuto_map, 3);
MapValue(elem, "actlimited", &pact->actlimited, TFAuto_map, 3);
ReadAttr(elem, "ctrlrange", 2, pact->ctrlrange, text);
ReadAttr(elem, "forcerange", 2, pact->forcerange, text);
ReadAttr(elem, "actrange", 2, pact->actrange, text);
@@ -1518,7 +1507,7 @@ void mjXReader::OneActuator(XMLElement* elem, mjCActuator* pact) {
pact->dyntype = mjDYN_INTEGRATOR;
pact->gaintype = mjGAIN_FIXED;
pact->biastype = mjBIAS_AFFINE;
pact->actlimited = true;
pact->actlimited = 1;
pact->biasprm[1] = -pact->gainprm[0];
// require actrange
if (!ReadAttr(elem, "actrange", 2, pact->actrange, text)) {
@@ -1544,7 +1533,7 @@ void mjXReader::OneActuator(XMLElement* elem, mjCActuator* pact) {
}
// implied parameters
pact->ctrllimited = true;
pact->ctrllimited = 1;
pact->dyntype = mjDYN_NONE;
pact->gaintype = mjGAIN_AFFINE;
pact->biastype = mjBIAS_NONE;
@@ -1622,7 +1611,7 @@ void mjXReader::OneActuator(XMLElement* elem, mjCActuator* pact) {
}
// implied parameters
pact->ctrllimited = true;
pact->ctrllimited = 1;
pact->dyntype = mjDYN_NONE;
pact->gaintype = mjGAIN_FIXED;
pact->biastype = mjBIAS_NONE;
@@ -1728,9 +1717,7 @@ void mjXReader::OneComposite(XMLElement* elem, mjCBody* pbody, mjCDef* def) {
ReadAttr(ejnt, "solimpfix", mjNIMP, comp.def[kind].equality.solimp, text, false, false);
// joint attributes
if (MapValue(ejnt, "limited", &n, bool_map, 2)) {
comp.def[kind].joint.limited = (n==1);
}
MapValue(elem, "limited", &comp.def[kind].joint.limited, TFAuto_map, 3);
ReadAttrInt(ejnt, "group", &comp.def[kind].joint.group);
ReadAttr(ejnt, "solreflimit", mjNREF, comp.def[kind].joint.solref_limit, text, false, false);
ReadAttr(ejnt, "solimplimit", mjNIMP, comp.def[kind].joint.solimp_limit, text, false, false);
@@ -1762,9 +1749,7 @@ void mjXReader::OneComposite(XMLElement* elem, mjCBody* pbody, mjCDef* def) {
ReadAttr(eten, "solimpfix", mjNIMP, comp.def[kind].equality.solimp, text, false, false);
// tendon attributes
if (MapValue(eten, "limited", &n, bool_map, 2)) {
comp.def[kind].tendon.limited = (n==1);
}
MapValue(elem, "limited", &comp.def[kind].tendon.limited, TFAuto_map, 3);
ReadAttrInt(eten, "group", &comp.def[kind].tendon.group);
ReadAttr(eten, "solreflimit", mjNREF, comp.def[kind].tendon.solref_limit, text, false, false);
ReadAttr(eten, "solimplimit", mjNIMP, comp.def[kind].tendon.solimp_limit, text, false, false);
+32 -5
View File
@@ -207,6 +207,13 @@ void mjXWriter::OneJoint(XMLElement* elem, mjCJoint* pjoint, mjCDef* def) {
}
}
// special handling of limits
bool range_defined = pjoint->range[0]!=0 || pjoint->range[1]!=0;
bool limited_inferred = def->joint.limited==2 && pjoint->limited==range_defined;
if (writingdefaults || !limited_inferred) {
WriteAttrKey(elem, "limited", TFAuto_map, 3, pjoint->limited, def->joint.limited);
}
// defaults and regular
if (pjoint->type != def->joint.type) {
WriteAttrTxt(elem, "type", FindValue(joint_map, joint_sz, pjoint->type));
@@ -214,7 +221,6 @@ void mjXWriter::OneJoint(XMLElement* elem, mjCJoint* pjoint, mjCDef* def) {
WriteAttrInt(elem, "group", pjoint->group, def->joint.group);
WriteAttr(elem, "ref", 1, &pjoint->ref, &zero);
WriteAttr(elem, "springref", 1, &pjoint->springref, &zero);
WriteAttrKey(elem, "limited", bool_map, 2, pjoint->limited, def->joint.limited);
WriteAttr(elem, "solreflimit", mjNREF, pjoint->solref_limit, def->joint.solref_limit);
WriteAttr(elem, "solimplimit", mjNIMP, pjoint->solimp_limit, def->joint.solimp_limit);
WriteAttr(elem, "solreffriction", mjNREF, pjoint->solref_friction, def->joint.solref_friction);
@@ -491,9 +497,15 @@ void mjXWriter::OneTendon(XMLElement* elem, mjCTendon* pten, mjCDef* def) {
WriteAttrTxt(elem, "class", pten->classname);
}
// special handling of limits
bool range_defined = pten->range[0]!=0 || pten->range[1]!=0;
bool limited_inferred = def->tendon.limited==2 && pten->limited==range_defined;
if (writingdefaults || !limited_inferred) {
WriteAttrKey(elem, "limited", TFAuto_map, 3, pten->limited, def->tendon.limited);
}
// defaults and regular
WriteAttrInt(elem, "group", pten->group, def->tendon.group);
WriteAttrKey(elem, "limited", bool_map, 2, pten->limited, def->tendon.limited);
WriteAttr(elem, "solreflimit", mjNREF, pten->solref_limit, def->tendon.solref_limit);
WriteAttr(elem, "solimplimit", mjNIMP, pten->solimp_limit, def->tendon.solimp_limit);
WriteAttr(elem, "solreffriction", mjNREF, pten->solref_friction, def->tendon.solref_friction);
@@ -563,11 +575,26 @@ void mjXWriter::OneActuator(XMLElement* elem, mjCActuator* pact, mjCDef* def) {
}
}
// special handling of limits
bool range_defined, limited_inferred;
range_defined = pact->ctrlrange[0]!=0 || pact->ctrlrange[1]!=0;
limited_inferred = def->actuator.ctrllimited==2 && pact->ctrllimited==range_defined;
if (writingdefaults || !limited_inferred) {
WriteAttrKey(elem, "ctrllimited", TFAuto_map, 3, pact->ctrllimited, def->actuator.ctrllimited);
}
range_defined = pact->forcerange[0]!=0 || pact->forcerange[1]!=0;
limited_inferred = def->actuator.forcelimited==2 && pact->forcelimited==range_defined;
if (writingdefaults || !limited_inferred) {
WriteAttrKey(elem, "forcelimited", TFAuto_map, 3, pact->forcelimited, def->actuator.forcelimited);
}
range_defined = pact->actrange[0]!=0 || pact->actrange[1]!=0;
limited_inferred = def->actuator.actlimited==2 && pact->actlimited==range_defined;
if (writingdefaults || !limited_inferred) {
WriteAttrKey(elem, "actlimited", TFAuto_map, 3, pact->actlimited, def->actuator.actlimited);
}
// defaults and regular
WriteAttrInt(elem, "group", pact->group, def->actuator.group);
WriteAttrKey(elem, "ctrllimited", bool_map, 2, pact->ctrllimited, def->actuator.ctrllimited);
WriteAttrKey(elem, "forcelimited", bool_map, 2, pact->forcelimited, def->actuator.forcelimited);
WriteAttrKey(elem, "actlimited", bool_map, 2, pact->actlimited, def->actuator.actlimited);
WriteAttr(elem, "ctrlrange", 2, pact->ctrlrange, def->actuator.ctrlrange);
WriteAttr(elem, "forcerange", 2, pact->forcerange, def->actuator.forcerange);
WriteAttr(elem, "actrange", 2, pact->actrange, def->actuator.actrange);
+46
View File
@@ -0,0 +1,46 @@
<mujoco>
<default>
<default class="limited_false">
<joint limited="false"/>
</default>
<default class="limited_true">
<joint limited="true"/>
<default class="limited_auto">
<joint limited="auto"/>
</default>
</default>
<default class="range_defined">
<joint range="-1.57 1.57"/>
</default>
</default>
<!--
`user` attributes of the joints below indicate the expected state of the associated
jnt_limited property in the compiled model.
-->
<worldbody>
<body>
<joint user="0"/>
<joint user="1" range="0 1"/>
<joint user="0" range="0 1" limited="false"/>
<joint user="1" range="0 1" limited="true"/>
<joint user="0" range="0 1" class="limited_false"/>
<joint user="1" range="0 1" class="limited_true"/>
<geom size="1"/>
</body>
<!-- adding another body since bodies are allowed a maximum of 6 DoFs -->
<body>
<joint user="1" range="0 1" class="limited_false" limited="true"/>
<joint user="0" range="0 1" class="limited_true" limited="false"/>
<joint user="1" class="range_defined"/>
<joint user="1" range="0 1" class="limited_auto"/>
<joint user="1" range="0 1" limited="auto"/>
<joint user="0" limited="auto"/>
<geom size="1"/>
</body>
<body>
<!-- free joints should remain unlimited even if a (meaningless) range is defined -->
<joint user="0" type="free" class="range_defined"/>
<geom size="1"/>
</body>
</worldbody>
</mujoco>
+21 -1
View File
@@ -39,7 +39,7 @@ using ::testing::NotNull;
// ------------------------ test keyframes -------------------------------------
static const char* const kKeyframePath = "user/testdata/keyframe.xml";
constexpr char kKeyframePath[] = "user/testdata/keyframe.xml";
TEST_F(MujocoTest, KeyFrameTest) {
const std::string xml_path = GetTestDataFilePath(kKeyframePath);
@@ -567,5 +567,25 @@ TEST_F(UserDataTest, NSensorTooSmall) {
EXPECT_THAT(error.data(), HasSubstr("nuser_sensor"));
}
// ------------- test for auto parsing of *limited fields -------------
using LimitedTest = MujocoTest;
constexpr char kKeyAutoLimits[] = "user/testdata/auto_limits.xml";
// check joint limit values when automatically inferred based on range
TEST_F(LimitedTest, JointLimited) {
const std::string xml_path = GetTestDataFilePath(kKeyAutoLimits);
mjModel* model = mj_loadXML(xml_path.c_str(), nullptr, nullptr, 0);
ASSERT_THAT(model, NotNull());
// see `user/testdata/auto_limits.xml` for expected values
for (int i=0; i < model->njnt; i++) {
EXPECT_EQ(model->jnt_limited[i], (mjtByte)model->jnt_user[i]);
}
mj_deleteModel(model);
}
} // namespace
} // namespace mujoco
+304 -2
View File
@@ -127,7 +127,164 @@ TEST_F(XMLWriterTest, DropsInertialIfFromGeom) {
mj_deleteModel(model);
}
TEST_F(XMLWriterTest, KeepsActlimited) {
TEST_F(XMLWriterTest, DoesNotKeepInferredJointLimited) {
static constexpr char xml[] = R"(
<mujoco>
<compiler angle="radian"/>
<worldbody>
<body>
<joint name="hinge" range="-1 1"/>
<geom size="1"/>
</body>
</worldbody>
</mujoco>
)";
mjModel* model = LoadModelFromString(xml);
std::string saved_xml = SaveAndReadXml(model);
EXPECT_THAT(saved_xml, HasSubstr("range=\"-1 1\""));
EXPECT_THAT(saved_xml, Not(HasSubstr("limited=\"true\"")));
mj_deleteModel(model);
}
TEST_F(XMLWriterTest, DoesNotKeepExplicitJointLimited) {
static constexpr char xml[] = R"(
<mujoco>
<compiler angle="radian"/>
<worldbody>
<body>
<joint name="hinge" limited="true" range="-1 1"/>
<geom size="1"/>
</body>
</worldbody>
</mujoco>
)";
mjModel* model = LoadModelFromString(xml);
std::string saved_xml = SaveAndReadXml(model);
EXPECT_THAT(saved_xml, HasSubstr("range=\"-1 1\""));
EXPECT_THAT(saved_xml, Not(HasSubstr("limited=\"true\"")));
mj_deleteModel(model);
}
TEST_F(XMLWriterTest, KeepsJointLimitedFalse) {
static constexpr char xml[] = R"(
<mujoco>
<compiler angle="radian"/>
<worldbody>
<body>
<joint name="hinge" limited="false" range="-1 1"/>
<geom size="1"/>
</body>
</worldbody>
</mujoco>
)";
mjModel* model = LoadModelFromString(xml);
std::string saved_xml = SaveAndReadXml(model);
EXPECT_THAT(saved_xml, HasSubstr("limited=\"false\" range=\"-1 1\""));
mj_deleteModel(model);
}
TEST_F(XMLWriterTest, DoesNotKeepInferredTendonLimited) {
static constexpr char xml[] = R"(
<mujoco>
<compiler angle="radian"/>
<worldbody>
<body>
<joint type="slide"/>
<geom size="1"/>
<site name="s1"/>
</body>
<site name="s2"/>
</worldbody>
<tendon>
<spatial range="-1 1">
<site site="s1"/>
<site site="s2"/>
</spatial>
</tendon>
</mujoco>
)";
mjModel* model = LoadModelFromString(xml);
std::string saved_xml = SaveAndReadXml(model);
EXPECT_THAT(saved_xml, HasSubstr("range=\"-1 1\""));
EXPECT_THAT(saved_xml, Not(HasSubstr("limited=\"true\"")));
mj_deleteModel(model);
}
TEST_F(XMLWriterTest, DoesNotKeepExplicitTendonLimited) {
static constexpr char xml[] = R"(
<mujoco>
<compiler angle="radian"/>
<worldbody>
<body>
<joint type="slide"/>
<geom size="1"/>
<site name="s1"/>
</body>
<site name="s2"/>
</worldbody>
<tendon>
<spatial limited="true" range="-1 1">
<site site="s1"/>
<site site="s2"/>
</spatial>
</tendon>
</mujoco>
)";
mjModel* model = LoadModelFromString(xml);
std::string saved_xml = SaveAndReadXml(model);
EXPECT_THAT(saved_xml, HasSubstr("range=\"-1 1\""));
EXPECT_THAT(saved_xml, Not(HasSubstr("limited=\"true\"")));
mj_deleteModel(model);
}
TEST_F(XMLWriterTest, KeepsTendonLimitedFalse) {
static constexpr char xml[] = R"(
<mujoco>
<compiler angle="radian"/>
<worldbody>
<body>
<joint type="slide"/>
<geom size="1"/>
<site name="s1"/>
</body>
<site name="s2"/>
</worldbody>
<tendon>
<spatial limited="false" range="-1 1">
<site site="s1"/>
<site site="s2"/>
</spatial>
</tendon>
</mujoco>
)";
mjModel* model = LoadModelFromString(xml);
std::string saved_xml = SaveAndReadXml(model);
EXPECT_THAT(saved_xml, HasSubstr("limited=\"false\" range=\"-1 1\""));
mj_deleteModel(model);
}
TEST_F(XMLWriterTest, DoesNotKeepInferredActlimited) {
static constexpr char xml[] = R"(
<mujoco>
<worldbody>
<body>
<joint name="hinge"/>
<geom size="1"/>
</body>
</worldbody>
<actuator>
<general dyntype="filter" joint="hinge" actrange="-1 1"/>
</actuator>
</mujoco>
)";
mjModel* model = LoadModelFromString(xml);
std::string saved_xml = SaveAndReadXml(model);
EXPECT_THAT(saved_xml, HasSubstr("actrange=\"-1 1\""));
EXPECT_THAT(saved_xml, Not(HasSubstr("actlimited=\"true\"")));
mj_deleteModel(model);
}
TEST_F(XMLWriterTest, DoesNotKeepExplicitActlimited) {
static constexpr char xml[] = R"(
<mujoco>
<worldbody>
@@ -143,7 +300,152 @@ TEST_F(XMLWriterTest, KeepsActlimited) {
)";
mjModel* model = LoadModelFromString(xml);
std::string saved_xml = SaveAndReadXml(model);
EXPECT_THAT(saved_xml, HasSubstr("actlimited=\"true\" actrange=\"-1 1\""));
EXPECT_THAT(saved_xml, HasSubstr("actrange=\"-1 1\""));
EXPECT_THAT(saved_xml, Not(HasSubstr("actlimited=\"true\"")));
mj_deleteModel(model);
}
TEST_F(XMLWriterTest, KeepsActlimitedFalse) {
static constexpr char xml[] = R"(
<mujoco>
<worldbody>
<body>
<joint name="hinge"/>
<geom size="1"/>
</body>
</worldbody>
<actuator>
<general dyntype="filter" joint="hinge" actlimited="false" actrange="-1 1"/>
</actuator>
</mujoco>
)";
mjModel* model = LoadModelFromString(xml);
std::string saved_xml = SaveAndReadXml(model);
EXPECT_THAT(saved_xml, HasSubstr("actlimited=\"false\" actrange=\"-1 1\""));
mj_deleteModel(model);
}
TEST_F(XMLWriterTest, DoesNotKeepInferredCtrllimited) {
static constexpr char xml[] = R"(
<mujoco>
<worldbody>
<body>
<joint name="hinge"/>
<geom size="1"/>
</body>
</worldbody>
<actuator>
<general joint="hinge" ctrlrange="-1 1"/>
</actuator>
</mujoco>
)";
mjModel* model = LoadModelFromString(xml);
std::string saved_xml = SaveAndReadXml(model);
EXPECT_THAT(saved_xml, HasSubstr("ctrlrange=\"-1 1\""));
EXPECT_THAT(saved_xml, Not(HasSubstr("ctrllimited=\"true\"")));
mj_deleteModel(model);
}
TEST_F(XMLWriterTest, DoesNotKeepExplicitCtrllimited) {
static constexpr char xml[] = R"(
<mujoco>
<worldbody>
<body>
<joint name="hinge"/>
<geom size="1"/>
</body>
</worldbody>
<actuator>
<general joint="hinge" ctrllimited="true" ctrlrange="-1 1"/>
</actuator>
</mujoco>
)";
mjModel* model = LoadModelFromString(xml);
std::string saved_xml = SaveAndReadXml(model);
EXPECT_THAT(saved_xml, HasSubstr("ctrlrange=\"-1 1\""));
EXPECT_THAT(saved_xml, Not(HasSubstr("ctrllimited=\"true\"")));
mj_deleteModel(model);
}
TEST_F(XMLWriterTest, KeepsCtrllimitedFalse) {
static constexpr char xml[] = R"(
<mujoco>
<worldbody>
<body>
<joint name="hinge"/>
<geom size="1"/>
</body>
</worldbody>
<actuator>
<general joint="hinge" ctrllimited="false" ctrlrange="-1 1"/>
</actuator>
</mujoco>
)";
mjModel* model = LoadModelFromString(xml);
std::string saved_xml = SaveAndReadXml(model);
EXPECT_THAT(saved_xml, HasSubstr("ctrllimited=\"false\" ctrlrange=\"-1 1\""));
mj_deleteModel(model);
}
TEST_F(XMLWriterTest, DoesNotKeepInferredForcelimited) {
static constexpr char xml[] = R"(
<mujoco>
<worldbody>
<body>
<joint name="hinge"/>
<geom size="1"/>
</body>
</worldbody>
<actuator>
<general joint="hinge" forcerange="-1 1"/>
</actuator>
</mujoco>
)";
mjModel* model = LoadModelFromString(xml);
std::string saved_xml = SaveAndReadXml(model);
EXPECT_THAT(saved_xml, HasSubstr("forcerange=\"-1 1\""));
EXPECT_THAT(saved_xml, Not(HasSubstr("forcelimited=\"true\"")));
mj_deleteModel(model);
}
TEST_F(XMLWriterTest, DoesNotKeepExplicitForcelimited) {
static constexpr char xml[] = R"(
<mujoco>
<worldbody>
<body>
<joint name="hinge"/>
<geom size="1"/>
</body>
</worldbody>
<actuator>
<general joint="hinge" forcelimited="true" forcerange="-1 1"/>
</actuator>
</mujoco>
)";
mjModel* model = LoadModelFromString(xml);
std::string saved_xml = SaveAndReadXml(model);
EXPECT_THAT(saved_xml, HasSubstr("forcerange=\"-1 1\""));
EXPECT_THAT(saved_xml, Not(HasSubstr("forcelimited=\"true\"")));
mj_deleteModel(model);
}
TEST_F(XMLWriterTest, KeepsForcelimitedFalse) {
static constexpr char xml[] = R"(
<mujoco>
<worldbody>
<body>
<joint name="hinge"/>
<geom size="1"/>
</body>
</worldbody>
<actuator>
<general joint="hinge" forcelimited="false" forcerange="-1 1"/>
</actuator>
</mujoco>
)";
mjModel* model = LoadModelFromString(xml);
std::string saved_xml = SaveAndReadXml(model);
EXPECT_THAT(saved_xml, HasSubstr("forcelimited=\"false\" forcerange=\"-1 1\""));
mj_deleteModel(model);
}