Merge branch 'main' into open3d-removal

This commit is contained in:
Abhishek Joshi
2024-07-30 16:32:01 -05:00
53 changed files with 786 additions and 397 deletions
+4 -4
View File
@@ -323,7 +323,7 @@ frame (``point``) treated as attached to the body, the Jacobian has both transla
(``jacr``) components. Passing ``NULL`` for either pointer will skip that part of the computation. Each component is a
3-by-nv matrix. Each row of this matrix is the gradient of the corresponding coordinate of the specified point with
respect to the degrees-of-freedom. The :ref:`pipeline stages<piStages>` required for Jacobian computations to be
consistenst with the current generalized positions ``mjData.qpos`` are :ref:`mj_kinematics` and :ref:`mj_comPos`.
consistent with the current generalized positions ``mjData.qpos`` are :ref:`mj_kinematics` and :ref:`mj_comPos`.
.. _mj_jacBody:
@@ -1882,7 +1882,7 @@ Twice continuously differentiable sigmoid function using a quintic polynomial:
Interaction
^^^^^^^^^^^
These function implement abstract mouse interactions, allowing control over cameras and perturbations. Their use is well
These functions implement abstract mouse interactions, allowing control over cameras and perturbations. Their use is well
illustrated in :ref:`simulate<saSimulate>`.
.. _mjv_defaultCamera:
@@ -2066,8 +2066,8 @@ an illustration.
Visualization
^^^^^^^^^^^^^
The functions in this section implement abstract visualization. The results are used by the OpenGL rendered, and can
also be used by users wishing to implement their own rendered, or hook up MuJoCo to advanced rendering tools such as
The functions in this section implement abstract visualization. The results are used by the OpenGL renderer, and can
also be used by users wishing to implement their own renderer, or hook up MuJoCo to advanced rendering tools such as
Unity or Unreal Engine. See :ref:`simulate<saSimulate>` for illustration of how to use these functions.
.. _mjv_defaultOption:
+4 -4
View File
@@ -180,7 +180,7 @@ frame (``point``) treated as attached to the body, the Jacobian has both transla
(``jacr``) components. Passing ``NULL`` for either pointer will skip that part of the computation. Each component is a
3-by-nv matrix. Each row of this matrix is the gradient of the corresponding coordinate of the specified point with
respect to the degrees-of-freedom. The :ref:`pipeline stages<piStages>` required for Jacobian computations to be
consistenst with the current generalized positions ``mjData.qpos`` are :ref:`mj_kinematics` and :ref:`mj_comPos`.
consistent with the current generalized positions ``mjData.qpos`` are :ref:`mj_kinematics` and :ref:`mj_comPos`.
.. _mj_jacBody:
@@ -270,7 +270,7 @@ bodyexclude=-1 can be used to indicate that all bodies are included.
.. _Interaction:
These function implement abstract mouse interactions, allowing control over cameras and perturbations. Their use is well
These functions implement abstract mouse interactions, allowing control over cameras and perturbations. Their use is well
illustrated in :ref:`simulate<saSimulate>`.
.. _mjv_select:
@@ -283,8 +283,8 @@ an illustration.
.. _Visualization-api:
The functions in this section implement abstract visualization. The results are used by the OpenGL rendered, and can
also be used by users wishing to implement their own rendered, or hook up MuJoCo to advanced rendering tools such as
The functions in this section implement abstract visualization. The results are used by the OpenGL renderer, and can
also be used by users wishing to implement their own renderer, or hook up MuJoCo to advanced rendering tools such as
Unity or Unreal Engine. See :ref:`simulate<saSimulate>` for illustration of how to use these functions.
.. _OpenGLrendering:
+14 -9
View File
@@ -577,6 +577,11 @@ from its default.
This flag disables implicit integration with respect to joint damping in the Euler integrator. See the
:ref:`Numerical Integration<geIntegration>` section for more details.
.. _option-flag-autoreset:
:at:`autoreset`: :at-val:`[disable, enable], "enable"`
This flag disables the automatic reseting of the simulation state when numerical issues are detected.
.. _option-flag-override:
:at:`override`: :at-val:`[disable, enable], "disable"`
@@ -1751,7 +1756,7 @@ properties are grouped together.
.. _material-rgb:
:el-prefix:`material/` |-| **rgb** (?)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This element references a texture asset used to specify base color / albedo values.
@@ -1763,7 +1768,7 @@ This element references a texture asset used to specify base color / albedo valu
.. _material-normal:
:el-prefix:`material/` |-| **normal** (?)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This element references a texture asset used to specify the bump map (surface normals).
@@ -1775,7 +1780,7 @@ This element references a texture asset used to specify the bump map (surface no
.. _material-occlusion:
:el-prefix:`material/` |-| **occlusion** (?)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This element references a texture asset used to specify ambient occlusion.
@@ -1787,7 +1792,7 @@ This element references a texture asset used to specify ambient occlusion.
.. _material-roughness:
:el-prefix:`material/` |-| **roughness** (?)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This element references a texture asset used to specify the roughness map.
@@ -1799,7 +1804,7 @@ This element references a texture asset used to specify the roughness map.
.. _material-metallic:
:el-prefix:`material/` |-| **metallic** (?)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This element references a texture asset used to specify the metallic map.
@@ -1811,7 +1816,7 @@ This element references a texture asset used to specify the metallic map.
.. _material-opacity:
:el-prefix:`material/` |-| **opacity** (?)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This element references a texture asset used to specify the opacity map (alpha channel, transparency).
@@ -1823,7 +1828,7 @@ This element references a texture asset used to specify the opacity map (alpha c
.. _material-emissive:
:el-prefix:`material/` |-| **emissive** (?)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This element references a texture asset used to specify light emission.
@@ -1835,7 +1840,7 @@ This element references a texture asset used to specify light emission.
.. _material-orm:
:el-prefix:`material/` |-| **orm** (?)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This element references a texture asset used to specify a packed ORM map, where occlusion, roughness, and metallic
are joined into the corresponding RGB values of a single texture.
@@ -1848,7 +1853,7 @@ are joined into the corresponding RGB values of a single texture.
.. _material-rgba:
:el-prefix:`material/` |-| **rgba** (?)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This element references a texture asset used to specify a packed map where albedo and opacity are joined into the same
4-channel texture.
+3 -3
View File
@@ -35,11 +35,11 @@
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`warmstart<option-flag-warmstart>` | :ref:`filterparent<option-flag-filterparent>` | :ref:`actuation<option-flag-actuation>` | :ref:`refsafe<option-flag-refsafe>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`sensor<option-flag-sensor>` | :ref:`midphase<option-flag-midphase>` | :ref:`eulerdamp<option-flag-eulerdamp>` | :ref:`override<option-flag-override>` | |
| | | | :ref:`sensor<option-flag-sensor>` | :ref:`midphase<option-flag-midphase>` | :ref:`eulerdamp<option-flag-eulerdamp>` | :ref:`autoreset<option-flag-autoreset>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`energy<option-flag-energy>` | :ref:`fwdinv<option-flag-fwdinv>` | :ref:`invdiscrete<option-flag-invdiscrete>` | :ref:`multiccd<option-flag-multiccd>` | |
| | | | :ref:`override<option-flag-override>` | :ref:`energy<option-flag-energy>` | :ref:`fwdinv<option-flag-fwdinv>` | :ref:`invdiscrete<option-flag-invdiscrete>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`island<option-flag-island>` | | | | |
| | | | :ref:`multiccd<option-flag-multiccd>` | :ref:`island<option-flag-island>` | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| mujoco |br| |L| | | .. table:: |
+8 -5
View File
@@ -7,16 +7,15 @@ Upcoming version (not yet released)
General
^^^^^^^
1. Renamed ``mjModel.tex_rbg`` to ``mjModel.tex_data``.
2. Added a new disable flag ``mjDSBL_AUTORESETNAN`` to disable automatic reset when NaNs or infinities are produced.
2. Added a new :ref:`autoreset<option-flag-autoreset>` flag to disable automatic reset when NaNs or infinities are
detected.
3. Added sub-elements to the MJCF :ref:`material<asset-material>` element, to allow specification of multiple textures
for rendering (e.g., :ref:`occlusion-roughness-metallic<material-orm>`). Note that the MuJoCo renderer doesn't support
these new features, and they are made available for use with external renderers.
for rendering (e.g., :ref:`occlusion-roughness-metallic<material-orm>`). Note that the MuJoCo renderer doesn't
support these new features, and they are made available for use with external renderers.
MJX
^^^
4. Added more fields to ``mjx.Model`` and ``mjx.Data`` for further compatibility with the corresponding MuJoCo structs.
5. Added support for :ref:`fixed tendons <tendon-fixed>`.
6. Added support for tendon length limits (``mjCNSTR_LIMIT_TENDON`` in :ref:`mjtConstraint`).
@@ -28,6 +27,10 @@ Python bindings
9. Added support for asset dictionary argument in ``mujoco.spec.from_file``, ``mujoco.spec.from_string`` and
``mujoco.spec.compile``.
Bug fixes
^^^^^^^^^
10. Fixed a bug where implicit integrators did not take into account disabled actuators (:github:issue:`1838`).
Version 3.2.0 (Jul 15, 2024)
----------------------------
+1 -1
View File
@@ -1328,7 +1328,7 @@ representations of the constraint Jacobian and related matrices.
**PGS** : Projected Gauss-Seidel method
This is the most common algorithm used in physics simulators, and used to be the default in MuJoCo, until we
developed the Newton method which appears to be better in every way. PGS uses the dual formulation. Unlike
gradient-based method which improve the solution along oblique directions, Gauss-Seidel works on one scalar component
gradient-based methods which improve the solution along oblique directions, Gauss-Seidel works on one scalar component
at a time, and sets it to its optimal value given the current values of all other components. One sweep of PGS has
the computational complexity of one matrix-vector multiplication (although the constants are larger). It has
first-order convergence but nevertheless makes rapid progress in a few iterations.
+1 -1
View File
@@ -418,7 +418,7 @@ typedef enum mjtDisableBit_ { // disable default feature bitflags
mjDSBL_SENSOR = 1<<12, // sensors
mjDSBL_MIDPHASE = 1<<13, // mid-phase collision filtering
mjDSBL_EULERDAMP = 1<<14, // implicit integration of joint damping in Euler integrator
mjDSBL_AUTORESETNAN = 1<<15, // automatic reset when numerical issues are detected
mjDSBL_AUTORESET = 1<<15, // automatic reset when numerical issues are detected
mjNDISABLE = 16 // number of disable flags
} mjtDisableBit;
+2 -2
View File
@@ -1407,7 +1407,7 @@ Using the :ref:`flexcomp<body-flexcomp>` element, we can create flexes from mesh
automatically generate all the bodies/vertices and connect them with suitable elements. We can also create grids and
other topologies automatically. This machinery makes it easy to create very large flexes, involving thousands or even
tens of thousands of bodies, elements and edges. Obviously such simulations will not be fast. Even for medium-sized
flexes, pruning of collision pairs and essential. This is why we have developed elaborate methods for pruning
flexes, pruning of collision pairs is essential. This is why we have developed elaborate methods for pruning
self-collisions; see XML reference.
In case of 3D flexes made of tetrahedra, it may be useful to examine how the flex is "triangulated" internally. We have
@@ -1724,7 +1724,7 @@ better visualize and understand the contact configuration and resulting forces.
model by design, since without it the inverse dynamics are not defined. This is discussed in detail in the
:ref:`softness and slip<Soft>` clarification. This type of slippage can be addressed in two ways.
a. Increase the :ref:`impration<option-impratio>` parameter. This will reduce (but not entirely prevent) slow
a. Increase the :ref:`impratio<option-impratio>` parameter. This will reduce (but not entirely prevent) slow
slippage. Note that high impratio values work well only with :ref:`elliptic cones<option-cone>`.
b. Enable the noslip solver by increasing :ref:`noslip_iterations<option-noslip_iterations>` to a positive integer.
A small number (1, 2 or 3) is usually sufficient. The noslip post-processing solver will entirely prevent slip,
+1 -1
View File
@@ -9,7 +9,7 @@ aims to facilitate research and development in robotics, biomechanics, graphics
other areas that demand fast and accurate simulation of articulated structures interacting with their environment.
Initially developed by Roboti LLC, it was acquired and made `freely available
<https://github.com/google-deepmind/mujoco/blob/main/LICENSE>`__ by DeepMind in October 2021, and open sourced in May
2022. The MuJoCo codebase is available at the `deepmind/mujoco <https://github.com/google-deepmind/mujoco>`__ repository
2022. The MuJoCo codebase is available at the `google-deepmind/mujoco <https://github.com/google-deepmind/mujoco>`__ repository
on GitHub.
MuJoCo is a C/C++ library with a C API, intended for researchers and developers. The runtime simulation module is tuned
+1 -1
View File
@@ -185,7 +185,7 @@ faithfully restored.
Plugins must declare the number of floating point values required for each instance via the ``nstate`` callback of its
:ref:`mjpPlugin` struct. Note that this number can depend on the exact configuration of the instance. During
:ref:`mj_makeData`, MuJoCo allocate the requisite number of slots in the ``plugin_state`` field of :ref:`mjData` for
:ref:`mj_makeData`, MuJoCo allocates the requisite number of slots in the ``plugin_state`` field of :ref:`mjData` for
each plugin instance. The ``plugin_stateadr`` field in :ref:`mjModel` indicates the position within the overall
``plugin_state`` array at which each plugin instance can find its state values.
+2 -2
View File
@@ -20,7 +20,7 @@ Overview
The new API augments the traditional workflow of creating and editing models using XML files, breaking up the *parse* and
*compile* steps. As summarized in the the :ref:`Overview chapter<Instance>`, the traditional workflow is:
1. Create an XML model description file (MJCF or URDF) and ascociated assets. |br|
1. Create an XML model description file (MJCF or URDF) and associated assets. |br|
2. Call :ref:`mj_loadXML`, obtain an :ref:`mjModel` instance.
The new workflow is:
@@ -107,7 +107,7 @@ Known issues
to `user_api_test.cc <https://github.com/google-deepmind/mujoco/blob/main/test/user/user_api_test.cc>`__ and the MJCF
parser in `xml_native_reader.cc <https://github.com/google-deepmind/mujoco/blob/main/src/xml/xml_native_reader.cc>`__,
which is already using this API.
- One of the central design consideration of the new API is incremental compilation, meaning that after making small
- One of the central design considerations of the new API is incremental compilation, meaning that after making small
changes to a spec that has already been compiled, subsequent re-compilation will be very fast. While the code is
written to support incremental compilation, this functionality is not fully implemented and will be added in the
future, resulting in faster re-compilation times.
+1 -1
View File
@@ -945,7 +945,7 @@ this body quaternion, the quaternions of all other objects attached to the body
multiplication. The function :ref:`mj_local2Global` converts from local body coordinates to global Cartesian
coordinates.
:ref:`mju_negPose` and :ref:`mju_trnVecPose`. A pose is a grouping of a 3D position and a unit quaternion orientation.
A pose is a grouping of a 3D position and a unit quaternion orientation.
There is no separate data structure; the grouping is in terms of logic. This represents a position and orientation in
space, or in other words a spatial frame. Note that OpenGL uses 4-by-4 matrices to represent the same information,
except here we use a quaternion for orientation. The function mju_mulPose multiplies two poses, meaning that it
+1 -1
View File
@@ -328,7 +328,7 @@ ___________________________________
Robotis `MuJoCo plug-in for Unity <https://roboti.us/download.html>`_ steps the simulation in an external Python
process, and uses Unity only for rendering. In contrast, our plug-in relies on Unity to step the simulation. It should
be possible to use our plug-in while an external process "drives" the simulation, for example by seting ``qpos``,
be possible to use our plug-in while an external process "drives" the simulation, for example by setting ``qpos``,
calling ``mj_kinematics``, synchronizing the transforms, and then using Unity to render or compute game logic. In order
to establish communication with an external process, you can use Unity's `ML-Agents
<https://github.com/Unity-Technologies/ml-agents>`_ package.
+1 -1
View File
@@ -61,7 +61,7 @@ typedef enum mjtDisableBit_ { // disable default feature bitflags
mjDSBL_SENSOR = 1<<12, // sensors
mjDSBL_MIDPHASE = 1<<13, // mid-phase collision filtering
mjDSBL_EULERDAMP = 1<<14, // implicit integration of joint damping in Euler integrator
mjDSBL_AUTORESETNAN = 1<<15, // automatic reset when numerical issues are detected
mjDSBL_AUTORESET = 1<<15, // automatic reset when numerical issues are detected
mjNDISABLE = 16 // number of disable flags
} mjtDisableBit;
+1 -1
View File
@@ -42,7 +42,7 @@ ENUMS: Mapping[str, EnumDecl] = dict([
('mjDSBL_SENSOR', 4096),
('mjDSBL_MIDPHASE', 8192),
('mjDSBL_EULERDAMP', 16384),
('mjDSBL_AUTORESETNAN', 32768),
('mjDSBL_AUTORESET', 32768),
('mjNDISABLE', 16),
]),
)),
+1 -1
View File
@@ -57,7 +57,7 @@
<site name="weight2" pos="-.013 -.013 .013" size="0.005"/>
</body>
<!-- The gravcomp value of 7.2 is the ratio of air and helium desities at room temperature. -->
<!-- The gravcomp value of 7.2 is the ratio of air and helium densities at room temperature. -->
<body name="pink" gravcomp="7.2" pos="-.2 .1 .2" childclass="pink">
<freejoint />
<geom name="pink" type="ellipsoid" size=".11 .11 .15"/>
+2 -2
View File
@@ -4,7 +4,7 @@
The `mujoco.pid` actuator plugin implements a configurable [PID controller](https://en.wikipedia.org/wiki/Proportional%E2%80%93integral%E2%80%93derivative_controller):
$$f(t) = K_\text{p} e(t) + K_\text{i} \int_0^t e(\tau) \,\mathrm{d}\tau + K_\text{d} \frac{\mathrm{d}e(t)}{\mathrm{d}t},$$
$$f(t) = K_\text{p} e(t) + K_\text{i} \int_0^t e(\tau) \mathrm{d}\tau + K_\text{d} \frac{\mathrm{d}e(t)}{\mathrm{d}t},$$
where $e(t) = u(t) - \ell(t)$ is the difference between the control $u$ and the actuator length $\ell$.
You can use it like:
@@ -43,5 +43,5 @@ The available options are:
|`kp` | 0 | **P** gain for the controller. |
|`ki` | 0 | **I** gain for the controller.<p/>If nonzero, one activation variable will be added to `mjData.act`, containing the current I term (in units of force). |
|`kd` | 0 | **D** gain for the controller. |
|`imax` | Optional | If specified, the force produced by the I term will be clipped to the range `[-imax, -imax]`. |
|`imax` | Optional | If specified, the force produced by the I term will be clipped to the range `[-imax, imax]`. |
|`slewmax` | Optional | The maximum rate at which the setpoint for the PID controller can change.<p/>If a bigger change is requested between two timesteps, it will be clipped to the range `[ctrl - slewmax * dt, ctrl + slewmax * dt]`<p/>If specified, one activation variable will be added to `mjData.act` containing the previous value of `ctrl`. |
-1
View File
@@ -568,7 +568,6 @@ PYBIND11_MODULE(_specs, m) {
// ============================= MJSBODY =====================================
mjsBody.def_property_readonly(
"id", [](raw::MjsBody& self) -> int { return mjs_getId(self.element); });
mjsBody.def("delete", [](raw::MjsBody& self) { mjs_delete(self.element); });
mjsBody.def(
"add_body",
[](raw::MjsBody& self, raw::MjsDefault* default_) -> raw::MjsBody* {
+52
View File
@@ -98,6 +98,34 @@ class SpecsTest(absltest.TestCase):
</mujoco>
"""),)
def test_load_xml(self):
filename = '../../test/testdata/model.xml'
state_type = mujoco.mjtState.mjSTATE_INTEGRATION
# Load from file.
spec1 = mujoco.MjSpec()
spec1.from_file(filename)
model1 = spec1.compile()
data1 = mujoco.MjData(model1)
mujoco.mj_step(model1, data1)
size1 = mujoco.mj_stateSize(model1, state_type)
state1 = np.empty(size1, np.float64)
mujoco.mj_getState(model1, data1, state1, state_type)
# Load from string.
spec2 = mujoco.MjSpec()
with open(filename, 'r') as file:
spec2.from_string(file.read().rstrip())
model2 = spec2.compile()
data2 = mujoco.MjData(model2)
mujoco.mj_step(model2, data2)
size2 = mujoco.mj_stateSize(model2, state_type)
state2 = np.empty(size2, np.float64)
mujoco.mj_getState(model2, data2, state2, state_type)
# Check that the state is the same.
np.testing.assert_array_equal(state1, state2)
def test_compile_errors_with_line_info(self):
spec = mujoco.MjSpec()
@@ -269,6 +297,30 @@ class SpecsTest(absltest.TestCase):
model = spec.compile({'cube.obj': cube})
self.assertEqual(model.nmeshvert, 8)
def test_delete(self):
filename = '../../test/testdata/model.xml'
spec = mujoco.MjSpec()
spec.from_file(filename)
model = spec.compile()
self.assertIsNotNone(model)
self.assertEqual(model.nsite, 11)
self.assertEqual(model.nsensor, 11)
head = spec.find_body('head')
self.assertIsNotNone(head)
site = head.first_site()
self.assertIsNotNone(site)
site.delete()
spec.sensors[-1].delete()
spec.sensors[-1].delete()
model = spec.compile()
self.assertIsNotNone(model)
self.assertEqual(model.nsite, 10)
self.assertEqual(model.nsensor, 9)
if __name__ == '__main__':
absltest.main()
+20 -1
View File
@@ -16,7 +16,9 @@
#include <Python.h>
#include <algorithm>
#include <array>
#include <cctype>
#include <cstddef>
#include <cstdint>
#include <cstring>
@@ -84,6 +86,22 @@ constexpr auto XArrayShapeImpl(const std::string_view dim1_str) {
inline std::size_t NConMax(const mjData* d) {
return d->narena / sizeof(mjContact);
}
// strip path prefix from filename and make lowercase
std::string StripPath(const char* name) {
std::string filename(name);
size_t start = filename.find_last_of("/\\");
// get name without path
if (start != std::string::npos) {
filename = filename.substr(start + 1, filename.size() - start - 1);
}
// make lowercase
std::transform(filename.begin(), filename.end(), filename.begin(),
[](unsigned char c) { return std::tolower(c); });
return filename;
}
} // namespace
// ==================== MJOPTION ===============================================
@@ -323,8 +341,9 @@ static raw::MjModel* LoadModelFileImpl(
mj_defaultVFS(&vfs);
vfs_ptr = &vfs;
for (const auto& asset : assets) {
std::string buffer_name = StripPath(asset.name);
const int vfs_error = InterceptMjErrors(mj_addBufferVFS)(
vfs_ptr, asset.name, asset.content, asset.content_size);
vfs_ptr, buffer_name.c_str(), asset.content, asset.content_size);
if (vfs_error) {
throw py::value_error("assets dict is too big");
}
+24 -2
View File
@@ -199,6 +199,16 @@ void scanPluginLibraries() {
//------------------------------------------- simulation -------------------------------------------
const char* Diverged(int disableflags, const mjData* d) {
if (disableflags & mjDSBL_AUTORESET) {
for (mjtWarning w : {mjWARN_BADQACC, mjWARN_BADQVEL, mjWARN_BADQPOS}) {
if (d->warning[w].number > 0) {
return mju_warningText(w, d->warning[w].lastinfo);
}
}
}
return nullptr;
}
mjModel* LoadModel(const char* file, mj::Simulate& sim) {
// this copy is needed so that the mju::strlen call below compiles
@@ -356,7 +366,13 @@ void PhysicsLoop(mj::Simulate& sim) {
// run single step, let next iteration deal with timing
mj_step(m, d);
stepped = true;
const char* message = Diverged(m->opt.disableflags, d);
if (message) {
sim.run = 0;
mju::strcpy_arr(sim.load_error, message);
} else {
stepped = true;
}
}
// in-sync: step until ahead of cpu
@@ -381,7 +397,13 @@ void PhysicsLoop(mj::Simulate& sim) {
// call mj_step
mj_step(m, d);
stepped = true;
const char* message = Diverged(m->opt.disableflags, d);
if (message) {
sim.run = 0;
mju::strcpy_arr(sim.load_error, message);
} else {
stepped = true;
}
// break if reset
if (d->time < prevSim) {
+1 -6
View File
@@ -739,18 +739,12 @@ void MakePhysicsSection(mj::Simulate* sim) {
for (int i=0; i<mjNDISABLE; i++) {
mju::strcpy_arr(defFlag[0].name, mjDISABLESTRING[i]);
defFlag[0].pdata = sim->disable + i;
if ((1 << i) == mjDSBL_AUTORESETNAN) {
defFlag[0].state = 0;
} else {
defFlag[0].state = 2;
}
mjui_add(&sim->ui0, defFlag);
}
mjui_add(&sim->ui0, defEnableFlags);
for (int i=0; i<mjNENABLE; i++) {
mju::strcpy_arr(defFlag[0].name, mjENABLESTRING[i]);
defFlag[0].pdata = sim->enable + i;
defFlag[0].state = 2;
mjui_add(&sim->ui0, defFlag);
}
// add contact override
@@ -1956,6 +1950,7 @@ void Simulate::Sync() {
if (pending_.reset) {
mj_resetData(m_, d_);
mj_forward(m_, d_);
load_error[0] = '\0';
update_profiler = true;
update_sensor = true;
scrub_index = 0;
+5
View File
@@ -834,6 +834,11 @@ void mjd_actuator_vel(const mjModel* m, mjData* d) {
// process actuators
for (int i=0; i < m->nu; i++) {
// skip if disabled
if (mj_actuatorDisabled(m, i)) {
continue;
}
mjtNum bias_vel = 0, gain_vel = 0;
// affine bias
+4 -4
View File
@@ -52,7 +52,7 @@ void mj_checkPos(const mjModel* m, mjData* d) {
for (int i=0; i < m->nq; i++) {
if (mju_isBad(d->qpos[i])) {
mj_warning(d, mjWARN_BADQPOS, i);
if (!(m->opt.disableflags & mjDSBL_AUTORESETNAN)) {
if (!mjDISABLED(mjDSBL_AUTORESET)) {
mj_resetData(m, d);
}
d->warning[mjWARN_BADQPOS].number++;
@@ -69,7 +69,7 @@ void mj_checkVel(const mjModel* m, mjData* d) {
for (int i=0; i < m->nv; i++) {
if (mju_isBad(d->qvel[i])) {
mj_warning(d, mjWARN_BADQVEL, i);
if (!(m->opt.disableflags & mjDSBL_AUTORESETNAN)) {
if (!mjDISABLED(mjDSBL_AUTORESET)) {
mj_resetData(m, d);
}
d->warning[mjWARN_BADQVEL].number++;
@@ -86,12 +86,12 @@ void mj_checkAcc(const mjModel* m, mjData* d) {
for (int i=0; i < m->nv; i++) {
if (mju_isBad(d->qacc[i])) {
mj_warning(d, mjWARN_BADQACC, i);
if (!(m->opt.disableflags & mjDSBL_AUTORESETNAN)) {
if (!mjDISABLED(mjDSBL_AUTORESET)) {
mj_resetData(m, d);
}
d->warning[mjWARN_BADQACC].number++;
d->warning[mjWARN_BADQACC].lastinfo = i;
if (!(m->opt.disableflags & mjDSBL_AUTORESETNAN)) {
if (!mjDISABLED(mjDSBL_AUTORESET)) {
mj_forward(m, d);
}
return;
+1 -1
View File
@@ -58,7 +58,7 @@ const char* mjDISABLESTRING[mjNDISABLE] = {
"Sensor",
"Midphase",
"Eulerdamp",
"AutoResetNaN"
"AutoReset"
};
+2 -2
View File
@@ -148,7 +148,7 @@ int mjs_detachBody(mjSpec* s, mjsBody* b) {
mjCModel* model = static_cast<mjCModel*>(s->element);
mjCBody* body = static_cast<mjCBody*>(b->element);
*model -= *body;
mjs_delete(b->element);
delete body;
return 0;
}
@@ -181,7 +181,7 @@ void mjs_addSpec(mjSpec* s, mjSpec* child) {
// delete object, it will call the appropriate destructor since ~mjCBase is virtual
void mjs_delete(mjsElement* element) {
mjCBase* object = static_cast<mjCBase*>(element);
delete object;
object->model->DeleteElement(element);
}
+6 -7
View File
@@ -900,12 +900,12 @@ bool mjCFlexcomp::MakeMesh(mjCModel* model, char* error, int error_sz) {
}
// load resource
std::string filename = mjuu_combinePaths(mjs_getString(model->spec.modelfiledir),
mjs_getString(model->spec.meshdir), file);
std::string filename = mjuu_combinePaths(mjs_getString(model->spec.meshdir), file);
mjResource* resource = nullptr;
try {
resource = mjCBase::LoadResource(filename, 0);
resource = mjCBase::LoadResource(mjs_getString(model->spec.modelfiledir),
filename, 0);
} catch (mjCError err) {
return comperr(error, err.message, error_sz);
}
@@ -999,12 +999,11 @@ bool mjCFlexcomp::MakeGMSH(mjCModel* model, char* error, int error_sz) {
}
// open resource
std::string filename = mjuu_combinePaths(mjs_getString(model->spec.modelfiledir),
mjs_getString(model->spec.meshdir), file);
mjResource* resource = nullptr;
try {
resource = mjCBase::LoadResource(filename, 0);
std::string filename = mjuu_combinePaths(mjs_getString(model->spec.meshdir), file);
resource = mjCBase::LoadResource(mjs_getString(model->spec.modelfiledir),
filename, 0);
} catch (mjCError err) {
return comperr(error, err.message, error_sz);
}
+7 -7
View File
@@ -402,8 +402,8 @@ void mjCMesh::Compile(const mjVFS* vfs) {
throw mjCError(this, "unsupported content type: '%s'", asset_type.c_str());
}
std::string filename = mjuu_combinePaths(model->modelfiledir_, model->meshdir_, file_);
mjResource* resource = LoadResource(filename, vfs);
std::string filename = mjuu_combinePaths(model->meshdir_, file_);
mjResource* resource = LoadResource(model->modelfiledir_, filename, vfs);
try {
if (asset_type == "model/stl") {
@@ -2095,8 +2095,8 @@ void mjCSkin::Compile(const mjVFS* vfs) {
throw mjCError(this, "Unknown skin file type: %s", file_.c_str());
}
std::string filename = mjuu_combinePaths(model->modelfiledir_, model->meshdir_, file_);
mjResource* resource = LoadResource(filename, vfs);
std::string filename = mjuu_combinePaths(model->meshdir_, file_);
mjResource* resource = LoadResource(model->modelfiledir_, filename, vfs);
try {
LoadSKN(resource);
@@ -2152,7 +2152,7 @@ void mjCSkin::Compile(const mjVFS* vfs) {
if (pmat) {
matid = pmat->id;
} else if (!material_.empty()) {
throw mjCError(this, "unkown material '%s' in skin", material_.c_str());
throw mjCError(this, "unknown material '%s' in skin", material_.c_str());
}
// set total vertex weights to 0
@@ -2452,7 +2452,7 @@ void mjCFlex::ResolveReferences(const mjCModel* m) {
if (pbody) {
vertbodyid.push_back(pbody->id);
} else {
throw mjCError(this, "unkown body '%s' in flex", vertbody.c_str());
throw mjCError(this, "unknown body '%s' in flex", vertbody.c_str());
}
}
}
@@ -2515,7 +2515,7 @@ void mjCFlex::Compile(const mjVFS* vfs) {
if (pmat) {
matid = pmat->id;
} else if (!material_.empty()) {
throw mjCError(this, "unkown material '%s' in flex", material_.c_str());
throw mjCError(this, "unknown material '%s' in flex", material_.c_str());
}
// resolve body ids
+77 -8
View File
@@ -250,15 +250,22 @@ mjCModel& mjCModel::operator+=(const mjCModel& other) {
template <class T>
void mjCModel::RemoveFromList(std::vector<T*>& list) {
void mjCModel::RemoveFromList(std::vector<T*>& list, const mjCModel& other) {
int nlist = (int)list.size();
int removed = 0;
for (int i = 0; i < nlist; i++) {
T* element = list[i];
element->id -= removed;
try {
// check if the element contains an error
element->NameSpace(&other);
element->CopyFromSpec();
element->ResolveReferences(&other);
} catch (mjCError err) {
continue;
}
try {
// check if the element references something that was removed
// TODO: do not remove elements that contain user errors
element->NameSpace(this);
element->CopyFromSpec();
element->ResolveReferences(this);
@@ -296,12 +303,12 @@ mjCModel& mjCModel::operator-=(const mjCBody& subtree) {
ProcessLists(/*checkrepeat=*/false);
// check if we have to remove anything else
RemoveFromList(pairs_);
RemoveFromList(excludes_);
RemoveFromList(tendons_);
RemoveFromList(equalities_);
RemoveFromList(actuators_);
RemoveFromList(sensors_);
RemoveFromList(pairs_, oldmodel);
RemoveFromList(excludes_, oldmodel);
RemoveFromList(tendons_, oldmodel);
RemoveFromList(equalities_, oldmodel);
RemoveFromList(actuators_, oldmodel);
RemoveFromList(sensors_, oldmodel);
// restore to the original state
if (!compiled) {
@@ -333,6 +340,68 @@ mjCModel_& mjCModel::operator+=(mjCDef& subtree) {
template <class T>
void deletefromlist(std::vector<T*>* list, mjsElement* element) {
if (!list) {
return;
}
for (int j = 0; j < list->size(); ++j) {
list->at(j)->id = -1;
if (list->at(j) == element) {
delete list->at(j);
list->erase(list->begin() + j);
j--;
}
}
}
// discard all invalid elements from all lists
void mjCModel::DeleteElement(mjsElement* el) {
mjCBody *world = bodies_[0];
if (compiled) {
ResetTreeLists();
}
switch (el->elemtype) {
case mjOBJ_BODY:
throw mjCError(NULL, "bodies cannot be deleted, use detach instead");
break;
case mjOBJ_GEOM:
deletefromlist(&(static_cast<mjCGeom*>(el)->body->geoms), el);
break;
case mjOBJ_SITE:
deletefromlist(&(static_cast<mjCSite*>(el)->body->sites), el);
break;
case mjOBJ_JOINT:
deletefromlist(&(static_cast<mjCJoint*>(el)->body->joints), el);
break;
case mjOBJ_LIGHT:
deletefromlist(&(static_cast<mjCLight*>(el)->body->lights), el);
break;
case mjOBJ_CAMERA:
deletefromlist(&(static_cast<mjCCamera*>(el)->body->cameras), el);
break;
default:
deletefromlist(object_lists_[el->elemtype], el);
break;
}
if (compiled) {
MakeLists(world);
ProcessLists(/*checkrepeat=*/false);
}
}
// TODO: we should not use C-type casting with multiple C++ inheritance
void mjCModel::CreateObjectLists() {
for (int i = 0; i < mjNOBJECT; ++i) {
+4 -1
View File
@@ -202,6 +202,9 @@ class mjCModel : public mjCModel_, private mjSpec {
// delete all elements
template <class T> void DeleteAll(std::vector<T*>& elements);
// delete object from the corresponding list
void DeleteElement(mjsElement* el);
// API for access to model elements (outside tree)
int NumObjects(mjtObj type); // number of objects in specified list
mjCBase* GetObject(mjtObj type, int id); // pointer to specified object
@@ -345,7 +348,7 @@ class mjCModel : public mjCModel_, private mjSpec {
const std::vector<T*>& sources);
// delete from list the elements that cause an error
template <class T> void RemoveFromList(std::vector<T*>& list);
template <class T> void RemoveFromList(std::vector<T*>& list, const mjCModel& other);
// create mjCBase lists from children lists
void CreateObjectLists();
+9 -7
View File
@@ -701,10 +701,12 @@ void mjCBase::NameSpace(const mjCModel* m) {
// load resource if found (fallback to OS filesystem)
mjResource* mjCBase::LoadResource(std::string filename, const mjVFS* vfs) {
mjResource* mjCBase::LoadResource(const std::string& modelfiledir,
const std::string& filename,
const mjVFS* vfs) {
// try reading from provided VFS or fallback to OS filesystem
std::array<char, 1024> error;
mjResource* resource = mju_openResource(filename.c_str(), vfs,
mjResource* resource = mju_openResource(modelfiledir.c_str(), filename.c_str(), vfs,
error.data(), error.size());
if (!resource) {
throw mjCError(nullptr, "%s", error.data());
@@ -3159,8 +3161,8 @@ void mjCHField::Compile(const mjVFS* vfs) {
throw mjCError(this, "unsupported content type: '%s'", asset_type.c_str());
}
std::string filename = mjuu_combinePaths(model->modelfiledir_, model->meshdir_, file_);
mjResource* resource = LoadResource(filename, vfs);
std::string filename = mjuu_combinePaths(model->meshdir_, file_);
mjResource* resource = LoadResource(model->modelfiledir_, filename, vfs);
try {
if (asset_type == "image/png") {
@@ -3594,7 +3596,7 @@ void mjCTexture::LoadFlip(std::string filename, const mjVFS* vfs,
throw mjCError(this, "unsupported content type: '%s'", asset_type.c_str());
}
mjResource* resource = LoadResource(filename, vfs);
mjResource* resource = LoadResource(model->modelfiledir_, filename, vfs);
try {
if (asset_type == "image/png") {
@@ -3797,7 +3799,7 @@ void mjCTexture::LoadCubeSeparate(const mjVFS* vfs) {
}
// make filename
std::string filename = mjuu_combinePaths(model->modelfiledir_, model->texturedir_, cubefiles_[i]);
std::string filename = mjuu_combinePaths(model->texturedir_, cubefiles_[i]);
// load PNG or custom
unsigned int w, h;
@@ -3895,7 +3897,7 @@ void mjCTexture::Compile(const mjVFS* vfs) {
}
// make filename
std::string filename = mjuu_combinePaths(model->modelfiledir_, model->texturedir_, file_);
std::string filename = mjuu_combinePaths(model->texturedir_, file_);
// dispatch
if (type==mjTEXTURE_2D) {
+2 -1
View File
@@ -181,7 +181,8 @@ class mjCBase : public mjCBase_ {
public:
// load resource if found (fallback to OS filesystem)
static mjResource* LoadResource(std::string filename, const mjVFS* vfs);
static mjResource* LoadResource(const std::string& modelfiledir,
const std::string& filename, const mjVFS* vfs);
// Get and sanitize content type from raw_text if not empty, otherwise parse
// content type from resource_name; throw on failure
+14 -7
View File
@@ -23,6 +23,7 @@
#include <cstdio>
#include <cstring>
#include <ctime>
#include <string>
#include <vector>
#if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__))
@@ -117,8 +118,8 @@ int FileModified(const mjResource* resource, const char*timestamp) {
// open the given resource; if the name doesn't have a prefix matching with a
// resource provider, then the OS filesystem is used
mjResource* mju_openResource(const char* name, const mjVFS* vfs,
char* error, size_t nerror) {
mjResource* mju_openResource(const char* dir, const char* name,
const mjVFS* vfs, char* error, size_t nerror) {
// no error so far
if (error) {
error[0] = '\0';
@@ -136,8 +137,10 @@ mjResource* mju_openResource(const char* name, const mjVFS* vfs,
// clear out resource
memset(resource, 0, sizeof(mjResource));
// copy name
resource->name = (char*) mju_malloc(sizeof(char) * (strlen(name) + 1));
// make space for filename
std::string fullname = mjuu_combinePaths(dir, name);
std::size_t n = fullname.size();
resource->name = (char*) mju_malloc(sizeof(char) * (n + 1));
if (resource->name == nullptr) {
if (error) {
strncpy(error, "could not allocate memory", nerror);
@@ -146,10 +149,11 @@ mjResource* mju_openResource(const char* name, const mjVFS* vfs,
mju_closeResource(resource);
return nullptr;
}
memcpy(resource->name, name, sizeof(char) * (strlen(name) + 1));
// first priority is to check the VFS
if (vfs != nullptr) {
memcpy(resource->name, name,
sizeof(char) * (std::strlen(name) + 1));
const mjpResourceProvider* provider = GetVfsResourceProvider();
resource->data = (void*) vfs;
resource->provider = provider;
@@ -158,8 +162,11 @@ mjResource* mju_openResource(const char* name, const mjVFS* vfs,
}
}
// copy full path over
memcpy(resource->name, fullname.c_str(), sizeof(char) * (n + 1));
// find provider based off prefix of name
const mjpResourceProvider* provider = mjp_getResourceProvider(name);
const mjpResourceProvider* provider = mjp_getResourceProvider(resource->name);
if (provider != nullptr) {
resource->provider = provider;
resource->data = nullptr;
@@ -170,7 +177,7 @@ mjResource* mju_openResource(const char* name, const mjVFS* vfs,
if (error) {
snprintf(error, nerror, "could not open '%s'"
"using a resource provider matching prefix '%s'",
name, provider->prefix);
resource->name, provider->prefix);
}
mju_closeResource(resource);
+2 -4
View File
@@ -18,8 +18,6 @@
#define MUJOCO_SRC_ENGINE_ENGINE_RESOURCE_H_
#include <cstddef>
#include <cstdint>
#include <vector>
#include <mujoco/mjexport.h>
#include <mujoco/mujoco.h>
@@ -30,8 +28,8 @@ extern "C" {
// open the given resource; if the name doesn't have a prefix matching with a
// resource provider, then the OS filesystem is used
MJAPI mjResource* mju_openResource(const char* name, const mjVFS* vfs,
char* error, std::size_t nerror);
MJAPI mjResource* mju_openResource(const char* dir, const char* name,
const mjVFS* vfs, char* error, size_t nerror);
// close the given resource; no-op if resource is NULL
MJAPI void mju_closeResource(mjResource* resource);
+1 -1
View File
@@ -996,7 +996,7 @@ std::string FilePath::PathReduce(const std::string& str) {
if (IsSeperator(str[i])) {
std::string temp = str.substr(j, i - j);
j = i + 1;
if (temp == ".." && !dirs.empty()) {
if (temp == ".." && !dirs.empty() && dirs.back() != "..") {
dirs.pop_back();
} else if (temp != ".") {
dirs.push_back(std::move(temp));
+12 -7
View File
@@ -142,9 +142,13 @@ int Open(mjResource* resource) {
const VFS* cvfs = GetVFSImpl(vfs);
const VFSFile* file = cvfs->GetFile(StripPath(resource->name));
if (file == nullptr) {
return 0;
file = cvfs->GetFile(FilePath(resource->name));
if (file == nullptr) {
return 0;
}
}
resource->data = (void*) file;
resource->timestamp[0] = '\0';
if (file->filestamp) {
mju_encodeBase64(resource->timestamp, (uint8_t*) &file->filestamp,
@@ -160,8 +164,7 @@ int Read(mjResource* resource, const void** buffer) {
return -1;
}
const VFS* vfs = GetVFSImpl(static_cast<const mjVFS*>(resource->data));
const VFSFile* file = vfs->GetFile(StripPath(resource->name));
const VFSFile* file = static_cast<const VFSFile*>(resource->data);
if (file == nullptr) {
*buffer = nullptr;
return -1;
@@ -193,8 +196,7 @@ int Modified(const mjResource* resource, const char* timestamp) {
if (!filestamp) return 3; // no hash (assume modified)
if (resource) {
const VFS* cvfs = GetVFSImpl(static_cast<const mjVFS*>(resource->data));
const VFSFile* file = cvfs->GetFile(StripPath(resource->name));
const VFSFile* file = static_cast<const VFSFile*>(resource->data);
if (file == nullptr) return 4; // missing file (assume modified)
if (!file->filestamp) return 5; // missing filestamp (assume modified)
@@ -245,7 +247,7 @@ int mj_addBufferVFS(mjVFS* vfs, const char* name, const void* buffer,
std::vector<uint8_t> inbuffer;
VFS* cvfs = GetVFSImpl(vfs);
VFSFile* file;
if (!(file = cvfs->AddFile(StripPath(name), std::move(inbuffer), 0))) {
if (!(file = cvfs->AddFile(FilePath(name), std::move(inbuffer), 0))) {
return 2; // AddFile failed, repeated name
}
file->filedata.reserve(nbuffer);
@@ -256,7 +258,10 @@ int mj_addBufferVFS(mjVFS* vfs, const char* name, const void* buffer,
// delete file from VFS, return 0: success, -1: not found in VFS
int mj_deleteFileVFS(mjVFS* vfs, const char* filename) {
VFS* cvfs = GetVFSImpl(vfs);
return cvfs->DeleteFile(StripPath(filename));
if (cvfs->DeleteFile(StripPath(filename))) {
return cvfs->DeleteFile(FilePath(filename));
}
return 0;
}
// delete all files from VFS
+68 -82
View File
@@ -48,9 +48,11 @@ namespace {
using tinyxml2::XMLDocument;
using tinyxml2::XMLElement;
using tinyxml2::XMLNode;
using mujoco::user::FilePath;
namespace mju = ::mujoco::util;
// We are using "locale-sensitive" sprintf to read and write XML.
// When MuJoCo is being used as a plug-in for an application that respects the system locale
// (e.g. Unity), the user's locale setting can affect the formatting of numbers into strings.
@@ -97,29 +99,30 @@ class LocaleOverride {
};
#endif
} // namespace
// Main writer function - calls mjXWrite
std::string mjWriteXML(const mjSpec* spec, char* error, int error_sz) {
LocaleOverride locale_override;
// check for empty model
if (!spec) {
mjCopyError(error, "Cannot write empty model", error_sz);
return "";
void RegisterResourceProvider() {
// register string resource provider if not registered before
if (mjp_getResourceProvider("LoadModelFromString:") == nullptr) {
mjpResourceProvider resourceProvider;
mjp_defaultResourceProvider(&resourceProvider);
resourceProvider.prefix = "LoadModelFromString";
resourceProvider.open = +[](mjResource* resource) {
resource->data = &(resource->name[strlen("LoadModelFromString:")]);
return 1;
};
resourceProvider.read =
+[](mjResource* resource, const void** buffer) {
*buffer = resource->data;
return (int) strlen((const char*) resource->data);
};
resourceProvider.close = +[](mjResource* resource) {};
mjp_registerResourceProvider(&resourceProvider);
}
mjXWriter writer;
writer.SetModel(spec);
return writer.Write(error, error_sz);
}
// find include elements recursively, replace them with subtree from xml file
static void mjIncludeXML(mjXReader& reader, XMLElement* elem,
std::string_view dir, const mjVFS* vfs,
std::unordered_set<std::string>& included) {
void IncludeXML(mjXReader& reader, XMLElement* elem,
const FilePath& dir, const mjVFS* vfs,
std::unordered_set<std::string>& included) {
// capture directory defaults on first pass of XML tree
if (!strcasecmp(elem->Value(), "compiler")) {
auto assetdir_attr = mjXUtil::ReadAttrStr(elem, "assetdir");
@@ -142,7 +145,7 @@ static void mjIncludeXML(mjXReader& reader, XMLElement* elem,
if (strcasecmp(elem->Value(), "include")) {
XMLElement* child = elem->FirstChildElement();
for (; child; child = child->NextSiblingElement()) {
mjIncludeXML(reader, child, dir, vfs, included);
IncludeXML(reader, child, dir, vfs, included);
}
return;
}
@@ -153,36 +156,32 @@ static void mjIncludeXML(mjXReader& reader, XMLElement* elem,
}
// get filename
auto file_attr = mjXUtil::ReadAttrStr(elem, "file", true);
auto file_attr = mjXUtil::ReadAttrFile(elem, "file", vfs,
reader.ModelFileDir(), true);
if (!file_attr.has_value()) {
throw mjXError(elem, "Include element missing file attribute");
}
std::string filename = file_attr.value();
FilePath filename = file_attr.value();
// block repeated include files
if (included.find(filename) != included.end()) {
if (included.find(filename.Str()) != included.end()) {
throw mjXError(elem, "File '%s' already included", filename.c_str());
}
// TODO: b/325905702 - We have a messy wrapper here to remain backwards
// compatible, which will be removed in the near future.
std::string fullname;
if (!mjuu_isabspath(filename)) {
fullname = reader.ModelFileDir() + filename;
} else {
fullname = filename;
}
// legacy behavior: try to load in top level directory
std::array<char, 1024> error;
mjResource *resource = mju_openResource(fullname.c_str(), vfs,
mjResource *resource = mju_openResource(reader.ModelFileDir().c_str(),
filename.c_str(), vfs,
error.data(), error.size());
if (resource == nullptr) {
// new behavior: try to load in relative directory
if (!mjuu_isabspath(filename)) {
fullname = std::string(dir) + filename;
resource = mju_openResource(fullname.c_str(), vfs, error.data(), error.size());
if (!filename.IsAbs()) {
FilePath fullname = dir + filename;
resource = mju_openResource(reader.ModelFileDir().c_str(),
fullname.c_str(), vfs, error.data(), error.size());
}
}
@@ -190,18 +189,13 @@ static void mjIncludeXML(mjXReader& reader, XMLElement* elem,
throw mjXError(elem, "%s", error.data());
}
if (!mjuu_isabspath(filename)) {
filename = std::string(dir) + filename;
}
filename = dir + filename;
const char* include_dir = nullptr;
int ninclude_dir = 0;
mju_getResourceDir(resource, &include_dir, &ninclude_dir);
std::string next_dir = std::string(include_dir, ninclude_dir);
if (!mjuu_isabspath(filename)) {
next_dir = std::string(dir) + next_dir;
}
elem->SetAttribute("dir", next_dir.data());
FilePath next_dir = FilePath(std::string(include_dir, ninclude_dir));
elem->SetAttribute("dir", next_dir.c_str());
const char* xmlstring = nullptr;
int buffer_size = mju_readResource(resource, (const void**) &xmlstring);
@@ -228,7 +222,7 @@ static void mjIncludeXML(mjXReader& reader, XMLElement* elem,
}
// remember that file was included
included.insert(filename);
included.insert(filename.Str());
// get and check root element
XMLElement* docroot = doc.RootElement();
@@ -262,21 +256,21 @@ static void mjIncludeXML(mjXReader& reader, XMLElement* elem,
// recursively run include
child = include->FirstChildElement();
for (; child; child = child->NextSiblingElement()) {
mjIncludeXML(reader, child, next_dir, vfs, included);
IncludeXML(reader, child, next_dir, vfs, included);
}
}
} // namespace
// Main parser function
mjSpec* mjParseXML(const char* filename, const mjVFS* vfs,
char* error, int error_sz) {
mjSpec* ParseXML(const char* filename, const mjVFS* vfs,
char* error, int nerror) {
LocaleOverride locale_override;
// check arguments
if (!filename) {
if (error) {
std::snprintf(error, error_sz, "mjParseXML: filename argument required\n");
std::snprintf(error, nerror, "ParseXML: filename argument required\n");
}
return nullptr;
}
@@ -290,24 +284,24 @@ mjSpec* mjParseXML(const char* filename, const mjVFS* vfs,
// get data source
const char* xmlstring = nullptr;
std::array<char, 1024> rerror;
mjResource* resource = mju_openResource(filename, vfs,
mjResource* resource = mju_openResource("", filename, vfs,
rerror.data(), rerror.size());
if (resource == nullptr) {
std::snprintf(error, error_sz, "mjParseXML: %s", rerror.data());
std::snprintf(error, nerror, "ParseXML: %s", rerror.data());
return nullptr;
}
int buffer_size = mju_readResource(resource, (const void**) &xmlstring);
if (buffer_size < 0) {
if (error) {
std::snprintf(error, error_sz,
"mjParseXML: error reading file '%s'", filename);
std::snprintf(error, nerror,
"ParseXML: error reading file '%s'", filename);
}
mju_closeResource(resource);
return nullptr;
} else if (!buffer_size) {
if (error) {
std::snprintf(error, error_sz, "mjParseXML: empty file '%s'", filename);
std::snprintf(error, nerror, "ParseXML: empty file '%s'", filename);
}
mju_closeResource(resource);
return nullptr;
@@ -321,7 +315,7 @@ mjSpec* mjParseXML(const char* filename, const mjVFS* vfs,
// error checking
if (doc.Error()) {
if (error) {
snprintf(error, error_sz, "XML parse error %d:\n%s\n",
snprintf(error, nerror, "XML parse error %d:\n%s\n",
doc.ErrorID(), doc.ErrorStr());
}
mju_closeResource(resource);
@@ -332,7 +326,7 @@ mjSpec* mjParseXML(const char* filename, const mjVFS* vfs,
XMLElement* root = doc.RootElement();
if (!root) {
mju_closeResource(resource);
mjCopyError(error, "XML root element not found", error_sz);
mjCopyError(error, "XML root element not found", nerror);
return nullptr;
}
@@ -357,7 +351,7 @@ mjSpec* mjParseXML(const char* filename, const mjVFS* vfs,
std::unordered_set<std::string> included = {filename};
mjXReader parser;
parser.SetModelFileDir(mjs_getString(spec->modelfiledir));
mjIncludeXML(parser, root, mjs_getString(spec->modelfiledir), vfs, included);
IncludeXML(parser, root, FilePath(), vfs, included);
// parse MuJoCo model
parser.SetModel(spec);
@@ -385,7 +379,7 @@ mjSpec* mjParseXML(const char* filename, const mjVFS* vfs,
// catch known errors
catch (mjXError err) {
mjCopyError(error, err.message, error_sz);
mjCopyError(error, err.message, nerror);
mj_deleteSpec(spec);
return nullptr;
}
@@ -393,32 +387,24 @@ mjSpec* mjParseXML(const char* filename, const mjVFS* vfs,
return spec;
}
static void RegisterResourceProvider() {
// register string resource provider if not registered before
if (mjp_getResourceProvider("LoadModelFromString:") == nullptr) {
mjpResourceProvider resourceProvider;
mjp_defaultResourceProvider(&resourceProvider);
resourceProvider.prefix = "LoadModelFromString";
resourceProvider.open = +[](mjResource* resource) {
resource->data = &(resource->name[strlen("LoadModelFromString:")]);
return 1;
};
resourceProvider.read =
+[](mjResource* resource, const void** buffer) {
*buffer = resource->data;
return (int) strlen((const char*) resource->data);
};
resourceProvider.close = +[](mjResource* resource) {};
mjp_registerResourceProvider(&resourceProvider);
}
}
mjSpec* ParseSpecFromString(std::string_view xml, char* error,
int error_size) {
mjSpec* ParseSpecFromString(std::string_view xml, char* error, int nerror) {
RegisterResourceProvider();
std::string xml2 = {xml.begin(), xml.end()};
std::string str = "LoadModelFromString:" + xml2;
return mjParseXML(str.c_str(), nullptr, error, error_size);
return ParseXML(str.c_str(), nullptr, error, nerror);
}
// Main writer function - calls mjXWrite
std::string WriteXML(const mjSpec* spec, char* error, int nerror) {
LocaleOverride locale_override;
// check for empty model
if (!spec) {
mjCopyError(error, "Cannot write empty model", nerror);
return "";
}
mjXWriter writer;
writer.SetModel(spec);
return writer.Write(error, nerror);
}
+5 -8
View File
@@ -16,22 +16,19 @@
#define MUJOCO_SRC_XML_XML_H_
#include <string>
#include <string_view>
#include <mujoco/mjexport.h>
#include <mujoco/mjmodel.h>
#include <mujoco/mjspec.h>
// Top level API
// Main writer function
std::string mjWriteXML(const mjSpec* spec, char* error, int error_sz);
// Main parser function
mjSpec* mjParseXML(const char* filename, const mjVFS* vfs, char* error, int error_sz);
mjSpec* ParseXML(const char* filename, const mjVFS* vfs, char* error, int nerror);
// Returns a newly-allocated mjSpec, loaded from the contents of xml.
// On failure returns nullptr and populates the error array if present.
mjSpec* ParseSpecFromString(std::string_view xml, char* error = nullptr, int error_size = 0);
mjSpec* ParseSpecFromString(std::string_view xml, char* error = nullptr, int nerror = 0);
// Main writer function
std::string WriteXML(const mjSpec* spec, char* error, int nerror);
#endif // MUJOCO_SRC_XML_XML_H_
+10 -6
View File
@@ -37,6 +37,8 @@
//---------------------------------- Globals -------------------------------------------------------
namespace {
// global user model class
class GlobalModel {
public:
@@ -61,7 +63,7 @@ std::optional<std::string> GlobalModel::ToXML(const mjModel* m, char* error,
return std::nullopt;
}
mj_copyBack(spec_, m);
std::string result = mjWriteXML(spec_, error, error_sz);
std::string result = WriteXML(spec_, error, error_sz);
if (result.empty()) {
return std::nullopt;
}
@@ -86,6 +88,8 @@ GlobalModel& GetGlobalModel() {
return global_model;
}
} // namespace
//---------------------------------- Functions -----------------------------------------------------
// parse XML file in MJCF or URDF format, compile it, return low-level model
@@ -96,7 +100,7 @@ mjModel* mj_loadXML(const char* filename, const mjVFS* vfs,
// parse new model
std::unique_ptr<mjSpec, std::function<void(mjSpec*)>> spec(
mjParseXML(filename, vfs, error, error_sz),
ParseXML(filename, vfs, error, error_sz),
[](mjSpec* s) { mj_deleteSpec(s); });
if (!spec) {
return nullptr;
@@ -188,7 +192,7 @@ int mj_printSchema(const char* filename, char* buffer, int buffer_sz, int flg_ht
// load model from binary MJB resource
mjModel* mj_loadModel(const char* filename, const mjVFS* vfs) {
std::array<char, 1024> error;
mjResource* resource = mju_openResource(filename, vfs,
mjResource* resource = mju_openResource("", filename, vfs,
error.data(), error.size());
if (resource == nullptr) {
mju_warning("%s", error.data());
@@ -211,7 +215,7 @@ mjModel* mj_loadModel(const char* filename, const mjVFS* vfs) {
// parse spec from file
mjSpec* mj_parseXML(const char* filename, const mjVFS* vfs, char* error, int error_sz) {
return mjParseXML(filename, vfs, error, error_sz);
return ParseXML(filename, vfs, error, error_sz);
}
@@ -225,7 +229,7 @@ mjSpec* mj_parseXMLString(const char* xml, const mjVFS* vfs, char* error, int er
// save spec to XML file, return 1 on success, 0 otherwise
int mj_saveXML(const mjSpec* s, const char* filename, char* error, int error_sz) {
std::string result = mjWriteXML(s, error, error_sz);
std::string result = WriteXML(s, error, error_sz);
if (result.empty()) {
return 0;
}
@@ -241,7 +245,7 @@ int mj_saveXML(const mjSpec* s, const char* filename, char* error, int error_sz)
// save spec to string, return 1 on success, 0 otherwise
int mj_saveXMLString(const mjSpec* s, char* xml, int xml_sz, char* error, int error_sz) {
std::string result = mjWriteXML(s, error, error_sz);
std::string result = WriteXML(s, error, error_sz);
if (result.size() >= xml_sz) {
std::string error_msg = "Output string too short, should be at least " +
std::to_string(result.size()+1);
+67 -55
View File
@@ -49,6 +49,7 @@
namespace {
using std::string;
using std::vector;
using mujoco::user::FilePath;
using tinyxml2::XMLElement;
void ReadPluginConfigs(tinyxml2::XMLElement* elem, mjsPlugin* p) {
@@ -114,9 +115,9 @@ const char* MJCF[nMJCF][mjXATTRNUM] = {
"solver", "iterations", "ls_iterations", "noslip_iterations", "mpr_iterations",
"sdf_iterations", "sdf_initpoints", "actuatorgroupdisable"},
{"<"},
{"flag", "?", "21", "constraint", "equality", "frictionloss", "limit", "contact",
{"flag", "?", "22", "constraint", "equality", "frictionloss", "limit", "contact",
"passive", "gravity", "clampctrl", "warmstart",
"filterparent", "actuation", "refsafe", "sensor", "midphase", "eulerdamp",
"filterparent", "actuation", "refsafe", "sensor", "midphase", "eulerdamp", "autoreset",
"override", "energy", "fwdinv", "invdiscrete", "multiccd", "island"},
{">"},
@@ -892,7 +893,7 @@ void mjXReader::Parse(XMLElement* root, const mjVFS* vfs) {
readingdefaults = true;
for (XMLElement* section = FirstChildElement(root, "default"); section;
section = NextSiblingElement(section, "default")) {
Default(section, nullptr);
Default(section, nullptr, vfs);
}
readingdefaults = false;
@@ -918,7 +919,7 @@ void mjXReader::Parse(XMLElement* root, const mjVFS* vfs) {
for (XMLElement* section = FirstChildElement(root, "deformable"); section;
section = NextSiblingElement(section, "deformable")) {
Deformable(section);
Deformable(section, vfs);
}
for (XMLElement* section = FirstChildElement(root, "equality"); section;
@@ -948,7 +949,7 @@ void mjXReader::Parse(XMLElement* root, const mjVFS* vfs) {
for (XMLElement* section = FirstChildElement(root, "worldbody"); section;
section = NextSiblingElement(section, "worldbody")) {
Body(section, mjs_findBody(spec, "world"), nullptr);
Body(section, mjs_findBody(spec, "world"), nullptr, vfs);
}
}
@@ -1121,6 +1122,7 @@ void mjXReader::Option(XMLElement* section, mjOption* opt) {
READDSBL("sensor", mjDSBL_SENSOR)
READDSBL("midphase", mjDSBL_MIDPHASE)
READDSBL("eulerdamp", mjDSBL_EULERDAMP)
READDSBL("autoreset", mjDSBL_AUTORESET)
#undef READDSBL
#define READENBL(NAME, MASK) \
@@ -1388,7 +1390,7 @@ void mjXReader::OneFlex(XMLElement* elem, mjsFlex* pflex) {
// mesh element parser
void mjXReader::OneMesh(XMLElement* elem, mjsMesh* pmesh) {
void mjXReader::OneMesh(XMLElement* elem, mjsMesh* pmesh, const mjVFS* vfs) {
int n;
string text, name, content_type;
@@ -1399,7 +1401,7 @@ void mjXReader::OneMesh(XMLElement* elem, mjsMesh* pmesh) {
if (ReadAttrTxt(elem, "content_type", content_type)) {
*pmesh->content_type = content_type;
}
auto file = ReadAttrFile(elem, "file", MeshDir());
auto file = ReadAttrFile(elem, "file", vfs, MeshDir());
if (file) {
mjs_setString(pmesh->file, file->c_str());
}
@@ -1460,7 +1462,7 @@ void mjXReader::OneMesh(XMLElement* elem, mjsMesh* pmesh) {
// skin element parser
void mjXReader::OneSkin(XMLElement* elem, mjsSkin* pskin) {
void mjXReader::OneSkin(XMLElement* elem, mjsSkin* pskin, const mjVFS* vfs) {
string text, name, material;
float data[4];
@@ -1468,7 +1470,7 @@ void mjXReader::OneSkin(XMLElement* elem, mjsSkin* pskin) {
if (ReadAttrTxt(elem, "name", name)) {
mjs_setString(pskin->name, name.c_str());
}
auto file = ReadAttrFile(elem, "file", AssetDir());
auto file = ReadAttrFile(elem, "file", vfs, AssetDir());
if (file.has_value()) {
mjs_setString(pskin->file, file->c_str());
}
@@ -2535,7 +2537,7 @@ void mjXReader::OneComposite(XMLElement* elem, mjsBody* pbody, mjsDefault* def)
// make flexcomp
void mjXReader::OneFlexcomp(XMLElement* elem, mjsBody* pbody) {
void mjXReader::OneFlexcomp(XMLElement* elem, mjsBody* pbody, const mjVFS* vfs) {
string text, material;
int n;
@@ -2553,7 +2555,12 @@ void mjXReader::OneFlexcomp(XMLElement* elem, mjsBody* pbody) {
ReadAttr(elem, "scale", 3, fcomp.scale, text);
ReadAttr(elem, "mass", 1, &fcomp.mass, text);
ReadAttr(elem, "inertiabox", 1, &fcomp.inertiabox, text);
fcomp.file = ReadAttrFile(elem, "file", modelfiledir_).value_or("");
auto maybe_file = ReadAttrFile(elem, "file", vfs, modelfiledir_);
if (maybe_file.has_value()) {
fcomp.file = std::move(maybe_file.value().Str());
} else {
fcomp.file = "";
}
if (ReadAttrTxt(elem, "material", material)) {
mjs_setString(dflex.material, material.c_str());
}
@@ -2684,7 +2691,7 @@ void mjXReader::OnePlugin(XMLElement* elem, mjsPlugin* plugin) {
//------------------ MJCF-specific sections --------------------------------------------------------
// default section parser
void mjXReader::Default(XMLElement* section, const mjsDefault* def) {
void mjXReader::Default(XMLElement* section, const mjsDefault* def, const mjVFS* vfs) {
XMLElement* elem;
string text, name;
@@ -2715,7 +2722,7 @@ void mjXReader::Default(XMLElement* section, const mjsDefault* def) {
name = elem->Value();
// read mesh
if (name=="mesh") OneMesh(elem, def->mesh);
if (name=="mesh") OneMesh(elem, def->mesh, vfs);
// read material
else if (name=="material") OneMaterial(elem, def->material);
@@ -2769,7 +2776,7 @@ void mjXReader::Default(XMLElement* section, const mjsDefault* def) {
// read default
if (name=="default") {
Default(elem, def);
Default(elem, def, vfs);
}
// advance
@@ -3130,7 +3137,7 @@ void mjXReader::Asset(XMLElement* section, const mjVFS* vfs) {
if (ReadAttrTxt(elem, "content_type", content_type)) {
mjs_setString(ptex->content_type, content_type.c_str());
}
auto file = ReadAttrFile(elem, "file", TextureDir());
auto file = ReadAttrFile(elem, "file", vfs, TextureDir());
if (file.has_value()) {
mjs_setString(ptex->file, file->c_str());
}
@@ -3171,14 +3178,18 @@ void mjXReader::Asset(XMLElement* section, const mjVFS* vfs) {
}
// separate files
std::vector<string> cubefiles(6);
cubefiles[0] = ReadAttrFile(elem, "fileright", TextureDir()).value_or("");
cubefiles[1] = ReadAttrFile(elem, "fileleft", TextureDir()).value_or("");
cubefiles[2] = ReadAttrFile(elem, "fileup", TextureDir()).value_or("");
cubefiles[3] = ReadAttrFile(elem, "filedown", TextureDir()).value_or("");
cubefiles[4] = ReadAttrFile(elem, "filefront", TextureDir()).value_or("");
cubefiles[5] = ReadAttrFile(elem, "fileback", TextureDir()).value_or("");
std::vector<std::string> cubefiles(6);
std::vector<std::string> cubefile_names = {"fileright", "fileleft",
"fileup", "filedown",
"filefront", "fileback"};
for (int i = 0; i < cubefiles.size(); i++) {
auto maybe_file = ReadAttrFile(elem, cubefile_names[i].c_str(), vfs,
TextureDir());
if (maybe_file.has_value()) {
cubefiles[i] = maybe_file.value().Str();
} else {
cubefiles[i] = "";
}
mjs_setInStringVec(ptex->cubefiles, i, cubefiles[i].c_str());
}
}
@@ -3194,14 +3205,14 @@ void mjXReader::Asset(XMLElement* section, const mjVFS* vfs) {
else if (name=="mesh") {
// create mesh and parse
mjsMesh* pmesh = mjs_addMesh(spec, def);
OneMesh(elem, pmesh);
OneMesh(elem, pmesh, vfs);
}
// skin sub-element... deprecate ???
else if (name=="skin") {
// create skin and parse
mjsSkin* pskin = mjs_addSkin(spec);
OneSkin(elem, pskin);
OneSkin(elem, pskin, vfs);
}
// hfield sub-element
@@ -3220,7 +3231,7 @@ void mjXReader::Asset(XMLElement* section, const mjVFS* vfs) {
if (ReadAttrTxt(elem, "content_type", content_type)) {
mjs_setString(phf->content_type, content_type.c_str());
}
auto file = ReadAttrFile(elem, "file", AssetDir());
auto file = ReadAttrFile(elem, "file", vfs, AssetDir());
if (file.has_value()) {
mjs_setString(phf->file, file->c_str());
}
@@ -3264,7 +3275,7 @@ void mjXReader::Asset(XMLElement* section, const mjVFS* vfs) {
// model sub-element
else if (name=="model") {
auto filename = modelfiledir_ + ReadAttrFile(elem, "file", "").value();
auto filename = modelfiledir_ + ReadAttrFile(elem, "file", vfs).value();
// parse the child
std::array<char, 1024> error;
@@ -3291,7 +3302,8 @@ void mjXReader::Asset(XMLElement* section, const mjVFS* vfs) {
// body/world section parser; recursive
void mjXReader::Body(XMLElement* section, mjsBody* pbody, mjsFrame* frame) {
void mjXReader::Body(XMLElement* section, mjsBody* pbody, mjsFrame* frame,
const mjVFS* vfs) {
string text, name;
XMLElement* elem;
int n;
@@ -3417,7 +3429,7 @@ void mjXReader::Body(XMLElement* section, mjsBody* pbody, mjsFrame* frame) {
// flexcomp sub-element
else if (name=="flexcomp") {
// parse flexcomp
OneFlexcomp(elem, pbody);
OneFlexcomp(elem, pbody, vfs);
}
// frame sub-element
@@ -3449,7 +3461,7 @@ void mjXReader::Body(XMLElement* section, mjsBody* pbody, mjsFrame* frame) {
ReadQuat(elem, "quat", pframe->quat, text);
ReadAlternative(elem, pframe->alt);
Body(elem, pbody, pframe);
Body(elem, pbody, pframe, vfs);
}
// replicate sub-element
@@ -3486,12 +3498,16 @@ void mjXReader::Body(XMLElement* section, mjsBody* pbody, mjsFrame* frame) {
double pos[3] = {0, 0, 0};
double quat[4] = {1, 0, 0, 0};
for (int i = 0; i < count; i++) {
// create parent frame
mjsFrame* pframe = mjs_addFrame(subtree, frame);
mjs_setString(pframe->info, ("line = " + std::to_string(elem->GetLineNum())).c_str());
mjs_setDefault(pframe->element, childdef ? childdef : def);
// parent frame that will be used to attach the subtree
mjsFrame* pframe = mjs_addFrame(subtree, frame);
mjs_setDefault(pframe->element, childdef ? childdef : def);
mjs_setString(pframe->info, ("line = " + std::to_string(elem->GetLineNum())).c_str());
// parse subtree
Body(elem, subtree, pframe, vfs);
// update pframe and attach
for (int i = 0; i < count; i++) {
// accumulate rotation
mjuu_setvec(pframe->pos, pos[0], pos[1], pos[2]);
mjuu_frameaccum(pos, quat, offset, rotation);
@@ -3507,9 +3523,6 @@ void mjXReader::Body(XMLElement* section, mjsBody* pbody, mjsFrame* frame) {
std::string suffix = separator;
UpdateString(suffix, count, i);
// process subtree
Body(elem, subtree, pframe);
// attach to parent
if (mjs_attachFrame(pbody, pframe, /*prefix=*/"", suffix.c_str()) != 0) {
throw mjXError(elem, mjs_getError(spec));
@@ -3567,7 +3580,7 @@ void mjXReader::Body(XMLElement* section, mjsBody* pbody, mjsFrame* frame) {
mjs_setFrame(pchild->element, frame);
// make recursive call
Body(elem, pchild, nullptr);
Body(elem, pchild, nullptr, vfs);
}
// attachment
@@ -3685,7 +3698,7 @@ void mjXReader::Equality(XMLElement* section) {
// deformable section parser
void mjXReader::Deformable(XMLElement* section) {
void mjXReader::Deformable(XMLElement* section, const mjVFS* vfs) {
string name;
XMLElement* elem;
@@ -3712,7 +3725,7 @@ void mjXReader::Deformable(XMLElement* section) {
else if (name=="skin") {
// create skin and parse
mjsSkin* pskin = mjs_addSkin(spec);
OneSkin(elem, pskin);
OneSkin(elem, pskin, vfs);
}
// advance to next element
@@ -4227,36 +4240,35 @@ mjsDefault* mjXReader::GetClass(XMLElement* section) {
return def;
}
void mjXReader::SetModelFileDir(std::string modelfiledir) {
modelfiledir_ = modelfiledir;
void mjXReader::SetModelFileDir(const std::string& modelfiledir) {
modelfiledir_ = FilePath(modelfiledir);
}
void mjXReader::SetAssetDir(std::string assetdir) {
assetdir_ = assetdir;
void mjXReader::SetAssetDir(const std::string& assetdir) {
assetdir_ = FilePath(assetdir);
}
void mjXReader::SetMeshDir(std::string meshdir) {
meshdir_ = meshdir;
void mjXReader::SetMeshDir(const std::string& meshdir) {
meshdir_ = FilePath(meshdir);
}
void mjXReader::SetTextureDir(std::string texturedir) {
texturedir_ = texturedir;
void mjXReader::SetTextureDir(const std::string& texturedir) {
texturedir_ = FilePath(texturedir);
}
std::string mjXReader::AssetDir() const {
return mjuu_combinePaths(modelfiledir_, assetdir_);
FilePath mjXReader::AssetDir() const {
return modelfiledir_ + assetdir_;
}
std::string mjXReader::MeshDir() const {
FilePath mjXReader::MeshDir() const {
if (meshdir_.empty()) {
return AssetDir();
}
return mjuu_combinePaths(modelfiledir_, meshdir_);
return modelfiledir_ + meshdir_;
}
std::string mjXReader::TextureDir() const {
FilePath mjXReader::TextureDir() const {
if (texturedir_.empty()) {
return AssetDir();
}
return mjuu_combinePaths(modelfiledir_, texturedir_);
return modelfiledir_ + texturedir_;
}
+22 -20
View File
@@ -22,6 +22,7 @@
#include <mujoco/mujoco.h>
#include <mujoco/mjspec.h>
#include "user/user_util.h"
#include "xml/xml_base.h"
#include "xml/xml_util.h"
@@ -33,13 +34,13 @@ class mjXReader : public mjXBase {
void Parse(tinyxml2::XMLElement* root, const mjVFS* vfs = nullptr); // parse XML document
void PrintSchema(std::stringstream& str, bool html, bool pad); // print text or HTML schema
void SetModelFileDir(std::string modelfiledir);
const std::string& ModelFileDir() const { return modelfiledir_; }
void SetModelFileDir(const std::string& modelfiledir);
const mujoco::user::FilePath& ModelFileDir() const { return modelfiledir_; }
// setters for directory defaults
void SetAssetDir(std::string assetdir);
void SetMeshDir(std::string meshdir);
void SetTextureDir(std::string texturedir);
void SetAssetDir(const std::string& assetdir);
void SetMeshDir(const std::string& meshdir);
void SetTextureDir(const std::string& texturedir);
// XML sections embedded in all formats
static void Compiler(tinyxml2::XMLElement* section, mjSpec* spec); // compiler section
@@ -48,16 +49,17 @@ class mjXReader : public mjXBase {
private:
// XML section specific to MJCF
void Default(tinyxml2::XMLElement* section, const mjsDefault* def); // default section
void Default(tinyxml2::XMLElement* section, const mjsDefault* def,
const mjVFS* vfs); // default section
void Extension(tinyxml2::XMLElement* section); // extension section
void Custom(tinyxml2::XMLElement* section); // custom section
void Visual(tinyxml2::XMLElement* section); // visual section
void Statistic(tinyxml2::XMLElement* section); // statistic section
void Asset(tinyxml2::XMLElement* section, const mjVFS* vfs); // asset section
void Body(tinyxml2::XMLElement* section, mjsBody* pbody,
mjsFrame* pframe); // body/world section
mjsFrame* pframe, const mjVFS* vfs); // body/world section
void Contact(tinyxml2::XMLElement* section); // contact section
void Deformable(tinyxml2::XMLElement* section); // deformable section
void Deformable(tinyxml2::XMLElement* section, const mjVFS* vfs); // deformable section
void Equality(tinyxml2::XMLElement* section); // equality section
void Tendon(tinyxml2::XMLElement* section); // tendon section
void Actuator(tinyxml2::XMLElement* section); // actuator section
@@ -66,8 +68,8 @@ class mjXReader : public mjXBase {
// single element parsers, used in defaults and main body
void OneFlex(tinyxml2::XMLElement* elem, mjsFlex* pflex);
void OneMesh(tinyxml2::XMLElement* elem, mjsMesh* pmesh);
void OneSkin(tinyxml2::XMLElement* elem, mjsSkin* pskin);
void OneMesh(tinyxml2::XMLElement* elem, mjsMesh* pmesh, const mjVFS* vfs);
void OneSkin(tinyxml2::XMLElement* elem, mjsSkin* pskin, const mjVFS* vfs);
void OneMaterial(tinyxml2::XMLElement* elem, mjsMaterial* pmaterial);
void OneJoint(tinyxml2::XMLElement* elem, mjsJoint* pjoint);
void OneGeom(tinyxml2::XMLElement* elem, mjsGeom* pgeom);
@@ -79,23 +81,23 @@ class mjXReader : public mjXBase {
void OneTendon(tinyxml2::XMLElement* elem, mjsTendon* ptendon);
void OneActuator(tinyxml2::XMLElement* elem, mjsActuator* pactuator);
void OneComposite(tinyxml2::XMLElement* elem, mjsBody* pbody, mjsDefault* def);
void OneFlexcomp(tinyxml2::XMLElement* elem, mjsBody* pbody);
void OneFlexcomp(tinyxml2::XMLElement* elem, mjsBody* pbody, const mjVFS* vfs);
void OnePlugin(tinyxml2::XMLElement* elem, mjsPlugin* plugin);
mjXSchema schema; // schema used for validation
mjsDefault* GetClass(tinyxml2::XMLElement* section); // get default class name
mjXSchema schema; // schema used for validation
mjsDefault* GetClass(tinyxml2::XMLElement* section); // get default class name
bool readingdefaults; // true while reading defaults
// accessors for directory defaults
std::string AssetDir() const;
std::string MeshDir() const;
std::string TextureDir() const;
mujoco::user::FilePath AssetDir() const;
mujoco::user::FilePath MeshDir() const;
mujoco::user::FilePath TextureDir() const;
std::string modelfiledir_;
std::string assetdir_;
std::string meshdir_;
std::string texturedir_;
mujoco::user::FilePath modelfiledir_;
mujoco::user::FilePath assetdir_;
mujoco::user::FilePath meshdir_;
mujoco::user::FilePath texturedir_;
};
// MJCF schema
+1
View File
@@ -1001,6 +1001,7 @@ void mjXWriter::Option(XMLElement* root) {
WRITEDSBL("sensor", mjDSBL_SENSOR)
WRITEDSBL("midphase", mjDSBL_MIDPHASE)
WRITEDSBL("eulerdamp", mjDSBL_EULERDAMP)
WRITEDSBL("autoreset", mjDSBL_AUTORESET)
#undef WRITEDSBL
#define WRITEENBL(NAME, MASK) \
+36 -34
View File
@@ -45,6 +45,7 @@ namespace {
using tinyxml2::XMLAttribute;
using tinyxml2::XMLElement;
using mujoco::user::FilePath;
namespace mju = ::mujoco::util;
@@ -74,6 +75,36 @@ static std::optional<T> ParseInfOrNan(const std::string& s) {
return std::nullopt;
}
FilePath ResolveFilePath(XMLElement* e, const FilePath& filename,
const FilePath& dir, const mjVFS* vfs) {
std::string path = "";
if (filename.IsAbs()) {
return filename;
}
// TODO(kylebayes): We first look in the base model directory for files to
// remain backwards compatible.
FilePath fullname = dir + filename;
mjResource *resource = mju_openResource("", fullname.c_str(), vfs,
nullptr, 0);
if (resource != nullptr) {
mju_closeResource(resource);
return filename;
}
XMLElement* parent = e->Parent()->ToElement();
for (; parent; parent = parent->Parent()->ToElement()) {
if (!std::strcmp(parent->Value(), "include")) {
auto file_attr = mjXUtil::ReadAttrStr(parent, "dir", false);
if (file_attr.has_value()) {
path = file_attr.value();
}
break;
}
}
return FilePath(path) + filename;
}
} // namespace
@@ -155,36 +186,6 @@ XMLElement* NextSiblingElement(XMLElement* e, const char* name) {
return nullptr;
}
static std::string ResolveFilePath(XMLElement* e, std::string filename,
const std::string& dir) {
std::string path = "";
if (mjuu_isabspath(filename)) {
return filename;
}
// TODO(kylebayes): We first look in the base model directory for files to
// remain backwards compatible.
std::string full_filename = mjuu_combinePaths(dir, filename);
mjResource *resource = mju_openResource(full_filename.c_str(), nullptr,
nullptr, 0);
if (resource != nullptr) {
mju_closeResource(resource);
return filename;
}
XMLElement* parent = e->Parent()->ToElement();
for (; parent; parent = parent->Parent()->ToElement()) {
if (!std::strcmp(parent->Value(), "include")) {
auto file_attr = mjXUtil::ReadAttrStr(parent, "dir", false);
if (file_attr.has_value()) {
path = file_attr.value();
}
break;
}
}
return mjuu_combinePaths(path, filename);
}
// constructor
mjXSchema::mjXSchema(const char* schema[][mjXATTRNUM], unsigned nrow) {
// set name and type
@@ -621,14 +622,15 @@ mjXUtil::ReadAttrStr(XMLElement* elem, const char* attr, bool required) {
}
// if attribute is present, return attribute as a filename
std::optional<std::string>
mjXUtil::ReadAttrFile(XMLElement* elem, const char* attr,
const std::string& dir, bool required) {
std::optional<FilePath>
mjXUtil::ReadAttrFile(XMLElement* elem, const char* attr, const mjVFS* vfs,
const FilePath& dir, bool required) {
auto maybe_str = ReadAttrStr(elem, attr, required);
if (!maybe_str.has_value()) {
return std::nullopt;
}
return ResolveFilePath(elem, maybe_str.value(), dir);
FilePath filename(maybe_str.value());
return ResolveFilePath(elem, filename, dir, vfs);
}
// if attribute is present, return numerical value of attribute
+15 -6
View File
@@ -26,6 +26,8 @@
#include "tinyxml2.h"
#include <mujoco/mujoco.h>
#include "user/user_util.h"
// error string copy
void mjCopyError(char* dst, const char* src, int maxlen);
@@ -101,26 +103,33 @@ class mjXUtil {
// if attribute is present, return vector of numerical data
template<typename T>
static std::optional<std::vector<T>> ReadAttrVec(tinyxml2::XMLElement* elem, const char* attr,
static std::optional<std::vector<T>> ReadAttrVec(tinyxml2::XMLElement* elem,
const char* attr,
bool required = false);
// if attribute is present, return attribute as a string
static std::optional<std::string> ReadAttrStr(tinyxml2::XMLElement* elem, const char* attr,
static std::optional<std::string> ReadAttrStr(tinyxml2::XMLElement* elem,
const char* attr,
bool required = false);
// if attribute is present, return attribute as a filename
static std::optional<std::string> ReadAttrFile(tinyxml2::XMLElement* elem, const char* attr,
const std::string& dir = "", bool required = false);
static std::optional<mujoco::user::FilePath>
ReadAttrFile(tinyxml2::XMLElement* elem, const char* attr,
const mjVFS* vfs,
const mujoco::user::FilePath& dir = mujoco::user::FilePath(),
bool required = false);
// if attribute is present, return numerical value of attribute
template<typename T>
static std::optional<T> ReadAttrNum(tinyxml2::XMLElement* elem, const char* attr,
static std::optional<T> ReadAttrNum(tinyxml2::XMLElement* elem,
const char* attr,
bool required = false);
// if attribute is present, return array of numerical data
// N should be small as data is allocated on the stack
template<typename T, int N>
static std::optional<std::array<T, N>> ReadAttrArr(tinyxml2::XMLElement* elem, const char* attr,
static std::optional<std::array<T, N>> ReadAttrArr(tinyxml2::XMLElement* elem,
const char* attr,
bool required = false) {
std::array<T, N> arr;
int n = 0;
+64
View File
@@ -159,6 +159,70 @@ TEST_F(DerivativeTest, SmoothDvel) {
}
}
// disabled actuators do not contribute to d_qfrc_actuator/d_qvel
TEST_F(DerivativeTest, DisabledActuators) {
// model with only a position actuator
static constexpr char xml1[] = R"(
<mujoco>
<option integrator="implicitfast"/>
<worldbody>
<body>
<joint name="joint" type="slide"/>
<geom size=".1"/>
</body>
</worldbody>
<actuator>
<position joint="joint" group="1" kp="2000" kv="200"/>
</actuator>
</mujoco>
)";
mjModel* m1 = LoadModelFromString(xml1);
mjData* d1 = mj_makeData(m1);
d1->ctrl[0] = 6;
while (d1->time < 1)
mj_step(m1, d1);
// model with a position actuator and an intvelocity actuator
static constexpr char xml2[] = R"(
<mujoco>
<option integrator="implicitfast" actuatorgroupdisable="2"/>
<worldbody>
<body>
<joint name="joint" type="slide"/>
<geom size=".1"/>
</body>
</worldbody>
<actuator>
<position joint="joint" group="1" kp="2000" kv="200"/>
<intvelocity joint="joint" group="2" kp="2000" kv="200" actrange="-6 6"/>
</actuator>
</mujoco>
)";
mjModel* m2 = LoadModelFromString(xml2);
mjData* d2 = mj_makeData(m2);
d2->ctrl[0] = 6;
d2->ctrl[1] = 6;
while (d2->time < 1)
mj_step(m2, d2);
// expect same qvel in both models
EXPECT_EQ(d1->qvel[0], d2->qvel[0]);
mj_deleteData(d2);
mj_deleteModel(m2);
mj_deleteData(d1);
mj_deleteModel(m1);
}
// compare analytic and fin-diff d_qfrc_passive/d_qvel
TEST_F(DerivativeTest, PassiveDvel) {
for (const char* local_path : {kTumblingThinObjectPath,
@@ -0,0 +1,56 @@
<mujoco>
<default>
<site size=".015" rgba="1 0 0 1"/>
</default>
<worldbody>
<light pos="0 0 3"/>
<geom type="plane" size="2 2 .1"/>
<site name="0a" pos="0 0 1"/>
<geom name="0g" type="cylinder" size=".05 .2" zaxis="0 1 .2" pos="0 0 0.5"/>
<body pos="0 0 .1">
<freejoint/>
<geom size=".05"/>
<site name="0b" pos="0 0 .05"/>
</body>
<site name="1a" pos=".4 0 1"/>
<geom name="1g" type="cylinder" size=".05 .2" zaxis="0 1 .2" pos=".4 0 0.5"/>
<site name="1s" pos=".52 0 .5"/>
<body pos=".4 0 .1">
<freejoint/>
<geom size=".05"/>
<site name="1b" pos="0 0 .05"/>
</body>
<site name="2a" pos="-.4 0 1"/>
<geom name="2g" type="cylinder" size=".05 .2" zaxis="0 1 .2" pos="-.4 0 0.5"/>
<site name="2s" pos="-.44 0 .5"/>
<body pos="-.4 0 .1">
<freejoint/>
<geom size=".05"/>
<site name="2b" pos="0 0 .05"/>
</body>
</worldbody>
<tendon>
<spatial range="0 0.8">
<site site="0a"/>
<geom geom="0g"/>
<site site="0b"/>
</spatial>
<spatial range="0 0.8">
<site site="1a"/>
<geom geom="1g" sidesite="1s"/>
<site site="1b"/>
</spatial>
<spatial range="0 0.8">
<site site="2a"/>
<geom geom="2g" sidesite="2s"/>
<site site="2b"/>
</spatial>
</tendon>
</mujoco>
+56
View File
@@ -0,0 +1,56 @@
<mujoco>
<default>
<site size=".015" rgba="1 0 0 1"/>
</default>
<worldbody>
<light pos="0 0 3"/>
<geom type="plane" size="2 2 .1"/>
<site name="0a" pos="0 0 1"/>
<geom name="0g" size=".1" pos="0 0 0.5"/>
<body pos="0 0 .1">
<freejoint/>
<geom size=".05"/>
<site name="0b" pos="0 0 .05"/>
</body>
<site name="1a" pos=".4 0 1"/>
<geom name="1g" size=".1" pos=".4 0 0.5"/>
<site name="1s" pos=".52 0 .5"/>
<body pos=".4 0 .1">
<freejoint/>
<geom size=".05"/>
<site name="1b" pos="0 0 .05"/>
</body>
<site name="2a" pos="-.4 0 1"/>
<geom name="2g" size=".1" pos="-.4 0 0.5"/>
<site name="2s" pos="-.49 0 .5"/>
<body pos="-.4 0 .1">
<freejoint/>
<geom size=".05"/>
<site name="2b" pos="0 0 .05"/>
</body>
</worldbody>
<tendon>
<spatial range="0 0.8">
<site site="0a"/>
<geom geom="0g"/>
<site site="0b"/>
</spatial>
<spatial range="0 0.8">
<site site="1a"/>
<geom geom="1g" sidesite="1s"/>
<site site="1b"/>
</spatial>
<spatial range="0 0.8">
<site site="2a"/>
<geom geom="2g" sidesite="2s"/>
<site site="2b"/>
</spatial>
</tendon>
</mujoco>
+2 -2
View File
@@ -47,7 +47,7 @@ void CacheText(mjCCache& cache, const std::string& model,
mjVFS vfs;
mj_defaultVFS(&vfs);
mj_addBufferVFS(&vfs, name.c_str(), text.data(), text.size());
mjResource* resource = mju_openResource(name.c_str(), &vfs, nullptr, 0);
mjResource* resource = mju_openResource("", name.c_str(), &vfs, nullptr, 0);
std::shared_ptr<const void> data(&text, +[](const void* data) {});
cache.Insert(model, resource, data, text.size());
mju_closeResource(resource);
@@ -61,7 +61,7 @@ GetCachedText(mjCCache& cache, const std::string& model,
mjVFS vfs;
mj_defaultVFS(&vfs);
mj_addBufferVFS(&vfs, name.c_str(), text.data(), std::strlen(text.c_str()));
mjResource* resource = mju_openResource(name.c_str(), &vfs, nullptr, 0);
mjResource* resource = mju_openResource("", name.c_str(), &vfs, nullptr, 0);
bool inserted = cache.PopulateData(resource,
[&cached_text](const void* data) {
cached_text = *(static_cast<const std::string*>(data));
+6 -6
View File
@@ -233,7 +233,7 @@ TEST_F(ResourceTest, GeneralTest) {
EXPECT_GT(i, 0);
// open resource
mjResource* resource = mju_openResource("str:file", nullptr, nullptr, 0);
mjResource* resource = mju_openResource("", "str:file", nullptr, nullptr, 0);
ASSERT_THAT(resource, NotNull());
const char* buffer = NULL;
@@ -256,7 +256,7 @@ TEST_F(ResourceTest, GeneralFailureTest) {
static std::array<char, 1024> error;
// open resource
mjResource* resource = mju_openResource("str:notfound", nullptr,
mjResource* resource = mju_openResource("", "str:notfound", nullptr,
error.data(), error.size());
ASSERT_THAT(resource, IsNull());
@@ -281,7 +281,7 @@ TEST_F(ResourceTest, NameWithValidPrefix) {
};
// open resource
mjResource* resource = mju_openResource("nop:found", nullptr, nullptr, 0);
mjResource* resource = mju_openResource("", "nop:found", nullptr, nullptr, 0);
ASSERT_THAT(resource, NotNull());
mju_closeResource(resource);
}
@@ -304,7 +304,7 @@ TEST_F(ResourceTest, NameWithUpperCasePrefix) {
};
// open resource
mjResource* resource = mju_openResource("NOP:found", nullptr, nullptr, 0);
mjResource* resource = mju_openResource("", "NOP:found", nullptr, nullptr, 0);
ASSERT_THAT(resource, NotNull());
mju_closeResource(resource);
}
@@ -327,7 +327,7 @@ TEST_F(ResourceTest, NameWithInvalidPrefix) {
};
// open resource
mjResource* resource = mju_openResource("nopfound", nullptr, nullptr, 0);
mjResource* resource = mju_openResource("", "nopfound", nullptr, nullptr, 0);
ASSERT_THAT(resource, IsNull());
}
@@ -338,7 +338,7 @@ TEST_F(ResourceTest, OSFilesystemTimestamps) {
const char* const file = "engine/testdata/collision_box/boxbox_deep.xml";
const std::string xml_path = GetTestDataFilePath(file);
mjResource* resource = mju_openResource(xml_path.c_str(), nullptr,
mjResource* resource = mju_openResource("", xml_path.c_str(), nullptr,
nullptr, 0);
mju_decodeBase64((uint8_t*) &t, resource->timestamp);
+5
View File
@@ -46,6 +46,11 @@ TEST_F(UserUtilTest, PathReduce2) {
EXPECT_EQ(path.Str(), "../hello/world/");
}
TEST_F(UserUtilTest, PathReduce3) {
FilePath path = FilePath("../../hello/world.txt");
EXPECT_EQ(path.Str(), "../../hello/world.txt");
}
TEST_F(UserUtilTest, PathReduceWin) {
FilePath path = FilePath("C:\\hello\\..\\world");
EXPECT_EQ(path.Str(), "C:\\world");
+4 -5
View File
@@ -30,7 +30,7 @@ using ::testing::NotNull;
using UserVfsTest = MujocoTest;
static bool HasFile(const mjVFS* vfs, const std::string& filename) {
mjResource* resource = mju_openResource(filename.c_str(), vfs, nullptr, 0);
mjResource* resource = mju_openResource("", filename.c_str(), vfs, nullptr, 0);
bool result = resource != nullptr;
mju_closeResource(resource);
return result;
@@ -196,14 +196,13 @@ TEST_F(UserVfsTest, AddBufferRepeat) {
mj_deleteVFS(&vfs);
}
TEST_F(UserVfsTest, BufferStripPath) {
TEST_F(UserVfsTest, BufferPath) {
mjVFS vfs;
mj_defaultVFS(&vfs);
std::string buffer = "<mujoco/>";
const void* ptr = static_cast<const void*>(buffer.c_str());
mj_addBufferVFS(&vfs, "dir/model", ptr, buffer.size());
EXPECT_TRUE(HasFile(&vfs, "MODEL"));
EXPECT_TRUE(HasFile(&vfs, "dir\\model"));
EXPECT_TRUE(HasFile(&vfs, "files/../dir/model"));
mj_deleteVFS(&vfs);
}
@@ -222,7 +221,7 @@ TEST_F(UserVfsTest, Timestamps) {
mj_defaultVFS(&vfs);
mj_addBufferVFS(&vfs, "cube.obj", cube, sizeof(cube));
mjResource* resource = mju_openResource("cube.obj", &vfs, nullptr, 0);
mjResource* resource = mju_openResource("", "cube.obj", &vfs, nullptr, 0);
// same timestamps
EXPECT_EQ(mju_isModifiedResource(resource, resource->timestamp), 0);
+77 -65
View File
@@ -15,7 +15,6 @@
// Tests for xml/xml_native_reader.cc.
#include <array>
#include <cstring>
#include <limits>
#include <memory>
#include <string>
@@ -613,6 +612,18 @@ static constexpr unsigned char kTinyPng[] = {
0x82
};
// mesh OBJ file of a cube
static constexpr char kTinyObj[] = R"(
v -1 -1 1
v 1 -1 1
v -1 1 1
v 1 1 1
v -1 1 -1
v 1 1 -1
v -1 -1 -1
v 1 -1 -1)";
TEST_F(XMLReaderTest, IncludeTest) {
static constexpr char xml[] = R"(
<mujoco>
@@ -649,7 +660,7 @@ TEST_F(XMLReaderTest, IncludeTest) {
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(),
error.size(), vfs.get());
ASSERT_THAT(model, NotNull());
ASSERT_THAT(model, NotNull()) << error.data();
EXPECT_EQ(mj_name2id(model, mjOBJ_GEOM, "ball"), 2);
EXPECT_EQ(mj_name2id(model, mjOBJ_GEOM, "another_box"), 3);
mj_deleteModel(model);
@@ -726,22 +737,22 @@ TEST_F(XMLReaderTest, IncludePathTest) {
<geom name="another_box" type="box" size="2 2 2"/>
</mujoco>)";
MockFilesystem fs("IncludePathTest");
fs.AddFile("model.xml", (const unsigned char*) xml, sizeof(xml));
std::string modelpath = fs.FullPath("model.xml");
mjVFS vfs;
mj_defaultVFS(&vfs);
mj_addBufferVFS(&vfs, "model.xml", xml, sizeof(xml));
mj_addBufferVFS(&vfs, "submodels/model1.xml", xml1, sizeof(xml1));
mj_addBufferVFS(&vfs, "submodels/model2.xml", xml2, sizeof(xml2));
mj_addBufferVFS(&vfs, "submodels/subsubmodels/model3.xml", xml3,
sizeof(xml3));
fs.ChangeDirectory("submodels/");
fs.AddFile("model1.xml", (const unsigned char*) xml1, sizeof(xml1));
fs.AddFile("model2.xml", (const unsigned char*) xml2, sizeof(xml2));
fs.AddFile("subsubmodels/model3.xml", (const unsigned char*) xml3, sizeof(xml3));
fs.ChangeDirectory("/");
mjModel* model = mj_loadXML(modelpath.c_str(), nullptr,
nullptr, 0);
ASSERT_THAT(model, NotNull());
std::array<char, 1024> error;
mjModel* model = mj_loadXML("model.xml", &vfs, error.data(),
error.size());
ASSERT_THAT(model, NotNull()) << error.data();
EXPECT_EQ(mj_name2id(model, mjOBJ_GEOM, "ball"), 2);
EXPECT_EQ(mj_name2id(model, mjOBJ_GEOM, "another_box"), 3);
mj_deleteModel(model);
mj_deleteVFS(&vfs);
}
TEST_F(XMLReaderTest, FallbackIncludePathTest) {
@@ -770,22 +781,21 @@ TEST_F(XMLReaderTest, FallbackIncludePathTest) {
<geom name="another_box" type="box" size="2 2 2"/>
</mujoco>)";
MockFilesystem fs("FallbackIncludePathTest");
fs.AddFile("model.xml", (const unsigned char*) xml, sizeof(xml));
std::string modelpath = fs.FullPath("model.xml");
fs.AddFile("model1.xml", (const unsigned char*) xml1, sizeof(xml1));
fs.AddFile("submodels/model2.xml", (const unsigned char*) xml2, sizeof(xml2));
fs.AddFile("subsubmodels/model3.xml", (const unsigned char*) xml3,
sizeof(xml3));
mjVFS vfs;
mj_defaultVFS(&vfs);
mj_addBufferVFS(&vfs, "model.xml", xml, sizeof(xml));
mj_addBufferVFS(&vfs, "model1.xml", xml1, sizeof(xml1));
mj_addBufferVFS(&vfs, "submodels/model2.xml", xml2, sizeof(xml2));
mj_addBufferVFS(&vfs, "subsubmodels/model3.xml", xml3, sizeof(xml3));
std::array<char, 1024> error;
mjModel* model = mj_loadXML(modelpath.c_str(), nullptr,
mjModel* model = mj_loadXML("model.xml", &vfs,
error.data(), error.size());
ASSERT_THAT(model, NotNull()) << error.data();
EXPECT_EQ(mj_name2id(model, mjOBJ_GEOM, "ball"), 2);
EXPECT_EQ(mj_name2id(model, mjOBJ_GEOM, "another_box"), 3);
mj_deleteModel(model);
mj_deleteVFS(&vfs);
}
TEST_F(XMLReaderTest, MaterialTextureTest) {
@@ -807,14 +817,14 @@ TEST_F(XMLReaderTest, MaterialTextureTest) {
</mujoco>
)";
MockFilesystem fs("MaterialTextureTest");
fs.AddFile("tiny0.png", kTinyPng, sizeof(kTinyPng));
fs.AddFile("tiny1.png", kTinyPng, sizeof(kTinyPng));
fs.AddFile("model.xml", (const unsigned char*) xml, sizeof(xml));
std::string modelpath = fs.FullPath("model.xml");
mjVFS vfs;
mj_defaultVFS(&vfs);
mj_addBufferVFS(&vfs, "tiny0.png", kTinyPng, sizeof(kTinyPng));
mj_addBufferVFS(&vfs, "tiny1.png", kTinyPng, sizeof(kTinyPng));
mj_addBufferVFS(&vfs, "model.xml", xml, sizeof(xml));
char error[1024];
mjModel* model = mj_loadXML(modelpath.c_str(), nullptr, error, 1024);
mjModel* model = mj_loadXML("model.xml", &vfs, error, 1024);
EXPECT_THAT(model, NotNull()) << error;
EXPECT_EQ(model->mat_texid[mjTEXROLE_RGB], 1);
@@ -823,6 +833,7 @@ TEST_F(XMLReaderTest, MaterialTextureTest) {
EXPECT_EQ(model->mat_texid[mjTEXROLE_OCCLUSION], 0);
mj_deleteModel(model);
mj_deleteVFS(&vfs);
}
TEST_F(XMLReaderTest, LegacyMaterialTextureTest) {
@@ -839,19 +850,20 @@ TEST_F(XMLReaderTest, LegacyMaterialTextureTest) {
</mujoco>
)";
MockFilesystem fs("LegacyMaterialTextureTest");
fs.AddFile("tiny0.png", kTinyPng, sizeof(kTinyPng));
fs.AddFile("tiny1.png", kTinyPng, sizeof(kTinyPng));
fs.AddFile("model.xml", (const unsigned char*) xml, sizeof(xml));
std::string modelpath = fs.FullPath("model.xml");
mjVFS vfs;
mj_defaultVFS(&vfs);
mj_addBufferVFS(&vfs, "tiny0.png", kTinyPng, sizeof(kTinyPng));
mj_addBufferVFS(&vfs, "tiny1.png", kTinyPng, sizeof(kTinyPng));
mj_addBufferVFS(&vfs, "model.xml", xml, sizeof(xml));
char error[1024];
mjModel* model = mj_loadXML(modelpath.c_str(), nullptr, error, 1024);
mjModel* model = mj_loadXML("model.xml", &vfs, error, 1024);
EXPECT_THAT(model, NotNull()) << error;
EXPECT_EQ(model->mat_texid[mjTEXROLE_RGB], 1);
mj_deleteModel(model);
mj_deleteVFS(&vfs);
}
TEST_F(XMLReaderTest, MaterialTextureFailTest) {
@@ -871,12 +883,6 @@ TEST_F(XMLReaderTest, MaterialTextureFailTest) {
</mujoco>
)";
MockFilesystem fs("MaterialTextureFailTest");
fs.AddFile("tiny0.png", kTinyPng, sizeof(kTinyPng));
fs.AddFile("tiny1.png", kTinyPng, sizeof(kTinyPng));
fs.AddFile("model.xml", (const unsigned char*) xml, sizeof(xml));
std::string modelpath = fs.FullPath("model.xml");
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(m, IsNull());
@@ -905,26 +911,32 @@ TEST_F(XMLReaderTest, IncludeAssetsTest) {
static constexpr char subassets[] = R"(
<mujoco>
<texture file="subtiny.png" type="2d"/>
<mesh name="cube" file="cube.obj"/>
<material name="submaterial" texture="subtiny"/>
</mujoco>
)";
MockFilesystem fs("IncludeAssetsTest");
fs.AddFile("assets/tiny.png", kTinyPng, sizeof(kTinyPng));
fs.AddFile("assets/subassets/subtiny.png", kTinyPng, sizeof(kTinyPng));
fs.AddFile("assets/assets.xml", (const unsigned char*) assets,
sizeof(assets));
fs.AddFile("assets/subassets/assets.xml", (const unsigned char*) subassets,
sizeof(subassets));
fs.AddFile("model.xml", (const unsigned char*) xml, sizeof(xml));
std::string modelpath = fs.FullPath("model.xml");
mjVFS vfs;
mj_defaultVFS(&vfs);
mj_addBufferVFS(&vfs, "assets/tiny.png", kTinyPng, sizeof(kTinyPng));
mj_addBufferVFS(&vfs, "assets/subassets/subtiny.png", kTinyPng,
sizeof(kTinyPng));
mj_addBufferVFS(&vfs, "assets/subassets/cube.obj", kTinyObj,
sizeof(kTinyObj));
mj_addBufferVFS(&vfs, "assets/assets.xml", assets, sizeof(assets));
mj_addBufferVFS(&vfs, "assets/subassets/assets.xml", subassets,
sizeof(subassets));
mj_addBufferVFS(&vfs, "model.xml", xml, sizeof(xml));
// loading the file should be successful
mjModel* model = mj_loadXML(modelpath.c_str(), nullptr, nullptr, 0);
std::array<char, 1024> error;
mjModel* model = mj_loadXML("model.xml", &vfs, error.data(),
error.size());
EXPECT_THAT(model, NotNull());
ASSERT_THAT(model, NotNull()) << error.data();
mj_deleteModel(model);
mj_deleteVFS(&vfs);
}
TEST_F(XMLReaderTest, FallbackIncludeAssetsTest) {
@@ -952,26 +964,26 @@ TEST_F(XMLReaderTest, FallbackIncludeAssetsTest) {
</mujoco>
)";
MockFilesystem fs("FallbackIncludeAssetsTest");
fs.AddFile("assets/tiny.png", kTinyPng, sizeof(kTinyPng));
mjVFS vfs;
mj_defaultVFS(&vfs);
mj_addBufferVFS(&vfs, "assets/tiny.png", kTinyPng, sizeof(kTinyPng));
// need to fallback for backwards compatibility
fs.AddFile("subtiny.png", kTinyPng, sizeof(kTinyPng));
mj_addBufferVFS(&vfs, "subtiny.png", kTinyPng, sizeof(kTinyPng));
fs.AddFile("assets/assets.xml", (const unsigned char*) assets,
sizeof(assets));
fs.AddFile("assets/subassets/assets.xml", (const unsigned char*) subassets,
sizeof(subassets));
fs.AddFile("model.xml", (const unsigned char*) xml, sizeof(xml));
std::string modelpath = fs.FullPath("model.xml");
mj_addBufferVFS(&vfs, "assets/assets.xml", assets, sizeof(assets));
mj_addBufferVFS(&vfs, "assets/subassets/assets.xml", subassets,
sizeof(subassets));
mj_addBufferVFS(&vfs, "model.xml", xml, sizeof(xml));
// loading the file should be successful
std::array<char, 1024> error;
mjModel* model = mj_loadXML(modelpath.c_str(), nullptr,
mjModel* model = mj_loadXML("model.xml", &vfs,
error.data(), error.size());
EXPECT_THAT(model, NotNull());
ASSERT_THAT(model, NotNull()) << error.data();
mj_deleteModel(model);
mj_deleteVFS(&vfs);
}
TEST_F(XMLReaderTest, IncludeAbsoluteTest) {
@@ -1013,7 +1025,7 @@ TEST_F(XMLReaderTest, IncludeAbsoluteTest) {
// loading the file should be successful
mjModel* model = mj_loadXML(modelpath.c_str(), nullptr,
error.data(), error.size());
EXPECT_THAT(model, NotNull());
ASSERT_THAT(model, NotNull()) << error.data();
mj_deleteModel(model);
}
@@ -1054,7 +1066,7 @@ TEST_F(XMLReaderTest, IncludeAbsoluteMeshDirTest) {
// loading the file should be successful
mjModel* model = mj_loadXML(modelpath.c_str(), nullptr,
error.data(), error.size());
ASSERT_THAT(model, NotNull()) << "Failed to load model: " << error.data();
ASSERT_THAT(model, NotNull()) << error.data();
mj_deleteModel(model);
}
+1 -1
View File
@@ -157,7 +157,7 @@ public enum mjtDisableBit : int{
mjDSBL_SENSOR = 4096,
mjDSBL_MIDPHASE = 8192,
mjDSBL_EULERDAMP = 16384,
mjDSBL_AUTORESETNAN = 32768,
mjDSBL_AUTORESET = 32768,
mjNDISABLE = 16,
}
public enum mjtEnableBit : int{