Merge branch 'main' into open3d-removal
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
----------------------------
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -328,7 +328,7 @@ ___________________________________
|
||||
|
||||
Roboti’s `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.
|
||||
|
||||
@@ -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
@@ -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),
|
||||
]),
|
||||
)),
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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`. |
|
||||
|
||||
@@ -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* {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -58,7 +58,7 @@ const char* mjDISABLESTRING[mjNDISABLE] = {
|
||||
"Sensor",
|
||||
"Midphase",
|
||||
"Eulerdamp",
|
||||
"AutoResetNaN"
|
||||
"AutoReset"
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
|
||||
Reference in New Issue
Block a user