Merge branch 'original/main' into vidur/remove-installing-glfw3-headers

This commit is contained in:
Vidur
2025-07-01 16:27:15 -05:00
149 changed files with 8827 additions and 1843 deletions
+22 -10
View File
@@ -28,7 +28,7 @@ set(MSVC_INCREMENTAL_DEFAULT ON)
project(
mujoco
VERSION 3.3.3
VERSION 3.3.4
DESCRIPTION "MuJoCo Physics Simulator"
HOMEPAGE_URL "https://mujoco.org"
)
@@ -42,14 +42,26 @@ option(MUJOCO_BUILD_EXAMPLES "Build samples for MuJoCo" ON)
option(MUJOCO_BUILD_SIMULATE "Build simulate library for MuJoCo" ON)
option(MUJOCO_BUILD_TESTS "Build tests for MuJoCo" ON)
option(MUJOCO_TEST_PYTHON_UTIL "Build and test utility libraries for Python bindings" ON)
option(MUJOCO_BUILD_USD_PLUGINS "Build OpenUSD plugins" OFF)
option(MUJOCO_WITH_USD "Build with OpenUSD" OFF)
# USD libs to compile against.
set(MUJOCO_USD_ALLOWED_TARGET_VALUES "USD" "Houdini")
set(MUJOCO_USD_TARGET "USD" CACHE STRING "Select the USD target for the project.")
set_property(CACHE MUJOCO_USD_TARGET
PROPERTY STRINGS ${MUJOCO_USD_ALLOWED_TARGET_VALUES}
)
# Option to provide a path to an existing USD build directory or to Houdini HFS directory.
set(USD_DIR "" CACHE PATH "Path to an existing USD build directory.")
set(HOUDINI_HFS_DIR "" CACHE PATH "Path to Houdini HFS directory to build USD plugins against.")
if(USD_DIR)
if(EXISTS "${USD_DIR}")
# If the path is provided, set MUJOCO_WITH_USD to ON and add the path to CMAKE_PREFIX_PATH.
set(MUJOCO_WITH_USD ON CACHE BOOL "Build with OpenUSD" FORCE)
list(PREPEND CMAKE_PREFIX_PATH "${USD_DIR}")
message(STATUS "Using custom USD build directory: ${USD_DIR}")
else()
message(WARNING "Invalid path provided for USD_DIR: ${USD_DIR}")
endif()
elseif(HOUDINI_HFS_DIR)
if(EXISTS "${HOUDINI_HFS_DIR}")
set(MUJOCO_WITH_USD ON CACHE BOOL "Build with OpenUSD" FORCE)
message(STATUS "Building Mujoco USD against Houdini HFS: ${HOUDINI_HFS_DIR}")
endif()
endif()
if(APPLE AND (MUJOCO_BUILD_EXAMPLES OR MUJOCO_BUILD_SIMULATE))
enable_language(OBJC)
@@ -189,8 +201,8 @@ if(MUJOCO_BUILD_EXAMPLES)
add_subdirectory(sample)
endif()
if(MUJOCO_BUILD_USD_PLUGINS)
add_subdirectory(src/experimental/usd/plugins)
if(MUJOCO_WITH_USD)
add_subdirectory(src/experimental/usd)
endif()
if(BUILD_TESTING AND MUJOCO_BUILD_TESTS)
+5 -5
View File
@@ -15,7 +15,7 @@
# Build configuration for third party libraries used in MuJoCo.
set(MUJOCO_DEP_VERSION_lodepng
b4ed2cd7ecf61d29076169b49199371456d4f90b
17d08dd26cac4d63f43af217ebd70318bfb8189c
CACHE STRING "Version of `lodepng` to be fetched."
)
set(MUJOCO_DEP_VERSION_tinyxml2
@@ -35,21 +35,21 @@ set(MUJOCO_DEP_VERSION_ccd
CACHE STRING "Version of `ccd` to be fetched."
)
set(MUJOCO_DEP_VERSION_qhull
0c8fc90d2037588024d9964515c1e684f6007ecc
c7bee59d068a69f427b1273e71cdc5bc455a5bdd
CACHE STRING "Version of `qhull` to be fetched."
)
set(MUJOCO_DEP_VERSION_Eigen3
464c1d097891a1462ab28bf8bb763c1683883892
d0b490ee091629068e0c11953419eb089f9e6bb2
CACHE STRING "Version of `Eigen3` to be fetched."
)
set(MUJOCO_DEP_VERSION_abseil
d9e4955c65cd4367dd6bf46f4ccb8cd3d100540b # LTS 20250127.1
bc257a88f7c1939f24e0379f14a3589e926c950c # LTS 20250512.0
CACHE STRING "Version of `abseil` to be fetched."
)
set(MUJOCO_DEP_VERSION_gtest
6910c9d9165801d8827d628cb72eb7ea9dd538c5 # v1.16.0
52eb8108c5bdec04579160ae17225d66034bd723 # v1.17.0
CACHE STRING "Version of `gtest` to be fetched."
)
+1 -1
View File
@@ -179,7 +179,7 @@ function(target_add_rpath)
set_target_properties(
${_target}
PROPERTIES INSTALL_RPATH ${_install_rpath}
PROPERTIES INSTALL_RPATH "${_install_rpath}"
INSTALL_RPATH_USE_LINK_PATH ${_ARGS_USE_LINK_PATH}
BUILD_WITH_INSTALL_RPATH TRUE
MACOSX_RPATH ON # This is ON by default.
+4 -4
View File
@@ -1,6 +1,6 @@
1 VERSIONINFO
FILEVERSION 3,3,3,0
PRODUCTVERSION 3,3,3,0
FILEVERSION 3,3,4,0
PRODUCTVERSION 3,3,4,0
FILEOS 0x4
FILETYPE 0x1
{
@@ -9,9 +9,9 @@ FILETYPE 0x1
BLOCK "040904b0"
{
VALUE "ProductName", "MuJoCo"
VALUE "ProductVersion", "3.3.3"
VALUE "ProductVersion", "3.3.4"
VALUE "FileDescription", "MuJoCo"
VALUE "FileVersion", "3.3.3"
VALUE "FileVersion", "3.3.4"
VALUE "InternalName", "mujoco.dll"
VALUE "OriginalFilename", "mujoco.dll"
VALUE "CompanyName", "Google DeepMind"
+4 -4
View File
@@ -1,8 +1,8 @@
MUJOCO ICON "mujoco.ico"
1 VERSIONINFO
FILEVERSION 3,3,3,0
PRODUCTVERSION 3,3,3,0
FILEVERSION 3,3,4,0
PRODUCTVERSION 3,3,4,0
FILEOS 0x4
FILETYPE 0x1
{
@@ -11,9 +11,9 @@ FILETYPE 0x1
BLOCK "040904b0"
{
VALUE "ProductName", "MuJoCo"
VALUE "ProductVersion", "3.3.3"
VALUE "ProductVersion", "3.3.4"
VALUE "FileDescription", "MuJoCo"
VALUE "FileVersion", "3.3.3"
VALUE "FileVersion", "3.3.4"
VALUE "InternalName", "simulate.exe"
VALUE "OriginalFilename", "simulate.exe"
VALUE "CompanyName", "Google DeepMind"
+1 -1
View File
@@ -517,7 +517,7 @@ shown in the table below. Their names are in the format ``mjKEY_XXX``. They corr
- Maximum number of UI rectangles.
Defined in `mjui.h <https://github.com/google-deepmind/mujoco/blob/main/include/mujoco/mjui.h>`_.
* - ``mjVERSION_HEADER``
- 333
- 334
- The version of the MuJoCo headers; changes with every release. This is an integer equal to 100x the software
version, so 210 corresponds to version 2.1. Defined in mujoco.h. The API function :ref:`mj_version` returns a
number with the same meaning but for the compiled library.
+2 -20
View File
@@ -418,7 +418,7 @@ and :math:`\dot q` is the generalized velocity ``mjData.qvel``, then :math:`h =
.. mujoco-include:: mj_name2id
Get id of object with the specified mjtObj type and name, returns -1 if id not found.
Get id of object with the specified :ref:`mjtObj` type and name, returns -1 if id not found.
.. _mj_id2name:
@@ -427,7 +427,7 @@ Get id of object with the specified mjtObj type and name, returns -1 if id not f
.. mujoco-include:: mj_id2name
Get name of object with the specified mjtObj type and id, returns NULL if name not found.
Get name of object with the specified :ref:`mjtObj` type and id, returns ``NULL`` if name not found.
.. _mj_fullM:
@@ -3783,24 +3783,6 @@ Attachment
Attach child to a parent, return the attached element if success or NULL otherwise.
.. _mjs_detachBody:
`mjs_detachBody <#mjs_detachBody>`__
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. mujoco-include:: mjs_detachBody
Delete body and descendants from mjSpec, remove all references, return 0 on success.
.. _mjs_detachDefault:
`mjs_detachDefault <#mjs_detachDefault>`__
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. mujoco-include:: mjs_detachDefault
Delete default class and descendants from mjSpec, remove all references, return 0 on success.
.. _AddTreeElements:
Tree elements
+8
View File
@@ -231,6 +231,14 @@ generalized velocities to subtree angular momentum. More precisely if :math:`h`
body index ``body`` in ``mjData.subtree_angmom`` (reported by the :ref:`subtreeangmom<sensor-subtreeangmom>` sensor)
and :math:`\dot q` is the generalized velocity ``mjData.qvel``, then :math:`h = H \dot q`.
.. _mj_name2id:
Get id of object with the specified :ref:`mjtObj` type and name, returns -1 if id not found.
.. _mj_id2name:
Get name of object with the specified :ref:`mjtObj` type and id, returns ``NULL`` if name not found.
.. _mj_geomDistance:
Returns the smallest signed distance between two geoms and optionally the segment from ``geom1`` to ``geom2``.
+31 -12
View File
@@ -724,13 +724,20 @@ has any effect. The settings here are global and apply to the entire model.
compiler converts degrees into radians, and mjModel always uses radians. For URDF models the parser sets this
attribute to "radian" internally, regardless of the XML setting.
.. image:: images/changelog/meshfit.png
:align: right
:width: 40%
.. _compiler-fitaabb:
:at:`fitaabb`: :at-val:`[false, true], "false"`
The compiler is able to replace a mesh with a geometric primitive fitted to that mesh; see :ref:`geom <body-geom>`
below. If this attribute is "true", the fitting procedure uses the axis-aligned bounding box (aabb) of the mesh.
Otherwise it uses the equivalent-inertia box of the mesh. The type of geometric primitive used for fitting is
specified separately for each geom.
specified separately for each geom. The models used to generate the image on the right can be found
`here <https://github.com/google-deepmind/mujoco/blob/main/test/user/testdata/fitmesh_inertiabox.xml>`__ (fit inertia
box) and `here <https://github.com/google-deepmind/mujoco/blob/main/test/user/testdata/fitmesh_aabb.xml>`__ (fit
aabb).
.. _compiler-eulerseq:
@@ -792,12 +799,14 @@ has any effect. The settings here are global and apply to the entire model.
:at:`fusestatic`: :at-val:`[false, true], "false" for MJCF, "true" for URDF`
This attribute controls a compiler optimization feature where static bodies are fused with their parent, and any
elements defined in those bodies are reassigned to the parent. This feature can only be used in models which do not
have elements capable of named references inside the kinematic tree - namely skins, contact pairs, excludes, tendons,
actuators, sensors, tuples, cameras, lights. If a model has any these elements, fusestatic does nothing even if
enabled. This optimization is particularly useful when importing URDF models which often have many dummy bodies, but
can also be used to optimize MJCF models. After optimization, the new model has identical kinematics and dynamics as
the original but is faster to simulate.
elements defined in those bodies are reassigned to the parent. Static bodies are fused with their parent unless
- They are referenced by another element in the model.
- They contain a site which is referenced by a :ref:`force<sensor-force>` or :ref:`torque<sensor-torque>` sensor.
This optimization is particularly useful when importing URDF models which often have many dummy bodies, but can also
be used to optimize MJCF models. After optimization, the new model has identical kinematics and dynamics as the
original but is faster to simulate.
.. _compiler-inertiafromgeom:
@@ -1228,7 +1237,7 @@ The full list of processing steps applied by the compiler to each mesh is as fol
:at:`content_type`: :at-val:`string, optional`
If the file attribute is specified, then this sets the
`Media Type <https://www.iana.org/assignments/media-types/media-types.xhtml>`_ (formerly known as MIME type) of the
`Media Type <https://www.iana.org/assignments/media-types/media-types.xhtml>`__ (formerly known as MIME type) of the
file to be loaded. Any filename extensions will be overloaded. Currently ``model/vnd.mujoco.msh``, ``model/obj``,
and ``model/stl`` are supported.
@@ -1776,15 +1785,19 @@ properties are grouped together.
.. _asset-material-metallic:
:at:`metallic`: :at-val:`real, "0"`
:at:`metallic`: :at-val:`real, "-1"`
This attribute corresponds to uniform metallicity coefficient applied to the entire material. This attribute has no
effect in MuJoCo's native renderer, but it can be useful when rendering scenes with an external renderer.
effect in MuJoCo's native renderer, but it can be useful when rendering scenes with a physically-based renderer. In
this case, if a non-negative value is specified, this metallic value should be multiplied by the metallic texture
sampled value to obtain the final metallicity of the material.
.. _asset-material-roughness:
:at:`roughness`: :at-val:`real, "1"`
:at:`roughness`: :at-val:`real, "-1"`
This attribute corresponds to uniform roughness coefficient applied to the entire material. This attribute has no
effect in MuJoCo's native renderer, but it can be useful when rendering scenes with an external renderer.
effect in MuJoCo's native renderer, but it can be useful when rendering scenes with a physically-based renderer. In
this case, if a non-negative value is specified, this roughness value should be multiplied by the roughness texture
sampled value to obtain the final roughness of the material.
.. _asset-material-rgba:
@@ -7528,6 +7541,12 @@ coordinated visual settings corresponding to a "theme", and then include this fi
While all settings in mjVisual are global, the settings here could not be fit into any of the other subsections. So this
is effectively a miscellaneous subsection.
.. _visual-global-cameraid:
:at:`cameraid`: :at-val:`int, "-1"`
The id of the camera used when initially loading the model in the visualizer. The default value of -1 means the free
camera. In order to specify a :ref:`modeled camera<body-camera>`, use the camera's id as given by :ref:`mj_name2id`.
.. _visual-global-orthographic:
:at:`orthographic`: :at-val:`[false, true], "false"`
+5 -3
View File
@@ -1298,11 +1298,13 @@
| :ref:`global | ? | :class: mjcf-attributes |
| <visual-global>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`orthographic<visual-global-orthographic>` | :ref:`fovy<visual-global-fovy>` | :ref:`ipd<visual-global-ipd>` | :ref:`azimuth<visual-global-azimuth>` | |
| | | | :ref:`cameraid<visual-global-cameraid>` | :ref:`orthographic<visual-global-orthographic>` | :ref:`fovy<visual-global-fovy>` | :ref:`ipd<visual-global-ipd>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`elevation<visual-global-elevation>` | :ref:`linewidth<visual-global-linewidth>` | :ref:`glow<visual-global-glow>` | :ref:`offwidth<visual-global-offwidth>` | |
| | | | :ref:`azimuth<visual-global-azimuth>` | :ref:`elevation<visual-global-elevation>` | :ref:`linewidth<visual-global-linewidth>` | :ref:`glow<visual-global-glow>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`offheight<visual-global-offheight>` | :ref:`realtime<visual-global-realtime>` | :ref:`ellipsoidinertia<visual-global-ellipsoidinertia>` | :ref:`bvactive<visual-global-bvactive>` | |
| | | | :ref:`offwidth<visual-global-offwidth>` | :ref:`offheight<visual-global-offheight>` | :ref:`realtime<visual-global-realtime>` | :ref:`ellipsoidinertia<visual-global-ellipsoidinertia>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`bvactive<visual-global-bvactive>` | | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| visual |br| |_| |L| | | .. table:: |
+45 -18
View File
@@ -2,37 +2,64 @@
Changelog
=========
Upcoming version (not yet release)
----------------------------------
Upcoming version (not yet released)
-----------------------------------
.. admonition:: Breaking API changes
:class: attention
- The functions ``mjs_detachBody`` and ``mjs_detachDefault`` have been replaced by :ref:`mjs_delete`.
- The Python functions ``element.delete`` have been replaced by ``spec.delete(element)``.
General
^^^^^^^
- Refactored island implementation so that island data is memory-contiguous. This speeds up island processing in the
solver and clears the way for the addition of the Newton and PGS solvers (currently only CG is supported).
- Removed the :at:`shell` plugin. This is now supported by :ref:`flexcomp<body-flexcomp>` and is active depending on
the :ref:`elastic2d<flexcomp-elasticity-elastic2d>` attribute (off by default).
- Replaced the :ref:`directional<body-light-directional>` (boolean) field for lights with a
:ref:`type<body-light-type>` field (of type :ref:`mjtLightType<mjtLightType>`) to allow for additional lighting
types.
- Added new sub-component :ref:`mj_makeM` which combines the :ref:`mj_crb` call with additional logic to support the
introduction in 3.3.1 of :ref:`tendon armature<tendon-spatial-armature>`. In addition to the traditional
``mjData.qM``, :ref:`mj_makeM` also computes ``mjData.M``, a CSR representation of the same matrix.
- Added a new function :ref:`mj_copyBack` to copy real-valued arrays in an mjModel to a compatible mjSpec.
- Added support for setting the initial camera in the viewer using
:ref:`visual/global/cameraid<visual-global-cameraid>`.
- Added support to only sync the state in the Python :ref:`passive viewer<PyViewerPassive>`'s ``Sync`` method, this is
useful to improve performance. The default behavior is unchanged and copies the entire model and data.
Bug fixes
^^^^^^^^^
- Inverse dynamics were not being computed correctly when :ref:`tendon armature<tendon-spatial-armature>` was present,
now fixed.
Version 3.3.3 (June 10, 2025)
-----------------------------
General
^^^^^^^
1. Refactored island implementation so that island data is memory-contiguous. This speeds up island processing in the
solver and clears the way for the addition of the Newton and PGS solvers (currently only CG is supported).
2. Removed the :at:`shell` plugin. This is now supported by :ref:`flexcomp<body-flexcomp>` and is active depending on
the :ref:`elastic2d<flexcomp-elasticity-elastic2d>` attribute (off by default).
3. Replaced the :ref:`directional<body-light-directional>` (boolean) field for lights with a
:ref:`type<body-light-type>` field (of type :ref:`mjtLightType<mjtLightType>`) to allow for additional lighting
types.
4. Added new sub-component :ref:`mj_makeM` which combines the :ref:`mj_crb` call with additional logic to support the
introduction in 3.3.1 of :ref:`tendon armature<tendon-spatial-armature>`. In addition to the traditional
``mjData.qM``, :ref:`mj_makeM` also computes ``mjData.M``, a CSR representation of the same matrix.
5. Added a new function :ref:`mj_copyBack` to copy real-valued arrays in an mjModel to a compatible mjSpec.
6. Removed the limitation of :ref:`fusestatic<compiler-fusestatic>` to models which contain no references. The fusestatic
flag will now fuse all bodies which are not referenced and ignore bodies which are referenced.
Simulate
^^^^^^^^
- The struct ``mjv_sceneState`` has been removed. This struct was used for partial synchronization of ``mjModel`` and
``mjData`` when the Python viewer is used in passive mode. This functionality is now provided by :ref:`mjv_copyModel`
and :ref:`mjv_copyData`, which don't copy arrays which are not required for visualization.
7. The struct ``mjv_sceneState`` has been removed. This struct was used for partial synchronization of ``mjModel`` and
``mjData`` when the Python viewer is used in passive mode. This functionality is now provided by :ref:`mjv_copyModel`
and :ref:`mjv_copyData`, which don't copy arrays which are not required for visualization.
.. image:: images/changelog/procedural_terrain_generation.png
:width: 25%
:width: 33%
:align: right
Python bindings
^^^^^^^^^^^^^^^
- Added examples of procedural terrain generation to the Model Editing tutorial: |mjspec_colab|
8. Added examples of procedural terrain generation to the Model Editing tutorial: |mjspec_colab|
MJX
^^^
9. Added tendon armature.
Version 3.3.2 (April 28, 2025)
------------------------------
+1 -2
View File
@@ -583,8 +583,7 @@ Fast implicit-in-velocity (``implicitfast``)
performance.
**Euler**:
Use ``Euler`` for compatibillity with older models and :ref:`MJX<Mjx>`. Specifically for MJX,
setting the :ref:`eulerdamp<option-flag-eulerdamp>` disable flag can :ref:`improve performance<MjxPerformance>`.
Use ``Euler`` for compatibillity with older models.
**implicitfast**:
The ``implicitfast`` integrator has similar computational cost to ``Euler``, yet provides
increased stability, and is therefore a strict improvement. It is the recommended integrator for most models.
+27 -6
View File
@@ -795,8 +795,8 @@ struct mjLROpt_ { // options for mj_setLengthRange()
mjtNum tolrange; // convergence tolerance (relative to range)
};
typedef struct mjLROpt_ mjLROpt;
struct mjVFS_ { // virtual file system for loading from memory
void* impl_; // internal pointer to VFS memory
struct mjVFS_ { // virtual file system for loading from memory
void* impl_; // internal pointer to VFS memory
};
typedef struct mjVFS_ mjVFS;
struct mjOption_ { // physics options
@@ -844,7 +844,8 @@ struct mjOption_ { // physics options
typedef struct mjOption_ mjOption;
struct mjVisual_ { // visualization options
struct { // global parameters
int orthographic; // is the free camera orthographic (0: no, 1: yes)
int cameraid; // initial camera id (-1: free)
int orthographic; // is the free camera orthographic (0: no, 1: yes)
float fovy; // y field-of-view of free camera (orthographic ? length : degree)
float ipd; // inter-pupilary distance for free camera
float azimuth; // initial azimuth of free camera (degrees)
@@ -958,6 +959,7 @@ struct mjModel_ {
int nbvh; // number of total bounding volumes in all bodies
int nbvhstatic; // number of static bounding volumes (aabb stored in mjModel)
int nbvhdynamic; // number of dynamic bounding volumes (aabb stored in mjData)
int noct; // number of total octree cells in all meshes
int njnt; // number of joints
int ngeom; // number of geoms
int nsite; // number of sites
@@ -1091,6 +1093,11 @@ struct mjModel_ {
int* bvh_nodeid; // geom or elem id of node; -1: non-leaf (nbvh x 1)
mjtNum* bvh_aabb; // local bounding box (center, size) (nbvhstatic x 6)
// octree spatial partitioning
int* oct_depth; // depth in the octree (noct x 1)
int* oct_child; // children of octree node (noct x 8)
mjtNum* oct_aabb; // octree node bounding box (center, size) (noct x 6)
// joints
int* jnt_type; // type of joint (mjtJoint) (njnt x 1)
int* jnt_qposadr; // start addr in 'qpos' for joint's data (njnt x 1)
@@ -1278,6 +1285,8 @@ struct mjModel_ {
int* mesh_facenum; // number of faces (nmesh x 1)
int* mesh_bvhadr; // address of bvh root (nmesh x 1)
int* mesh_bvhnum; // number of bvh (nmesh x 1)
int* mesh_octadr; // address of octree root (nmesh x 1)
int* mesh_octnum; // number of octree nodes (nmesh x 1)
int* mesh_normaladr; // first normal address (nmesh x 1)
int* mesh_normalnum; // number of normals (nmesh x 1)
int* mesh_texcoordadr; // texcoord data address; -1: no texcoord (nmesh x 1)
@@ -2875,6 +2884,7 @@ struct mjvOption_ { // abstract visualization options
mjtByte skingroup[mjNGROUP]; // skin visualization by group
mjtByte flags[mjNVISFLAG]; // visualization flags (indexed by mjtVisFlag)
int bvh_depth; // depth of the bounding volume hierarchy to be visualized
int oct_depth; // depth of the octree to be visualized
int flex_layer; // element layer to be visualized for 3D flex
};
typedef struct mjvOption_ mjvOption;
@@ -3410,8 +3420,6 @@ void mju_defaultTask(mjTask* task);
void mju_taskJoin(mjTask* task);
mjsElement* mjs_attach(mjsElement* parent, const mjsElement* child,
const char* prefix, const char* suffix);
int mjs_detachBody(mjSpec* s, mjsBody* b);
int mjs_detachDefault(mjSpec* s, mjsDefault* d);
mjsBody* mjs_addBody(mjsBody* body, const mjsDefault* def);
mjsSite* mjs_addSite(mjsBody* body, const mjsDefault* def);
mjsJoint* mjs_addJoint(mjsBody* body, const mjsDefault* def);
@@ -3420,7 +3428,7 @@ mjsGeom* mjs_addGeom(mjsBody* body, const mjsDefault* def);
mjsCamera* mjs_addCamera(mjsBody* body, const mjsDefault* def);
mjsLight* mjs_addLight(mjsBody* body, const mjsDefault* def);
mjsFrame* mjs_addFrame(mjsBody* body, mjsFrame* parentframe);
int mjs_delete(mjsElement* element);
int mjs_delete(mjSpec* spec, mjsElement* element);
mjsActuator* mjs_addActuator(mjSpec* s, const mjsDefault* def);
mjsSensor* mjs_addSensor(mjSpec* s);
mjsFlex* mjs_addFlex(mjSpec* s);
@@ -3438,6 +3446,19 @@ mjsTuple* mjs_addTuple(mjSpec* s);
mjsKey* mjs_addKey(mjSpec* s);
mjsPlugin* mjs_addPlugin(mjSpec* s);
mjsDefault* mjs_addDefault(mjSpec* s, const char* classname, const mjsDefault* parent);
const char* mjs_setToMotor(mjsActuator* actuator);
const char* mjs_setToPosition(mjsActuator* actuator, double kp, double kv[1],
double dampratio[1], double timeconst[1], double inheritrange);
const char* mjs_setToIntVelocity(mjsActuator* actuator, double kp, double kv[1],
double dampratio[1], double timeconst[1], double inheritrange);
const char* mjs_setToVelocity(mjsActuator* actuator, double kv);
const char* mjs_setToDamper(mjsActuator* actuator, double kv);
const char* mjs_setToCylinder(mjsActuator* actuator, double timeconst,
double bias, double area, double diameter);
const char* mjs_setToMuscle(mjsActuator* actuator, double timeconst[2], double tausmooth,
double range[2], double force, double scale, double lmin,
double lmax, double vmax, double fpmax, double fvmax);
const char* mjs_setToAdhesion(mjsActuator* actuator, double gain);
mjsMesh* mjs_addMesh(mjSpec* s, const mjsDefault* def);
mjsHField* mjs_addHField(mjSpec* s);
mjsSkin* mjs_addSkin(mjSpec* s);
+3 -5
View File
@@ -206,7 +206,7 @@ The following features are **fully supported** in MJX:
* - Category
- Feature
* - Dynamics
- :ref:`Forward <mj_forward>`
- :ref:`Forward <mj_forward>`, :ref:`Inverse <mj_inverse>`
* - :ref:`Joint <mjtJoint>`
- ``FREE``, ``BALL``, ``SLIDE``, ``HINGE``
* - :ref:`Transmission <mjtTrn>`
@@ -235,8 +235,6 @@ The following features are **fully supported** in MJX:
- 1, 3, 4, 6 (1 is not supported with ``ELLIPTIC``)
* - :ref:`Solver <mjtSolver>`
- ``CG``, ``NEWTON``
* - Dynamics
- :ref:`Inverse <mj_inverse>`
* - Fluid Model
- :ref:`flInertia`
* - :ref:`Tendons <tendon>`
@@ -248,6 +246,8 @@ The following features are **fully supported** in MJX:
``FRAMEANGVEL``, ``SUBTREELINVEL``, ``SUBTREEANGMOM``, ``TOUCH``, ``ACCELEROMETER``, ``FORCE``, ``TORQUE``,
``ACTUATORFRC``, ``JOINTACTFRC``, ``TENDONACTFRC``, ``FRAMELINACC``, ``FRAMEANGACC``
(``ACCELEROMETER``, ``FORCE``, ``TORQUE`` not supported with connect or weld equality constraints)
* - Lights
- Positions and directions of lights
The following features are **in development** and coming soon:
@@ -268,8 +268,6 @@ The following features are **in development** and coming soon:
- :ref:`flEllipsoid`
* - :ref:`Sensors <mjtSensor>`
- All except ``PLUGIN``, ``USER``
* - Lights
- Positions and directions of lights
The following features are **unsupported**:
+20 -8
View File
@@ -96,25 +96,37 @@ Elements cannot be created directly; they are returned to the user by the corres
my_geom->type = mjGEOM_BOX; // set geom type
my_geom->size[0] = my_geom->size[1] = my_geom->size[2] = 0.5; // set box size
mjModel* model = mj_compile(spec, NULL); // compile to mjModel
...
mj_deleteModel(model); // free model
mj_deleteSpec(spec); // free spec
The ``NULL`` second argument to :ref:`mjs_addGeom` is the optional default class pointer. When using defaults
procedurally, default classes are passed in explicitly to element constructors. The global defaults of all elements
(used when no default class is passed in) can be inspected in
`user_init.c <https://github.com/google-deepmind/mujoco/blob/main/src/user/user_init.c>`__.
.. _meMemory:
Memory management
^^^^^^^^^^^^^^^^^
As seen in the examples above, model elements are never allocated by the user directly, but rather returned by a
constructor. The library takes ownership of all elements and frees them when the parent :ref:`mjSpec` is deleted using
:ref:`mj_deleteSpec`. The user is only responsible for freeing :ref:`mjSpec` structs.
.. _meAttachment:
Attachment
^^^^^^^^^^
This framework introduces a powerful new feature: attaching and detaching model subtrees. This feature is already used
to power the :ref:`attach<body-attach>` an :ref:`replicate<replicate>` meta-elements in MJCF. Attachment allows the user
to move or copy a subtree from one model into another, while also copying or moving related referenced assets and
referencing elements from outside the kinematic tree (e.g., actuators and sensors). Similarly, detaching a subtree will
remove all associated elements from the model. The default behavior is to move the child into the parent while
attaching, so subsequent changes to the child will also change the parent. Alternatively, the user can choose to make an
entirely new copy during attach using :ref:`mjs_setDeepCopy`. This flag is temporarily set to true while parsing XMLs.
It is possible to :ref:`attach a body to a frame<mjs_attach>`:
This framework introduces a powerful new feature: attaching and deleting model subtrees. This feature is already used to
power the :ref:`attach<body-attach>` an :ref:`replicate<replicate>` meta-elements in MJCF. Attachment allows the user to
move or copy a subtree from one model into another, while also copying or moving related referenced assets and
referencing elements from outside the kinematic tree (e.g., actuators and sensors). Similarly, deleting a subtree will
remove all associated elements from the model. The default behavior ("shallow copy") is to move the child into the
parent while attaching, so subsequent changes to the child will also change the parent. Alternatively, the user can
choose to make an entirely new copy during attach using :ref:`mjs_setDeepCopy`. This flag is temporarily set to true
while parsing XMLs. It is possible to :ref:`attach a body to a frame<mjs_attach>`:
.. code-block:: C
+38 -31
View File
@@ -50,25 +50,17 @@ Interactive viewer
An interactive GUI viewer is provided as part of the Python package in the ``mujoco.viewer`` module. It is based on the
same codebase as the :ref:`simulate<saSimulate>` application that ships with the MuJoCo binary releases. Three distinct
use cases are supported:
.. _PyViewerApp:
Standalone app
--------------
- ``python -m mujoco.viewer`` launches an empty visualization session, where a model can be loaded by drag-and-drop.
- ``python -m mujoco.viewer --mjcf=/path/to/some/mjcf.xml`` launches a visualization session for the specified
model file.
use cases are supported: :ref:`managed viewer<PyViewerManaged>`, :ref:`standalone app<PyViewerApp>`, and :ref:`passive
viewer<PyViewerPassive>`.
.. _PyViewerManaged:
Managed viewer
--------------
Called from a Python program/script, through the function ``viewer.launch``. This function *blocks user code* to
support precise timing of the physics loop. This mode should be used if user code is implemented as
:ref:`engine plugins<exPlugin>` or :ref:`physics callbacks<glPhysics>`, and is called by MuJoCo during :ref:`mj_step`.
The ``viewer.launch`` function launches the interactive viewer and *blocks user code* which is useful to support precise
timing of the physics loop. This mode should be used if user code is implemented as :ref:`engine
plugins<exPlugin>` or :ref:`physics callbacks<glPhysics>`, and is called by MuJoCo during :ref:`mj_step`.
- ``viewer.launch()`` launches an empty visualization session, where a model can be loaded by drag-and-drop.
- ``viewer.launch(model)`` launches a visualization session for the given ``mjModel`` where the visualizer
@@ -76,21 +68,33 @@ support precise timing of the physics loop. This mode should be used if user cod
- ``viewer.launch(model, data)`` is the same as above, except that the visualizer operates directly on the given
``mjData`` instance -- upon exit the ``data`` object will have been modified.
.. _PyViewerApp:
Standalone app
--------------
The ``mujoco.viewer`` Python package uses the ``if __name__ == '__main__'`` mechanism to allow the :ref:`managed
viewer<PyViewerManaged>` to be called directly from the command line as a standalone app:
- ``python -m mujoco.viewer`` launches an empty visualization session, where a model can be loaded by drag-and-drop.
- ``python -m mujoco.viewer --mjcf=/path/to/some/mjcf.xml`` launches a visualization session for the specified
model file.
.. _PyViewerPassive:
Passive viewer
--------------
By calling ``viewer.launch_passive(model, data)``. This function *does not block*, allowing user code to continue
execution. In this mode, the user's script is responsible for timing and advancing the physics state, and mouse-drag
perturbations will not work unless the user explicitly synchronizes incoming events.
The ``viewer.launch_passive`` function launches the interactive viewer in a way which *does not block*, allowing user
code to continue execution. In this mode, the user's script is responsible for timing and advancing the physics state,
and mouse-drag perturbations will not work unless the user explicitly synchronizes incoming events.
.. warning::
On MacOS, ``launch_passive`` requires that the user script is executed via a special ``mjpython`` launcher.
The ``mjpython`` command is installed as part of the ``mujoco`` package, and can be used as a drop-in replacement
for the usual ``python`` command and supports an identical set of command line flags and arguments. For example,
a script can be executed via ``mjpython my_script.py``, and an IPython shell can be launched via
``mjpython -m IPython``.
On MacOS, ``launch_passive`` requires that the user script is executed via a special ``mjpython`` launcher, this is
needed to circumvent a platform limitation which requires the main thread to be one that does the rendering. The
``mjpython`` command is installed as part of the ``mujoco`` package, and can be used as a drop-in replacement for the
usual ``python`` command and supports an identical set of command line flags and arguments. For example, a script can
be executed via ``mjpython my_script.py``, and an IPython shell can be launched via ``mjpython -m IPython``.
The ``launch_passive`` function returns a handle which can be used to interact with the viewer. It has the following
attributes:
@@ -103,10 +107,14 @@ attributes:
state. These include the ``mjModel`` and ``mjData`` instance passed to ``launch_passive``, and also the ``cam``,
``opt``, and ``pert`` properties of the viewer handle.
- ``sync()``: synchronizes state between ``mjModel``, ``mjData``, and GUI user inputs since the previous call to
``sync``. In order to allow user scripts to make arbitrary modifications to ``mjModel`` and ``mjData`` without
needing to hold the viewer lock, the passive viewer does not access or modify these structs outside of ``sync``
calls.
- ``sync(state_only=False)``: synchronizes between the user's ``mjModel``, ``mjData`` and the GUI. In order to allow
user scripts to make arbitrary modifications to ``mjModel`` and ``mjData`` without needing to hold the viewer lock,
the passive viewer does not access or modify these structs outside of ``sync`` calls. If the ``state_only`` argument
is ``True``, instead of syncing everything, only the ``mjData`` fields corresponding to
:ref:`mjSTATE_INTEGRATION<mjtState>` are synced, followed by a call to :ref:`mj_forward`. The latter option is much
faster, but would not pick up arbitrary changes as in the default case. Changes made via the GUI are picked up in
either case but changing e.g., ``mjModel.geom_rgba`` via code will be picked up when ``state_only=False`` but not when
``state_only=True``.
User scripts must call ``sync`` in order for the viewer to reflect physics state changes. The ``sync`` function
also transfers user inputs from the GUI back into ``mjOption`` (inside ``mjModel``) and ``mjData``, including
@@ -603,11 +611,10 @@ Lists of all elements in a spec can be accessed using named properties, using th
Element removal
^^^^^^^^^^^^^^^
For elements that can have children (bodies and defaults), the methods ``spec.detach_body(body)`` and
``spec.detach_default(def)`` remove, respectively, ``body`` and ``def`` from the spec, together with all of their
children. When detaching body subtrees, all elements which reference elements in the subtree, will also be removed. For
all other elements, the method ``delete()`` removes the corresponding element from the spec, e.g.
``spec.geom('my_geom').delete()`` will remove the geom named "my_geom" and all of the elements that reference it.
The method ``delete()`` removes the corresponding element from the spec, e.g. ``spec.delete(spec.geom('my_geom'))`` will
remove the geom named "my_geom" and all of the elements that reference it. For elements that can have children (bodies
and defaults), ``delete`` also removes all of their children. When deleting body subtrees, all elements which reference
elements in the subtree, will also be removed.
Tree traversal
^^^^^^^^^^^^^^
@@ -1061,5 +1068,5 @@ non-exhaustive list of specific mujoco-py features:
This is the one context in which the MuJoCo library (and therefore also ``mujoco``) is stateful: it holds a copy in
memory of the last XML that was compiled, which is used in :ref:`mujoco.mj_saveLastXML(fname) <mj_saveLastXML>`. Note
that mujoco-pys implementation has a convenient extra feature, whereby the pose (as determined by ``sim.data``s
state) is transformed to a keyframe thats added to the model before saving. This extra feature is not currently
state) is transformed to a keyframe thats added to the model before saving. This extra feature is not currently
available in ``mujoco``.
+2 -2
View File
@@ -37,14 +37,14 @@ _____
The MuJoCo app needs to be run at least once before the native library can be used, in order to register the library as
a trusted binary. Then, copy the dynamic library file from
``/Applications/MuJoCo.app/Contents/Frameworks/mujoco.framework/Versions/Current/libmujoco.3.3.3.dylib`` (it can be
``/Applications/MuJoCo.app/Contents/Frameworks/mujoco.framework/Versions/Current/libmujoco.3.3.4.dylib`` (it can be
found by browsing the contents of ``MuJoCo.app``) and rename it as ``mujoco.dylib``.
Linux
_____
Expand the ``tar.gz`` archive to ``~/.mujoco``. Then copy the dynamic library from
``~/.mujoco/mujoco-3.3.3/lib/libmujoco.so.3.3.3`` and rename it as ``libmujoco.so``.
``~/.mujoco/mujoco-3.3.4/lib/libmujoco.so.3.3.4`` and rename it as ``libmujoco.so``.
Windows
_______
@@ -0,0 +1,41 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MUJOCO_SRC_EXPERIMENTAL_USD_LAYER_SINK_H_
#define MUJOCO_SRC_EXPERIMENTAL_USD_LAYER_SINK_H_
#include <vector>
#include <mujoco/mujoco.h>
#include <pxr/usd/sdf/path.h>
#include <pxr/usd/usd/common.h>
namespace mujoco {
namespace usd {
// A sink for writing pose data to an SDF layer.
// Opinions will be authored on the edit target layer for the
// passed stage argument.
class LayerSink {
public:
explicit LayerSink(pxr::UsdStageRefPtr stage);
void Update(const mjData* const data, std::vector<pxr::SdfPath> body_paths_);
private:
pxr::UsdStageRefPtr stage_;
};
} // namespace usd
} // namespace mujoco
#endif // MUJOCO_SRC_EXPERIMENTAL_USD_LAYER_SINK_H_
@@ -17,18 +17,18 @@
/// \file mjcPhysics/actuatorAPI.h
#include "./api.h"
#include "./tokens.h"
#include "pxr/base/gf/matrix4d.h"
#include "pxr/base/gf/vec3d.h"
#include "pxr/base/gf/vec3f.h"
#include "pxr/base/tf/token.h"
#include "pxr/base/tf/type.h"
#include "pxr/base/vt/value.h"
#include "pxr/pxr.h"
#include "pxr/usd/usd/apiSchemaBase.h"
#include "pxr/usd/usd/prim.h"
#include "pxr/usd/usd/stage.h"
#include <mujoco/experimental/usd/mjcPhysics/api.h>
#include <mujoco/experimental/usd/mjcPhysics/tokens.h>
#include <pxr/base/gf/matrix4d.h>
#include <pxr/base/gf/vec3d.h>
#include <pxr/base/gf/vec3f.h>
#include <pxr/base/tf/token.h>
#include <pxr/base/tf/type.h>
#include <pxr/base/vt/value.h>
#include <pxr/pxr.h>
#include <pxr/usd/usd/apiSchemaBase.h>
#include <pxr/usd/usd/prim.h>
#include <pxr/usd/usd/stage.h>
PXR_NAMESPACE_OPEN_SCOPE
@@ -775,23 +775,6 @@ class MjcPhysicsActuatorAPI : public UsdAPISchemaBase {
MJCPHYSICS_API
UsdRelationship CreateMjcRefSiteRel() const;
public:
// --------------------------------------------------------------------- //
// MJCCRANKSITE
// --------------------------------------------------------------------- //
/// If specified, the actuator acts on a slider-crank mechanism which is
/// implicitly determined by the actuator (i.e., it is not a separate model
/// element). The target site corresponds to the pin joining the crank and the
/// connecting rod.
///
MJCPHYSICS_API
UsdRelationship GetMjcCrankSiteRel() const;
/// See GetMjcCrankSiteRel(), and also
/// \ref Usd_Create_Or_Get_Property for when to use Get vs Create
MJCPHYSICS_API
UsdRelationship CreateMjcCrankSiteRel() const;
public:
// --------------------------------------------------------------------- //
// MJCSLIDERSITE
@@ -15,7 +15,7 @@
#ifndef MJCPHYSICS_API_H
#define MJCPHYSICS_API_H
#include "pxr/base/arch/export.h"
#include <pxr/base/arch/export.h>
#if defined(PXR_STATIC)
#define MJCPHYSICS_API
@@ -17,18 +17,18 @@
/// \file mjcPhysics/collisionAPI.h
#include "./api.h"
#include "./tokens.h"
#include "pxr/base/gf/matrix4d.h"
#include "pxr/base/gf/vec3d.h"
#include "pxr/base/gf/vec3f.h"
#include "pxr/base/tf/token.h"
#include "pxr/base/tf/type.h"
#include "pxr/base/vt/value.h"
#include "pxr/pxr.h"
#include "pxr/usd/usd/apiSchemaBase.h"
#include "pxr/usd/usd/prim.h"
#include "pxr/usd/usd/stage.h"
#include <mujoco/experimental/usd/mjcPhysics/api.h>
#include <mujoco/experimental/usd/mjcPhysics/tokens.h>
#include <pxr/base/gf/matrix4d.h>
#include <pxr/base/gf/vec3d.h>
#include <pxr/base/gf/vec3f.h>
#include <pxr/base/tf/token.h>
#include <pxr/base/tf/type.h>
#include <pxr/base/vt/value.h>
#include <pxr/pxr.h>
#include <pxr/usd/usd/apiSchemaBase.h>
#include <pxr/usd/usd/prim.h>
#include <pxr/usd/usd/stage.h>
PXR_NAMESPACE_OPEN_SCOPE
@@ -0,0 +1,622 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MJCPHYSICS_GENERATED_JOINTAPI_H
#define MJCPHYSICS_GENERATED_JOINTAPI_H
/// \file mjcPhysics/jointAPI.h
#include <mujoco/experimental/usd/mjcPhysics/api.h>
#include <mujoco/experimental/usd/mjcPhysics/tokens.h>
#include <pxr/base/gf/matrix4d.h>
#include <pxr/base/gf/vec3d.h>
#include <pxr/base/gf/vec3f.h>
#include <pxr/base/tf/token.h>
#include <pxr/base/tf/type.h>
#include <pxr/base/vt/value.h>
#include <pxr/pxr.h>
#include <pxr/usd/usd/apiSchemaBase.h>
#include <pxr/usd/usd/prim.h>
#include <pxr/usd/usd/stage.h>
PXR_NAMESPACE_OPEN_SCOPE
class SdfAssetPath;
// -------------------------------------------------------------------------- //
// PHYSICSJOINTSAPI //
// -------------------------------------------------------------------------- //
/// \class MjcPhysicsJointAPI
///
/// API describing a Mujoco joint.
///
/// For any described attribute \em Fallback \em Value or \em Allowed \em Values
/// below that are text/tokens, the actual token is published and defined in
/// \ref MjcPhysicsTokens. So to set an attribute to the value "rightHanded",
/// use MjcPhysicsTokens->rightHanded as the value.
///
class MjcPhysicsJointAPI : public UsdAPISchemaBase {
public:
/// Compile time constant representing what kind of schema this class is.
///
/// \sa UsdSchemaKind
static const UsdSchemaKind schemaKind = UsdSchemaKind::SingleApplyAPI;
/// Construct a MjcPhysicsJointAPI on UsdPrim \p prim .
/// Equivalent to MjcPhysicsJointAPI::Get(prim.GetStage(), prim.GetPath())
/// for a \em valid \p prim, but will not immediately throw an error for
/// an invalid \p prim
explicit MjcPhysicsJointAPI(const UsdPrim &prim = UsdPrim())
: UsdAPISchemaBase(prim) {}
/// Construct a MjcPhysicsJointAPI on the prim held by \p schemaObj .
/// Should be preferred over MjcPhysicsJointAPI(schemaObj.GetPrim()),
/// as it preserves SchemaBase state.
explicit MjcPhysicsJointAPI(const UsdSchemaBase &schemaObj)
: UsdAPISchemaBase(schemaObj) {}
/// Destructor.
MJCPHYSICS_API
virtual ~MjcPhysicsJointAPI();
/// Return a vector of names of all pre-declared attributes for this schema
/// class and all its ancestor classes. Does not include attributes that
/// may be authored by custom/extended methods of the schemas involved.
MJCPHYSICS_API
static const TfTokenVector &GetSchemaAttributeNames(
bool includeInherited = true);
/// Return a MjcPhysicsJointAPI holding the prim adhering to this
/// schema at \p path on \p stage. If no prim exists at \p path on
/// \p stage, or if the prim at that path does not adhere to this schema,
/// return an invalid schema object. This is shorthand for the following:
///
/// \code
/// MjcPhysicsJointAPI(stage->GetPrimAtPath(path));
/// \endcode
///
MJCPHYSICS_API
static MjcPhysicsJointAPI Get(const UsdStagePtr &stage, const SdfPath &path);
/// Returns true if this <b>single-apply</b> API schema can be applied to
/// the given \p prim. If this schema can not be a applied to the prim,
/// this returns false and, if provided, populates \p whyNot with the
/// reason it can not be applied.
///
/// Note that if CanApply returns false, that does not necessarily imply
/// that calling Apply will fail. Callers are expected to call CanApply
/// before calling Apply if they want to ensure that it is valid to
/// apply a schema.
///
/// \sa UsdPrim::GetAppliedSchemas()
/// \sa UsdPrim::HasAPI()
/// \sa UsdPrim::CanApplyAPI()
/// \sa UsdPrim::ApplyAPI()
/// \sa UsdPrim::RemoveAPI()
///
MJCPHYSICS_API
static bool CanApply(const UsdPrim &prim, std::string *whyNot = nullptr);
/// Applies this <b>single-apply</b> API schema to the given \p prim.
/// This information is stored by adding "PhysicsJointsAPI" to the
/// token-valued, listOp metadata \em apiSchemas on the prim.
///
/// \return A valid MjcPhysicsJointAPI object is returned upon success.
/// An invalid (or empty) MjcPhysicsJointAPI object is returned upon
/// failure. See \ref UsdPrim::ApplyAPI() for conditions
/// resulting in failure.
///
/// \sa UsdPrim::GetAppliedSchemas()
/// \sa UsdPrim::HasAPI()
/// \sa UsdPrim::CanApplyAPI()
/// \sa UsdPrim::ApplyAPI()
/// \sa UsdPrim::RemoveAPI()
///
MJCPHYSICS_API
static MjcPhysicsJointAPI Apply(const UsdPrim &prim);
protected:
/// Returns the kind of schema this class belongs to.
///
/// \sa UsdSchemaKind
MJCPHYSICS_API
UsdSchemaKind _GetSchemaKind() const override;
private:
// needs to invoke _GetStaticTfType.
friend class UsdSchemaRegistry;
MJCPHYSICS_API
static const TfType &_GetStaticTfType();
static bool _IsTypedSchema();
// override SchemaBase virtuals.
MJCPHYSICS_API
const TfType &_GetTfType() const override;
public:
// --------------------------------------------------------------------- //
// MJCSPRINGDAMPER
// --------------------------------------------------------------------- //
/// When both numbers are positive, the compiler will override any stiffness
/// and damping values specified with the attributes below, and will instead
/// set them automatically so that the resulting mass-spring-damper for this
/// joint has the desired time constant (first value) and damping ratio
/// (second value). This is done by taking into account the joint inertia in
/// the model reference configuration. Note that the format is the same as the
/// solref parameter of the constraint solver.
///
/// | ||
/// | -- | -- |
/// | Declaration | `uniform double[] mjc:springdamper = [0, 0]` |
/// | C++ Type | VtArray<double> |
/// | \ref Usd_Datatypes "Usd Type" | SdfValueTypeNames->DoubleArray |
/// | \ref SdfVariability "Variability" | SdfVariabilityUniform |
MJCPHYSICS_API
UsdAttribute GetMjcSpringdamperAttr() const;
/// See GetMjcSpringdamperAttr(), and also
/// \ref Usd_Create_Or_Get_Property for when to use Get vs Create.
/// If specified, author \p defaultValue as the attribute's default,
/// sparsely (when it makes sense to do so) if \p writeSparsely is \c true -
/// the default for \p writeSparsely is \c false.
MJCPHYSICS_API
UsdAttribute CreateMjcSpringdamperAttr(
VtValue const &defaultValue = VtValue(),
bool writeSparsely = false) const;
public:
// --------------------------------------------------------------------- //
// MJCSOLREFLIMIT
// --------------------------------------------------------------------- //
/// Constraint solver parameters for simulating joint limits.
///
/// | ||
/// | -- | -- |
/// | Declaration | `uniform double[] mjc:solreflimit = [0.02, 1]` |
/// | C++ Type | VtArray<double> |
/// | \ref Usd_Datatypes "Usd Type" | SdfValueTypeNames->DoubleArray |
/// | \ref SdfVariability "Variability" | SdfVariabilityUniform |
MJCPHYSICS_API
UsdAttribute GetMjcSolreflimitAttr() const;
/// See GetMjcSolreflimitAttr(), and also
/// \ref Usd_Create_Or_Get_Property for when to use Get vs Create.
/// If specified, author \p defaultValue as the attribute's default,
/// sparsely (when it makes sense to do so) if \p writeSparsely is \c true -
/// the default for \p writeSparsely is \c false.
MJCPHYSICS_API
UsdAttribute CreateMjcSolreflimitAttr(VtValue const &defaultValue = VtValue(),
bool writeSparsely = false) const;
public:
// --------------------------------------------------------------------- //
// MJCSOLIMPLIMIT
// --------------------------------------------------------------------- //
/// Constraint solver parameters for simulating joint limits.
///
/// | ||
/// | -- | -- |
/// | Declaration | `uniform double[] mjc:solimplimit = [0.9, 0.95, 0.001,
/// 0.5, 2]` | | C++ Type | VtArray<double> | | \ref Usd_Datatypes "Usd Type"
/// | SdfValueTypeNames->DoubleArray | | \ref SdfVariability "Variability" |
/// SdfVariabilityUniform |
MJCPHYSICS_API
UsdAttribute GetMjcSolimplimitAttr() const;
/// See GetMjcSolimplimitAttr(), and also
/// \ref Usd_Create_Or_Get_Property for when to use Get vs Create.
/// If specified, author \p defaultValue as the attribute's default,
/// sparsely (when it makes sense to do so) if \p writeSparsely is \c true -
/// the default for \p writeSparsely is \c false.
MJCPHYSICS_API
UsdAttribute CreateMjcSolimplimitAttr(VtValue const &defaultValue = VtValue(),
bool writeSparsely = false) const;
public:
// --------------------------------------------------------------------- //
// MJCSOLREFFRICTION
// --------------------------------------------------------------------- //
/// Constraint solver parameters for simulating dry friction.
///
/// | ||
/// | -- | -- |
/// | Declaration | `uniform double[] mjc:solreffriction = [0.02, 1]` |
/// | C++ Type | VtArray<double> |
/// | \ref Usd_Datatypes "Usd Type" | SdfValueTypeNames->DoubleArray |
/// | \ref SdfVariability "Variability" | SdfVariabilityUniform |
MJCPHYSICS_API
UsdAttribute GetMjcSolreffrictionAttr() const;
/// See GetMjcSolreffrictionAttr(), and also
/// \ref Usd_Create_Or_Get_Property for when to use Get vs Create.
/// If specified, author \p defaultValue as the attribute's default,
/// sparsely (when it makes sense to do so) if \p writeSparsely is \c true -
/// the default for \p writeSparsely is \c false.
MJCPHYSICS_API
UsdAttribute CreateMjcSolreffrictionAttr(
VtValue const &defaultValue = VtValue(),
bool writeSparsely = false) const;
public:
// --------------------------------------------------------------------- //
// MJCSOLIMPFRICTION
// --------------------------------------------------------------------- //
/// Constraint solver parameters for simulating dry friction.
///
/// | ||
/// | -- | -- |
/// | Declaration | `uniform double[] mjc:solimpfriction = [0.9, 0.95, 0.001,
/// 0.5, 2]` | | C++ Type | VtArray<double> | | \ref Usd_Datatypes "Usd Type"
/// | SdfValueTypeNames->DoubleArray | | \ref SdfVariability "Variability" |
/// SdfVariabilityUniform |
MJCPHYSICS_API
UsdAttribute GetMjcSolimpfrictionAttr() const;
/// See GetMjcSolimpfrictionAttr(), and also
/// \ref Usd_Create_Or_Get_Property for when to use Get vs Create.
/// If specified, author \p defaultValue as the attribute's default,
/// sparsely (when it makes sense to do so) if \p writeSparsely is \c true -
/// the default for \p writeSparsely is \c false.
MJCPHYSICS_API
UsdAttribute CreateMjcSolimpfrictionAttr(
VtValue const &defaultValue = VtValue(),
bool writeSparsely = false) const;
public:
// --------------------------------------------------------------------- //
// MJCSTIFFNESS
// --------------------------------------------------------------------- //
/// Joint stiffness. If this value is positive, a spring will be created with
/// equilibrium position given by springref below. The spring force is
/// computed along with the other passive forces.
///
/// | ||
/// | -- | -- |
/// | Declaration | `uniform double mjc:stiffness = 0` |
/// | C++ Type | double |
/// | \ref Usd_Datatypes "Usd Type" | SdfValueTypeNames->Double |
/// | \ref SdfVariability "Variability" | SdfVariabilityUniform |
MJCPHYSICS_API
UsdAttribute GetMjcStiffnessAttr() const;
/// See GetMjcStiffnessAttr(), and also
/// \ref Usd_Create_Or_Get_Property for when to use Get vs Create.
/// If specified, author \p defaultValue as the attribute's default,
/// sparsely (when it makes sense to do so) if \p writeSparsely is \c true -
/// the default for \p writeSparsely is \c false.
MJCPHYSICS_API
UsdAttribute CreateMjcStiffnessAttr(VtValue const &defaultValue = VtValue(),
bool writeSparsely = false) const;
public:
// --------------------------------------------------------------------- //
// MJCACTUATORFRCRANGEMIN
// --------------------------------------------------------------------- //
/// Minimum range for clamping total actuator forces acting on this joint. See
/// Force limits for details. It is available only for scalar joints (hinge
/// and slider) and ignored for ball and free joints. The compiler expects the
/// first value to be smaller than the second value. Setting this attribute
/// without specifying actuatorfrclimited is an error if compiler-autolimits
/// is 'false'.
///
/// | ||
/// | -- | -- |
/// | Declaration | `uniform double mjc:actuatorfrcrange:min = 0` |
/// | C++ Type | double |
/// | \ref Usd_Datatypes "Usd Type" | SdfValueTypeNames->Double |
/// | \ref SdfVariability "Variability" | SdfVariabilityUniform |
MJCPHYSICS_API
UsdAttribute GetMjcActuatorfrcrangeMinAttr() const;
/// See GetMjcActuatorfrcrangeMinAttr(), and also
/// \ref Usd_Create_Or_Get_Property for when to use Get vs Create.
/// If specified, author \p defaultValue as the attribute's default,
/// sparsely (when it makes sense to do so) if \p writeSparsely is \c true -
/// the default for \p writeSparsely is \c false.
MJCPHYSICS_API
UsdAttribute CreateMjcActuatorfrcrangeMinAttr(
VtValue const &defaultValue = VtValue(),
bool writeSparsely = false) const;
public:
// --------------------------------------------------------------------- //
// MJCACTUATORFRCRANGEMAX
// --------------------------------------------------------------------- //
/// Maximum range for clamping total actuator forces acting on this joint. See
/// Force limits for details. It is available only for scalar joints (hinge
/// and slider) and ignored for ball and free joints. The compiler expects the
/// first value to be smaller than the second value. Setting this attribute
/// without specifying actuatorfrclimited is an error if compiler-autolimits
/// is 'false'.
///
/// | ||
/// | -- | -- |
/// | Declaration | `uniform double mjc:actuatorfrcrange:max = 0` |
/// | C++ Type | double |
/// | \ref Usd_Datatypes "Usd Type" | SdfValueTypeNames->Double |
/// | \ref SdfVariability "Variability" | SdfVariabilityUniform |
MJCPHYSICS_API
UsdAttribute GetMjcActuatorfrcrangeMaxAttr() const;
/// See GetMjcActuatorfrcrangeMaxAttr(), and also
/// \ref Usd_Create_Or_Get_Property for when to use Get vs Create.
/// If specified, author \p defaultValue as the attribute's default,
/// sparsely (when it makes sense to do so) if \p writeSparsely is \c true -
/// the default for \p writeSparsely is \c false.
MJCPHYSICS_API
UsdAttribute CreateMjcActuatorfrcrangeMaxAttr(
VtValue const &defaultValue = VtValue(),
bool writeSparsely = false) const;
public:
// --------------------------------------------------------------------- //
// MJCACTUATORFRCLIMITED
// --------------------------------------------------------------------- //
/// This attribute specifies whether actuator forces acting on the joint
/// should be clamped. See Force limits for details. It is available only for
/// scalar joints (hinge and slider) and ignored for ball and free joints.
/// This attribute interacts with the actuatorfrcrange attribute. If this
/// attribute is 'false', actuator force clamping is disabled. If it is
/// 'true', actuator force clamping is enabled. If this attribute is 'auto',
/// and autolimits is set in compiler, actuator force clamping will be enabled
/// if actuatorfrcrange is defined.
///
/// | ||
/// | -- | -- |
/// | Declaration | `uniform token mjc:actuatorfrclimited = "auto"` |
/// | C++ Type | TfToken |
/// | \ref Usd_Datatypes "Usd Type" | SdfValueTypeNames->Token |
/// | \ref SdfVariability "Variability" | SdfVariabilityUniform |
/// | \ref MjcPhysicsTokens "Allowed Values" | false, true, auto |
MJCPHYSICS_API
UsdAttribute GetMjcActuatorfrclimitedAttr() const;
/// See GetMjcActuatorfrclimitedAttr(), and also
/// \ref Usd_Create_Or_Get_Property for when to use Get vs Create.
/// If specified, author \p defaultValue as the attribute's default,
/// sparsely (when it makes sense to do so) if \p writeSparsely is \c true -
/// the default for \p writeSparsely is \c false.
MJCPHYSICS_API
UsdAttribute CreateMjcActuatorfrclimitedAttr(
VtValue const &defaultValue = VtValue(),
bool writeSparsely = false) const;
public:
// --------------------------------------------------------------------- //
// MJCACTUATORGRAVCOMP
// --------------------------------------------------------------------- //
/// If this flag is enabled, gravity compensation applied to this joint is
/// added to actuator forces (mjData.qfrc_actuator) rather than passive forces
/// (mjData.qfrc_passive). Notionally, this means that gravity compensation is
/// the result of a control system rather than natural buoyancy. In practice,
/// enabling this flag is useful when joint-level actuator force clamping is
/// used. In this case, the total actuation force applied on a joint,
/// including gravity compensation, is guaranteed to not exceed the specified
/// limits. See Force limits and actuatorfrcrange for more details on this
/// type of force limit.
///
/// | ||
/// | -- | -- |
/// | Declaration | `uniform bool mjc:actuatorgravcomp = 0` |
/// | C++ Type | bool |
/// | \ref Usd_Datatypes "Usd Type" | SdfValueTypeNames->Bool |
/// | \ref SdfVariability "Variability" | SdfVariabilityUniform |
MJCPHYSICS_API
UsdAttribute GetMjcActuatorgravcompAttr() const;
/// See GetMjcActuatorgravcompAttr(), and also
/// \ref Usd_Create_Or_Get_Property for when to use Get vs Create.
/// If specified, author \p defaultValue as the attribute's default,
/// sparsely (when it makes sense to do so) if \p writeSparsely is \c true -
/// the default for \p writeSparsely is \c false.
MJCPHYSICS_API
UsdAttribute CreateMjcActuatorgravcompAttr(
VtValue const &defaultValue = VtValue(),
bool writeSparsely = false) const;
public:
// --------------------------------------------------------------------- //
// MJCMARGIN
// --------------------------------------------------------------------- //
/// The distance threshold below which limits become active. Recall that the
/// Constraint solver normally generates forces as soon as a constraint
/// becomes active, even if the margin parameter makes that happen at a
/// distance. This attribute together with solreflimit and solimplimit can be
/// used to model a soft joint limit.
///
/// | ||
/// | -- | -- |
/// | Declaration | `uniform double mjc:margin = 0` |
/// | C++ Type | double |
/// | \ref Usd_Datatypes "Usd Type" | SdfValueTypeNames->Double |
/// | \ref SdfVariability "Variability" | SdfVariabilityUniform |
MJCPHYSICS_API
UsdAttribute GetMjcMarginAttr() const;
/// See GetMjcMarginAttr(), and also
/// \ref Usd_Create_Or_Get_Property for when to use Get vs Create.
/// If specified, author \p defaultValue as the attribute's default,
/// sparsely (when it makes sense to do so) if \p writeSparsely is \c true -
/// the default for \p writeSparsely is \c false.
MJCPHYSICS_API
UsdAttribute CreateMjcMarginAttr(VtValue const &defaultValue = VtValue(),
bool writeSparsely = false) const;
public:
// --------------------------------------------------------------------- //
// MJCREF
// --------------------------------------------------------------------- //
/// The reference position or angle of the joint. This attribute is only used
/// for slide and hinge joints. It defines the joint value corresponding to
/// the initial model configuration. The amount of spatial transformation that
/// the joint applies at runtime equals the current joint value stored in
/// mjData.qpos minus this reference value stored in mjModel.qpos0. The
/// meaning of these vectors was discussed in the Stand-alone section in the
/// Overview chapter.
///
/// | ||
/// | -- | -- |
/// | Declaration | `uniform double mjc:ref = 0` |
/// | C++ Type | double |
/// | \ref Usd_Datatypes "Usd Type" | SdfValueTypeNames->Double |
/// | \ref SdfVariability "Variability" | SdfVariabilityUniform |
MJCPHYSICS_API
UsdAttribute GetMjcRefAttr() const;
/// See GetMjcRefAttr(), and also
/// \ref Usd_Create_Or_Get_Property for when to use Get vs Create.
/// If specified, author \p defaultValue as the attribute's default,
/// sparsely (when it makes sense to do so) if \p writeSparsely is \c true -
/// the default for \p writeSparsely is \c false.
MJCPHYSICS_API
UsdAttribute CreateMjcRefAttr(VtValue const &defaultValue = VtValue(),
bool writeSparsely = false) const;
public:
// --------------------------------------------------------------------- //
// MJCSPRINGREF
// --------------------------------------------------------------------- //
/// The joint position or angle in which the joint spring (if any) achieves
/// equilibrium. Similar to the vector mjModel.qpos0 which stores all joint
/// reference values specified with the ref attribute above, all spring
/// reference values specified with this attribute are stored in the vector
/// mjModel.qpos_spring. The model configuration corresponding to
/// mjModel.qpos_spring is also used to compute the spring reference lengths
/// of all tendons, stored in mjModel.tendon_lengthspring. This is because
/// tendons can also have springs.
///
/// | ||
/// | -- | -- |
/// | Declaration | `uniform double mjc:springref = 0` |
/// | C++ Type | double |
/// | \ref Usd_Datatypes "Usd Type" | SdfValueTypeNames->Double |
/// | \ref SdfVariability "Variability" | SdfVariabilityUniform |
MJCPHYSICS_API
UsdAttribute GetMjcSpringrefAttr() const;
/// See GetMjcSpringrefAttr(), and also
/// \ref Usd_Create_Or_Get_Property for when to use Get vs Create.
/// If specified, author \p defaultValue as the attribute's default,
/// sparsely (when it makes sense to do so) if \p writeSparsely is \c true -
/// the default for \p writeSparsely is \c false.
MJCPHYSICS_API
UsdAttribute CreateMjcSpringrefAttr(VtValue const &defaultValue = VtValue(),
bool writeSparsely = false) const;
public:
// --------------------------------------------------------------------- //
// MJCARMATURE
// --------------------------------------------------------------------- //
/// Additional inertia associated with movement of the joint that is not due
/// to body mass. This added inertia is usually due to a rotor (a.k.a
/// armature) spinning faster than the joint itself due to a geared
/// transmission. The value applies to all degrees of freedom created by this
/// joint. Besides increasing the realism of joints with geared transmission,
/// positive armature significantly improves simulation stability, even for
/// small values, and is a recommended possible fix when encountering
/// stability issues.
///
/// | ||
/// | -- | -- |
/// | Declaration | `uniform double mjc:armature = 0` |
/// | C++ Type | double |
/// | \ref Usd_Datatypes "Usd Type" | SdfValueTypeNames->Double |
/// | \ref SdfVariability "Variability" | SdfVariabilityUniform |
MJCPHYSICS_API
UsdAttribute GetMjcArmatureAttr() const;
/// See GetMjcArmatureAttr(), and also
/// \ref Usd_Create_Or_Get_Property for when to use Get vs Create.
/// If specified, author \p defaultValue as the attribute's default,
/// sparsely (when it makes sense to do so) if \p writeSparsely is \c true -
/// the default for \p writeSparsely is \c false.
MJCPHYSICS_API
UsdAttribute CreateMjcArmatureAttr(VtValue const &defaultValue = VtValue(),
bool writeSparsely = false) const;
public:
// --------------------------------------------------------------------- //
// MJCDAMPING
// --------------------------------------------------------------------- //
/// Damping applied to all degrees of freedom created by this joint. Unlike
/// friction loss which is computed by the constraint solver, damping is
/// simply a force linear in velocity. It is included in the passive forces.
/// Despite this simplicity, larger damping values can make numerical
/// integrators unstable, which is why our Euler integrator handles damping
/// implicitly. See Integration in the Computation chapter.
///
/// | ||
/// | -- | -- |
/// | Declaration | `uniform double mjc:damping = 0` |
/// | C++ Type | double |
/// | \ref Usd_Datatypes "Usd Type" | SdfValueTypeNames->Double |
/// | \ref SdfVariability "Variability" | SdfVariabilityUniform |
MJCPHYSICS_API
UsdAttribute GetMjcDampingAttr() const;
/// See GetMjcDampingAttr(), and also
/// \ref Usd_Create_Or_Get_Property for when to use Get vs Create.
/// If specified, author \p defaultValue as the attribute's default,
/// sparsely (when it makes sense to do so) if \p writeSparsely is \c true -
/// the default for \p writeSparsely is \c false.
MJCPHYSICS_API
UsdAttribute CreateMjcDampingAttr(VtValue const &defaultValue = VtValue(),
bool writeSparsely = false) const;
public:
// --------------------------------------------------------------------- //
// MJCFRICTIONLOSS
// --------------------------------------------------------------------- //
/// Friction loss due to dry friction. This value is the same for all degrees
/// of freedom created by this joint. Semantically friction loss does not make
/// sense for free joints, but the compiler allows it. To enable friction
/// loss, set this attribute to a positive value.
///
/// | ||
/// | -- | -- |
/// | Declaration | `uniform double mjc:frictionloss = 0` |
/// | C++ Type | double |
/// | \ref Usd_Datatypes "Usd Type" | SdfValueTypeNames->Double |
/// | \ref SdfVariability "Variability" | SdfVariabilityUniform |
MJCPHYSICS_API
UsdAttribute GetMjcFrictionlossAttr() const;
/// See GetMjcFrictionlossAttr(), and also
/// \ref Usd_Create_Or_Get_Property for when to use Get vs Create.
/// If specified, author \p defaultValue as the attribute's default,
/// sparsely (when it makes sense to do so) if \p writeSparsely is \c true -
/// the default for \p writeSparsely is \c false.
MJCPHYSICS_API
UsdAttribute CreateMjcFrictionlossAttr(
VtValue const &defaultValue = VtValue(),
bool writeSparsely = false) const;
public:
// ===================================================================== //
// Feel free to add custom code below this line, it will be preserved by
// the code generator.
//
// Just remember to:
// - Close the class declaration with };
// - Close the namespace with PXR_NAMESPACE_CLOSE_SCOPE
// - Close the include guard with #endif
// ===================================================================== //
// --(BEGIN CUSTOM CODE)--
};
PXR_NAMESPACE_CLOSE_SCOPE
#endif
@@ -0,0 +1,292 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MJCPHYSICS_GENERATED_KEYFRAME_H
#define MJCPHYSICS_GENERATED_KEYFRAME_H
/// \file mjcPhysics/keyframe.h
#include <mujoco/experimental/usd/mjcPhysics/api.h>
#include <mujoco/experimental/usd/mjcPhysics/tokens.h>
#include <pxr/base/gf/matrix4d.h>
#include <pxr/base/gf/vec3d.h>
#include <pxr/base/gf/vec3f.h>
#include <pxr/base/tf/token.h>
#include <pxr/base/tf/type.h>
#include <pxr/base/vt/value.h>
#include <pxr/pxr.h>
#include <pxr/usd/usd/prim.h>
#include <pxr/usd/usd/stage.h>
#include <pxr/usd/usd/typed.h>
PXR_NAMESPACE_OPEN_SCOPE
class SdfAssetPath;
// -------------------------------------------------------------------------- //
// KEYFRAME //
// -------------------------------------------------------------------------- //
/// \class MjcPhysicsKeyframe
///
/// Represents time independent keyframe values.
///
class MjcPhysicsKeyframe : public UsdTyped {
public:
/// Compile time constant representing what kind of schema this class is.
///
/// \sa UsdSchemaKind
static const UsdSchemaKind schemaKind = UsdSchemaKind::ConcreteTyped;
/// Construct a MjcPhysicsKeyframe on UsdPrim \p prim .
/// Equivalent to MjcPhysicsKeyframe::Get(prim.GetStage(), prim.GetPath())
/// for a \em valid \p prim, but will not immediately throw an error for
/// an invalid \p prim
explicit MjcPhysicsKeyframe(const UsdPrim &prim = UsdPrim())
: UsdTyped(prim) {}
/// Construct a MjcPhysicsKeyframe on the prim held by \p schemaObj .
/// Should be preferred over MjcPhysicsKeyframe(schemaObj.GetPrim()),
/// as it preserves SchemaBase state.
explicit MjcPhysicsKeyframe(const UsdSchemaBase &schemaObj)
: UsdTyped(schemaObj) {}
/// Destructor.
MJCPHYSICS_API
virtual ~MjcPhysicsKeyframe();
/// Return a vector of names of all pre-declared attributes for this schema
/// class and all its ancestor classes. Does not include attributes that
/// may be authored by custom/extended methods of the schemas involved.
MJCPHYSICS_API
static const TfTokenVector &GetSchemaAttributeNames(
bool includeInherited = true);
/// Return a MjcPhysicsKeyframe holding the prim adhering to this
/// schema at \p path on \p stage. If no prim exists at \p path on
/// \p stage, or if the prim at that path does not adhere to this schema,
/// return an invalid schema object. This is shorthand for the following:
///
/// \code
/// MjcPhysicsKeyframe(stage->GetPrimAtPath(path));
/// \endcode
///
MJCPHYSICS_API
static MjcPhysicsKeyframe Get(const UsdStagePtr &stage, const SdfPath &path);
/// Attempt to ensure a \a UsdPrim adhering to this schema at \p path
/// is defined (according to UsdPrim::IsDefined()) on this stage.
///
/// If a prim adhering to this schema at \p path is already defined on this
/// stage, return that prim. Otherwise author an \a SdfPrimSpec with
/// \a specifier == \a SdfSpecifierDef and this schema's prim type name for
/// the prim at \p path at the current EditTarget. Author \a SdfPrimSpec s
/// with \p specifier == \a SdfSpecifierDef and empty typeName at the
/// current EditTarget for any nonexistent, or existing but not \a Defined
/// ancestors.
///
/// The given \a path must be an absolute prim path that does not contain
/// any variant selections.
///
/// If it is impossible to author any of the necessary PrimSpecs, (for
/// example, in case \a path cannot map to the current UsdEditTarget's
/// namespace) issue an error and return an invalid \a UsdPrim.
///
/// Note that this method may return a defined prim whose typeName does not
/// specify this schema class, in case a stronger typeName opinion overrides
/// the opinion at the current EditTarget.
///
MJCPHYSICS_API
static MjcPhysicsKeyframe Define(const UsdStagePtr &stage,
const SdfPath &path);
protected:
/// Returns the kind of schema this class belongs to.
///
/// \sa UsdSchemaKind
MJCPHYSICS_API
UsdSchemaKind _GetSchemaKind() const override;
private:
// needs to invoke _GetStaticTfType.
friend class UsdSchemaRegistry;
MJCPHYSICS_API
static const TfType &_GetStaticTfType();
static bool _IsTypedSchema();
// override SchemaBase virtuals.
MJCPHYSICS_API
const TfType &_GetTfType() const override;
public:
// --------------------------------------------------------------------- //
// MJCQPOS
// --------------------------------------------------------------------- //
/// Vector of joint positions, copied into mjData.qpos when the simulation
/// state is set to this keyframe.
///
/// | ||
/// | -- | -- |
/// | Declaration | `double[] mjc:qpos` |
/// | C++ Type | VtArray<double> |
/// | \ref Usd_Datatypes "Usd Type" | SdfValueTypeNames->DoubleArray |
MJCPHYSICS_API
UsdAttribute GetMjcQposAttr() const;
/// See GetMjcQposAttr(), and also
/// \ref Usd_Create_Or_Get_Property for when to use Get vs Create.
/// If specified, author \p defaultValue as the attribute's default,
/// sparsely (when it makes sense to do so) if \p writeSparsely is \c true -
/// the default for \p writeSparsely is \c false.
MJCPHYSICS_API
UsdAttribute CreateMjcQposAttr(VtValue const &defaultValue = VtValue(),
bool writeSparsely = false) const;
public:
// --------------------------------------------------------------------- //
// MJCQVEL
// --------------------------------------------------------------------- //
/// Vector of joint velocities, copied into mjData.qvel when the simulation
/// state is set to this keyframe.
///
/// | ||
/// | -- | -- |
/// | Declaration | `double[] mjc:qvel` |
/// | C++ Type | VtArray<double> |
/// | \ref Usd_Datatypes "Usd Type" | SdfValueTypeNames->DoubleArray |
MJCPHYSICS_API
UsdAttribute GetMjcQvelAttr() const;
/// See GetMjcQvelAttr(), and also
/// \ref Usd_Create_Or_Get_Property for when to use Get vs Create.
/// If specified, author \p defaultValue as the attribute's default,
/// sparsely (when it makes sense to do so) if \p writeSparsely is \c true -
/// the default for \p writeSparsely is \c false.
MJCPHYSICS_API
UsdAttribute CreateMjcQvelAttr(VtValue const &defaultValue = VtValue(),
bool writeSparsely = false) const;
public:
// --------------------------------------------------------------------- //
// MJCACT
// --------------------------------------------------------------------- //
/// Vector of actuator activations, copied into mjData.act when the simulation
/// state is set to this keyframe.
///
/// | ||
/// | -- | -- |
/// | Declaration | `double[] mjc:act` |
/// | C++ Type | VtArray<double> |
/// | \ref Usd_Datatypes "Usd Type" | SdfValueTypeNames->DoubleArray |
MJCPHYSICS_API
UsdAttribute GetMjcActAttr() const;
/// See GetMjcActAttr(), and also
/// \ref Usd_Create_Or_Get_Property for when to use Get vs Create.
/// If specified, author \p defaultValue as the attribute's default,
/// sparsely (when it makes sense to do so) if \p writeSparsely is \c true -
/// the default for \p writeSparsely is \c false.
MJCPHYSICS_API
UsdAttribute CreateMjcActAttr(VtValue const &defaultValue = VtValue(),
bool writeSparsely = false) const;
public:
// --------------------------------------------------------------------- //
// MJCCTRL
// --------------------------------------------------------------------- //
/// Vector of controls, copied into mjData.ctrl when the simulation state is
/// set to this keyframe.
///
/// | ||
/// | -- | -- |
/// | Declaration | `double[] mjc:ctrl` |
/// | C++ Type | VtArray<double> |
/// | \ref Usd_Datatypes "Usd Type" | SdfValueTypeNames->DoubleArray |
MJCPHYSICS_API
UsdAttribute GetMjcCtrlAttr() const;
/// See GetMjcCtrlAttr(), and also
/// \ref Usd_Create_Or_Get_Property for when to use Get vs Create.
/// If specified, author \p defaultValue as the attribute's default,
/// sparsely (when it makes sense to do so) if \p writeSparsely is \c true -
/// the default for \p writeSparsely is \c false.
MJCPHYSICS_API
UsdAttribute CreateMjcCtrlAttr(VtValue const &defaultValue = VtValue(),
bool writeSparsely = false) const;
public:
// --------------------------------------------------------------------- //
// MJCMPOS
// --------------------------------------------------------------------- //
/// Vector of mocap body positions, copied into mjData.mocap_pos when the
/// simulation state is set to this keyframe.
///
/// | ||
/// | -- | -- |
/// | Declaration | `double[] mjc:mpos` |
/// | C++ Type | VtArray<double> |
/// | \ref Usd_Datatypes "Usd Type" | SdfValueTypeNames->DoubleArray |
MJCPHYSICS_API
UsdAttribute GetMjcMposAttr() const;
/// See GetMjcMposAttr(), and also
/// \ref Usd_Create_Or_Get_Property for when to use Get vs Create.
/// If specified, author \p defaultValue as the attribute's default,
/// sparsely (when it makes sense to do so) if \p writeSparsely is \c true -
/// the default for \p writeSparsely is \c false.
MJCPHYSICS_API
UsdAttribute CreateMjcMposAttr(VtValue const &defaultValue = VtValue(),
bool writeSparsely = false) const;
public:
// --------------------------------------------------------------------- //
// MJCMQUAT
// --------------------------------------------------------------------- //
/// Vector of mocap body quaternions, copied into mjData.mocap_quat when the
/// simulation state is set to this keyframe.
///
/// | ||
/// | -- | -- |
/// | Declaration | `double[] mjc:mquat` |
/// | C++ Type | VtArray<double> |
/// | \ref Usd_Datatypes "Usd Type" | SdfValueTypeNames->DoubleArray |
MJCPHYSICS_API
UsdAttribute GetMjcMquatAttr() const;
/// See GetMjcMquatAttr(), and also
/// \ref Usd_Create_Or_Get_Property for when to use Get vs Create.
/// If specified, author \p defaultValue as the attribute's default,
/// sparsely (when it makes sense to do so) if \p writeSparsely is \c true -
/// the default for \p writeSparsely is \c false.
MJCPHYSICS_API
UsdAttribute CreateMjcMquatAttr(VtValue const &defaultValue = VtValue(),
bool writeSparsely = false) const;
public:
// ===================================================================== //
// Feel free to add custom code below this line, it will be preserved by
// the code generator.
//
// Just remember to:
// - Close the class declaration with };
// - Close the namespace with PXR_NAMESPACE_CLOSE_SCOPE
// - Close the include guard with #endif
// ===================================================================== //
// --(BEGIN CUSTOM CODE)--
};
PXR_NAMESPACE_CLOSE_SCOPE
#endif
@@ -17,18 +17,18 @@
/// \file mjcPhysics/meshCollisionAPI.h
#include "./api.h"
#include "./tokens.h"
#include "pxr/base/gf/matrix4d.h"
#include "pxr/base/gf/vec3d.h"
#include "pxr/base/gf/vec3f.h"
#include "pxr/base/tf/token.h"
#include "pxr/base/tf/type.h"
#include "pxr/base/vt/value.h"
#include "pxr/pxr.h"
#include "pxr/usd/usd/apiSchemaBase.h"
#include "pxr/usd/usd/prim.h"
#include "pxr/usd/usd/stage.h"
#include <mujoco/experimental/usd/mjcPhysics/api.h>
#include <mujoco/experimental/usd/mjcPhysics/tokens.h>
#include <pxr/base/gf/matrix4d.h>
#include <pxr/base/gf/vec3d.h>
#include <pxr/base/gf/vec3f.h>
#include <pxr/base/tf/token.h>
#include <pxr/base/tf/type.h>
#include <pxr/base/vt/value.h>
#include <pxr/pxr.h>
#include <pxr/usd/usd/apiSchemaBase.h>
#include <pxr/usd/usd/prim.h>
#include <pxr/usd/usd/stage.h>
PXR_NAMESPACE_OPEN_SCOPE
@@ -17,18 +17,18 @@
/// \file mjcPhysics/sceneAPI.h
#include "./api.h"
#include "./tokens.h"
#include "pxr/base/gf/matrix4d.h"
#include "pxr/base/gf/vec3d.h"
#include "pxr/base/gf/vec3f.h"
#include "pxr/base/tf/token.h"
#include "pxr/base/tf/type.h"
#include "pxr/base/vt/value.h"
#include "pxr/pxr.h"
#include "pxr/usd/usd/apiSchemaBase.h"
#include "pxr/usd/usd/prim.h"
#include "pxr/usd/usd/stage.h"
#include <mujoco/experimental/usd/mjcPhysics/api.h>
#include <mujoco/experimental/usd/mjcPhysics/tokens.h>
#include <pxr/base/gf/matrix4d.h>
#include <pxr/base/gf/vec3d.h>
#include <pxr/base/gf/vec3f.h>
#include <pxr/base/tf/token.h>
#include <pxr/base/tf/type.h>
#include <pxr/base/vt/value.h>
#include <pxr/pxr.h>
#include <pxr/usd/usd/apiSchemaBase.h>
#include <pxr/usd/usd/prim.h>
#include <pxr/usd/usd/stage.h>
PXR_NAMESPACE_OPEN_SCOPE
@@ -17,17 +17,17 @@
/// \file mjcPhysics/siteAPI.h
#include "./api.h"
#include "pxr/base/gf/matrix4d.h"
#include "pxr/base/gf/vec3d.h"
#include "pxr/base/gf/vec3f.h"
#include "pxr/base/tf/token.h"
#include "pxr/base/tf/type.h"
#include "pxr/base/vt/value.h"
#include "pxr/pxr.h"
#include "pxr/usd/usd/apiSchemaBase.h"
#include "pxr/usd/usd/prim.h"
#include "pxr/usd/usd/stage.h"
#include <mujoco/experimental/usd/mjcPhysics/api.h>
#include <pxr/base/gf/matrix4d.h>
#include <pxr/base/gf/vec3d.h>
#include <pxr/base/gf/vec3f.h>
#include <pxr/base/tf/token.h>
#include <pxr/base/tf/type.h>
#include <pxr/base/vt/value.h>
#include <pxr/pxr.h>
#include <pxr/usd/usd/apiSchemaBase.h>
#include <pxr/usd/usd/prim.h>
#include <pxr/usd/usd/stage.h>
PXR_NAMESPACE_OPEN_SCOPE
@@ -26,10 +26,10 @@
#include <vector>
#include "./api.h"
#include "pxr/base/tf/staticData.h"
#include "pxr/base/tf/token.h"
#include "pxr/pxr.h"
#include <mujoco/experimental/usd/mjcPhysics/api.h>
#include <pxr/base/tf/staticData.h>
#include <pxr/base/tf/token.h>
#include <pxr/pxr.h>
PXR_NAMESPACE_OPEN_SCOPE
@@ -63,7 +63,8 @@ struct MjcPhysicsTokensType {
/// Fallback value for MjcPhysicsSceneAPI::GetJacobianAttr(), Fallback value
/// for MjcPhysicsActuatorAPI::GetMjcActLimitedAttr(), Fallback value for
/// MjcPhysicsActuatorAPI::GetMjcCtrlLimitedAttr(), Fallback value for
/// MjcPhysicsActuatorAPI::GetMjcForceLimitedAttr(), This token represents
/// MjcPhysicsActuatorAPI::GetMjcForceLimitedAttr(), Fallback value for
/// MjcPhysicsJointAPI::GetMjcActuatorfrclimitedAttr(), This token represents
/// the auto constraint Jacobian and matrices computed from it.
const TfToken auto_;
/// \brief "cg"
@@ -98,7 +99,8 @@ struct MjcPhysicsTokensType {
///
/// Possible value for MjcPhysicsActuatorAPI::GetMjcActLimitedAttr(), Possible
/// value for MjcPhysicsActuatorAPI::GetMjcCtrlLimitedAttr(), Possible value
/// for MjcPhysicsActuatorAPI::GetMjcForceLimitedAttr()
/// for MjcPhysicsActuatorAPI::GetMjcForceLimitedAttr(), Possible value for
/// MjcPhysicsJointAPI::GetMjcActuatorfrclimitedAttr()
const TfToken false_;
/// \brief "filter"
///
@@ -130,6 +132,10 @@ struct MjcPhysicsTokensType {
///
/// Fallback value for MjcPhysicsMeshCollisionAPI::GetInertiaAttr()
const TfToken legacy;
/// \brief "mjc:act"
///
/// MjcPhysicsKeyframe
const TfToken mjcAct;
/// \brief "mjc:actDim"
///
/// MjcPhysicsActuatorAPI
@@ -150,6 +156,26 @@ struct MjcPhysicsTokensType {
///
/// MjcPhysicsActuatorAPI
const TfToken mjcActRangeMin;
/// \brief "mjc:actuatorfrclimited"
///
/// MjcPhysicsJointAPI
const TfToken mjcActuatorfrclimited;
/// \brief "mjc:actuatorfrcrange:max"
///
/// MjcPhysicsJointAPI
const TfToken mjcActuatorfrcrangeMax;
/// \brief "mjc:actuatorfrcrange:min"
///
/// MjcPhysicsJointAPI
const TfToken mjcActuatorfrcrangeMin;
/// \brief "mjc:actuatorgravcomp"
///
/// MjcPhysicsJointAPI
const TfToken mjcActuatorgravcomp;
/// \brief "mjc:armature"
///
/// MjcPhysicsJointAPI
const TfToken mjcArmature;
/// \brief "mjc:biasPrm"
///
/// MjcPhysicsActuatorAPI
@@ -162,10 +188,10 @@ struct MjcPhysicsTokensType {
///
/// MjcPhysicsActuatorAPI
const TfToken mjcCrankLength;
/// \brief "mjc:crankSite"
/// \brief "mjc:ctrl"
///
/// MjcPhysicsActuatorAPI
const TfToken mjcCrankSite;
/// MjcPhysicsKeyframe
const TfToken mjcCtrl;
/// \brief "mjc:ctrlLimited"
///
/// MjcPhysicsActuatorAPI
@@ -178,6 +204,10 @@ struct MjcPhysicsTokensType {
///
/// MjcPhysicsActuatorAPI
const TfToken mjcCtrlRangeMin;
/// \brief "mjc:damping"
///
/// MjcPhysicsJointAPI
const TfToken mjcDamping;
/// \brief "mjc:dynPrm"
///
/// MjcPhysicsActuatorAPI
@@ -290,6 +320,10 @@ struct MjcPhysicsTokensType {
///
/// MjcPhysicsActuatorAPI
const TfToken mjcForceRangeMin;
/// \brief "mjc:frictionloss"
///
/// MjcPhysicsJointAPI
const TfToken mjcFrictionloss;
/// \brief "mjc:gainPrm"
///
/// MjcPhysicsActuatorAPI
@@ -318,6 +352,18 @@ struct MjcPhysicsTokensType {
///
/// MjcPhysicsActuatorAPI
const TfToken mjcLengthRangeMin;
/// \brief "mjc:margin"
///
/// MjcPhysicsJointAPI
const TfToken mjcMargin;
/// \brief "mjc:mpos"
///
/// MjcPhysicsKeyframe
const TfToken mjcMpos;
/// \brief "mjc:mquat"
///
/// MjcPhysicsKeyframe
const TfToken mjcMquat;
/// \brief "mjc:option:actuatorgroupdisable"
///
/// MjcPhysicsSceneAPI
@@ -422,6 +468,18 @@ struct MjcPhysicsTokensType {
///
/// MjcPhysicsSceneAPI
const TfToken mjcOptionWind;
/// \brief "mjc:qpos"
///
/// MjcPhysicsKeyframe
const TfToken mjcQpos;
/// \brief "mjc:qvel"
///
/// MjcPhysicsKeyframe
const TfToken mjcQvel;
/// \brief "mjc:ref"
///
/// MjcPhysicsJointAPI
const TfToken mjcRef;
/// \brief "mjc:refSite"
///
/// MjcPhysicsActuatorAPI
@@ -434,6 +492,34 @@ struct MjcPhysicsTokensType {
///
/// MjcPhysicsActuatorAPI
const TfToken mjcSliderSite;
/// \brief "mjc:solimpfriction"
///
/// MjcPhysicsJointAPI
const TfToken mjcSolimpfriction;
/// \brief "mjc:solimplimit"
///
/// MjcPhysicsJointAPI
const TfToken mjcSolimplimit;
/// \brief "mjc:solreffriction"
///
/// MjcPhysicsJointAPI
const TfToken mjcSolreffriction;
/// \brief "mjc:solreflimit"
///
/// MjcPhysicsJointAPI
const TfToken mjcSolreflimit;
/// \brief "mjc:springdamper"
///
/// MjcPhysicsJointAPI
const TfToken mjcSpringdamper;
/// \brief "mjc:springref"
///
/// MjcPhysicsJointAPI
const TfToken mjcSpringref;
/// \brief "mjc:stiffness"
///
/// MjcPhysicsJointAPI
const TfToken mjcStiffness;
/// \brief "muscle"
///
/// Possible value for MjcPhysicsActuatorAPI::GetMjcBiasTypeAttr(), Possible
@@ -478,7 +564,8 @@ struct MjcPhysicsTokensType {
///
/// Possible value for MjcPhysicsActuatorAPI::GetMjcActLimitedAttr(), Possible
/// value for MjcPhysicsActuatorAPI::GetMjcCtrlLimitedAttr(), Possible value
/// for MjcPhysicsActuatorAPI::GetMjcForceLimitedAttr()
/// for MjcPhysicsActuatorAPI::GetMjcForceLimitedAttr(), Possible value for
/// MjcPhysicsJointAPI::GetMjcActuatorfrclimitedAttr()
const TfToken true_;
/// \brief "user"
///
@@ -488,23 +575,31 @@ struct MjcPhysicsTokensType {
const TfToken user;
/// \brief "CollisionAPI"
///
/// Schema identifer and family for MjcPhysicsCollisionAPI
/// Schema identifier and family for MjcPhysicsCollisionAPI
const TfToken CollisionAPI;
/// \brief "Keyframe"
///
/// Schema identifier and family for MjcPhysicsKeyframe
const TfToken Keyframe;
/// \brief "MeshCollisionAPI"
///
/// Schema identifer and family for MjcPhysicsMeshCollisionAPI
/// Schema identifier and family for MjcPhysicsMeshCollisionAPI
const TfToken MeshCollisionAPI;
/// \brief "PhysicsActuatorAPI"
///
/// Schema identifer and family for MjcPhysicsActuatorAPI
/// Schema identifier and family for MjcPhysicsActuatorAPI
const TfToken PhysicsActuatorAPI;
/// \brief "PhysicsJointsAPI"
///
/// Schema identifier and family for MjcPhysicsJointAPI
const TfToken PhysicsJointsAPI;
/// \brief "SceneAPI"
///
/// Schema identifer and family for MjcPhysicsSceneAPI
/// Schema identifier and family for MjcPhysicsSceneAPI
const TfToken SceneAPI;
/// \brief "SiteAPI"
///
/// Schema identifer and family for MjcPhysicsSiteAPI
/// Schema identifier and family for MjcPhysicsSiteAPI
const TfToken SiteAPI;
/// A vector of all of the tokens listed above.
const std::vector<TfToken> allTokens;
+28
View File
@@ -0,0 +1,28 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MUJOCO_EXPERIMENTAL_SRC_USD_USD_TO_MJSPEC_H_
#define MUJOCO_EXPERIMENTAL_SRC_USD_USD_TO_MJSPEC_H_
#include <mujoco/mujoco.h>
#include <pxr/usd/usd/common.h>
// Given a USD stage, this function will do a best effort conversion to
// mjSpec.
//
// Particular care is taken for physics data to be lossless but visual
// data such as materials may be lossy.
MJAPI mjSpec* mj_parseUSDStage(pxr::UsdStageRefPtr stage);
#endif // MUJOCO_EXPERIMENTAL_SRC_USD_USD_TO_MJSPEC_H_
+52
View File
@@ -0,0 +1,52 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MUJOCO_SRC_EXPERIMENTAL_USD_WRITER_H_
#define MUJOCO_SRC_EXPERIMENTAL_USD_WRITER_H_
#include <functional>
#include <vector>
#include <mujoco/mujoco.h>
#include <pxr/usd/sdf/path.h>
#include <pxr/usd/usd/common.h>
namespace mujoco {
namespace usd {
using PoseSinkFn =
std::function<void(const mjData* const, std::vector<pxr::SdfPath>)>;
class Writer {
public:
Writer(pxr::UsdStageRefPtr stage, mjSpec* spec, mjModel_* model);
~Writer();
Writer(const Writer&) = delete;
Writer& operator=(const Writer&) = delete;
void Update(const mjData* const data);
void AddSink(PoseSinkFn sink_fn);
private:
void BuildMjUsdMapping();
pxr::UsdStageRefPtr stage_;
std::vector<pxr::SdfPath> body_id_to_path_;
mjSpec* spec_ = nullptr;
mjModel_* model_ = nullptr;
std::vector<PoseSinkFn> pose_sinks_;
};
} // namespace usd
} // namespace mujoco
#endif // MUJOCO_SRC_EXPERIMENTAL_USD_WRITER_H_
+12 -3
View File
@@ -452,8 +452,8 @@ typedef struct mjLROpt_ mjLROpt;
//---------------------------------- mjVFS ---------------------------------------------------------
struct mjVFS_ { // virtual file system for loading from memory
void* impl_; // internal pointer to VFS memory
struct mjVFS_ { // virtual file system for loading from memory
void* impl_; // internal pointer to VFS memory
};
typedef struct mjVFS_ mjVFS;
@@ -508,7 +508,8 @@ typedef struct mjOption_ mjOption;
struct mjVisual_ { // visualization options
struct { // global parameters
int orthographic; // is the free camera orthographic (0: no, 1: yes)
int cameraid; // initial camera id (-1: free)
int orthographic; // is the free camera orthographic (0: no, 1: yes)
float fovy; // y field-of-view of free camera (orthographic ? length : degree)
float ipd; // inter-pupilary distance for free camera
float azimuth; // initial azimuth of free camera (degrees)
@@ -630,6 +631,7 @@ struct mjModel_ {
int nbvh; // number of total bounding volumes in all bodies
int nbvhstatic; // number of static bounding volumes (aabb stored in mjModel)
int nbvhdynamic; // number of dynamic bounding volumes (aabb stored in mjData)
int noct; // number of total octree cells in all meshes
int njnt; // number of joints
int ngeom; // number of geoms
int nsite; // number of sites
@@ -763,6 +765,11 @@ struct mjModel_ {
int* bvh_nodeid; // geom or elem id of node; -1: non-leaf (nbvh x 1)
mjtNum* bvh_aabb; // local bounding box (center, size) (nbvhstatic x 6)
// octree spatial partitioning
int* oct_depth; // depth in the octree (noct x 1)
int* oct_child; // children of octree node (noct x 8)
mjtNum* oct_aabb; // octree node bounding box (center, size) (noct x 6)
// joints
int* jnt_type; // type of joint (mjtJoint) (njnt x 1)
int* jnt_qposadr; // start addr in 'qpos' for joint's data (njnt x 1)
@@ -950,6 +957,8 @@ struct mjModel_ {
int* mesh_facenum; // number of faces (nmesh x 1)
int* mesh_bvhadr; // address of bvh root (nmesh x 1)
int* mesh_bvhnum; // number of bvh (nmesh x 1)
int* mesh_octadr; // address of octree root (nmesh x 1)
int* mesh_octnum; // number of octree nodes (nmesh x 1)
int* mesh_normaladr; // first normal address (nmesh x 1)
int* mesh_normalnum; // number of normals (nmesh x 1)
int* mesh_texcoordadr; // texcoord data address; -1: no texcoord (nmesh x 1)
+1
View File
@@ -292,6 +292,7 @@ struct mjvOption_ { // abstract visualization options
mjtByte skingroup[mjNGROUP]; // skin visualization by group
mjtByte flags[mjNVISFLAG]; // visualization flags (indexed by mjtVisFlag)
int bvh_depth; // depth of the bounding volume hierarchy to be visualized
int oct_depth; // depth of the octree to be visualized
int flex_layer; // element layer to be visualized for 3D flex
};
typedef struct mjvOption_ mjvOption;
+6
View File
@@ -75,6 +75,7 @@
X( nbvh ) \
X( nbvhstatic ) \
X( nbvhdynamic ) \
X( noct ) \
X( njnt ) \
X( ngeom ) \
X( nsite ) \
@@ -216,6 +217,9 @@
X ( int, bvh_child, nbvh, 2 ) \
X ( int, bvh_nodeid, nbvh, 1 ) \
X ( mjtNum, bvh_aabb, nbvhstatic, 6 ) \
X ( int, oct_depth, noct, 1 ) \
X ( int, oct_child, noct, 8 ) \
X ( mjtNum, oct_aabb, noct, 6 ) \
X ( int, jnt_type, njnt, 1 ) \
X ( int, jnt_qposadr, njnt, 1 ) \
X ( int, jnt_dofadr, njnt, 1 ) \
@@ -390,6 +394,8 @@
X ( int, mesh_facenum, nmesh, 1 ) \
X ( int, mesh_bvhadr, nmesh, 1 ) \
X ( int, mesh_bvhnum, nmesh, 1 ) \
X ( int, mesh_octadr, nmesh, 1 ) \
X ( int, mesh_octnum, nmesh, 1 ) \
X ( int, mesh_graphadr, nmesh, 1 ) \
X ( mjtNum, mesh_scale, nmesh, 3 ) \
X ( mjtNum, mesh_pos, nmesh, 3 ) \
+35 -8
View File
@@ -16,7 +16,7 @@
#define MUJOCO_MUJOCO_H_
// header version; should match the library version as returned by mj_version()
#define mjVERSION_HEADER 333
#define mjVERSION_HEADER 334
// needed to define size_t, fabs and log10
#include <stdlib.h>
@@ -1411,11 +1411,6 @@ MJAPI void mju_taskJoin(mjTask* task);
MJAPI mjsElement* mjs_attach(mjsElement* parent, const mjsElement* child,
const char* prefix, const char* suffix);
// Delete body and descendants from mjSpec, remove all references, return 0 on success.
MJAPI int mjs_detachBody(mjSpec* s, mjsBody* b);
// Delete default class and descendants from mjSpec, remove all references, return 0 on success.
MJAPI int mjs_detachDefault(mjSpec* s, mjsDefault* d);
//---------------------------------- Tree elements -------------------------------------------------
@@ -1443,8 +1438,8 @@ MJAPI mjsLight* mjs_addLight(mjsBody* body, const mjsDefault* def);
// Add frame to body.
MJAPI mjsFrame* mjs_addFrame(mjsBody* body, mjsFrame* parentframe);
// Delete object corresponding to the given element, return 0 on success.
MJAPI int mjs_delete(mjsElement* element);
// Remove object corresponding to the given element, return 0 on success.
MJAPI int mjs_delete(mjSpec* spec, mjsElement* element);
//---------------------------------- Non-tree elements ---------------------------------------------
@@ -1501,6 +1496,38 @@ MJAPI mjsPlugin* mjs_addPlugin(mjSpec* s);
MJAPI mjsDefault* mjs_addDefault(mjSpec* s, const char* classname, const mjsDefault* parent);
//---------------------------------- Set actuator parameters ---------------------------------------
// Set actuator to motor, return error if any.
MJAPI const char* mjs_setToMotor(mjsActuator* actuator);
// Set actuator to position, return error if any.
MJAPI const char* mjs_setToPosition(mjsActuator* actuator, double kp, double kv[1],
double dampratio[1], double timeconst[1], double inheritrange);
// Set actuator to integrated velocity, return error if any.
MJAPI const char* mjs_setToIntVelocity(mjsActuator* actuator, double kp, double kv[1],
double dampratio[1], double timeconst[1], double inheritrange);
// Set actuator to velocity servo, return error if any.
MJAPI const char* mjs_setToVelocity(mjsActuator* actuator, double kv);
// Set actuator to activate damper, return error if any.
MJAPI const char* mjs_setToDamper(mjsActuator* actuator, double kv);
// Set actuator to hydraulic or pneumatic cylinder, return error if any.
MJAPI const char* mjs_setToCylinder(mjsActuator* actuator, double timeconst,
double bias, double area, double diameter);
// Set actuator to muscle, return error if any.a
MJAPI const char* mjs_setToMuscle(mjsActuator* actuator, double timeconst[2], double tausmooth,
double range[2], double force, double scale, double lmin,
double lmax, double vmax, double fpmax, double fvmax);
// Set actuator to active adhesion, return error if any.
MJAPI const char* mjs_setToAdhesion(mjsActuator* actuator, double gain);
//---------------------------------- Assets --------------------------------------------------------
// Add mesh.
+2
View File
@@ -48,6 +48,8 @@ from mujoco.mjx._src.smooth import rne
from mujoco.mjx._src.smooth import rne_postconstraint
from mujoco.mjx._src.smooth import subtree_vel
from mujoco.mjx._src.smooth import tendon
from mujoco.mjx._src.smooth import tendon_armature
from mujoco.mjx._src.smooth import tendon_bias
from mujoco.mjx._src.smooth import transmission
from mujoco.mjx._src.solver import solve
from mujoco.mjx._src.support import apply_ft
+2
View File
@@ -73,6 +73,7 @@ def fwd_position(m: Model, d: Data) -> Data:
d = smooth.camlight(m, d)
d = smooth.tendon(m, d)
d = smooth.crb(m, d)
d = smooth.tendon_armature(m, d)
d = smooth.factor_m(m, d)
d = collision_driver.collision(m, d)
d = constraint.make_constraint(m, d)
@@ -93,6 +94,7 @@ def fwd_velocity(m: Model, d: Data) -> Data:
d = smooth.com_vel(m, d)
d = passive.passive(m, d)
d = smooth.rne(m, d)
d = smooth.tendon_bias(m, d)
return d
+6 -2
View File
@@ -93,11 +93,15 @@ def inverse(m: Model, d: Data) -> Data:
d = discrete_acc(m, d)
d = inv_constraint(m, d)
d = smooth.rne(m, d, flg_acc=True)
d = smooth.rne(m, d)
d = smooth.tendon_bias(m, d)
d = sensor.sensor_acc(m, d)
qfrc_inverse = (
d.qfrc_bias + m.dof_armature * d.qacc - d.qfrc_passive - d.qfrc_constraint
d.qfrc_bias
+ support.mul_m(m, d, d.qacc)
- d.qfrc_passive
- d.qfrc_constraint
)
if m.opt.enableflags & EnableBit.INVDISCRETE:
+36
View File
@@ -19,6 +19,7 @@ from jax import numpy as jp
import mujoco
from mujoco import mjx
from mujoco.mjx._src import support
from mujoco.mjx._src import test_util
import numpy as np
# tolerance for difference between MuJoCo and MJX calculations - mostly
@@ -109,6 +110,41 @@ class InverseTest(parameterized.TestCase):
self.assertLess(fwdinv1, 1.0e-3)
_assert_eq(dxinv.qacc, dx.qacc, 'qacc')
def test_inverse_tendon_armature(self):
m = test_util.load_test_file('tendon/armature.xml')
d = mujoco.MjData(m)
d.qvel = np.random.uniform(low=-0.01, high=0.01, size=d.qvel.shape)
d.ctrl = np.random.uniform(low=-0.01, high=0.01, size=d.ctrl.shape)
d.qfrc_applied = np.random.uniform(
low=-0.01, high=0.01, size=d.qfrc_applied.shape
)
d.xfrc_applied = np.random.uniform(
low=-0.01, high=0.01, size=d.xfrc_applied.shape
)
mujoco.mj_step(m, d, 10)
mx = mjx.put_model(m)
dx = mjx.put_data(m, d)
dx = mjx.forward(mx, dx)
dxinv = mjx.inverse(mx, dx)
fwdinv0 = jp.linalg.norm(
dxinv.qfrc_constraint - dx.qfrc_constraint, ord=np.inf
)
fwdinv1 = jp.linalg.norm(
dxinv.qfrc_inverse
- (
dx.qfrc_applied + dx.qfrc_actuator + support.xfrc_accumulate(mx, dx)
),
ord=np.inf,
)
self.assertLess(fwdinv0, 1.0e-3)
self.assertLess(fwdinv1, 1.0e-3)
_assert_eq(dxinv.qacc, dx.qacc, 'qacc')
if __name__ == '__main__':
absltest.main()
+108 -118
View File
@@ -41,51 +41,51 @@ def _is_cuda_gpu_device(device: jax.Device) -> bool:
return device in cuda_devices
def _resolve_backend_impl(
def _resolve_impl(
device: jax.Device,
) -> types.BackendImpl:
"""Pick a default backend impl based on the device specified."""
) -> types.Impl:
"""Pick a default implementation based on the device specified."""
if _is_cuda_gpu_device(device):
# TODO(btaba): Remove flag once Warp is ready to launch.
mjx_warp_enabled = os.environ.get('MJX_WARP_ENABLED', 'f').lower() == 'true'
if mjx_warp_enabled:
logging.debug('Picking default backend implementation: Warp.')
return types.BackendImpl.WARP
logging.debug('Picking default implementation: Warp.')
return types.Impl.WARP
logging.info('MJX Warp is disabled via MJX_WARP_ENABLED=false.')
if device.platform in ('gpu', 'tpu'):
logging.debug('Picking default backend implementation: JAX.')
return types.BackendImpl.JAX
logging.debug('Picking default implementation: JAX.')
return types.Impl.JAX
if device.platform == 'cpu':
mjx_c_default = (
os.environ.get('MJX_C_DEFAULT_ENABLED', 'f').lower() == 'true'
)
if mjx_c_default:
logging.debug('Picking default backend implementation: C.')
return types.BackendImpl.C
return types.BackendImpl.JAX
logging.debug('Picking default implementation: C.')
return types.Impl.C
return types.Impl.JAX
raise ValueError(f'Unsupported device: {device}')
def _resolve_device(
backend_impl: types.BackendImpl,
impl: types.Impl,
) -> jax.Device:
"""Resolves a device based on the backend implementation."""
backend_impl = types.BackendImpl(backend_impl)
if backend_impl == types.BackendImpl.JAX:
"""Resolves a device based on the implementation."""
impl = types.Impl(impl)
if impl == types.Impl.JAX:
device_0 = jax.devices()[0]
logging.debug('Picking default device: %s.', device_0)
return device_0
if backend_impl == types.BackendImpl.C:
if impl == types.Impl.C:
cpu_0 = jax.devices('cpu')[0]
logging.debug('Picking default device: %s', cpu_0)
return cpu_0
if backend_impl == types.BackendImpl.WARP:
# WARP backend requires a CUDA GPU.
if impl == types.Impl.WARP:
# WARP implementation requires a CUDA GPU.
cuda_gpus = [d for d in jax.devices('cuda')]
if not cuda_gpus:
raise AssertionError(
@@ -96,64 +96,64 @@ def _resolve_device(
logging.debug('Picking default device: %s', cuda_gpus[0])
return cuda_gpus[0]
raise ValueError(f'Unsupported backend implementation: {backend_impl}')
raise ValueError(f'Unsupported implementation: {impl}')
def _check_backend_impl_device_compatibility(
backend_impl: Union[str, types.BackendImpl],
def _check_impl_device_compatibility(
impl: Union[str, types.Impl],
device: jax.Device,
) -> None:
"""Checks that the backend implementation is compatible with the device."""
if backend_impl is None:
raise ValueError('No backend implementation specified.')
"""Checks that the implementation is compatible with the device."""
if impl is None:
raise ValueError('No implementation specified.')
backend_impl = types.BackendImpl(backend_impl)
impl = types.Impl(impl)
if backend_impl == types.BackendImpl.WARP:
if impl == types.Impl.WARP:
if not _is_cuda_gpu_device(device):
raise AssertionError(
'Warp backend implementation requires a CUDA GPU device, got '
'Warp implementation requires a CUDA GPU device, got '
f'{device}.'
)
mjx_warp_enabled = os.environ.get('MJX_WARP_ENABLED', 'f').lower() == 'true'
if not mjx_warp_enabled:
raise AssertionError(
'Warp backend implementation is disabled via MJX_WARP_ENABLED=false.'
'Warp implementation is disabled via MJX_WARP_ENABLED=false.'
)
is_cpu_device = device.platform == 'cpu'
if backend_impl == types.BackendImpl.C:
if impl == types.Impl.C:
if not is_cpu_device:
raise AssertionError(
f'C backend implementation requires a CPU device, got {device}.'
f'C implementation requires a CPU device, got {device}.'
)
# NB: JAX backend works with any device.
# NB: JAX implementation works with any device.
def _resolve_backend_impl_and_device(
backend_impl: Optional[Union[str, types.BackendImpl]],
def _resolve_impl_and_device(
impl: Optional[Union[str, types.Impl]],
device: Optional[jax.Device] = None,
) -> Tuple[types.BackendImpl, jax.Device]:
"""Resolves a backend implementation and device."""
if backend_impl:
backend_impl = types.BackendImpl(backend_impl)
) -> Tuple[types.Impl, jax.Device]:
"""Resolves a implementation and device."""
if impl:
impl = types.Impl(impl)
has_backend_impl, has_device = backend_impl is not None, device is not None
if (has_backend_impl, has_device) == (True, True):
has_impl, has_device = impl is not None, device is not None
if (has_impl, has_device) == (True, True):
pass
elif (has_backend_impl, has_device) == (True, False):
device = _resolve_device(backend_impl)
elif (has_backend_impl, has_device) == (False, True):
backend_impl = _resolve_backend_impl(device)
elif (has_impl, has_device) == (True, False):
device = _resolve_device(impl)
elif (has_impl, has_device) == (False, True):
impl = _resolve_impl(device)
else:
device = jax.devices(jax.default_backend())[0]
logging.info('Using JAX default device: %s.', device)
backend_impl = _resolve_backend_impl(device)
impl = _resolve_impl(device)
_check_backend_impl_device_compatibility(backend_impl, device)
return backend_impl, device # pytype: disable=bad-return-type
_check_impl_device_compatibility(impl, device)
return impl, device # pytype: disable=bad-return-type
def _strip_weak_type(tree):
@@ -167,7 +167,7 @@ def _strip_weak_type(tree):
def _put_option(
o: mujoco.MjOption,
backend_impl: types.BackendImpl,
impl: types.Impl,
impl_fields: Optional[dict[str, Any]] = None,
) -> types.Option:
"""Returns mjx.Option given mujoco.MjOption."""
@@ -195,7 +195,7 @@ def _put_option(
fields['disableflags'] = types.DisableBit(o.disableflags)
fields['enableflags'] = types.EnableBit(o.enableflags)
if backend_impl == types.BackendImpl.JAX:
if impl == types.Impl.JAX:
has_fluid_params = o.density > 0 or o.viscosity > 0 or o.wind.any()
implicitfast = o.integrator == mujoco.mjtIntegrator.mjINT_IMPLICITFAST
if implicitfast and has_fluid_params:
@@ -203,12 +203,12 @@ def _put_option(
fields['has_fluid_params'] = has_fluid_params
return types.OptionJAX(**fields, **(impl_fields or {}))
if backend_impl == types.BackendImpl.C:
if impl == types.Impl.C:
c_field_keys = types.OptionC.__annotations__.keys() - fields.keys()
c_fields = {k: getattr(o, k, None) for k in c_field_keys}
return types.OptionC(**fields, **c_fields, **(impl_fields or {}))
raise NotImplementedError(f'Unsupported backend: {backend_impl}')
raise NotImplementedError(f'Unsupported implementation: {impl}')
def _put_statistic(s: mujoco.MjStatistic) -> types.Statistic:
@@ -248,23 +248,6 @@ def _put_model_jax(
if t == mujoco.mjtGeom.mjGEOM_MESH:
mesh_geomid.add(g)
# check for unsupported sensor and equality constraint combinations
sensor_rne_postconstraint = (
np.any(m.sensor_type == types.SensorType.ACCELEROMETER)
| np.any(m.sensor_type == types.SensorType.FORCE)
| np.any(m.sensor_type == types.SensorType.TORQUE)
| np.any(m.sensor_type == types.SensorType.FRAMELINACC)
| np.any(m.sensor_type == types.SensorType.FRAMEANGACC)
)
eq_connect_weld = np.any(m.eq_type == types.EqType.CONNECT) | np.any(
m.eq_type == types.EqType.WELD
)
if sensor_rne_postconstraint and eq_connect_weld:
raise NotImplementedError(
'rne_postconstraint not implemented with equality constraints:'
' connect, weld.'
)
for enum_field, enum_type, mj_type in (
(m.actuator_biastype, types.BiasType, mujoco.mjtBias),
(m.actuator_dyntype, types.DynType, mujoco.mjtDyn),
@@ -283,7 +266,7 @@ def _put_model_jax(
mj_field_names = {f.name for f in types.Model.fields() if f.name != '_impl'}
fields = {f: getattr(m, f) for f in mj_field_names}
fields['cam_mat0'] = fields['cam_mat0'].reshape((-1, 3, 3))
fields['opt'] = _put_option(m.opt, types.BackendImpl.JAX)
fields['opt'] = _put_option(m.opt, types.Impl.JAX)
fields['stat'] = _put_statistic(m.stat)
fields_jax = {}
@@ -340,7 +323,7 @@ def _put_model_c(
mj_field_names = {f.name for f in types.Model.fields() if f.name != '_impl'}
fields = {f: getattr(m, f) for f in mj_field_names}
fields['cam_mat0'] = fields['cam_mat0'].reshape((-1, 3, 3))
fields['opt'] = _put_option(m.opt, backend_impl=types.BackendImpl.C)
fields['opt'] = _put_option(m.opt, impl=types.Impl.C)
fields['stat'] = _put_statistic(m.stat)
c_impl_keys = (
@@ -359,7 +342,7 @@ def _put_model_c(
def put_model(
m: mujoco.MjModel,
device: Optional[jax.Device] = None,
backend_impl: Optional[Union[str, types.BackendImpl]] = None,
impl: Optional[Union[str, types.Impl]] = None,
_full_compat: bool = False, # pylint: disable=invalid-name
) -> types.Model:
"""Puts mujoco.MjModel onto a device, resulting in mjx.Model.
@@ -367,7 +350,7 @@ def put_model(
Args:
m: the model to put onto device
device: which device to use - if unspecified picks the default device
backend_impl: backend implementation to use
impl: implementation to use
_full_compat: put all MjModel fields onto device irrespective of MJX support
This is an experimental feature. Avoid using it for now.
@@ -375,28 +358,29 @@ def put_model(
an mjx.Model placed on device
Raises:
ValueError: if backend_impl is not supported
ValueError: if impl is not supported
DeprecationWarning: if _full_compat is True
"""
if _full_compat:
warnings.warn(
'mjx.put_model(..., _full_compat=True) is deprecated. Use'
' mjx.put_model(..., backend_impl=types.BackendImpl.C) instead.',
'mjx.put_model(..., _full_compat=True) is deprecated and will be'
' removed in MuJoCo >=3.4. Use mjx.put_model(..., impl=types.Impl.C)'
' instead.',
DeprecationWarning,
stacklevel=2,
)
backend_impl = types.BackendImpl.C
impl = types.Impl.C
backend_impl, device = _resolve_backend_impl_and_device(backend_impl, device)
if backend_impl == types.BackendImpl.JAX:
impl, device = _resolve_impl_and_device(impl, device)
if impl == types.Impl.JAX:
return _put_model_jax(m, device)
elif backend_impl == types.BackendImpl.C:
elif impl == types.Impl.C:
return _put_model_c(m, device)
elif backend_impl == types.BackendImpl.WARP:
raise NotImplementedError('Warp backend not implemented yet.')
elif impl == types.Impl.WARP:
raise NotImplementedError('Warp implementation not implemented yet.')
else:
raise ValueError(f'Unsupported backend implementation: {backend_impl}')
raise ValueError(f'Unsupported implementation: {impl}')
def _make_data_public_fields(m: types.Model) -> Dict[str, Any]:
@@ -476,7 +460,7 @@ def _make_data_contact_jax(
def _make_data_jax(
m: types.Model,
m: Union[types.Model, mujoco.MjModel],
device: Optional[jax.Device] = None,
) -> types.Data:
"""Allocate and initialize Data for the JAX implementation."""
@@ -564,7 +548,7 @@ def _make_data_jax(
def _make_data_c(
m: types.Model,
m: Union[types.Model, mujoco.MjModel],
device: Optional[jax.Device] = None,
) -> types.Data:
"""Allocate and initialize Data for the C implementation."""
@@ -582,11 +566,14 @@ def _make_data_c(
# TODO(stunya): remove the JAX contact from C data.
contact = _make_data_contact_jax(dim, efc_address)
nflexvert = m.nflexvert
nflexedge = m.nflexedge
nflexelem = m.nflexelem
nbvh = m.nbvh
nbvhdynamic = m.nbvhdynamic
def get(m, name: str):
return getattr(m._impl, name) if hasattr(m, '_impl') else getattr(m, name) # pylint: disable=protected-access
nflexvert = get(m, 'nflexvert')
nflexedge = get(m, 'nflexedge')
nflexelem = get(m, 'nflexelem')
nbvh = get(m, 'nbvh')
nbvhdynamic = get(m, 'nbvhdynamic')
zero_impl_fields = {
'solver_niter': (int_,),
'cdof': (m.nv, 6, float_),
@@ -626,7 +613,7 @@ def _make_data_c(
'qLDiagInv': (m.nv, float_),
'ten_velocity': (m.ntendon, float_),
'actuator_velocity': (m.nu, float_),
'plugin_data': (m.nplugin, np.uint64),
'plugin_data': (get(m, 'nplugin'), np.uint64),
'B_rownnz': (m.nbody, np.int32),
'B_rowadr': (m.nbody, np.int32),
'B_colind': (m.nB, np.int32),
@@ -696,7 +683,7 @@ def _make_data_c(
def make_data(
m: Union[types.Model, mujoco.MjModel],
device: Optional[jax.Device] = None,
backend_impl: Optional[Union[str, types.BackendImpl]] = None,
impl: Optional[Union[str, types.Impl]] = None,
_full_compat: bool = False, # pylint: disable=invalid-name
) -> types.Data:
"""Allocate and initialize Data.
@@ -704,7 +691,7 @@ def make_data(
Args:
m: the model to use
device: which device to use - if unspecified picks the default device
backend_impl: backend implementation to use
impl: implementation to use ('jax', 'warp')
_full_compat: put all fields onto device irrespective of MJX support This is
an experimental feature. Avoid using it for now. If using this flag, also
use _full_compat for put_model.
@@ -713,35 +700,34 @@ def make_data(
an initialized mjx.Data placed on device
Raises:
ValueError: if the model's backend_impl does not match the make_data
backend_impl
NotImplementedError: if the backend_impl is not implemented yet
ValueError: if the model's impl does not match the make_data impl
NotImplementedError: if the impl is not implemented yet
DeprecationWarning: if _full_compat is used
"""
if _full_compat:
warnings.warn(
'mjx.make_data(..., _full_compat=True) is deprecated. Use'
' mjx.make_data(..., backend_impl=types.BackendImpl.C) instead.',
' mjx.make_data(..., impl=types.Impl.C) instead.',
DeprecationWarning,
stacklevel=2,
)
backend_impl = types.BackendImpl.C
impl = types.Impl.C
backend_impl, device = _resolve_backend_impl_and_device(backend_impl, device)
impl, device = _resolve_impl_and_device(impl, device)
if isinstance(m, types.Model) and m.backend_impl != backend_impl:
if isinstance(m, types.Model) and m.impl != impl:
raise ValueError(
f'Model backend_impl {m.backend_impl} does not match make_data '
f'backend_impl {backend_impl}.'
f'Model impl {m.impl} does not match make_data '
f'implementation {impl}.'
)
if backend_impl == types.BackendImpl.JAX:
if impl == types.Impl.JAX:
return _make_data_jax(m, device)
elif backend_impl == types.BackendImpl.C:
elif impl == types.Impl.C:
return _make_data_c(m, device)
raise NotImplementedError(
f'make_data for backend_impl "{backend_impl}" not implemented yet.'
f'make_data for implementation "{impl}" not implemented yet.'
)
@@ -951,7 +937,7 @@ def _put_data_c(
if hasattr(d, f.name)
}
# TODO(stunya): support islanding via C backend impl.
# TODO(stunya): support islanding via C impl.
impl_fields['solver_niter'] = impl_fields['solver_niter'][0]
# TODO(btaba): remove dense actuator moment.
@@ -1039,7 +1025,7 @@ def put_data(
m: mujoco.MjModel,
d: mujoco.MjData,
device: Optional[jax.Device] = None,
backend_impl: Optional[Union[str, types.BackendImpl]] = None,
impl: Optional[Union[str, types.Impl]] = None,
_full_compat: bool = False, # pylint: disable=invalid-name
) -> types.Data:
"""Puts mujoco.MjData onto a device, resulting in mjx.Data.
@@ -1048,7 +1034,7 @@ def put_data(
m: the model to use
d: the data to put on device
device: which device to use - if unspecified picks the default device
backend_impl: backend implementation to use
impl: implementation to use ('jax', 'warp')
_full_compat: put all MjModel fields onto device irrespective of MJX support
This is an experimental feature. Avoid using it for now. If using this
flag, also use _full_compat for put_model.
@@ -1059,20 +1045,20 @@ def put_data(
if _full_compat:
warnings.warn(
'mjx.put_data(..., _full_compat=True) is deprecated. Use'
' mjx.put_data(..., backend_impl=types.BackendImpl.C) instead.',
' mjx.put_data(..., impl=types.Impl.C) instead.',
DeprecationWarning,
stacklevel=2,
)
backend_impl = types.BackendImpl.C
impl = types.Impl.C
backend_impl, device = _resolve_backend_impl_and_device(backend_impl, device)
if backend_impl == types.BackendImpl.JAX:
impl, device = _resolve_impl_and_device(impl, device)
if impl == types.Impl.JAX:
return _put_data_jax(m, d, device)
elif backend_impl == types.BackendImpl.C:
elif impl == types.Impl.C:
return _put_data_c(m, d, device)
raise NotImplementedError(
f'put_data for backend_impl "{backend_impl}" not implemented yet.'
f'put_data for implementation "{impl}" not implemented yet.'
)
@@ -1097,7 +1083,7 @@ def _get_data_into(
batch_size = d.qpos.shape[0] if batched else 1
dof_i, dof_j = [], []
if d.backend_impl == types.BackendImpl.JAX:
if d.impl == types.Impl.JAX:
for i in range(m.nv):
j = i
while j > -1:
@@ -1116,13 +1102,13 @@ def _get_data_into(
if ncon != result_i.ncon or nefc != result_i.nefc or nj != result_i.nJ:
mujoco._functions._realloc_con_efc(result_i, ncon=ncon, nefc=nefc, nJ=nj) # pylint: disable=protected-access
if d.backend_impl == types.BackendImpl.JAX:
if d.impl == types.Impl.JAX:
all_fields = types.Data.fields() + types.DataJAX.fields()
elif d.backend_impl == types.BackendImpl.C:
elif d.impl == types.Impl.C:
all_fields = types.Data.fields() + types.DataC.fields()
else:
raise NotImplementedError(
f'get_data_into for backend_impl "{d.backend_impl}" not implemented'
f'get_data_into for implementation "{d.impl}" not implemented'
' yet.'
)
@@ -1188,7 +1174,7 @@ def _get_data_into(
value = value.reshape(-1)
elif field.name.startswith('efc_'):
value = value[efc_active]
if d.backend_impl == types.BackendImpl.JAX:
if d.impl == types.Impl.JAX:
if field.name == 'qM' and not support.is_sparse(m):
value = value[dof_i, dof_j]
elif field.name == 'qLD' and not support.is_sparse(m):
@@ -1207,6 +1193,10 @@ def _get_data_into(
else:
setattr(result_i, field.name, value)
# TODO(taylorhowell): remove mapping once qM is deprecated
# map inertia (sparse) to reduced inertia (compressed sparse) representation
result_i.M[:] = result_i.qM[result_i.mapM2M]
# recalculate qLD and qLDiagInv as MJX and MuJoCo have different
# representations of the Cholesky decomposition.
mujoco.mj_factorM(m, result_i)
@@ -1226,12 +1216,12 @@ def get_data_into(
d = jax.device_get(d)
if d.backend_impl in (types.BackendImpl.JAX, types.BackendImpl.C):
if d.impl in (types.Impl.JAX, types.Impl.C):
# TODO(stunya): Split out _get_data_into once codepaths diverge enough.
return _get_data_into(result, m, d)
raise NotImplementedError(
f'get_data_into for backend_impl "{d.backend_impl}" not implemented yet.'
f'get_data_into for implementation "{d.impl}" not implemented yet.'
)
+132 -144
View File
@@ -26,8 +26,9 @@ from mujoco.mjx._src import io as mjx_io
from mujoco.mjx._src import test_util
# pylint: disable=g-importing-member
from mujoco.mjx._src.types import BackendImpl
from mujoco.mjx._src.types import ConeType
from mujoco.mjx._src.types import Impl
from mujoco.mjx._src.types import JacobianType
# pylint: enable=g-importing-member
import numpy as np
@@ -114,11 +115,11 @@ class ModelIOTest(parameterized.TestCase):
@parameterized.product(
xml=(_MULTIPLE_CONVEX_OBJECTS, _MULTIPLE_CONSTRAINTS),
backend_impl=('jax', 'c'),
impl=('jax', 'c'),
)
def test_put_model(self, xml, backend_impl):
def test_put_model(self, xml, impl):
m = mujoco.MjModel.from_xml_string(xml)
mx = mjx.put_model(m, backend_impl=backend_impl)
mx = mjx.put_model(m, impl=impl)
def assert_not_weak_type(x):
if isinstance(x, jax.Array):
@@ -140,10 +141,10 @@ class ModelIOTest(parameterized.TestCase):
self.assertEqual(mx.nM, m.nM)
self.assertAlmostEqual(mx.opt.timestep, m.opt.timestep)
if backend_impl == 'jax':
if impl == 'jax':
# fields restricted to MuJoCo should not be populated
self.assertFalse(hasattr(mx, 'bvh_aabb'))
elif backend_impl == 'c':
elif impl == 'c':
# Options specific to C are populated.
self.assertEqual(mx.opt.apirate, m.opt.apirate)
# Fields private to C backend impl are populated.
@@ -177,7 +178,7 @@ class ModelIOTest(parameterized.TestCase):
mujoco.MjModel.from_xml_string(
'<mujoco><option viscosity="3.0"/><worldbody/></mujoco>'
),
backend_impl='jax',
impl='jax',
)
self.assertTrue(m.opt.has_fluid_params)
@@ -218,7 +219,7 @@ class ModelIOTest(parameterized.TestCase):
</body>
</worldbody>
</mujoco>"""),
backend_impl='jax',
impl='jax',
)
def test_implicitfast_fluid_not_implemented(self):
@@ -229,18 +230,18 @@ class ModelIOTest(parameterized.TestCase):
<option viscosity="3.0" integrator="implicitfast"/>
<worldbody/>
</mujoco>"""),
backend_impl='jax',
impl='jax',
)
def test_wrap_inside(self):
m = test_util.load_test_file('tendon/wrap_sidesite.xml')
mx0 = mjx.put_model(m, backend_impl='jax')
mx0 = mjx.put_model(m, impl='jax')
np.testing.assert_equal(
mx0._impl.is_wrap_inside,
np.array([1, 0, 1, 0, 1, 1, 0]),
)
m.site_pos[2] = m.site_pos[1]
mx1 = mjx.put_model(m, backend_impl='jax')
mx1 = mjx.put_model(m, impl='jax')
np.testing.assert_equal(
mx1._impl.is_wrap_inside,
np.array([0, 0, 1, 0, 1, 0, 0]),
@@ -251,10 +252,10 @@ class DataIOTest(parameterized.TestCase):
"""IO tests for mjx.Data."""
@parameterized.parameters('jax', 'c')
def test_make_data(self, backend_impl: str):
def test_make_data(self, impl: str):
"""Test that make_data returns the correct shapes."""
m = mujoco.MjModel.from_xml_string(_MULTIPLE_CONVEX_OBJECTS)
d = mjx.make_data(m, backend_impl=backend_impl)
d = mjx.make_data(m, impl=impl)
nq = 22
nbody = 5
@@ -312,34 +313,34 @@ class DataIOTest(parameterized.TestCase):
self.assertEqual(d.qfrc_inverse.shape, (nv,))
self.assertEqual(d._impl.efc_force.shape, (nefc,))
if backend_impl == 'jax':
if impl == 'jax':
self.assertEqual(d._impl.qM.shape, (nv, nv))
self.assertEqual(d._impl.qLD.shape, (nv, nv))
self.assertEqual(d._impl.qLDiagInv.shape, (0,))
elif backend_impl == 'c':
elif impl == 'c':
self.assertEqual(d._impl.qM.shape, (nm,))
self.assertEqual(d._impl.qLD.shape, (nm,))
self.assertEqual(d._impl.qLDiagInv.shape, (nv,))
# test sparse
m.opt.jacobian = mujoco.mjtJacobian.mjJAC_SPARSE
d = mjx.make_data(m, backend_impl=backend_impl)
d = mjx.make_data(m, impl=impl)
self.assertEqual(d._impl.qM.shape, (nm,))
self.assertEqual(d._impl.qLD.shape, (nm,))
self.assertEqual(d._impl.qLDiagInv.shape, (nv,))
if backend_impl == 'c':
if impl == 'c':
# check C specific fields
self.assertEqual(d._impl.light_xpos.shape, (m.nlight, 3))
self.assertEqual(d._impl.bvh_active.shape, (m.nbvh,))
@parameterized.parameters('jax', 'c')
def test_put_data(self, backend_impl: str):
def test_put_data(self, impl: str):
"""Test that put_data puts the correct data for dense and sparse."""
m = mujoco.MjModel.from_xml_string(_MULTIPLE_CONSTRAINTS)
d = mujoco.MjData(m)
mujoco.mj_step(m, d, 2)
dx = mjx.put_data(m, d, backend_impl=backend_impl)
dx = mjx.put_data(m, d, impl=impl)
# check a few fields
np.testing.assert_allclose(dx.qpos, d.qpos)
@@ -356,12 +357,12 @@ class DataIOTest(parameterized.TestCase):
)
)
if backend_impl == 'jax':
if impl == 'jax':
# check that qM is transformed properly
qm = np.zeros((m.nv, m.nv), dtype=np.float64)
mujoco.mj_fullM(m, qm, d.qM)
np.testing.assert_allclose(qm, mjx.full_m(mjx.put_model(m), dx))
elif backend_impl == 'c':
elif impl == 'c':
np.testing.assert_allclose(dx._impl.qM, d.qM)
np.testing.assert_allclose(dx._impl.qLD, d.qLD)
np.testing.assert_allclose(dx._impl.qLDiagInv, d.qLDiagInv)
@@ -417,7 +418,7 @@ class DataIOTest(parameterized.TestCase):
m.opt.jacobian = mujoco.mjtJacobian.mjJAC_SPARSE
d = mujoco.MjData(m)
mujoco.mj_step(m, d, 2)
dx_sparse = mjx.put_data(m, d, backend_impl=backend_impl)
dx_sparse = mjx.put_data(m, d, impl=impl)
np.testing.assert_allclose(dx_sparse._impl.efc_J, dx._impl.efc_J, atol=1e-8)
# check sparse mass matrices are correct
@@ -431,25 +432,25 @@ class DataIOTest(parameterized.TestCase):
m.opt.jacobian = mujoco.mjtJacobian.mjJAC_DENSE
d = mujoco.MjData(m)
mujoco.mj_step(m, d, 2)
dx_from_dense = mjx.put_data(m, d, backend_impl=backend_impl)
if backend_impl == 'jax':
dx_from_dense = mjx.put_data(m, d, impl=impl)
if impl == 'jax':
qm = np.zeros((m.nv, m.nv))
mujoco.mj_fullM(m, qm, d.qM)
np.testing.assert_allclose(dx_from_dense._impl.qM, qm, atol=1e-8)
elif backend_impl == 'c':
elif impl == 'c':
np.testing.assert_allclose(dx_from_dense._impl.qM, d.qM, atol=1e-8)
@parameterized.parameters(
('jax', False), ('jax', True), ('c', False), ('c', True)
)
def test_get_data(self, backend_impl: str, sparse: bool):
def test_get_data(self, impl: str, sparse: bool):
"""Test that get_data makes correct MjData."""
m = mujoco.MjModel.from_xml_string(_MULTIPLE_CONSTRAINTS)
if sparse:
m.opt.jacobian = mujoco.mjtJacobian.mjJAC_SPARSE
d = mujoco.MjData(m)
mujoco.mj_step(m, d, 2)
dx = mjx.put_data(m, d, backend_impl=backend_impl)
dx = mjx.put_data(m, d, impl=impl)
d_2: mujoco.MjData = mjx.get_data(m, dx)
# check a few fields
@@ -505,7 +506,7 @@ class DataIOTest(parameterized.TestCase):
np.testing.assert_allclose(d_2.efc_aref, d.efc_aref)
np.testing.assert_allclose(d_2.contact.efc_address, d.contact.efc_address)
if backend_impl == 'c':
if impl == 'c':
# check fields specific to the C implementation
np.testing.assert_allclose(d_2.bvh_active, d.bvh_active)
@@ -540,13 +541,13 @@ class DataIOTest(parameterized.TestCase):
mjx.get_data(m, dx)
@parameterized.parameters('jax', 'c')
def test_get_data_batched(self, backend_impl):
def test_get_data_batched(self, impl):
"""Test that get_data makes correct List[MjData] for batched Data."""
m = mujoco.MjModel.from_xml_string(_MULTIPLE_CONSTRAINTS)
d = mujoco.MjData(m)
mujoco.mj_step(m, d, 2)
dx = mjx.put_data(m, d, backend_impl=backend_impl)
dx = mjx.put_data(m, d, impl=impl)
# second data in batch has contact dist > 0, disables contact
dx_b = jax.tree_util.tree_map(lambda x: jp.stack((x, x + 0.05)), dx)
ds = mjx.get_data(m, dx_b)
@@ -557,13 +558,13 @@ class DataIOTest(parameterized.TestCase):
self.assertEqual(ds[1].ncon, 0)
@parameterized.parameters('jax', 'c')
def test_get_data_into(self, backend_impl):
def test_get_data_into(self, impl):
"""Test that get_data_into correctly populates an MjData."""
m = mujoco.MjModel.from_xml_string(_MULTIPLE_CONSTRAINTS)
d = mujoco.MjData(m)
mujoco.mj_step(m, d, 2)
dx = mjx.put_data(m, d, backend_impl=backend_impl)
dx = mjx.put_data(m, d, impl=impl)
d_2 = mujoco.MjData(m)
mjx.get_data_into(d_2, m, dx)
@@ -580,32 +581,32 @@ class DataIOTest(parameterized.TestCase):
np.testing.assert_allclose(d_2.contact.frame, d.contact.frame)
@parameterized.parameters('jax', 'c')
def test_get_data_into_wrong_shape(self, backend_impl):
def test_get_data_into_wrong_shape(self, impl):
"""Tests that get_data_into throwsif input and output shapes don't match."""
m = mujoco.MjModel.from_xml_string(_MULTIPLE_CONSTRAINTS)
d = mujoco.MjData(m)
mujoco.mj_step(m, d, 2)
dx = mjx.put_data(m, d, backend_impl=backend_impl)
dx = mjx.put_data(m, d, impl=impl)
m_2 = mujoco.MjModel.from_xml_string(_MULTIPLE_CONVEX_OBJECTS)
d_2 = mujoco.MjData(m_2)
with self.assertRaisesRegex(ValueError, r'Input field.*has shape.*'):
mjx.get_data_into(d_2, m, dx)
@parameterized.parameters('jax', 'c')
def test_make_matches_put(self, backend_impl):
def test_make_matches_put(self, impl):
"""Test that make_data produces a pytree that matches put_data."""
m = mujoco.MjModel.from_xml_string(_MULTIPLE_CONSTRAINTS)
d = mujoco.MjData(m)
mujoco.mj_step(m, d, 2)
dx = mjx.put_data(m, d, backend_impl=backend_impl)
dx = mjx.put_data(m, d, impl=impl)
step_fn = lambda d: d.replace(time=d.time + 1)
step_fn_jit = jax.jit(step_fn).lower(dx).compile()
# placing an MjData onto device should yield the same treedef mjx.Data as
# calling make_data. they should be interchangeable for jax functions:
step_fn_jit(mjx.make_data(m, backend_impl=backend_impl))
step_fn_jit(mjx.make_data(m, impl=impl))
def test_contact_elliptic_condim1(self):
"""Test that condim=1 with ConeType.ELLIPTIC is not implemented."""
@@ -624,37 +625,24 @@ class DataIOTest(parameterized.TestCase):
with self.assertRaises(NotImplementedError):
mjx.make_data(m)
@parameterized.product(
sensor=['accelerometer', 'force', 'torque'], equality=['connect', 'weld']
)
def test_sensor_constraint_compatibility(self, sensor, equality):
"""Test unsupported sensor and equality constraint combinations."""
equality_constraint = f'{equality} body1="body1" body2="body2"'
if equality == 'connect':
equality_constraint += ' anchor="0 0 0"'
m = mujoco.MjModel.from_xml_string(f"""
<mujoco>
<worldbody>
<body name="body1">
<freejoint/>
<geom size="0.1"/>
<site name="site1"/>
</body>
<body name="body2">
<freejoint/>
<geom size="0.1"/>
</body>
</worldbody>
<equality>
<{equality_constraint}/>
</equality>
<sensor>
<{sensor} site="site1"/>
</sensor>
</mujoco>
""")
with self.assertRaises(NotImplementedError):
mjx.put_model(m, backend_impl='jax')
@parameterized.parameters(JacobianType.DENSE, JacobianType.SPARSE)
def test_qm_mapm2m(self, jacobian):
"""Test that qM is mapped to M."""
m = test_util.load_test_file('humanoid/humanoid.xml')
m.opt.jacobian = jacobian
d = mujoco.MjData(m)
mx = mjx.put_model(m, impl='jax')
dx = mjx.make_data(m, impl='jax')
dx = mjx.forward(mx, dx)
mjx.get_data_into(d, m, dx)
res_mj = np.zeros((1, m.nv))
mujoco.mj_solveM(m, d, res_mj, np.ones((1, m.nv)))
res = mjx._src.smooth.solve_m(mx, dx, jp.ones(m.nv))
np.testing.assert_allclose(res_mj[0], res, rtol=1e-3, atol=1e-3)
class FullCompatTest(parameterized.TestCase):
@@ -678,71 +666,71 @@ class FullCompatTest(parameterized.TestCase):
m = mujoco.MjModel.from_xml_string(xml)
with self.assertWarns(DeprecationWarning):
out = mjx_io.put_model(m, _full_compat=True)
self.assertEqual(out.backend_impl, BackendImpl.C)
self.assertEqual(out.impl, Impl.C)
with self.assertWarns(DeprecationWarning):
out = mjx_io.make_data(m, _full_compat=True)
self.assertEqual(out.backend_impl, BackendImpl.C)
self.assertEqual(out.impl, Impl.C)
# Test cases for `_resolve_backend_impl_and_device` where the device is
# Test cases for `_resolve_impl_and_device` where the device is
# specified by the user and the device is available.
_DEVICE_TEST_CASES = [
# Arguments use the following format:
# (device_type_str, backend_impl_str,
# (expected_device, expected_backend_impl)))
# (device_type_str, impl_str,
# (expected_device, expected_impl)))
# No backend specified.
('cpu', None, ('cpu', BackendImpl.C)),
('gpu-notnvidia', None, ('gpu', BackendImpl.JAX)),
('gpu-nvidia', None, ('gpu', BackendImpl.WARP)),
('tpu', None, ('tpu', BackendImpl.JAX)),
('cpu', None, ('cpu', Impl.C)),
('gpu-notnvidia', None, ('gpu', Impl.JAX)),
('gpu-nvidia', None, ('gpu', Impl.WARP)),
('tpu', None, ('tpu', Impl.JAX)),
# JAX backend specified.
('cpu', 'jax', ('cpu', BackendImpl.JAX)),
('gpu-notnvidia', 'jax', ('gpu', BackendImpl.JAX)),
('gpu-nvidia', 'jax', ('gpu', BackendImpl.JAX)),
('tpu', 'jax', ('tpu', BackendImpl.JAX)),
('cpu', 'jax', ('cpu', Impl.JAX)),
('gpu-notnvidia', 'jax', ('gpu', Impl.JAX)),
('gpu-nvidia', 'jax', ('gpu', Impl.JAX)),
('tpu', 'jax', ('tpu', Impl.JAX)),
# WARP backend specified.
('cpu', 'warp', ('cpu', 'error')),
('gpu-notnvidia', 'warp', ('cpu', 'error')),
('gpu-nvidia', 'warp', ('gpu', BackendImpl.WARP)),
('gpu-nvidia', 'warp', ('gpu', Impl.WARP)),
('tpu', 'warp', ('tpu', 'error')),
# C backend specified.
('cpu', 'c', ('cpu', BackendImpl.C)),
('cpu', 'c', ('cpu', Impl.C)),
('gpu-notnvidia', 'c', ('cpu', 'error')),
('gpu-nvidia', 'c', ('cpu', 'error')),
('tpu', 'c', ('tpu', 'error')),
]
# Test cases for `_resolve_backend_impl_and_device` where the user does NOT
# Test cases for `_resolve_impl_and_device` where the user does NOT
# specify a device. We mock the JAX default device.
_DEFAULT_DEVICE_TEST_CASES = [
# Arguments use the following format:
# (jax.default_device, backend_impl_str,
# (expected_device, expected_backend_impl))
# (jax.default_device, impl_str,
# (expected_device, expected_impl))
# No backend impl specified.
('cpu', None, ('cpu', BackendImpl.C)),
('gpu-notnvidia', None, ('gpu', BackendImpl.JAX)),
('gpu-nvidia', None, ('gpu', BackendImpl.WARP)),
('tpu', None, ('tpu', BackendImpl.JAX)),
('cpu', None, ('cpu', Impl.C)),
('gpu-notnvidia', None, ('gpu', Impl.JAX)),
('gpu-nvidia', None, ('gpu', Impl.WARP)),
('tpu', None, ('tpu', Impl.JAX)),
# JAX backend impl specified.
('cpu', 'jax', ('cpu', BackendImpl.JAX)),
('gpu-notnvidia', 'jax', ('gpu', BackendImpl.JAX)),
('gpu-nvidia', 'jax', ('gpu', BackendImpl.JAX)),
('tpu', 'jax', ('tpu', BackendImpl.JAX)),
('cpu', 'jax', ('cpu', Impl.JAX)),
('gpu-notnvidia', 'jax', ('gpu', Impl.JAX)),
('gpu-nvidia', 'jax', ('gpu', Impl.JAX)),
('tpu', 'jax', ('tpu', Impl.JAX)),
# WARP backend impl specified.
('cpu', 'warp', ('cpu', 'error')),
('gpu-notnvidia', 'warp', ('cpu', 'error')),
('gpu-nvidia', 'warp', ('gpu', BackendImpl.WARP)),
('gpu-nvidia', 'warp', ('gpu', Impl.WARP)),
('tpu', 'warp', ('tpu', 'error')),
# C backend impl specified, CPU should always be available.
('cpu', 'c', ('cpu', BackendImpl.C)),
('gpu-notnvidia', 'c', ('cpu', BackendImpl.C)),
('gpu-nvidia', 'c', ('cpu', BackendImpl.C)),
('tpu', 'c', ('cpu', BackendImpl.C)),
('cpu', 'c', ('cpu', Impl.C)),
('gpu-notnvidia', 'c', ('cpu', Impl.C)),
('gpu-nvidia', 'c', ('cpu', Impl.C)),
('tpu', 'c', ('cpu', Impl.C)),
]
class ResolveBackendImplAndDeviceTest(parameterized.TestCase):
"""Tests for the _resolve_backend_impl_and_device function."""
class ResolveImplAndDeviceTest(parameterized.TestCase):
"""Tests for the _resolve_impl_and_device function."""
def setUp(self):
super().setUp()
@@ -783,7 +771,7 @@ class ResolveBackendImplAndDeviceTest(parameterized.TestCase):
def test_resolve_with_device(
self,
device_type_str,
backend_impl_str,
impl_str,
expected,
):
"""Tests various combinations of device and backend impls."""
@@ -811,21 +799,21 @@ class ResolveBackendImplAndDeviceTest(parameterized.TestCase):
self.mock_jax_devices.side_effect = devices_side_effect
expected_device, expected_backend_impl = expected
if expected_backend_impl == 'error':
expected_device, expected_impl = expected
if expected_impl == 'error':
with self.assertRaises(AssertionError):
mjx_io._resolve_backend_impl_and_device(
backend_impl=backend_impl_str, device=input_device
mjx_io._resolve_impl_and_device(
impl=impl_str, device=input_device
)
return
actual_backend_impl, actual_device = (
mjx_io._resolve_backend_impl_and_device(
backend_impl=backend_impl_str, device=input_device
actual_impl, actual_device = (
mjx_io._resolve_impl_and_device(
impl=impl_str, device=input_device
)
)
self.assertEqual(actual_backend_impl, expected_backend_impl)
self.assertEqual(actual_impl, expected_impl)
self.assertIsNotNone(actual_device)
self.assertEqual(actual_device.platform, expected_device)
@@ -839,7 +827,7 @@ class ResolveBackendImplAndDeviceTest(parameterized.TestCase):
def test_resolve_without_device(
self,
default_device_str,
backend_impl_str,
impl_str,
expected,
):
"""Tests various combinations of jax.default_device and backend impls."""
@@ -878,32 +866,32 @@ class ResolveBackendImplAndDeviceTest(parameterized.TestCase):
lambda: default_device_side_effect_str
)
expected_device, expected_backend_impl = expected
expected_device, expected_impl = expected
if (
expected_backend_impl == 'error'
expected_impl == 'error'
and default_device_str != 'gpu-nvidia'
and backend_impl_str == 'warp'
and impl_str == 'warp'
):
with self.assertRaisesRegex(RuntimeError, 'cuda backend not supported'):
mjx_io._resolve_backend_impl_and_device(
backend_impl=backend_impl_str, device=None
mjx_io._resolve_impl_and_device(
impl=impl_str, device=None
)
return
if expected_backend_impl == 'error':
if expected_impl == 'error':
with self.assertRaises(AssertionError):
mjx_io._resolve_backend_impl_and_device(
backend_impl=backend_impl_str, device=None
mjx_io._resolve_impl_and_device(
impl=impl_str, device=None
)
return
actual_backend_impl, actual_device = (
mjx_io._resolve_backend_impl_and_device(
backend_impl=backend_impl_str, device=None
actual_impl, actual_device = (
mjx_io._resolve_impl_and_device(
impl=impl_str, device=None
)
)
self.assertEqual(actual_backend_impl, expected_backend_impl)
self.assertEqual(actual_impl, expected_impl)
self.assertIsNotNone(actual_device)
self.assertEqual(actual_device.platform, expected_device)
@@ -918,26 +906,26 @@ class ResolveBackendImplAndDeviceTest(parameterized.TestCase):
self.mock_default_backend.side_effect = lambda: 'gpu'
# Default to JAX instead of WARP on NVIDIA GPU.
backend_impl, device = mjx_io._resolve_backend_impl_and_device(
backend_impl=None, device=None
impl, device = mjx_io._resolve_impl_and_device(
impl=None, device=None
)
self.assertEqual(backend_impl, BackendImpl.JAX)
self.assertEqual(impl, Impl.JAX)
self.assertEqual(device.platform, 'gpu')
# Specifying an NVIDIA GPU should still choose JAX.
backend_impl, device = mjx_io._resolve_backend_impl_and_device(
backend_impl=None, device=self.mock_nvidia_gpu
impl, device = mjx_io._resolve_impl_and_device(
impl=None, device=self.mock_nvidia_gpu
)
self.assertEqual(backend_impl, BackendImpl.JAX)
self.assertEqual(impl, Impl.JAX)
self.assertEqual(device.platform, 'gpu')
# Requesting warp explicitly should fail since it is disabled.
with self.assertRaises(AssertionError):
mjx_io._resolve_backend_impl_and_device(
backend_impl='warp', device=self.mock_nvidia_gpu
mjx_io._resolve_impl_and_device(
impl='warp', device=self.mock_nvidia_gpu
)
with self.assertRaises(AssertionError):
mjx_io._resolve_backend_impl_and_device(backend_impl='warp', device=None)
mjx_io._resolve_impl_and_device(impl='warp', device=None)
@mock.patch.dict(os.environ, {'MJX_C_DEFAULT_ENABLED': 'false'})
def test_resolve_c_disabled(self):
@@ -950,30 +938,30 @@ class ResolveBackendImplAndDeviceTest(parameterized.TestCase):
self.mock_default_backend.side_effect = lambda: 'cpu'
# Default to JAX instead of C on CPU.
backend_impl, device = mjx_io._resolve_backend_impl_and_device(
backend_impl=None, device=None
impl, device = mjx_io._resolve_impl_and_device(
impl=None, device=None
)
self.assertEqual(backend_impl, BackendImpl.JAX)
self.assertEqual(impl, Impl.JAX)
self.assertEqual(device.platform, 'cpu')
# Specifing CPU should still choose JAX.
backend_impl, device = mjx_io._resolve_backend_impl_and_device(
backend_impl=None, device=self.mock_cpu
impl, device = mjx_io._resolve_impl_and_device(
impl=None, device=self.mock_cpu
)
self.assertEqual(backend_impl, BackendImpl.JAX)
self.assertEqual(impl, Impl.JAX)
self.assertEqual(device.platform, 'cpu')
# Specifying C should choose C!
backend_impl, device = mjx_io._resolve_backend_impl_and_device(
backend_impl='c', device=None
impl, device = mjx_io._resolve_impl_and_device(
impl='c', device=None
)
self.assertEqual(backend_impl, BackendImpl.C)
self.assertEqual(impl, Impl.C)
self.assertEqual(device.platform, 'cpu')
backend_impl, device = mjx_io._resolve_backend_impl_and_device(
backend_impl='c', device=self.mock_cpu
impl, device = mjx_io._resolve_impl_and_device(
impl='c', device=self.mock_cpu
)
self.assertEqual(backend_impl, BackendImpl.C)
self.assertEqual(impl, Impl.C)
self.assertEqual(device.platform, 'cpu')
+1 -1
View File
@@ -22,7 +22,7 @@ from mujoco.mjx._src import math
from mujoco.mjx._src import ray
from mujoco.mjx._src import smooth
from mujoco.mjx._src import support
from mujoco.mjx._src.types import BackendImpl
from mujoco.mjx._src.types import Impl
from mujoco.mjx._src.types import Data
from mujoco.mjx._src.types import DataJAX
from mujoco.mjx._src.types import DisableBit
+276 -5
View File
@@ -29,6 +29,7 @@ from mujoco.mjx._src.types import EqType
from mujoco.mjx._src.types import JointType
from mujoco.mjx._src.types import Model
from mujoco.mjx._src.types import ModelJAX
from mujoco.mjx._src.types import ObjType
from mujoco.mjx._src.types import TrnType
from mujoco.mjx._src.types import WrapType
# pylint: enable=g-importing-member
@@ -659,11 +660,126 @@ def rne_postconstraint(m: Model, d: Data) -> Data:
cfrc_contact.reshape((-1, 6))
)
# TODO(taylorhowell): connect and weld constraints
if np.any(m.eq_type == EqType.CONNECT):
raise NotImplementedError('Connect constraints are not implemented.')
if np.any(m.eq_type == EqType.WELD):
raise NotImplementedError('Weld constraints are not implemented.')
# cfrc_ext += connect, weld
cfrc_ext_equality = []
cfrc_ext_equality_adr = []
connect_id = m.eq_type == EqType.CONNECT
nconnect = connect_id.sum()
if nconnect:
cfrc_connect_force = d._impl.efc_force[: 3 * nconnect].reshape(
(nconnect, 3)
)
is_site = m.eq_objtype == ObjType.SITE
body1id = np.copy(m.eq_obj1id)
body2id = np.copy(m.eq_obj2id)
pos1 = m.eq_data[:, :3]
pos2 = m.eq_data[:, 3:6]
if m.nsite:
body1id[is_site] = m.site_bodyid[m.eq_obj1id[is_site]]
body2id[is_site] = m.site_bodyid[m.eq_obj2id[is_site]]
pos1 = jp.where(is_site[:, None], m.site_pos[m.eq_obj1id], pos1)
pos2 = jp.where(is_site[:, None], m.site_pos[m.eq_obj2id], pos2)
# body 1
k1_connect = body1id[connect_id]
k1_connect_mask = k1_connect != 0
offset1_connect = pos1[connect_id]
pos1_connect = jax.vmap(lambda pnt, mat, vec: mat @ pnt + vec)(
offset1_connect, d.xmat[k1_connect], d.xpos[k1_connect]
)
subtree_com1_connect = d.subtree_com[jp.array(m.body_rootid)[k1_connect]]
cfrc_com1_connect = jax.vmap(
lambda dif, frc, mask: mask * jp.concatenate([-jp.cross(dif, frc), frc])
)(subtree_com1_connect - pos1_connect, cfrc_connect_force, k1_connect_mask)
# body 2
k2_connect = body2id[connect_id]
k2_connect_mask = -1 * (k2_connect != 0)
offset2_connect = pos2[connect_id]
pos2_connect = jax.vmap(lambda pnt, mat, vec: mat @ pnt + vec)(
offset2_connect, d.xmat[k2_connect], d.xpos[k2_connect]
)
subtree_com2_connect = d.subtree_com[jp.array(m.body_rootid)[k2_connect]]
cfrc_com2_connect = jax.vmap(
lambda dif, frc, mask: mask * jp.concatenate([-jp.cross(dif, frc), frc])
)(subtree_com2_connect - pos2_connect, cfrc_connect_force, k2_connect_mask)
cfrc_ext_equality.append(jp.vstack([cfrc_com1_connect, cfrc_com2_connect]))
cfrc_ext_equality_adr.append(jp.concatenate([k1_connect, k2_connect]))
weld_id = m.eq_type == EqType.WELD
nweld = weld_id.sum()
if nweld:
cfrc_weld = d._impl.efc_force[
3 * nconnect : 3 * nconnect + 6 * nweld
].reshape((nweld, 6))
cfrc_weld_force = cfrc_weld[:, :3]
cfrc_weld_torque = cfrc_weld[:, 3:]
is_site = m.eq_objtype == ObjType.SITE
body1id = np.copy(m.eq_obj1id)
body2id = np.copy(m.eq_obj2id)
pos1 = m.eq_data[:, 3:6]
pos2 = m.eq_data[:, :3]
if m.nsite:
body1id[is_site] = m.site_bodyid[m.eq_obj1id[is_site]]
body2id[is_site] = m.site_bodyid[m.eq_obj2id[is_site]]
pos1 = jp.where(is_site[:, None], m.site_pos[m.eq_obj1id], pos1)
pos2 = jp.where(is_site[:, None], m.site_pos[m.eq_obj2id], pos2)
# body 1
k1_weld = body1id[weld_id]
k1_weld_mask = k1_weld != 0
offset1_weld = pos1[weld_id]
pos1_weld = jax.vmap(lambda pnt, mat, vec: mat @ pnt + vec)(
offset1_weld, d.xmat[k1_weld], d.xpos[k1_weld]
)
subtree_com1_weld = d.subtree_com[jp.array(m.body_rootid)[k1_weld]]
cfrc_com1_weld = jax.vmap(
lambda dif, frc, trq, mask: mask
* jp.concatenate([trq - jp.cross(dif, frc), frc])
)(
subtree_com1_weld - pos1_weld,
cfrc_weld_force,
cfrc_weld_torque,
k1_weld_mask,
)
# body 2
k2_weld = body2id[weld_id]
k2_weld_mask = -1 * (k2_weld != 0)
offset2_weld = pos2[weld_id]
pos2_weld = jax.vmap(lambda pnt, mat, vec: mat @ pnt + vec)(
offset2_weld, d.xmat[k2_weld], d.xpos[k2_weld]
)
subtree_com2_weld = d.subtree_com[jp.array(m.body_rootid)[k2_weld]]
cfrc_com2_weld = jax.vmap(
lambda dif, frc, trq, mask: mask
* jp.concatenate([trq - jp.cross(dif, frc), frc])
)(
subtree_com2_weld - pos2_weld,
cfrc_weld_force,
cfrc_weld_torque,
k2_weld_mask,
)
cfrc_ext_equality.append(jp.vstack([cfrc_com1_weld, cfrc_com2_weld]))
cfrc_ext_equality_adr.append(jp.concatenate([k1_weld, k2_weld]))
if nconnect or nweld:
cfrc_ext = cfrc_ext.at[jp.concatenate(cfrc_ext_equality_adr)].add(
jp.vstack(cfrc_ext_equality)
)
# forward pass over bodies: compute cacc, cfrc_int
def _forward(carry, cfrc_ext, cinert, cvel, body_dofadr, body_dofnum):
@@ -1184,3 +1300,158 @@ def transmission(m: Model, d: Data) -> Data:
{'_impl.actuator_length': length, '_impl.actuator_moment': moment}
)
return d
def tendon_armature(m: Model, d: Data) -> Data:
"""Add tendon armature to qM."""
if not isinstance(m._impl, ModelJAX) or not isinstance(d._impl, DataJAX):
raise ValueError('tendon_armature requires JAX backend implementation.')
if not m.ntendon:
return d
# TODO(taylorhowell): if sparse, compute sparse JTAJ
JTAJ = d._impl.ten_J.T @ jax.vmap(jp.multiply)(
d._impl.ten_J, m.tendon_armature
)
if support.is_sparse(m):
ij = []
for i in range(m.nv):
j = i
while j > -1:
ij.append((i, j))
j = m.dof_parentid[j]
i, j = (jp.array(x) for x in zip(*ij))
JTAJ = JTAJ[(i, j)]
return d.tree_replace({'_impl.qM': d._impl.qM + JTAJ})
def tendon_dot(m: Model, d: Data) -> jax.Array:
"""Compute time derivative of dense tendon Jacobian for one tendon."""
if not isinstance(m._impl, ModelJAX) or not isinstance(d._impl, DataJAX):
raise ValueError('tendon_dot requires JAX backend implementation.')
ten_Jdot = jp.zeros((m.ntendon, m.nv)) # pylint: disable=invalid-name
if not m.ntendon:
return ten_Jdot
# process pulleys
(wrap_id_pulley,) = np.nonzero(m.wrap_type == WrapType.PULLEY)
divisor = np.ones(m.nwrap)
for adr, num in zip(m.tendon_adr, m.tendon_num):
for id_pulley in wrap_id_pulley:
if adr <= id_pulley < adr + num:
divisor[id_pulley : adr + num] = np.maximum(
mujoco.mjMINVAL, m.wrap_prm[id_pulley]
)
# process spatial tendon sites
(wrap_id_site,) = np.nonzero(m.wrap_type == WrapType.SITE)
# find consecutive sites, skipping tendon transitions
(pair_id,) = np.nonzero(np.diff(wrap_id_site) == 1)
wrap_id_site_pair = np.setdiff1d(wrap_id_site[pair_id], m.tendon_adr[1:] - 1)
wrap_objid_site0 = m.wrap_objid[wrap_id_site_pair]
wrap_objid_site1 = m.wrap_objid[wrap_id_site_pair + 1]
site_bodyid0 = m.site_bodyid[wrap_objid_site0]
site_bodyid1 = m.site_bodyid[wrap_objid_site1]
site_xpos0 = d.site_xpos[wrap_objid_site0]
site_xpos1 = d.site_xpos[wrap_objid_site1]
subtree_com0 = d.subtree_com[m.body_rootid[site_bodyid0]]
subtree_com1 = d.subtree_com[m.body_rootid[site_bodyid1]]
site_vel0 = jax.vmap(lambda a, b: a[3:] - jp.cross(b, a[:3]))(
d.cvel[site_bodyid0], site_xpos0 - subtree_com0
)
site_vel1 = jax.vmap(lambda a, b: a[3:] - jp.cross(b, a[:3]))(
d.cvel[site_bodyid1], site_xpos1 - subtree_com1
)
@jax.vmap
def _momentdot(wpnt0, wpnt1, wvel0, wvel1, body0, body1):
# dpnt = 3D position difference, normalize
dpnt = wpnt1 - wpnt0
norm = math.norm(dpnt)
dpnt = jp.where(
norm < mujoco.mjMINVAL, jp.array([1.0, 0.0, 0.0]), dpnt / norm
)
# dvel = d / dt(dpnt)
dvel = wvel1 - wvel0
dot = jp.dot(dpnt, dvel)
dvel += dpnt * -dot
dvel = jp.where(norm > mujoco.mjMINVAL, dvel / norm, 0.0)
# get endpoint JacobianDots, subtract
jacp1, _ = support.jac_dot(m, d, wpnt0, body0)
jacp2, _ = support.jac_dot(m, d, wpnt1, body1)
jacdif = jacp2 - jacp1
# chain rule, first term: Jdot += d / dt(jac2 - jac1) * dpnt
tmp0 = jacdif @ dpnt
# get endpoint Jacobians, subtract
jacp1, _ = support.jac(m, d, wpnt0, body0)
jacp2, _ = support.jac(m, d, wpnt1, body1)
jacdif = jacp2 - jacp1
# chain rule, second term: Jdot += (jac2 - jac1) * d/dt (dpnt)
tmp1 = jacdif @ dvel
return jp.where(body0 != body1, tmp0 + tmp1, jp.zeros(m.nv))
momentdots = _momentdot(
site_xpos0,
site_xpos1,
site_vel0,
site_vel1,
site_bodyid0,
site_bodyid1,
)
if wrap_id_site_pair.size:
divisor_site_pair = divisor[wrap_id_site_pair]
momentdots /= divisor_site_pair[:, None]
tendon_nsite = np.array([
sum((wrap_id_site_pair >= adr) & (wrap_id_site_pair < adr + num))
for adr, num in zip(m.tendon_adr, m.tendon_num)
])
tendon_has_site = tendon_nsite > 0
(tendon_id_site,) = np.nonzero(tendon_has_site)
tendon_nsite = tendon_nsite[tendon_has_site]
tendon_with_site = tendon_nsite.size
ten_site_id = np.repeat(np.arange(tendon_with_site), tendon_nsite)
momentdot = jax.ops.segment_sum(momentdots, ten_site_id, tendon_with_site)
ten_Jdot = ten_Jdot.at[tendon_id_site].set(momentdot) # pylint: disable=invalid-name
# TODO(taylorhowell): time derivatives for geoms
return ten_Jdot
def tendon_bias(m: Model, d: Data) -> Data:
"""Add bias force due to tendon armature."""
if not isinstance(m._impl, ModelJAX) or not isinstance(d._impl, DataJAX):
raise ValueError('tendon_bias requires JAX backend implementation.')
if not m.ntendon:
return d
# get dense d/dt(tendon Jacobian) for each tendon
ten_Jdot = tendon_dot(m, d) # pylint: disable=invalid-name
# add bias term: qfrc += ten_J * armature * ten_Jdot @ qvel
coef = m.tendon_armature * jp.dot(ten_Jdot, d.qvel)
return d.tree_replace({
'qfrc_bias': (
d.qfrc_bias
+ jp.sum(jax.vmap(jp.multiply)(d._impl.ten_J, coef), axis=0)
)
})
+119 -4
View File
@@ -17,10 +17,12 @@
from absl.testing import absltest
from absl.testing import parameterized
import jax
from jax import numpy as jp
import mujoco
from mujoco import mjx
from mujoco.mjx._src import test_util
from mujoco.mjx._src.types import ConeType # pylint: disable=g-importing-member
from mujoco.mjx._src.types import JacobianType # pylint: disable=g-importing-member
import numpy as np
# tolerance for difference between MuJoCo and MJX smooth calculations - mostly
@@ -228,21 +230,98 @@ class SmoothTest(absltest.TestCase):
class RnePostConstraintTest(parameterized.TestCase):
_CONNECT_SITE = """
<equality>
<connect site1="site1" site2="site2"/>
</equality>
"""
_CONNECT_BODY = """
<equality>
<connect body1="body1" body2="body2" anchor="1 2 3"/>
</equality>
"""
_WELD_SITE = """
<equality>
<weld site1="site1" site2="site2"/>
</equality>
"""
_WELD_BODY = """
<equality>
<weld body1="body1" body2="body2"/>
</equality>
"""
_CONNECT_SITE_WELD_SITE = """
<equality>
<connect site1="site1" site2="site2"/>
<weld site1="site1" site2="site2"/>
</equality>
"""
_WELD_SITE_CONNECT_SITE = """
<equality>
<weld site1="site1" site2="site2"/>
<connect site1="site1" site2="site2"/>
</equality>
"""
_WELD_SITE_CONNECT_SITE_WELD_BODY = """
<equality>
<weld site1="site1" site2="site2"/>
<connect site1="site1" site2="site2"/>
<weld body1="body1" body2="body2"/>
</equality>
"""
_CONNECT_SITE_WELD_SITE_WELD_BODY = """
<equality>
<connect site1="site1" site2="site2"/>
<weld site1="site1" site2="site2"/>
<weld body1="body1" body2="body2"/>
</equality>
"""
_CONNECT_SITE_CONNECT_BODY_CONNECT_WELD = """
<equality>
<connect site1="site1" site2="site2"/>
<connect body1="body1" body2="body2" anchor="1 2 3"/>
<weld body1="body1" body2="body2"/>
</equality>
"""
@parameterized.parameters(ConeType)
def test_rnepostconstraint(self, cone_type):
@parameterized.parameters(
('', ConeType.PYRAMIDAL, None),
('', ConeType.ELLIPTIC, None),
(_CONNECT_SITE, ConeType.PYRAMIDAL, None),
(_CONNECT_BODY, ConeType.PYRAMIDAL, None),
(_WELD_SITE, ConeType.PYRAMIDAL, None),
(_WELD_BODY, ConeType.PYRAMIDAL, None),
(_CONNECT_SITE_WELD_SITE, ConeType.PYRAMIDAL, None),
(
_WELD_SITE_CONNECT_SITE,
ConeType.PYRAMIDAL,
np.array([6, 7, 8, 0, 1, 2, 3, 4, 5]),
),
(
_WELD_SITE_CONNECT_SITE_WELD_BODY,
ConeType.PYRAMIDAL,
np.array([6, 7, 8, 0, 1, 2, 3, 4, 5]),
),
(_CONNECT_SITE_WELD_SITE_WELD_BODY, ConeType.PYRAMIDAL, None),
(_CONNECT_SITE_CONNECT_BODY_CONNECT_WELD, ConeType.PYRAMIDAL, None),
)
def test_rnepostconstraint(self, equality, cone_type, efc_map):
"""Tests MJX rne_postconstraint function to match MuJoCo mj_rnePostConstraint."""
m = mujoco.MjModel.from_xml_string("""
m = mujoco.MjModel.from_xml_string(f"""
<mujoco>
<worldbody>
<geom name="floor" size="10 10 .05" type="plane"/>
<body pos="0 0 1">
<site name="site1"/>
<body name="body1">
</body>
<body pos="0 0 1" name="body2">
<joint type="ball" damping="1"/>
<geom type="capsule" size="0.1 0.5" fromto="0 0 0 0.5 0 0" condim="1"/>
<body pos="0.5 0 0">
<joint type="ball" damping="1"/>
<geom type="capsule" size="0.1 0.5" fromto="0 0 0 0.5 0 0" condim="3"/>
<site name="site2"/>
</body>
</body>
<body pos="0 1 1">
@@ -254,6 +333,7 @@ class RnePostConstraintTest(parameterized.TestCase):
</body>
</body>
</worldbody>
{equality}
<keyframe>
<key qpos='0.424577 0.450592 0.451703 -0.642391 0.729379 0.545151 0.407756 0.0674697 0.424577 1.450592 0.451703 -0.642391 0.729379 0.545151 0.407756 0.0674697'/>
</keyframe>
@@ -271,6 +351,11 @@ class RnePostConstraintTest(parameterized.TestCase):
mx = mjx.put_model(m)
dx = mjx.put_data(m, d)
if efc_map is not None:
efc_force = d.efc_force.copy()
efc_force[: len(efc_map)] = d.efc_force[efc_map]
dx = dx.tree_replace({'_impl.efc_force': jp.array(efc_force)})
# rne postconstraint
mujoco.mj_rnePostConstraint(m, d)
dx = jax.jit(mjx.rne_postconstraint)(mx, dx)
@@ -316,6 +401,36 @@ class TendonTest(parameterized.TestCase):
_assert_eq(d.wrap_obj, dx._impl.wrap_obj, 'wrap_obj')
_assert_eq(d.wrap_xpos, dx._impl.wrap_xpos, 'wrap_xpos')
@parameterized.parameters(JacobianType.DENSE, JacobianType.SPARSE)
def test_tendon_armature(self, jacobian):
"""Tests MJX tendon armature matches MuJoCo."""
m = test_util.load_test_file('tendon/armature.xml')
m.opt.jacobian = jacobian
d = mujoco.MjData(m)
mujoco.mj_resetDataKeyframe(m, d, 0)
mujoco.mj_forward(m, d)
mx = mjx.put_model(m)
dx = mjx.put_data(m, d)
dx = dx.tree_replace(
{'_impl.qM': jp.zeros((m.nv, m.nv)), 'qfrc_bias': jp.zeros(m.nv)}
)
dx = mjx.crb(mx, dx)
dx = mjx.tendon_armature(mx, dx)
if jacobian == JacobianType.DENSE:
qM = np.zeros((m.nv, m.nv)) # pylint: disable=invalid-name
mujoco.mj_fullM(m, qM, d.qM)
else:
qM = d.qM # pylint: disable=invalid-name
_assert_eq(dx._impl.qM, qM, 'qM')
dx = mjx.rne(mx, dx)
dx = mjx.tendon_bias(mx, dx)
_assert_eq(dx.qfrc_bias, d.qfrc_bias, 'qfrc_bias')
if __name__ == '__main__':
absltest.main()
+3 -1
View File
@@ -403,7 +403,9 @@ def _update_gradient(m: Model, d: Data, ctx: Context) -> Context:
else:
h = (d._impl.efc_J.T * d._impl.efc_D * ctx.active) @ d._impl.efc_J
h = support.full_m(m, d) + h
h_ = jax.scipy.linalg.cho_factor(h)
# Symmetrize to reduce the chance of numerical issues in cholesky.
h_sym = (h + h.T) * 0.5
h_ = jax.scipy.linalg.cho_factor(h_sym)
mgrad = jax.scipy.linalg.cho_solve(h_, grad)
else:
raise NotImplementedError(f'unsupported solver type: {m.opt.solver}')
+37
View File
@@ -142,6 +142,7 @@ def jac(
m: Model, d: Data, point: jax.Array, body_id: jax.Array
) -> Tuple[jax.Array, jax.Array]:
"""Compute pair of (NV, 3) Jacobians of global point attached to body."""
# TODO(taylorhowell): statically construct mask
fn = lambda carry, b: b if carry is None else b + carry
mask = (jp.arange(m.nbody) == body_id) * 1
mask = scan.body_tree(m, fn, 'b', 'b', mask, reverse=True)
@@ -155,6 +156,42 @@ def jac(
return jacp, jacr
def jac_dot(
m: Model, d: Data, point: jax.Array, body_id: jax.Array
) -> Tuple[jax.Array, jax.Array]:
"""Compute pair of (NV, 3) Jacobian time derivatives of global point attached to body."""
# TODO(taylorhowell): statically construct mask
fn = lambda carry, b: b if carry is None else b + carry
mask = (jp.arange(m.nbody) == body_id) * 1
mask = scan.body_tree(m, fn, 'b', 'b', mask, reverse=True)
mask = mask[jp.array(m.dof_bodyid)] > 0
offset = point - d.subtree_com[jp.array(m.body_rootid)[body_id]]
pvel_lin = d.cvel[body_id][3:] - jp.cross(offset, d.cvel[body_id][:3])
cdof = d._impl.cdof
cdof_dot = d._impl.cdof_dot
# check for quaternion
jnt_type = m.jnt_type[m.dof_jntid]
dof_adr = m.jnt_dofadr[m.dof_jntid]
is_quat = (jnt_type == JointType.BALL) | (
jnt_type == JointType.FREE & (np.arange(m.nv) >= dof_adr + 3)
)
# compute cdof_dot for quaternion (use current body cvel)
cdof_dot_quat = jax.vmap(math.motion_cross)(d.cvel[m.dof_bodyid], cdof)
cdof_dot = jp.where(is_quat[:, None], cdof_dot_quat, cdof_dot)
jacp = jax.vmap(
lambda a, b: a[3:] + jp.cross(a[:3], offset) + jp.cross(b[:3], pvel_lin)
)(cdof_dot, cdof)
jacp = jax.vmap(jp.multiply)(jacp, mask)
jacr = jax.vmap(jp.multiply)(cdof_dot[:, :3], mask) # pytype: disable=attribute-error
return jacp, jacr
def apply_ft(
m: Model,
d: Data,
+13
View File
@@ -379,6 +379,19 @@ class SupportTest(parameterized.TestCase):
' 15297169659434471387 != 2785811613804955188',
)
# what happens when we bind to an actuator that was removed?
bygone_actuators = []
for act in s.actuators:
bygone_actuators.append(act)
s.delete(act)
m = s.compile()
d = mujoco.MjData(m)
mx = mjx.put_model(m)
dx = mjx.put_data(m, d)
dx = mjx.step(mx, dx)
with self.assertRaisesRegex(KeyError, 'invalid id: -1'):
dx.bind(mx, bygone_actuators)
_CONTACTS = """
<mujoco>
<worldbody>
+9 -9
View File
@@ -24,8 +24,8 @@ from mujoco.mjx._src.dataclasses import PyTreeNode # pylint: disable=g-importin
import numpy as np
class BackendImpl(enum.Enum):
"""Backend implementation to use."""
class Impl(enum.Enum):
"""Implementation to use."""
C = 'c'
JAX = 'jax'
@@ -34,7 +34,7 @@ class BackendImpl(enum.Enum):
@classmethod
def _missing_(cls, value):
# This method is called only when lookup by value fails
# (e.g., BackendImpl('JAX') fails initially because 'JAX' != 'jax')
# (e.g., Impl('JAX') fails initially because 'JAX' != 'jax')
if not isinstance(value, str):
return None
for member in cls:
@@ -871,10 +871,10 @@ class Model(PyTreeNode):
_impl: Union[ModelC, ModelJAX]
@property
def backend_impl(self) -> BackendImpl:
def impl(self) -> Impl:
return {
ModelC: BackendImpl.C,
ModelJAX: BackendImpl.JAX,
ModelC: Impl.C,
ModelJAX: Impl.JAX,
}[type(self._impl)]
def __getattr__(self, name: str):
@@ -1119,10 +1119,10 @@ class Data(PyTreeNode):
_impl: Union[DataC, DataJAX]
@property
def backend_impl(self) -> BackendImpl:
def impl(self) -> Impl:
return {
DataC: BackendImpl.C,
DataJAX: BackendImpl.JAX,
DataC: Impl.C,
DataJAX: Impl.JAX,
}[type(self._impl)]
def __getattr__(self, name: str):
@@ -0,0 +1,24 @@
<mujoco>
<worldbody>
<site name="site0" pos="1 0 1"/>
<body>
<joint type="slide" axis="0 0 1"/>
<joint type="hinge" axis="0 1 0"/>
<geom type="box" size="0.1 0.1 0.1" mass="1" pos="1 0 0"/>
<site name="site1"/>
</body>
</worldbody>
<tendon>
<spatial armature="123">
<site site="site0"/>
<site site="site1"/>
</spatial>
<spatial armature="456">
<site site="site0"/>
<site site="site1"/>
</spatial>
</tendon>
<keyframe>
<key qpos="1.2345 1.2345" qvel="1.2345 1.2345"/>
</keyframe>
</mujoco>
+4 -4
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name="mujoco-mjx"
version = "3.3.3"
version = "3.3.4"
authors = [
{name = "Google DeepMind", email = "mujoco@deepmind.com"},
]
@@ -30,7 +30,7 @@ dependencies = [
"etils[epath]",
"jax",
"jaxlib",
"mujoco>=3.3.3.dev0",
"mujoco>=3.3.4.dev0",
"scipy",
"trimesh",
]
@@ -41,9 +41,9 @@ mjx-viewer = "mujoco.mjx.viewer:main"
[project.urls]
Homepage = "https://github.com/google-deepmind/mujoco/tree/main/mjx"
Documentation = "https://mujoco.readthedocs.io/en/3.3.3"
Documentation = "https://mujoco.readthedocs.io/en/3.3.4"
Repository = "https://github.com/google-deepmind/mujoco/tree/main/mjx"
Changelog = "https://mujoco.readthedocs.io/en/3.3.3/changelog.html"
Changelog = "https://mujoco.readthedocs.io/en/3.3.4/changelog.html"
[tool.isort]
force_single_line = true
+8 -2
View File
@@ -22,6 +22,12 @@
<statistic center="0 -.3 1" extent="2.5"/>
<asset>
<texture name="texcyl" type="2d" builtin="checker" rgb1=".3 .3 .3" rgb2=".15 0.15 0.15"
width="512" height="512" mark="cross" markrgb=".22 .22 .22"/>
<material name="matcyl" reflectance="0.3" texture="texcyl" texrepeat="3 3" texuniform="true"/>
</asset>
<worldbody>
<flexcomp name="cable" type="circle" count="40 1 1" spacing=".15 1 1" dim="1"
radius="0.02" pos="0 0 1" rgba="1 0 0 1">
@@ -30,12 +36,12 @@
<body name="left" pos="-.7 0 1">
<joint name="expand_left" type="slide" axis="-1 0 0" range="0 2" damping="20"/>
<joint name="rotate" type="hinge" axis="0 0 1"/>
<geom type="cylinder" size=".2 .2" density="1"/>
<geom type="cylinder" size=".2 .2" density="1" material="matcyl"/>
</body>
<body name="right" pos=".7 0 1">
<joint name="expand_right" type="slide" axis="1 0 0" range="0 2" damping="20"/>
<joint type="hinge" axis="0 0 1"/>
<geom type="cylinder" size=".2 .2" density="1"/>
<geom type="cylinder" size=".2 .2" density="1" material="matcyl"/>
</body>
</worldbody>
+31
View File
@@ -0,0 +1,31 @@
<mujoco>
<compiler texturedir="asset"/>
<asset>
<texture name="texspot" type="2d" file="spot.png"/>
<material name="matspot" texture="texspot"/>
<mesh name="spot" file="asset/spot.obj"/>
</asset>
<option sdf_iterations="20" sdf_initpoints="40"/>
<visual>
<map force="1000"/>
</visual>
<default>
<geom solref="0.01 1" solimp=".95 .99 .0001" friction="0.5"/>
</default>
<statistic meansize="0.2"/>
<include file="scene.xml"/>
<worldbody>
<body euler="90 0 0" pos="0 0 .7">
<geom type="sdf" name="cow1" mesh="spot" material="matspot"/>
</body>
<light name="left" pos="0 0 1"/>
<light name="right" pos="1 0 1"/>
</worldbody>
</mujoco>
+17 -5
View File
@@ -213,14 +213,21 @@ TouchStress::TouchStress(const mjModel* m, mjData* d, int instance,
// Get parent weld id.
int site_id = m->sensor_objid[id_];
int parent_body = m->body_weldid[m->site_bodyid[site_id]];
int parent_body = m->site_bodyid[site_id];
parent_weld_ = m->body_weldid[parent_body];
// Get geom id.
if (m->body_geomnum[parent_body] != 1) {
mju_error("Touch sensor must be attached to a body with exactly one geom");
int collision_geoms = 0;
for (int i = 0; i < m->body_geomnum[parent_body]; ++i) {
int geom_id = m->body_geomadr[parent_body]+i;
if (m->geom_contype[geom_id] || m->geom_conaffinity[geom_id]) {
collision_geoms++;
geom_id_ = geom_id;
}
}
if (collision_geoms == 0) {
mju_error("Touch sensor requires a body with at least one collision geom");
}
geom_id_ = m->body_geomadr[parent_body];
// Create bin edges.
x_edges_.assign(size[0] + 1, 0);
@@ -236,7 +243,7 @@ TouchStress::TouchStress(const mjModel* m, mjData* d, int instance,
mjtNum aer[3];
aer[0] = 0.5*(x_edges_[i+1]+x_edges_[i]);
aer[1] = 0.5*(y_edges_[j+1]+y_edges_[j]);
aer[2] = m->geom_size[3*geom_id_];
aer[2] = m->geom_rbound[geom_id_];
SphericalToCartesian(aer, pos_.data() + 3 * (i * size[1] + j));
dist_[i*size[1]+j] = mju_abs(aer[2]);
TangentFrame(aer, mat_.data() + 9 * (i * size[1] + j));
@@ -302,6 +309,11 @@ void TouchStress::Compute(const mjModel* m, mjData* d, int instance) {
geomtype[0] = (mjtGeom)m->geom_type[geom];
}
// Skip mesh geoms.
if (geomtype[0] == mjGEOM_MESH) {
continue;
}
// Set SDF parameters.
mjSDF geom_sdf;
geom_sdf.id = &sdf_instance[0];
+6 -6
View File
@@ -1827,7 +1827,7 @@
"\n",
"# Remove all bodies in the list from the spec\n",
"for body in delete_list:\n",
" spec.detach_body(body)\n",
" spec.delete(body)\n",
"\n",
"# # Add another humanoid\n",
"spec_humanoid = mj.MjSpec.from_file(humanoid_file)\n",
@@ -1900,8 +1900,8 @@
"shoulder_right = torso.add_frame(pos=arm_right.pos)\n",
"\n",
"# Remove the arms\n",
"spec.detach_body(arm_left)\n",
"spec.detach_body(arm_right)\n",
"spec.delete(arm_left)\n",
"spec.delete(arm_right)\n",
"\n",
"# Add new legs\n",
"shoulder_left.attach_body(leg_left, 'shoulder', 'left')\n",
@@ -1942,7 +1942,7 @@
"arm_right = spec.body('upper_arm_right')\n",
"torso = spec.body('torso')\n",
"shoulder_right = torso.add_frame(pos=arm_right.pos, quat=[0, 0.8509035, 0, 0.525322])\n",
"spec.detach_body(arm_right)\n",
"spec.delete(arm_right)\n",
"\n",
"# Attach Franka arm to humanoid\n",
"franka_arm = franka.body('fr3_link2')\n",
@@ -2000,7 +2000,7 @@
"\n",
"# Delete all key frames to avoid name conflicts\n",
"while humanoid.keys:\n",
" humanoid.keys[-1].delete()\n",
" humanoid.delete(keys[-1])\n",
"\n",
"# Create a grid of humanoids by attaching humanoid to spec multiple times\n",
"for i in range(4):\n",
@@ -2335,7 +2335,7 @@
"fly = mj.MjSpec.from_file(fly_file)\n",
"# Remove lights from fly so they are not duplicated in line-up\n",
"for light in fly.lights:\n",
" light.delete()\n",
" fly.delete(light)\n",
"\n",
"small_fly = scale_spec(fly, 1.25)\n",
"large_fly = scale_spec(fly, 2)\n",
+4 -4
View File
@@ -84,7 +84,7 @@ if(NOT TARGET mujoco)
if(MUJOCO_FRAMEWORK)
message("MuJoCo framework is at ${MUJOCO_FRAMEWORK}/mujoco.framework")
set(MUJOCO_LIBRARY
${MUJOCO_FRAMEWORK}/mujoco.framework/Versions/A/libmujoco.3.3.3.dylib
${MUJOCO_FRAMEWORK}/mujoco.framework/Versions/A/libmujoco.3.3.4.dylib
)
target_compile_options(mujoco INTERFACE -F${MUJOCO_FRAMEWORK})
endif()
@@ -92,7 +92,7 @@ if(NOT TARGET mujoco)
if(NOT MUJOCO_FRAMEWORK)
find_library(
MUJOCO_LIBRARY mujoco mujoco.3.3.3 HINTS ${MUJOCO_LIBRARY_DIR} REQUIRED
MUJOCO_LIBRARY mujoco mujoco.3.3.4 HINTS ${MUJOCO_LIBRARY_DIR} REQUIRED
)
find_path(MUJOCO_INCLUDE mujoco/mujoco.h HINTS ${MUJOCO_INCLUDE_DIR} REQUIRED)
message("MuJoCo is at ${MUJOCO_LIBRARY}")
@@ -140,7 +140,7 @@ findorfetch(
GIT_REPO
https://github.com/abseil/abseil-cpp
GIT_TAG
d9e4955c65cd4367dd6bf46f4ccb8cd3d100540b # LTS 20250127.1
bc257a88f7c1939f24e0379f14a3589e926c950c # LTS 20250512.0
TARGETS
${MUJOCO_PYTHON_ABSL_TARGETS}
EXCLUDE_FROM_ALL
@@ -173,7 +173,7 @@ findorfetch(
GIT_REPO
https://gitlab.com/libeigen/eigen
GIT_TAG
464c1d097891a1462ab28bf8bb763c1683883892
d0b490ee091629068e0c11953419eb089f9e6bb2
TARGETS
Eigen3::Eigen
EXCLUDE_FROM_ALL
+267 -41
View File
@@ -8987,46 +8987,6 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([
),
doc='Attach child to a parent, return the attached element if success or NULL otherwise.', # pylint: disable=line-too-long
)),
('mjs_detachBody',
FunctionDecl(
name='mjs_detachBody',
return_type=ValueType(name='int'),
parameters=(
FunctionParameterDecl(
name='s',
type=PointerType(
inner_type=ValueType(name='mjSpec'),
),
),
FunctionParameterDecl(
name='b',
type=PointerType(
inner_type=ValueType(name='mjsBody'),
),
),
),
doc='Delete body and descendants from mjSpec, remove all references, return 0 on success.', # pylint: disable=line-too-long
)),
('mjs_detachDefault',
FunctionDecl(
name='mjs_detachDefault',
return_type=ValueType(name='int'),
parameters=(
FunctionParameterDecl(
name='s',
type=PointerType(
inner_type=ValueType(name='mjSpec'),
),
),
FunctionParameterDecl(
name='d',
type=PointerType(
inner_type=ValueType(name='mjsDefault'),
),
),
),
doc='Delete default class and descendants from mjSpec, remove all references, return 0 on success.', # pylint: disable=line-too-long
)),
('mjs_addBody',
FunctionDecl(
name='mjs_addBody',
@@ -9202,6 +9162,12 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([
name='mjs_delete',
return_type=ValueType(name='int'),
parameters=(
FunctionParameterDecl(
name='spec',
type=PointerType(
inner_type=ValueType(name='mjSpec'),
),
),
FunctionParameterDecl(
name='element',
type=PointerType(
@@ -9209,7 +9175,7 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([
),
),
),
doc='Delete object corresponding to the given element, return 0 on success.', # pylint: disable=line-too-long
doc='Remove object corresponding to the given element, return 0 on success.', # pylint: disable=line-too-long
)),
('mjs_addActuator',
FunctionDecl(
@@ -9551,6 +9517,266 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([
),
doc='Add default.',
)),
('mjs_setToMotor',
FunctionDecl(
name='mjs_setToMotor',
return_type=PointerType(
inner_type=ValueType(name='char', is_const=True),
),
parameters=(
FunctionParameterDecl(
name='actuator',
type=PointerType(
inner_type=ValueType(name='mjsActuator'),
),
),
),
doc='Set actuator to motor, return error if any.',
)),
('mjs_setToPosition',
FunctionDecl(
name='mjs_setToPosition',
return_type=PointerType(
inner_type=ValueType(name='char', is_const=True),
),
parameters=(
FunctionParameterDecl(
name='actuator',
type=PointerType(
inner_type=ValueType(name='mjsActuator'),
),
),
FunctionParameterDecl(
name='kp',
type=ValueType(name='double'),
),
FunctionParameterDecl(
name='kv',
type=ArrayType(
inner_type=ValueType(name='double'),
extents=(1,),
),
),
FunctionParameterDecl(
name='dampratio',
type=ArrayType(
inner_type=ValueType(name='double'),
extents=(1,),
),
),
FunctionParameterDecl(
name='timeconst',
type=ArrayType(
inner_type=ValueType(name='double'),
extents=(1,),
),
),
FunctionParameterDecl(
name='inheritrange',
type=ValueType(name='double'),
),
),
doc='Set actuator to position, return error if any.',
)),
('mjs_setToIntVelocity',
FunctionDecl(
name='mjs_setToIntVelocity',
return_type=PointerType(
inner_type=ValueType(name='char', is_const=True),
),
parameters=(
FunctionParameterDecl(
name='actuator',
type=PointerType(
inner_type=ValueType(name='mjsActuator'),
),
),
FunctionParameterDecl(
name='kp',
type=ValueType(name='double'),
),
FunctionParameterDecl(
name='kv',
type=ArrayType(
inner_type=ValueType(name='double'),
extents=(1,),
),
),
FunctionParameterDecl(
name='dampratio',
type=ArrayType(
inner_type=ValueType(name='double'),
extents=(1,),
),
),
FunctionParameterDecl(
name='timeconst',
type=ArrayType(
inner_type=ValueType(name='double'),
extents=(1,),
),
),
FunctionParameterDecl(
name='inheritrange',
type=ValueType(name='double'),
),
),
doc='Set actuator to integrated velocity, return error if any.',
)),
('mjs_setToVelocity',
FunctionDecl(
name='mjs_setToVelocity',
return_type=PointerType(
inner_type=ValueType(name='char', is_const=True),
),
parameters=(
FunctionParameterDecl(
name='actuator',
type=PointerType(
inner_type=ValueType(name='mjsActuator'),
),
),
FunctionParameterDecl(
name='kv',
type=ValueType(name='double'),
),
),
doc='Set actuator to velocity servo, return error if any.',
)),
('mjs_setToDamper',
FunctionDecl(
name='mjs_setToDamper',
return_type=PointerType(
inner_type=ValueType(name='char', is_const=True),
),
parameters=(
FunctionParameterDecl(
name='actuator',
type=PointerType(
inner_type=ValueType(name='mjsActuator'),
),
),
FunctionParameterDecl(
name='kv',
type=ValueType(name='double'),
),
),
doc='Set actuator to activate damper, return error if any.',
)),
('mjs_setToCylinder',
FunctionDecl(
name='mjs_setToCylinder',
return_type=PointerType(
inner_type=ValueType(name='char', is_const=True),
),
parameters=(
FunctionParameterDecl(
name='actuator',
type=PointerType(
inner_type=ValueType(name='mjsActuator'),
),
),
FunctionParameterDecl(
name='timeconst',
type=ValueType(name='double'),
),
FunctionParameterDecl(
name='bias',
type=ValueType(name='double'),
),
FunctionParameterDecl(
name='area',
type=ValueType(name='double'),
),
FunctionParameterDecl(
name='diameter',
type=ValueType(name='double'),
),
),
doc='Set actuator to hydraulic or pneumatic cylinder, return error if any.', # pylint: disable=line-too-long
)),
('mjs_setToMuscle',
FunctionDecl(
name='mjs_setToMuscle',
return_type=PointerType(
inner_type=ValueType(name='char', is_const=True),
),
parameters=(
FunctionParameterDecl(
name='actuator',
type=PointerType(
inner_type=ValueType(name='mjsActuator'),
),
),
FunctionParameterDecl(
name='timeconst',
type=ArrayType(
inner_type=ValueType(name='double'),
extents=(2,),
),
),
FunctionParameterDecl(
name='tausmooth',
type=ValueType(name='double'),
),
FunctionParameterDecl(
name='range',
type=ArrayType(
inner_type=ValueType(name='double'),
extents=(2,),
),
),
FunctionParameterDecl(
name='force',
type=ValueType(name='double'),
),
FunctionParameterDecl(
name='scale',
type=ValueType(name='double'),
),
FunctionParameterDecl(
name='lmin',
type=ValueType(name='double'),
),
FunctionParameterDecl(
name='lmax',
type=ValueType(name='double'),
),
FunctionParameterDecl(
name='vmax',
type=ValueType(name='double'),
),
FunctionParameterDecl(
name='fpmax',
type=ValueType(name='double'),
),
FunctionParameterDecl(
name='fvmax',
type=ValueType(name='double'),
),
),
doc='Set actuator to muscle, return error if any.a',
)),
('mjs_setToAdhesion',
FunctionDecl(
name='mjs_setToAdhesion',
return_type=PointerType(
inner_type=ValueType(name='char', is_const=True),
),
parameters=(
FunctionParameterDecl(
name='actuator',
type=PointerType(
inner_type=ValueType(name='mjsActuator'),
),
),
FunctionParameterDecl(
name='gain',
type=ValueType(name='double'),
),
),
doc='Set actuator to active adhesion, return error if any.',
)),
('mjs_addMesh',
FunctionDecl(
name='mjs_addMesh',
+55
View File
@@ -278,6 +278,11 @@ STRUCTS: Mapping[str, StructDecl] = dict([
name='global',
type=AnonymousStructDecl(
fields=(
StructFieldDecl(
name='cameraid',
type=ValueType(name='int'),
doc='initial camera id (-1: free)',
),
StructFieldDecl(
name='orthographic',
type=ValueType(name='int'),
@@ -868,6 +873,11 @@ STRUCTS: Mapping[str, StructDecl] = dict([
type=ValueType(name='int'),
doc='number of dynamic bounding volumes (aabb stored in mjData)', # pylint: disable=line-too-long
),
StructFieldDecl(
name='noct',
type=ValueType(name='int'),
doc='number of total octree cells in all meshes',
),
StructFieldDecl(
name='njnt',
type=ValueType(name='int'),
@@ -1555,6 +1565,30 @@ STRUCTS: Mapping[str, StructDecl] = dict([
doc='local bounding box (center, size)',
array_extent=('nbvhstatic', 6),
),
StructFieldDecl(
name='oct_depth',
type=PointerType(
inner_type=ValueType(name='int'),
),
doc='depth in the octree',
array_extent=('noct',),
),
StructFieldDecl(
name='oct_child',
type=PointerType(
inner_type=ValueType(name='int'),
),
doc='children of octree node',
array_extent=('noct', 8),
),
StructFieldDecl(
name='oct_aabb',
type=PointerType(
inner_type=ValueType(name='mjtNum'),
),
doc='octree node bounding box (center, size)',
array_extent=('noct', 6),
),
StructFieldDecl(
name='jnt_type',
type=PointerType(
@@ -2915,6 +2949,22 @@ STRUCTS: Mapping[str, StructDecl] = dict([
doc='number of bvh',
array_extent=('nmesh',),
),
StructFieldDecl(
name='mesh_octadr',
type=PointerType(
inner_type=ValueType(name='int'),
),
doc='address of octree root',
array_extent=('nmesh',),
),
StructFieldDecl(
name='mesh_octnum',
type=PointerType(
inner_type=ValueType(name='int'),
),
doc='number of octree nodes',
array_extent=('nmesh',),
),
StructFieldDecl(
name='mesh_normaladr',
type=PointerType(
@@ -6868,6 +6918,11 @@ STRUCTS: Mapping[str, StructDecl] = dict([
type=ValueType(name='int'),
doc='depth of the bounding volume hierarchy to be visualized',
),
StructFieldDecl(
name='oct_depth',
type=ValueType(name='int'),
doc='depth of the octree to be visualized',
),
StructFieldDecl(
name='flex_layer',
type=ValueType(name='int'),
+4 -4
View File
@@ -7,13 +7,13 @@
<key>CFBundleIdentifier</key>
<string>org.mujoco.mjpython</string>
<key>CFBundleVersion</key>
<string>3.3.3</string>
<string>3.3.4</string>
<key>CFBundleGetInfoString</key>
<string>3.3.3</string>
<string>3.3.4</string>
<key>CFBundleLongVersionString</key>
<string>3.3.3</string>
<string>3.3.4</string>
<key>CFBundleShortVersionString</key>
<string>3.3.3</string>
<string>3.3.4</string>
<key>CFBundleExecutable</key>
<string>mjpython</string>
<key>CFBundleIconFile</key>
+1
View File
@@ -336,6 +336,7 @@ PYBIND11_MODULE(_simulate, pymodule) {
CallIfNotNull(&mujoco::Simulate::LoadMessageClear),
py::call_guard<py::gil_scoped_release>())
.def("sync", CallIfNotNull(&mujoco::Simulate::Sync),
py::arg("state_only") = false,
py::call_guard<py::gil_scoped_release>())
.def("add_to_history", CallIfNotNull(&mujoco::Simulate::AddToHistory),
py::call_guard<py::gil_scoped_release>())
+162 -38
View File
@@ -396,8 +396,8 @@ PYBIND11_MODULE(_specs, m) {
return mjs_addDefault(spec->ptr, classname.c_str(), parent);
},
py::return_value_policy::reference_internal);
mjSpec.def("detach_default", [](MjSpec& self, raw::MjsDefault& def) {
if (mjs_detachDefault(self.ptr, &def) != 0) {
mjSpec.def("delete", [](MjSpec& self, raw::MjsDefault& def) {
if (mjs_delete(self.ptr, def.element) != 0) {
throw pybind11::value_error(mjs_getError(self.ptr));
}
});
@@ -407,8 +407,8 @@ PYBIND11_MODULE(_specs, m) {
return mjs_getSpecDefault(self.ptr);
},
py::return_value_policy::reference_internal);
mjSpec.def("detach_body", [](MjSpec& self, raw::MjsBody& body) {
mjs_detachBody(self.ptr, &body);
mjSpec.def("delete", [](MjSpec& self, raw::MjsBody& body) {
mjs_delete(self.ptr, body.element);
});
mjSpec.def(
"attach",
@@ -759,7 +759,9 @@ PYBIND11_MODULE(_specs, m) {
// ============================= MJSFRAME ====================================
mjsFrame.def("delete", [](raw::MjsFrame& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsFrame& obj) {
mjs_delete(self.ptr, obj.element);
});
mjsFrame.def("set_frame", [](raw::MjsFrame& self, raw::MjsFrame& frame) {
if (mjs_setFrame(self.element, &frame) != 0) {
throw pybind11::value_error(mjs_getError(mjs_getSpec(self.element)));
@@ -796,7 +798,9 @@ PYBIND11_MODULE(_specs, m) {
py::return_value_policy::reference_internal);
// ============================= MJSGEOM =====================================
mjsGeom.def("delete", [](raw::MjsGeom& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsGeom& obj) {
mjs_delete(self.ptr, obj.element);
});
mjsGeom.def("set_frame", [](raw::MjsGeom& self, raw::MjsFrame& frame) {
if (mjs_setFrame(self.element, &frame) != 0) {
throw pybind11::value_error(mjs_getError(mjs_getSpec(self.element)));
@@ -824,7 +828,9 @@ PYBIND11_MODULE(_specs, m) {
py::return_value_policy::reference_internal);
// ============================= MJSJOINT ====================================
mjsJoint.def("delete", [](raw::MjsJoint& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsJoint& obj) {
mjs_delete(self.ptr, obj.element);
});
mjsJoint.def("set_frame", [](raw::MjsJoint& self, raw::MjsFrame& frame) {
if (mjs_setFrame(self.element, &frame) != 0) {
throw pybind11::value_error(mjs_getError(mjs_getSpec(self.element)));
@@ -852,7 +858,9 @@ PYBIND11_MODULE(_specs, m) {
py::return_value_policy::reference_internal);
// ============================= MJSSITE =====================================
mjsSite.def("delete", [](raw::MjsSite& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsSite& obj) {
mjs_delete(self.ptr, obj.element);
});
mjsSite.def("set_frame", [](raw::MjsSite& self, raw::MjsFrame& frame) {
if (mjs_setFrame(self.element, &frame) != 0) {
throw pybind11::value_error(mjs_getError(mjs_getSpec(self.element)));
@@ -897,8 +905,9 @@ PYBIND11_MODULE(_specs, m) {
py::return_value_policy::reference_internal);
// ============================= MJSCAMERA ===================================
mjsCamera.def("delete",
[](raw::MjsCamera& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsCamera& obj) {
mjs_delete(self.ptr, obj.element);
});
mjsCamera.def("set_frame", [](raw::MjsCamera& self, raw::MjsFrame& frame) {
if (mjs_setFrame(self.element, &frame) != 0) {
throw pybind11::value_error(mjs_getError(mjs_getSpec(self.element)));
@@ -926,7 +935,9 @@ PYBIND11_MODULE(_specs, m) {
py::return_value_policy::reference_internal);
// ============================= MJSLIGHT ====================================
mjsLight.def("delete", [](raw::MjsLight& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsLight& obj) {
mjs_delete(self.ptr, obj.element);
});
mjsLight.def("set_frame", [](raw::MjsLight& self, raw::MjsFrame& frame) {
if (mjs_setFrame(self.element, &frame) != 0) {
throw pybind11::value_error(mjs_getError(mjs_getSpec(self.element)));
@@ -954,8 +965,9 @@ PYBIND11_MODULE(_specs, m) {
py::return_value_policy::reference_internal);
// ============================= MJSMATERIAL =================================
mjsMaterial.def("delete",
[](raw::MjsMaterial& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsMaterial& obj) {
mjs_delete(self.ptr, obj.element);
});
mjsMaterial.def_property(
"classname",
[](raw::MjsMaterial& self) -> raw::MjsDefault* {
@@ -966,7 +978,9 @@ PYBIND11_MODULE(_specs, m) {
});
// ============================= MJSMESH =====================================
mjsMesh.def("delete", [](raw::MjsMesh& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsMesh& obj) {
mjs_delete(self.ptr, obj.element);
});
mjsMesh.def_property(
"classname",
[](raw::MjsMesh& self) -> raw::MjsDefault* {
@@ -977,7 +991,9 @@ PYBIND11_MODULE(_specs, m) {
});
// ============================= MJSPAIR =====================================
mjsPair.def("delete", [](raw::MjsPair& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsPair& obj) {
mjs_delete(self.ptr, obj.element);
});
mjsPair.def_property(
"classname",
[](raw::MjsPair& self) -> raw::MjsDefault* {
@@ -988,8 +1004,9 @@ PYBIND11_MODULE(_specs, m) {
});
// ============================= MJSEQUAL ====================================
mjsEquality.def("delete",
[](raw::MjsEquality& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsEquality& obj) {
mjs_delete(self.ptr, obj.element);
});
mjsEquality.def_property(
"classname",
[](raw::MjsEquality& self) -> raw::MjsDefault* {
@@ -1000,8 +1017,9 @@ PYBIND11_MODULE(_specs, m) {
});
// ============================= MJSACTUATOR =================================
mjsActuator.def("delete",
[](raw::MjsActuator& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsActuator& obj) {
mjs_delete(self.ptr, obj.element);
});
mjsActuator.def_property(
"classname",
[](raw::MjsActuator& self) -> raw::MjsDefault* {
@@ -1010,10 +1028,100 @@ PYBIND11_MODULE(_specs, m) {
[](raw::MjsActuator& self, raw::MjsDefault& default_) -> void {
mjs_setDefault(self.element, &default_);
});
mjsActuator.def("set_to_motor", [](raw::MjsActuator* self) {
std::string err = mjs_setToMotor(self);
if (!err.empty()) {
throw pybind11::value_error(err);
}
});
mjsActuator.def(
"set_to_position",
[](raw::MjsActuator* self, double kp, double kv, double dampratio,
double timeconst, bool inheritrange) {
std::string err = mjs_setToPosition(
self, kp, kv == -1 ? nullptr : &kv,
dampratio == -1 ? nullptr : &dampratio,
timeconst == -1 ? nullptr : &timeconst, inheritrange);
if (!err.empty()) {
throw pybind11::value_error(err);
}
},
py::arg("kp"), py::arg("kv") = -1, py::arg("dampratio") = -1,
py::arg("timeconst") = -1, py::arg("inheritrange") = false);
mjsActuator.def(
"set_to_intvelocity",
[](raw::MjsActuator* self, double kp, double kv, double dampratio,
double timeconst, bool inheritrange) {
std::string err = mjs_setToIntVelocity(
self, kp, kv == -1 ? nullptr : &kv,
dampratio == -1 ? nullptr : &dampratio,
timeconst == -1 ? nullptr : &timeconst, inheritrange);
if (!err.empty()) {
throw pybind11::value_error(err);
}
},
py::arg("kp"), py::arg("kv") = -1, py::arg("dampratio") = -1,
py::arg("timeconst") = -1, py::arg("inheritrange") = false);
mjsActuator.def(
"set_to_velocity",
[](raw::MjsActuator* self, double kv) {
std::string err = mjs_setToVelocity(self, kv);
if (!err.empty()) {
throw pybind11::value_error(err);
}
},
py::arg("kv"));
mjsActuator.def(
"set_to_damper",
[](raw::MjsActuator* self, double kv) {
std::string err = mjs_setToDamper(self, kv);
if (!err.empty()) {
throw pybind11::value_error(err);
}
},
py::arg("kv"));
mjsActuator.def(
"set_to_cylinder",
[](raw::MjsActuator* self, double timeconst, double bias, double area,
double diameter) {
std::string err =
mjs_setToCylinder(self, timeconst, bias, area, diameter);
if (!err.empty()) {
throw pybind11::value_error(err);
}
},
py::arg("timeconst"), py::arg("bias"), py::arg("area"),
py::arg("diameter") = -1);
mjsActuator.def(
"set_to_muscle",
[](raw::MjsActuator* self, double timeconst[2], double tausmooth,
double range[2], double force, double scale, double lmin, double lmax,
double vmax, double fpmax, double fvmax) {
std::string err =
mjs_setToMuscle(self, timeconst, tausmooth, range, force, scale,
lmin, lmax, vmax, fpmax, fvmax);
if (!err.empty()) {
throw pybind11::value_error(err);
}
},
py::arg("timeconst") = -1, py::arg("tausmooth"),
py::arg("range") = std::array<double, 2>{-1, -1}, py::arg("force") = -1,
py::arg("scale") = -1, py::arg("lmin") = -1, py::arg("lmax") = -1,
py::arg("vmax") = -1, py::arg("fpmax") = -1, py::arg("fvmax") = -1);
mjsActuator.def(
"set_to_adhesion",
[](raw::MjsActuator* self, double gain) {
std::string err = mjs_setToAdhesion(self, gain);
if (!err.empty()) {
throw pybind11::value_error(err);
}
},
py::arg("gain"));
// ============================= MJSTENDON ===================================
mjsTendon.def("delete",
[](raw::MjsTendon& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsTendon& obj) {
mjs_delete(self.ptr, obj.element);
});
mjsTendon.def(
"default",
[](raw::MjsTendon& self) -> raw::MjsDefault* {
@@ -1046,39 +1154,54 @@ PYBIND11_MODULE(_specs, m) {
py::return_value_policy::reference_internal);
// ============================= MJSSENSOR ===================================
mjsSensor.def("delete",
[](raw::MjsSensor& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsSensor& obj) {
mjs_delete(self.ptr, obj.element);
});
// ============================= MJSFLEX =====================================
mjsFlex.def("delete", [](raw::MjsFlex& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsFlex& obj) {
mjs_delete(self.ptr, obj.element);
});
// ============================= MJSHFIELD ===================================
mjsHField.def("delete",
[](raw::MjsHField& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsHField& obj) {
mjs_delete(self.ptr, obj.element);
});
// ============================= MJSSKIN =====================================
mjsSkin.def("delete", [](raw::MjsSkin& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsSkin& obj) {
mjs_delete(self.ptr, obj.element);
});
// ============================= MJSTEXTURE ==================================
mjsTexture.def("delete",
[](raw::MjsTexture& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsTexture& obj) {
mjs_delete(self.ptr, obj.element);
});
// ============================= MJSKEY ======================================
mjsKey.def("delete", [](raw::MjsKey& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsKey& obj) {
mjs_delete(self.ptr, obj.element);
});
// ============================= MJSTEXT =====================================
mjsText.def("delete", [](raw::MjsText& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsText& obj) {
mjs_delete(self.ptr, obj.element);
});
// ============================= MJSNUMERIC ==================================
mjsNumeric.def("delete",
[](raw::MjsNumeric& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsNumeric& obj) {
mjs_delete(self.ptr, obj.element);
});
// ============================= MJSEXCLUDE ==================================
mjsExclude.def("delete",
[](raw::MjsExclude& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsExclude& obj) {
mjs_delete(self.ptr, obj.element);
});
// ============================= MJSTUPLE ====================================
mjsTuple.def("delete", [](raw::MjsTuple& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsTuple& obj) {
mjs_delete(self.ptr, obj.element);
});
// ============================= MJSPLUGIN ===================================
mjsPlugin.def_property(
@@ -1087,8 +1210,9 @@ PYBIND11_MODULE(_specs, m) {
[](raw::MjsPlugin& self, raw::MjsPlugin* other) {
self.element = other->element;
});
mjsPlugin.def("delete",
[](raw::MjsPlugin& self) { mjs_delete(self.element); });
mjSpec.def("delete", [](MjSpec& self, raw::MjsPlugin& obj) {
mjs_delete(self.ptr, obj.element);
});
mjsPlugin.def_property(
"config",
[](raw::MjsPlugin& self) -> py::dict {
+70 -6
View File
@@ -604,7 +604,7 @@ class SpecsTest(absltest.TestCase):
# test delete default
def1 = spec.find_default('def1')
spec.detach_default(def1)
spec.delete(def1)
def1 = spec.find_default('def1')
self.assertIsNone(def1)
@@ -838,9 +838,9 @@ class SpecsTest(absltest.TestCase):
self.assertIsNotNone(site)
self.assertEqual(site, spec.site('head'))
site.delete()
spec.sensors[-1].delete()
spec.sensors[-1].delete()
spec.delete(site)
spec.delete(spec.sensors[-1])
spec.delete(spec.sensors[-1])
model = spec.compile()
self.assertIsNotNone(model)
@@ -861,7 +861,7 @@ class SpecsTest(absltest.TestCase):
body = spec.worldbody.add_body()
body.plugin = plugin
body.plugin.plugin_name = 'mujoco.elasticity.cable'
body.plugin.name = 'instance_name'
body.plugin.active = True
geom = body.add_geom()
@@ -930,7 +930,7 @@ class SpecsTest(absltest.TestCase):
""")
plugin = spec.plugins[0]
self.assertIsNotNone(plugin)
plugin.delete()
spec.delete(plugin)
model = spec.compile()
self.assertIsNotNone(model)
@@ -1001,6 +1001,14 @@ class SpecsTest(absltest.TestCase):
texture.data = np.zeros((2, 2, 3), dtype=np.uint8).tobytes()
spec.compile()
def test_find_unnamed_texture(self):
spec = mujoco.MjSpec()
texture_file = spec.add_texture(file='file.png')
texture_name = spec.add_texture(name='name')
self.assertEqual(spec.texture('file'), texture_file)
self.assertEqual(spec.texture('name'), texture_name)
self.assertIsNone(spec.texture('none'))
def test_attach_units(self):
child = mujoco.MjSpec()
parent = mujoco.MjSpec()
@@ -1232,5 +1240,61 @@ class SpecsTest(absltest.TestCase):
self.assertGreater(spec3._address, 0)
self.assertLen({spec1._address, spec2._address, spec3._address}, 3)
def test_actuator_shortname(self):
spec = mujoco.MjSpec()
actuator = spec.add_actuator(
gainprm=np.zeros((10, 1)),
dyntype=mujoco.mjtDyn.mjDYN_FILTER,
gaintype=mujoco.mjtGain.mjGAIN_AFFINE,
biastype=mujoco.mjtBias.mjBIAS_AFFINE,
)
actuator.set_to_motor()
self.assertEqual(actuator.gainprm[0], 1)
self.assertEqual(actuator.dyntype, mujoco.mjtDyn.mjDYN_NONE)
self.assertEqual(actuator.gaintype, mujoco.mjtGain.mjGAIN_FIXED)
self.assertEqual(actuator.biastype, mujoco.mjtBias.mjBIAS_NONE)
actuator.set_to_position(kp=2.0, kv=3.0, timeconst=4.0, inheritrange=True)
self.assertEqual(actuator.gainprm[0], 2)
self.assertEqual(actuator.biasprm[1], -2)
self.assertEqual(actuator.biasprm[2], -3)
self.assertEqual(actuator.dynprm[0], 4)
self.assertEqual(actuator.dyntype, mujoco.mjtDyn.mjDYN_FILTEREXACT)
self.assertEqual(actuator.gaintype, mujoco.mjtGain.mjGAIN_FIXED)
self.assertEqual(actuator.biastype, mujoco.mjtBias.mjBIAS_AFFINE)
self.assertEqual(actuator.inheritrange, True)
actuator.set_to_intvelocity(
kp=2.0, kv=3.0, timeconst=4.0, inheritrange=True
)
self.assertEqual(actuator.gainprm[0], 2)
self.assertEqual(actuator.biasprm[1], -2)
self.assertEqual(actuator.biasprm[2], -3)
self.assertEqual(actuator.dynprm[0], 4)
self.assertEqual(actuator.dyntype, mujoco.mjtDyn.mjDYN_INTEGRATOR)
self.assertEqual(actuator.gaintype, mujoco.mjtGain.mjGAIN_FIXED)
self.assertEqual(actuator.biastype, mujoco.mjtBias.mjBIAS_AFFINE)
self.assertEqual(actuator.inheritrange, True)
actuator.set_to_velocity(kv=5.0)
self.assertEqual(actuator.gainprm[0], 5)
self.assertEqual(actuator.biasprm[2], -5)
self.assertEqual(actuator.dyntype, mujoco.mjtDyn.mjDYN_NONE)
self.assertEqual(actuator.gaintype, mujoco.mjtGain.mjGAIN_FIXED)
self.assertEqual(actuator.biastype, mujoco.mjtBias.mjBIAS_AFFINE)
actuator.set_to_damper(kv=6.0)
self.assertEqual(actuator.gainprm[0], 0)
self.assertEqual(actuator.gainprm[2], -6)
self.assertEqual(actuator.dyntype, mujoco.mjtDyn.mjDYN_NONE)
self.assertEqual(actuator.gaintype, mujoco.mjtGain.mjGAIN_AFFINE)
self.assertEqual(actuator.biastype, mujoco.mjtBias.mjBIAS_NONE)
actuator.set_to_adhesion(gain=7.0)
self.assertEqual(actuator.gainprm[0], 7)
self.assertEqual(actuator.dyntype, mujoco.mjtDyn.mjDYN_NONE)
self.assertEqual(actuator.gaintype, mujoco.mjtGain.mjGAIN_FIXED)
self.assertEqual(actuator.biastype, mujoco.mjtBias.mjBIAS_NONE)
if __name__ == '__main__':
absltest.main()
+2
View File
@@ -175,6 +175,7 @@ PYBIND11_MODULE(_structs, m) {
});
DefineStructFunctions(mjVisualGlobal);
#define X(var) mjVisualGlobal.def_readwrite(#var, &raw::MjVisualGlobal::var)
X(cameraid);
X(orthographic);
X(fovy);
X(ipd);
@@ -1109,6 +1110,7 @@ This is useful for example when the MJB is not available as a file on disk.)"));
X(label);
X(frame);
X(bvh_depth);
X(oct_depth);
X(flex_layer);
#undef X
+2 -2
View File
@@ -230,10 +230,10 @@ class Handle:
return sim.lock()
return contextlib.nullcontext()
def sync(self):
def sync(self, state_only: bool = False):
sim = self._get_sim()
if sim is not None:
sim.sync() # locks internally
sim.sync(state_only) # locks internally
def update_hfield(self, hfieldid: int):
sim = self._get_sim()
+3 -3
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "mujoco"
version = "3.3.3"
version = "3.3.4"
authors = [
{name = "Google DeepMind", email = "mujoco@deepmind.com"},
]
@@ -35,9 +35,9 @@ dynamic = ["readme", "scripts"]
[project.urls]
Homepage = "https://github.com/google-deepmind/mujoco"
Documentation = "https://mujoco.readthedocs.io/en/3.3.3"
Documentation = "https://mujoco.readthedocs.io/en/3.3.4"
Repository = "https://github.com/google-deepmind/mujoco"
Changelog = "https://mujoco.readthedocs.io/en/3.3.3/changelog.html"
Changelog = "https://mujoco.readthedocs.io/en/3.3.4/changelog.html"
[tool.setuptools]
include-package-data = false
+197 -78
View File
@@ -5,6 +5,7 @@
"id": "6adc68e0-a943-44ab-9af5-4bc62cc19f34",
"metadata": {
"editable": true,
"id": "6adc68e0-a943-44ab-9af5-4bc62cc19f34",
"tags": []
},
"source": [
@@ -16,7 +17,7 @@
"\n",
"This notebook describes the `rollout` module included in the MuJoCo Python library. It performs simulation \"rollouts\" with an underlying C++ function. The rollouts can be multithreaded.\n",
"\n",
"Below, the usage of each argument is explained with examples. Then some examples for advanced use cases are provided. Finally, `rollout` is benchmarked against pure python and MJX. \n",
"Below, the usage of each argument is explained with examples. Then some examples for advanced use cases are provided. Finally, `rollout` is benchmarked against pure python and MJX.\n",
"\n",
"Note the benchmarks were designed to run on >16 thread CPU and an RTX 4090 or A100. They do not run in a reasonable amount of time on a typical free colab runtime.\n",
"\n",
@@ -41,6 +42,7 @@
"id": "5d8a6604-0948-4a42-a48d-249c7f0c462b",
"metadata": {
"editable": true,
"id": "5d8a6604-0948-4a42-a48d-249c7f0c462b",
"tags": []
},
"source": [
@@ -53,6 +55,7 @@
"id": "0f9fbad1-59d0-40ac-b2b6-99f37313670f",
"metadata": {
"editable": true,
"id": "0f9fbad1-59d0-40ac-b2b6-99f37313670f",
"tags": [
"hide-input"
]
@@ -154,7 +157,9 @@
{
"cell_type": "markdown",
"id": "fc69d0f4",
"metadata": {},
"metadata": {
"id": "fc69d0f4"
},
"source": [
"# Helper Functions"
]
@@ -165,6 +170,7 @@
"id": "082482c7",
"metadata": {
"editable": true,
"id": "082482c7",
"tags": [
"hide-input"
]
@@ -251,7 +257,7 @@
" light.cutoff = 45\n",
" light.diffuse = [0.8, 0.8, 0.8]\n",
" light.dir = [0, 0, -1]\n",
" light.directional = 0\n",
" light.type = mujoco.mjtLightType.mjLIGHT_SPOT\n",
" light.exponent = 10\n",
" light.headlight = 0\n",
" light.specular = [0.3, 0.3, 0.3]\n",
@@ -267,7 +273,9 @@
{
"cell_type": "markdown",
"id": "c0570c2c",
"metadata": {},
"metadata": {
"id": "c0570c2c"
},
"source": [
"# Using `rollout`\n",
"\n",
@@ -280,9 +288,11 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 0,
"id": "849b93e5",
"metadata": {},
"metadata": {
"id": "849b93e5"
},
"outputs": [],
"source": [
"#@title Benchmarked models\n",
@@ -368,7 +378,9 @@
{
"cell_type": "markdown",
"id": "55d171f7-541b-4441-aa18-da86d6716410",
"metadata": {},
"metadata": {
"id": "55d171f7-541b-4441-aa18-da86d6716410"
},
"source": [
"## Usage\n",
"\n",
@@ -380,9 +392,11 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 0,
"id": "9cd2f94a-11df-4247-986c-5a56af69a1f5",
"metadata": {},
"metadata": {
"id": "9cd2f94a-11df-4247-986c-5a56af69a1f5"
},
"outputs": [],
"source": [
"print(rollout.rollout.__doc__)"
@@ -392,6 +406,7 @@
"cell_type": "markdown",
"id": "b6f7a094-8352-4b07-99ee-5278e3036cd5",
"metadata": {
"id": "b6f7a094-8352-4b07-99ee-5278e3036cd5",
"tags": []
},
"source": [
@@ -403,9 +418,11 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 0,
"id": "849af5f2-9de1-4cb9-bc3a-c9b7acf0e3fe",
"metadata": {},
"metadata": {
"id": "849af5f2-9de1-4cb9-bc3a-c9b7acf0e3fe"
},
"outputs": [],
"source": [
"nbatch = 100 # Simulate this many tops\n",
@@ -437,16 +454,20 @@
{
"cell_type": "markdown",
"id": "aa2cf151-bf9a-4a23-b7fe-6a766979d93f",
"metadata": {},
"metadata": {
"id": "aa2cf151-bf9a-4a23-b7fe-6a766979d93f"
},
"source": [
"Our model has an angular velocity sensor the middle of the top. Let's plot the response using the `sensordata` array that rollout returns."
]
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 0,
"id": "957b8566-da31-410b-b385-e78241c5247a",
"metadata": {},
"metadata": {
"id": "957b8566-da31-410b-b385-e78241c5247a"
},
"outputs": [],
"source": [
"plt.figure(figsize=(12, 8))\n",
@@ -462,7 +483,9 @@
{
"cell_type": "markdown",
"id": "58044bc1-f98c-4bbf-a703-40ba075552a0",
"metadata": {},
"metadata": {
"id": "58044bc1-f98c-4bbf-a703-40ba075552a0"
},
"source": [
"### Example: different models\n",
"100 gray tops is kind of boring. It would be better if they were colorful and different sizes!\n",
@@ -474,9 +497,11 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 0,
"id": "7c39e79e-8942-4fea-b306-ea0cb3c826e2",
"metadata": {},
"metadata": {
"id": "7c39e79e-8942-4fea-b306-ea0cb3c826e2"
},
"outputs": [],
"source": [
"# Make 100 tippe tops with different colors and sizes\n",
@@ -544,16 +569,20 @@
{
"cell_type": "markdown",
"id": "cf485c08-72be-4169-89b6-9d93df8ebbe3",
"metadata": {},
"metadata": {
"id": "cf485c08-72be-4169-89b6-9d93df8ebbe3"
},
"source": [
"Because the models are now different, the measurements of the gyro sensor are not consistent even though the initial state for each rollout was the same."
]
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 0,
"id": "b8a5d3d4-24e7-41a1-b3bd-7b63c1812b03",
"metadata": {},
"metadata": {
"id": "b8a5d3d4-24e7-41a1-b3bd-7b63c1812b03"
},
"outputs": [],
"source": [
"plt.figure(figsize=(12, 8))\n",
@@ -569,7 +598,9 @@
{
"cell_type": "markdown",
"id": "3841a669-6cd1-427e-a629-20a10a6e3a34",
"metadata": {},
"metadata": {
"id": "3841a669-6cd1-427e-a629-20a10a6e3a34"
},
"source": [
"### Example: control inputs\n",
"Open loop controls can be passed to `rollout` via the `control` argument. If passed, `nstep` no longer needs to be specified as it can be inferred from the size of `control`.\n",
@@ -579,9 +610,11 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 0,
"id": "2a184873-8d24-45da-b444-8d21f5dcd733",
"metadata": {},
"metadata": {
"id": "2a184873-8d24-45da-b444-8d21f5dcd733"
},
"outputs": [],
"source": [
"# Episode parameters.\n",
@@ -632,7 +665,9 @@
{
"cell_type": "markdown",
"id": "4d89e4fb-7711-4d23-8ff3-eb5030fa8bf7",
"metadata": {},
"metadata": {
"id": "4d89e4fb-7711-4d23-8ff3-eb5030fa8bf7"
},
"source": [
"`rollout`'s `control_spec` argument can be used to indicate `control` contains values for actuators, generalized forces, cartesian forces, mocap poses, and/or the activation/deactivation of equality constraints. Internally, this is managed through [mj_setState](https://mujoco.readthedocs.io/en/stable/APIreference/APIfunctions.html#mj-setstate) and `control_spec` corresponds to `mj_setState`'s `spec` argument.\n",
"\n",
@@ -641,9 +676,11 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 0,
"id": "4b02bb61-912d-47de-a956-aadfcd4c5cd5",
"metadata": {},
"metadata": {
"id": "4b02bb61-912d-47de-a956-aadfcd4c5cd5"
},
"outputs": [],
"source": [
"xfrc_size = mujoco.mj_stateSize(humanoid_model, mujoco.mjtState.mjSTATE_XFRC_APPLIED)\n",
@@ -676,7 +713,9 @@
{
"cell_type": "markdown",
"id": "0961c3ec-a691-4875-9a55-227a3d29c472",
"metadata": {},
"metadata": {
"id": "0961c3ec-a691-4875-9a55-227a3d29c472"
},
"source": [
"# Advanced usage"
]
@@ -684,7 +723,9 @@
{
"cell_type": "markdown",
"id": "VfYIyXWcLKfg",
"metadata": {},
"metadata": {
"id": "VfYIyXWcLKfg"
},
"source": [
"## skip_checks\n",
"\n",
@@ -706,9 +747,11 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 0,
"id": "d02cc8e8-63cd-4852-ab3c-364a18025a95",
"metadata": {},
"metadata": {
"id": "d02cc8e8-63cd-4852-ab3c-364a18025a95"
},
"outputs": [],
"source": [
"nbatch = 1000\n",
@@ -752,7 +795,9 @@
{
"cell_type": "markdown",
"id": "92627030-4726-4689-be8b-f1ba75905104",
"metadata": {},
"metadata": {
"id": "92627030-4726-4689-be8b-f1ba75905104"
},
"source": [
"As expected, as `nstep` increases, the benefits of using skip checks fades quickly. However, at low nstep and high batch sizes, it can make a significant difference.\n",
"\n",
@@ -762,7 +807,9 @@
{
"cell_type": "markdown",
"id": "d32a77b5-24bd-4d17-80ac-15cc4d03731c",
"metadata": {},
"metadata": {
"id": "d32a77b5-24bd-4d17-80ac-15cc4d03731c"
},
"source": [
"## Reusing threadpools (`Rollout` class)\n",
"\n",
@@ -773,9 +820,11 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 0,
"id": "dd05bbdf-f389-4e4e-b389-d47fe976cb49",
"metadata": {},
"metadata": {
"id": "dd05bbdf-f389-4e4e-b389-d47fe976cb49"
},
"outputs": [],
"source": [
"nbatch = 100\n",
@@ -813,7 +862,9 @@
{
"cell_type": "markdown",
"id": "9b3e14a1-71f3-430d-a3c2-1aadcf6c2671",
"metadata": {},
"metadata": {
"id": "9b3e14a1-71f3-430d-a3c2-1aadcf6c2671"
},
"source": [
"## Reusing threadpools (`rollout` method)\n",
"\n",
@@ -824,9 +875,11 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 0,
"id": "b6aa6801",
"metadata": {},
"metadata": {
"id": "b6aa6801"
},
"outputs": [],
"source": [
"nbatch = 1000\n",
@@ -846,16 +899,20 @@
{
"cell_type": "markdown",
"id": "144378d3",
"metadata": {},
"metadata": {
"id": "144378d3"
},
"source": [
"Second, if `rollout` reuses the same threadpool between calls, it is no longer safe to call `rollout` from multiple threads. For example the following is not allowed (the offending lines are commented out to avoid crashing the interpreter):"
]
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 0,
"id": "7f46a6d8",
"metadata": {},
"metadata": {
"id": "7f46a6d8"
},
"outputs": [],
"source": [
"thread1 = threading.Thread(target=lambda: rollout.rollout(top_model, top_datas, initial_states, nstep=nstep, persistent_pool=True))\n",
@@ -871,7 +928,9 @@
{
"cell_type": "markdown",
"id": "78c1f864-5238-4e27-a7ec-d03c45484d9a",
"metadata": {},
"metadata": {
"id": "78c1f864-5238-4e27-a7ec-d03c45484d9a"
},
"source": [
"## chunk_size\n",
"\n",
@@ -882,9 +941,11 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 0,
"id": "a1be8f93",
"metadata": {},
"metadata": {
"id": "a1be8f93"
},
"outputs": [],
"source": [
"nbatch = 100\n",
@@ -928,7 +989,9 @@
{
"cell_type": "markdown",
"id": "84c746e0-5d2e-47dc-ac76-f2b6f790b7c7",
"metadata": {},
"metadata": {
"id": "84c746e0-5d2e-47dc-ac76-f2b6f790b7c7"
},
"source": [
"## Warmstarting\n",
"\n",
@@ -941,9 +1004,11 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 0,
"id": "d4d9f660-f83c-432e-a579-124a7ecab4fb",
"metadata": {},
"metadata": {
"id": "d4d9f660-f83c-432e-a579-124a7ecab4fb"
},
"outputs": [],
"source": [
"top_model_cg = copy.copy(top_model)\n",
@@ -1004,7 +1069,9 @@
{
"cell_type": "markdown",
"id": "7c2cf4fa",
"metadata": {},
"metadata": {
"id": "7c2cf4fa"
},
"source": [
"As expected, the middle animation (with warmstarting) matches the continuous rollout on the left. However, the model that did not use warmstarting diverged."
]
@@ -1012,7 +1079,9 @@
{
"cell_type": "markdown",
"id": "7944637f",
"metadata": {},
"metadata": {
"id": "7944637f"
},
"source": [
"# Benchmarks\n",
"\n",
@@ -1029,9 +1098,11 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 0,
"id": "cb6355dd",
"metadata": {},
"metadata": {
"id": "cb6355dd"
},
"outputs": [],
"source": [
"def python_rollout(model, data, nbatch, nstep):\n",
@@ -1043,7 +1114,9 @@
{
"cell_type": "markdown",
"id": "6fe4a78b",
"metadata": {},
"metadata": {
"id": "6fe4a78b"
},
"source": [
"To run nbatch rollouts with `rollout`, we need to make an array of nbatch initial states to start the rollouts from.\n",
"\n",
@@ -1055,9 +1128,11 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 0,
"id": "74f143e2",
"metadata": {},
"metadata": {
"id": "74f143e2"
},
"outputs": [],
"source": [
"def nthread_rollout(model, data, nbatch, nstep, nthread, rollout_):\n",
@@ -1071,7 +1146,9 @@
{
"cell_type": "markdown",
"id": "b75dc44c",
"metadata": {},
"metadata": {
"id": "b75dc44c"
},
"source": [
"Next, we benchmark the Python loop and `rollout` in both single threaded and multithreaded modes. The three benchmarks take about 2.5 minutes in total to run in total on an AMD 5800X3D."
]
@@ -1081,7 +1158,8 @@
"execution_count": 0,
"id": "0301e3ee",
"metadata": {
"cellView": "form"
"cellView": "form",
"id": "0301e3ee"
},
"outputs": [],
"source": [
@@ -1204,16 +1282,20 @@
{
"cell_type": "markdown",
"id": "08fb0c12",
"metadata": {},
"metadata": {
"id": "08fb0c12"
},
"source": [
"### Tippe Top Benchmark"
]
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 0,
"id": "f7e54830",
"metadata": {},
"metadata": {
"id": "f7e54830"
},
"outputs": [],
"source": [
"nominal_nbatch = 256 # Batch size to use when testing different nstep\n",
@@ -1232,16 +1314,20 @@
{
"cell_type": "markdown",
"id": "edefb26e",
"metadata": {},
"metadata": {
"id": "edefb26e"
},
"source": [
"### Humanoid Benchmark"
]
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 0,
"id": "c9e58c6c",
"metadata": {},
"metadata": {
"id": "c9e58c6c"
},
"outputs": [],
"source": [
"nominal_nbatch = 256 # Batch size to use when testing different nstep\n",
@@ -1260,16 +1346,20 @@
{
"cell_type": "markdown",
"id": "468903bb",
"metadata": {},
"metadata": {
"id": "468903bb"
},
"source": [
"### Humanoid100 Benchmark"
]
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 0,
"id": "83d775d4",
"metadata": {},
"metadata": {
"id": "83d775d4"
},
"outputs": [],
"source": [
"nominal_nbatch = 128 # Batch size to use when testing different nstep\n",
@@ -1293,7 +1383,9 @@
{
"cell_type": "markdown",
"id": "d1133084",
"metadata": {},
"metadata": {
"id": "d1133084"
},
"source": [
"# MJX versus `rollout`"
]
@@ -1301,7 +1393,9 @@
{
"cell_type": "markdown",
"id": "c1638f2d",
"metadata": {},
"metadata": {
"id": "c1638f2d"
},
"source": [
"Next we will benchmark `rollout` and MJX using the tippe top and humanoid models (humanoid100 is not supported by MJX).\n",
"\n",
@@ -1315,7 +1409,8 @@
"execution_count": 0,
"id": "7c86d157",
"metadata": {
"cellView": "form"
"cellView": "form",
"id": "7c86d157"
},
"outputs": [],
"source": [
@@ -1436,16 +1531,20 @@
{
"cell_type": "markdown",
"id": "a2dafd2e",
"metadata": {},
"metadata": {
"id": "a2dafd2e"
},
"source": [
"### MJX Tippe Top Benchmark"
]
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 0,
"id": "98c580b0",
"metadata": {},
"metadata": {
"id": "98c580b0"
},
"outputs": [],
"source": [
"nominal_nbatch = 16384 # Batch size to use when testing different nstep\n",
@@ -1461,16 +1560,20 @@
{
"cell_type": "markdown",
"id": "205da5da",
"metadata": {},
"metadata": {
"id": "205da5da"
},
"source": [
"### MJX Humanoid Benchmark"
]
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 0,
"id": "53166ae1",
"metadata": {},
"metadata": {
"id": "53166ae1"
},
"outputs": [],
"source": [
"nominal_nbatch = 4096 # Batch size to use when testing different nstep\n",
@@ -1486,7 +1589,9 @@
{
"cell_type": "markdown",
"id": "fb2caa72",
"metadata": {},
"metadata": {
"id": "fb2caa72"
},
"source": [
"### MJX Multiple Humanoids in one model\n",
"\n",
@@ -1499,9 +1604,11 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 0,
"id": "3d6be608",
"metadata": {},
"metadata": {
"id": "3d6be608"
},
"outputs": [],
"source": [
"max_humanoids = 10\n",
@@ -1549,9 +1656,11 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 0,
"id": "b6c5fc2e",
"metadata": {},
"metadata": {
"id": "b6c5fc2e"
},
"outputs": [],
"source": [
"#@title Plot MJX nhumanoid benchmark\n",
@@ -1584,6 +1693,16 @@
"\n",
"plot_mjx_nhumanoid_benchmark(t_rollout, t_mjx, nbatch, nstep, max_humanoids)"
]
},
{
"cell_type": "code",
"execution_count": 0,
"id": "UW0aoKXK7ALd",
"metadata": {
"id": "UW0aoKXK7ALd"
},
"outputs": [],
"source": []
}
],
"metadata": {
+1 -1
View File
@@ -24,7 +24,7 @@ set(MSVC_INCREMENTAL_DEFAULT ON)
project(
mujoco_samples
VERSION 3.3.3
VERSION 3.3.4
DESCRIPTION "MuJoCo samples binaries"
HOMEPAGE_URL "https://mujoco.org"
)
+25 -1
View File
@@ -29,7 +29,7 @@ set(MUJOCO_DEP_VERSION_lodepng
project(
mujoco_simulate
VERSION 3.3.3
VERSION 3.3.4
DESCRIPTION "MuJoCo simulate binaries"
HOMEPAGE_URL "https://mujoco.org"
)
@@ -183,6 +183,14 @@ if(SIMULATE_BUILD_EXECUTABLE)
lodepng
)
if (MUJOCO_WITH_USD)
target_link_libraries(
simulate
mujoco::usd
mujoco::usd::mjcf
)
endif()
target_link_options(simulate PRIVATE ${MUJOCO_SIMULATE_LINK_OPTIONS})
if(APPLE AND MUJOCO_BUILD_MACOS_FRAMEWORKS)
@@ -249,6 +257,22 @@ if(SIMULATE_BUILD_EXECUTABLE)
MUJOCO_ENABLE_RPATH
)
if (MUJOCO_WITH_USD)
target_compile_definitions(simulate PUBLIC SIMULATE_WITH_USD)
# Add support to RPATH for the samples.
target_add_rpath(
TARGETS
simulate
INSTALL_DIRECTORY
"${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}"
LIB_DIRS
${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/mujocoUsd ${USD_DIR}/lib
DEPENDS
MUJOCO_ENABLE_RPATH
)
endif()
install(
TARGETS simulate
EXPORT ${PROJECT_NAME}
+51 -4
View File
@@ -25,6 +25,12 @@
#include <string>
#include <thread>
#if defined(SIMULATE_WITH_USD)
#include <mujoco/experimental/usd/usd.h>
#include <pxr/pxr.h>
#include <pxr/usd/usd/common.h>
#include <pxr/usd/usd/stage.h>
#endif
#include <mujoco/mujoco.h>
#include "glfw_adapter.h"
#include "simulate.h"
@@ -224,13 +230,49 @@ mjModel* LoadModel(const char* file, mj::Simulate& sim) {
char loadError[kErrorLength] = "";
mjModel* mnew = 0;
auto load_start = mj::Simulate::Clock::now();
if (mju::strlen_arr(filename)>4 &&
!std::strncmp(filename + mju::strlen_arr(filename) - 4, ".mjb",
mju::sizeof_arr(filename) - mju::strlen_arr(filename)+4)) {
std::string filename_str(filename);
std::string extension;
size_t dot_pos = filename_str.rfind('.');
if (dot_pos != std::string::npos && dot_pos < filename_str.length() - 1) {
extension = filename_str.substr(dot_pos);
}
if (extension == ".mjb") {
mnew = mj_loadModel(filename, nullptr);
if (!mnew) {
mju::strcpy_arr(loadError, "could not load binary model");
}
#if defined(SIMULATE_WITH_USD)
} else if (extension == ".usda" || extension == ".usd" ||
extension == ".usdc" || extension == ".usdz" ) {
auto stage = pxr::UsdStage::Open(filename);
if (!stage) {
mju::strcpy_arr(loadError, "could not open USD stage");
} else {
mjSpec* spec = mj_parseUSDStage(stage);
if (!spec) {
mju::strcpy_arr(loadError, "could not parse USD stage to mjSpec");
} else {
mjModel* model = mj_compile(spec, nullptr);
if (!model) {
mju::strcpy_arr(loadError,
"could not compile USD parsed mjSpec to mjModel:\n");
mju::strcat_arr(loadError, mjs_getError(spec));
} else {
// handle compile warning
if (mjs_isWarning(spec)) {
mju::strcpy_arr(
loadError,
"warning while compiling USD parsed mjSpec to mjModel:\n");
mju::strcat_arr(loadError, mjs_getError(spec));
}
}
mnew = model;
}
}
#endif
} else {
mnew = mj_loadXML(filename, nullptr, loadError, kErrorLength);
@@ -353,7 +395,7 @@ void PhysicsLoop(mj::Simulate& sim) {
// requested slow-down factor
double slowdown = 100 / sim.percentRealTime[sim.real_time_index];
// misalignment condition: distance from target sim time is bigger than syncmisalign
// misalignment condition: distance from target sim time is bigger than syncMisalign
bool misaligned =
std::abs(Seconds(elapsedCPU).count()/slowdown - elapsedSim) > syncMisalign;
@@ -495,6 +537,11 @@ int main(int argc, char** argv) {
// scan for libraries in the plugin directory to load additional plugins
scanPluginLibraries();
#if defined(SIMULATE_WITH_USD)
// If USD is used, print the version.
std::printf("OpenUSD version v%d.%02d\n", PXR_MINOR_VERSION, PXR_PATCH_VERSION);
#endif
mjvCamera cam;
mjv_defaultCamera(&cam);
+83 -5
View File
@@ -112,6 +112,7 @@ enum {
// right ui
SECT_JOINT = 0,
SECT_CONTROL,
SECT_EQUALITY,
NSECT1
};
@@ -1127,6 +1128,36 @@ void MakeControlSection(mj::Simulate* sim) {
}
}
// make equality section of UI
void MakeEqualitySection(mj::Simulate* sim) {
mjuiDef defEquality[] = {
{mjITEM_SECTION, "Equality", mjPRESERVE, nullptr, "AE"},
{mjITEM_END}
};
mjuiDef defCheckBox[] = {
{mjITEM_CHECKBYTE, "", 2, nullptr, ""},
{mjITEM_END}
};
// add section
mjui_add(&sim->ui1, defEquality);
// add equalities, exit if UI limit reached
for (int i= 0; i < sim->m_->neq && i<mjMAXUIITEM; i++) {
// set data
defCheckBox[0].pdata = &sim->d_->eq_active[i];
// set name
if (!sim->equality_names_[i].empty()) {
mju::strcpy_arr(defCheckBox[0].name, sim->equality_names_[i].c_str());
} else {
mju::sprintf_arr(defCheckBox[0].name, "equality %d", i);
}
mjui_add(&sim->ui1, defCheckBox);
}
}
// make model-dependent UI sections
void MakeUiSections(mj::Simulate* sim, const mjModel* m, const mjData* d) {
// clear model-dependent sections of UI
@@ -1140,14 +1171,23 @@ void MakeUiSections(mj::Simulate* sim, const mjModel* m, const mjData* d) {
MakeGroupSection(sim);
MakeJointSection(sim);
MakeControlSection(sim);
MakeEqualitySection(sim);
}
//---------------------------------- utility functions ---------------------------------------------
// align and scale view
void AlignAndScaleView(mj::Simulate* sim, const mjModel* m) {
// use default free camera parameters
mjv_defaultFreeCamera(m, &sim->cam);
// if the id is valid, use the initial fixed camera
if (m->vis.global.cameraid >= 0 && m->vis.global.cameraid < m->ncam) {
sim->cam.fixedcamid = m->vis.global.cameraid;
sim->cam.type = mjCAMERA_FIXED;
}
// otherwise use default free camera
else {
mjv_defaultFreeCamera(m, &sim->cam);
}
}
@@ -1872,7 +1912,7 @@ Simulate::Simulate(std::unique_ptr<PlatformUIAdapter> platform_ui,
//------------------------- Synchronize render and physics threads ---------------------------------
// operations which require holding the mutex, prevents racing with physics thread
void Simulate::Sync() {
void Simulate::Sync(bool state_only) {
MutexLock lock(this->mtx);
if (!m_) {
@@ -1931,6 +1971,18 @@ void Simulate::Sync() {
}
}
for (int i = 0; i < m_->neq; ++i) {
if (eq_active_[i] != eq_active_prev_[i]) {
d_->eq_active[i] = eq_active_[i];
} else {
eq_active_[i] = d_->eq_active[i];
}
if (eq_active_prev_[i] != eq_active_[i]) {
pending_.ui_update_equality = true;
eq_active_prev_[i] = eq_active_[i];
}
}
// in passive mode, synchronize user's mjModel with changes made via the UI
if (is_passive_) {
// synchronize mjModel.opt
@@ -2107,8 +2159,17 @@ void Simulate::Sync() {
if (!is_passive_) {
mjv_updateScene(m_, d_, &this->opt, &this->pert, &this->cam, mjCAT_ALL, &this->scn);
} else {
mjv_copyModel(m_passive_, m_);
mjv_copyData(d_passive_, m_passive_, d_);
if (state_only) {
int state_size = mj_stateSize(m_, mjSTATE_INTEGRATION);
mjtNum* state = new mjtNum[state_size];
mj_getState(m_, d_, state, mjSTATE_INTEGRATION);
mj_setState(m_passive_, d_passive_, state, mjSTATE_INTEGRATION);
mj_forward(m_passive_, d_passive_);
delete[] state;
} else {
mjv_copyModel(m_passive_, m_);
mjv_copyData(d_passive_, m_passive_, d_);
}
// append geoms from user_scn to scratch space
if (user_scn) {
@@ -2261,6 +2322,12 @@ void Simulate::LoadOnRenderThread() {
actuator_names_.emplace_back(this->m_->names + this->m_->name_actuatoradr[i]);
}
equality_names_.clear();
equality_names_.reserve(this->m_->neq);
for (int i = 0; i < this->m_->neq; ++i) {
equality_names_.emplace_back(this->m_->names + this->m_->name_eqadr[i]);
}
qpos_.resize(this->m_->nq);
std::memcpy(qpos_.data(), this->d_->qpos, sizeof(this->d_->qpos[0]) * this->m_->nq);
qpos_prev_ = qpos_;
@@ -2269,6 +2336,10 @@ void Simulate::LoadOnRenderThread() {
std::memcpy(ctrl_.data(), this->d_->ctrl, sizeof(this->d_->ctrl[0]) * this->m_->nu);
ctrl_prev_ = ctrl_;
eq_active_.resize(this->m_->neq);
std::memcpy(eq_active_.data(), this->d_->eq_active, sizeof(this->d_->eq_active[0]) * this->m_->neq);
eq_active_prev_ = eq_active_;
// allocate history buffer: smaller of {2000 states, 100 MB}
if (!this->is_passive_) {
constexpr int kMaxHistoryBytes = 1e8;
@@ -2518,6 +2589,13 @@ void Simulate::Render() {
pending_.ui_update_ctrl = false;
}
if (pending_.ui_update_equality) {
if (this->ui1_enable && this->ui1.sect[SECT_EQUALITY].state) {
mjui_update(SECT_EQUALITY, -1, &this->ui1, &this->uistate, &this->platform_ui->mjr_context());
}
pending_.ui_update_equality = false;
}
// render scene
mjr_render(rect, &this->scn, &this->platform_ui->mjr_context());
+9 -3
View File
@@ -54,9 +54,10 @@ class Simulate {
std::unique_ptr<PlatformUIAdapter> platform_ui_adapter,
mjvCamera* cam, mjvOption* opt, mjvPerturb* pert, bool is_passive);
// Synchronize mjModel and mjData state with UI inputs, and update
// visualization.
void Sync();
// Synchronize state with UI inputs, and update visualization. If state_only
// is false mjData and mjModel will be updated, otherwise only the subset of
// mjData corresponding to mjSTATE_INTEGRATION will be synced.
void Sync(bool state_only = false);
void UpdateHField(int hfieldid);
void UpdateMesh(int meshid);
@@ -122,6 +123,8 @@ class Simulate {
std::vector<std::optional<std::pair<mjtNum, mjtNum>>> actuator_ctrlrange_;
std::vector<std::string> actuator_names_;
std::vector<std::string> equality_names_;
std::vector<mjtNum> history_; // history buffer (nhistory x state_size)
// mjModel and mjData fields that can be modified by the user through the GUI
@@ -129,6 +132,8 @@ class Simulate {
std::vector<mjtNum> qpos_prev_;
std::vector<mjtNum> ctrl_;
std::vector<mjtNum> ctrl_prev_;
std::vector<mjtByte> eq_active_;
std::vector<mjtByte> eq_active_prev_;
// in passive mode the user owns m_ and d_, these "passive" instances are
// owned by Simulate, updated from the user by the Sync() method
@@ -167,6 +172,7 @@ class Simulate {
bool ui_update_visualization;
bool ui_update_joint;
bool ui_update_ctrl;
bool ui_update_equality;
bool ui_remake_ctrl;
} pending_ = {};
+28 -5
View File
@@ -36,6 +36,19 @@
//---------------------------- primitives sdf ---------------------------------------------
static void radialField3d(mjtNum field[3], const mjtNum a[3], const mjtNum x[3],
const mjtNum size[3]) {
field[0] = -size[0] / a[0];
field[1] = -size[1] / a[1];
field[2] = -size[2] / a[2];
mju_normalize3(field);
// flip sign if necessary
if (x[0] < 0) field[0] = -field[0];
if (x[1] < 0) field[1] = -field[1];
if (x[2] < 0) field[2] = -field[2];
}
static mjtNum geomDistance(const mjModel* m, const mjData* d, const mjpPlugin* p,
int i, const mjtNum x[3], mjtGeom type) {
mjtNum a[3], b[3];
@@ -48,13 +61,23 @@ static mjtNum geomDistance(const mjModel* m, const mjData* d, const mjpPlugin* p
case mjGEOM_SPHERE:
return mju_norm3(x) - size[0];
case mjGEOM_BOX:
// compute shortest distance to box surface if outside, otherwise
// intersect with a unit gradient that linearly rotates from radial to the face normals
a[0] = mju_abs(x[0]) - size[0];
a[1] = mju_abs(x[1]) - size[1];
a[2] = mju_abs(x[2]) - size[2];
b[0] = mju_max(a[0], 0);
b[1] = mju_max(a[1], 0);
b[2] = mju_max(a[2], 0);
return mju_norm3(b) + mju_min(mju_max(a[0], mju_max(a[1], a[2])), 0);
if (a[0] >= 0 || a[1] >= 0 || a[2] >= 0) {
b[0] = mju_max(a[0], 0);
b[1] = mju_max(a[1], 0);
b[2] = mju_max(a[2], 0);
return mju_norm3(b) + mju_min(mju_max(a[0], mju_max(a[1], a[2])), 0);
}
radialField3d(b, a, x, size);
mjtNum t[3];
t[0] = -a[0] / mju_abs(b[0]);
t[1] = -a[1] / mju_abs(b[1]);
t[2] = -a[2] / mju_abs(b[2]);
return -mju_min(t[0], mju_min(t[1], t[2])) * mju_norm3(b);
case mjGEOM_CAPSULE:
a[0] = x[0];
a[1] = x[1];
@@ -111,7 +134,7 @@ static void geomGradient(mjtNum gradient[3], const mjModel* m, const mjData* d,
int k = a[0] > a[1] ? 0 : 1;
int l = a[2] > a[k] ? 2 : k;
if (a[l] < 0) {
gradient[l] = x[l] / mju_abs(x[l]);
radialField3d(gradient, a, x, size);
} else {
b[0] = mju_max(a[0], 0);
b[1] = mju_max(a[1], 0);
+239 -240
View File
@@ -430,244 +430,246 @@ void mj_instantiateEquality(const mjModel* m, mjData* d) {
// find active equality constraints
for (int i=0; i < m->neq; i++) {
if (d->eq_active[i]) {
// get constraint data
data = m->eq_data + mjNEQDATA*i;
id[0] = m->eq_obj1id[i];
id[1] = m->eq_obj2id[i];
size = 0;
NV = 0;
NV2 = 0;
int body_id[2];
if (!d->eq_active[i]) {
continue;
}
// process according to type
switch ((mjtEq) m->eq_type[i]) {
case mjEQ_CONNECT: // connect bodies with ball joint
// find global points, body semantic
if (m->eq_objtype[i] == mjOBJ_BODY) {
for (int j=0; j < 2; j++) {
mju_mulMatVec3(pos[j], d->xmat + 9*id[j], data + 3*j);
mju_addTo3(pos[j], d->xpos + 3*id[j]);
body_id[j] = id[j];
}
// get constraint data
data = m->eq_data + mjNEQDATA*i;
id[0] = m->eq_obj1id[i];
id[1] = m->eq_obj2id[i];
size = 0;
NV = 0;
NV2 = 0;
int body_id[2];
// process according to type
switch ((mjtEq) m->eq_type[i]) {
case mjEQ_CONNECT: // connect bodies with ball joint
// find global points, body semantic
if (m->eq_objtype[i] == mjOBJ_BODY) {
for (int j=0; j < 2; j++) {
mju_mulMatVec3(pos[j], d->xmat + 9*id[j], data + 3*j);
mju_addTo3(pos[j], d->xpos + 3*id[j]);
body_id[j] = id[j];
}
// find global points, site semantic
else {
for (int j=0; j < 2; j++) {
mju_copy3(pos[j], d->site_xpos + 3*id[j]);
body_id[j] = m->site_bodyid[id[j]];
}
}
// compute position error
mju_sub3(cpos, pos[0], pos[1]);
// compute Jacobian difference (opposite of contact: 0 - 1)
NV = mj_jacDifPair(m, d, chain, body_id[1], body_id[0], pos[1], pos[0],
jac[1], jac[0], jacdif, NULL, NULL, NULL);
// copy difference into jac[0]
mju_copy(jac[0], jacdif, 3*NV);
size = 3;
break;
case mjEQ_WELD: // fix relative position and orientation
// find global points, body semantic
if (m->eq_objtype[i] == mjOBJ_BODY) {
for (int j=0; j < 2; j++) {
mjtNum* anchor = data + 3*(1-j);
mju_mulMatVec3(pos[j], d->xmat + 9*id[j], anchor);
mju_addTo3(pos[j], d->xpos + 3*id[j]);
body_id[j] = id[j];
}
}
// find global points, site semantic
else {
for (int j=0; j < 2; j++) {
mju_copy3(pos[j], d->site_xpos + 3*id[j]);
body_id[j] = m->site_bodyid[id[j]];
}
}
// compute position error
mju_sub3(cpos, pos[0], pos[1]);
// get torquescale coefficient
mjtNum torquescale = data[10];
// compute error Jacobian (opposite of contact: 0 - 1)
NV = mj_jacDifPair(m, d, chain, body_id[1], body_id[0], pos[1], pos[0],
jac[1], jac[0], jacdif,
jac[1]+3*nv, jac[0]+3*nv, jacdif+3*nv);
// copy difference into jac[0], compress translation:rotation if sparse
mju_copy(jac[0], jacdif, 3*NV);
mju_copy(jac[0]+3*NV, jacdif+3*nv, 3*NV);
// orientation, body semantic
if (m->eq_objtype[i] == mjOBJ_BODY) {
// compute orientation error: neg(q1) * q0 * relpose (axis components only)
mjtNum* relpose = data+6;
mju_mulQuat(quat, d->xquat+4*id[0], relpose); // quat = q0*relpose
mju_negQuat(quat1, d->xquat+4*id[1]); // quat1 = neg(q1)
}
// orientation, site semantic
else {
mjtNum quat_site1[4];
mju_mulQuat(quat, d->xquat+4*body_id[0], m->site_quat+4*id[0]);
mju_mulQuat(quat_site1, d->xquat+4*body_id[1], m->site_quat+4*id[1]);
mju_negQuat(quat1, quat_site1);
}
mju_mulQuat(quat2, quat1, quat);
mju_scl3(cpos+3, quat2+1, torquescale); // scale axis components by torquescale
// correct rotation Jacobian: 0.5 * neg(q1) * (jac0-jac1) * q0 * relpose
for (int j=0; j < NV; j++) {
// axis = [jac0-jac1]_col(j)
axis[0] = jac[0][3*NV+j];
axis[1] = jac[0][4*NV+j];
axis[2] = jac[0][5*NV+j];
// apply formula
mju_mulQuatAxis(quat2, quat1, axis); // quat2 = neg(q1)*(jac0-jac1)
mju_mulQuat(quat3, quat2, quat); // quat3 = neg(q1)*(jac0-jac1)*q0*relpose
// correct Jacobian
jac[0][3*NV+j] = 0.5*quat3[1];
jac[0][4*NV+j] = 0.5*quat3[2];
jac[0][5*NV+j] = 0.5*quat3[3];
}
// scale rotational jacobian by torquescale
mju_scl(jac[0]+3*NV, jac[0]+3*NV, torquescale, 3*NV);
size = 6;
break;
case mjEQ_JOINT: // couple joint values with cubic
case mjEQ_TENDON: // couple tendon lengths with cubic
// get scalar positions and their Jacobians
for (int j=0; j < 1+(id[1] >= 0); j++) {
if (m->eq_type[i] == mjEQ_JOINT) { // joint object
pos[j][0] = d->qpos[m->jnt_qposadr[id[j]]];
ref[j] = m->qpos0[m->jnt_qposadr[id[j]]];
// make Jacobian: sparse or dense
if (issparse) {
// add first or second joint
if (j == 0) {
NV = 1;
chain[0] = m->jnt_dofadr[id[j]];
jac[j][0] = 1;
} else {
NV2 = 1;
chain2[0] = m->jnt_dofadr[id[j]];
jac[j][0] = 1;
}
} else {
mju_zero(jac[j], nv);
jac[j][m->jnt_dofadr[id[j]]] = 1;
}
} else { // tendon object
pos[j][0] = d->ten_length[id[j]];
ref[j] = m->tendon_length0[id[j]];
// set tendon_efcadr
if (d->tendon_efcadr[id[j]] == -1) {
d->tendon_efcadr[id[j]] = i;
}
// copy Jacobian: sparse or dense
if (issparse) {
// add first or second chain
if (j == 0) {
NV = d->ten_J_rownnz[id[j]];
mju_copyInt(chain, d->ten_J_colind+d->ten_J_rowadr[id[j]], NV);
mju_copy(jac[j], d->ten_J+d->ten_J_rowadr[id[j]], NV);
} else {
NV2 = d->ten_J_rownnz[id[j]];
mju_copyInt(chain2, d->ten_J_colind+d->ten_J_rowadr[id[j]], NV2);
mju_copy(jac[j], d->ten_J+d->ten_J_rowadr[id[j]], NV2);
}
} else {
mju_copy(jac[j], d->ten_J+id[j]*nv, nv);
}
}
}
// both objects defined
if (id[1] >= 0) {
// compute position error
dif = pos[1][0] - ref[1];
cpos[0] = pos[0][0] - ref[0] - data[0] -
(data[1]*dif + data[2]*dif*dif + data[3]*dif*dif*dif + data[4]*dif*dif*dif*dif);
// compute derivative
deriv = data[1] + 2*data[2]*dif + 3*data[3]*dif*dif + 4*data[4]*dif*dif*dif;
// compute Jacobian: sparse or dense
if (issparse) {
NV = mju_combineSparse(jac[0], jac[1], 1, -deriv, NV, NV2, chain,
chain2, sparse_buf, buf_ind);
} else {
mju_addToScl(jac[0], jac[1], -deriv, nv);
}
}
// only one object defined
else {
// compute position error
cpos[0] = pos[0][0] - ref[0] - data[0];
// jac[0] already has the correct Jacobian
}
size = 1;
break;
case mjEQ_FLEX:
flex_edgeadr = m->flex_edgeadr[id[0]];
flex_edgenum = m->flex_edgenum[id[0]];
// add one constraint per non-rigid edge
for (int e=flex_edgeadr; e < flex_edgeadr+flex_edgenum; e++) {
// skip rigid
if (m->flexedge_rigid[e]) {
continue;
}
// position error
cpos[0] = d->flexedge_length[e] - m->flexedge_length0[e];
// add constraint: sparse or dense
if (issparse) {
mj_addConstraint(m, d, d->flexedge_J+d->flexedge_J_rowadr[e], cpos, 0, 0,
1, mjCNSTR_EQUALITY, i,
d->flexedge_J_rownnz[e],
d->flexedge_J_colind+d->flexedge_J_rowadr[e]);
} else {
mj_addConstraint(m, d, d->flexedge_J+e*nv, cpos, 0, 0,
1, mjCNSTR_EQUALITY, i,
0, NULL);
}
}
break;
default: // SHOULD NOT OCCUR
mjERROR("invalid equality constraint type %d", m->eq_type[i]);
}
// add constraint
if (size) {
mj_addConstraint(m, d, jac[0], cpos, 0, 0,
size, mjCNSTR_EQUALITY, i,
issparse ? NV : 0,
issparse ? chain : NULL);
// find global points, site semantic
else {
for (int j=0; j < 2; j++) {
mju_copy3(pos[j], d->site_xpos + 3*id[j]);
body_id[j] = m->site_bodyid[id[j]];
}
}
// compute position error
mju_sub3(cpos, pos[0], pos[1]);
// compute Jacobian difference (opposite of contact: 0 - 1)
NV = mj_jacDifPair(m, d, chain, body_id[1], body_id[0], pos[1], pos[0],
jac[1], jac[0], jacdif, NULL, NULL, NULL);
// copy difference into jac[0]
mju_copy(jac[0], jacdif, 3*NV);
size = 3;
break;
case mjEQ_WELD: // fix relative position and orientation
// find global points, body semantic
if (m->eq_objtype[i] == mjOBJ_BODY) {
for (int j=0; j < 2; j++) {
mjtNum* anchor = data + 3*(1-j);
mju_mulMatVec3(pos[j], d->xmat + 9*id[j], anchor);
mju_addTo3(pos[j], d->xpos + 3*id[j]);
body_id[j] = id[j];
}
}
// find global points, site semantic
else {
for (int j=0; j < 2; j++) {
mju_copy3(pos[j], d->site_xpos + 3*id[j]);
body_id[j] = m->site_bodyid[id[j]];
}
}
// compute position error
mju_sub3(cpos, pos[0], pos[1]);
// get torquescale coefficient
mjtNum torquescale = data[10];
// compute error Jacobian (opposite of contact: 0 - 1)
NV = mj_jacDifPair(m, d, chain, body_id[1], body_id[0], pos[1], pos[0],
jac[1], jac[0], jacdif,
jac[1]+3*nv, jac[0]+3*nv, jacdif+3*nv);
// copy difference into jac[0], compress translation:rotation if sparse
mju_copy(jac[0], jacdif, 3*NV);
mju_copy(jac[0]+3*NV, jacdif+3*nv, 3*NV);
// orientation, body semantic
if (m->eq_objtype[i] == mjOBJ_BODY) {
// compute orientation error: neg(q1) * q0 * relpose (axis components only)
mjtNum* relpose = data+6;
mju_mulQuat(quat, d->xquat+4*id[0], relpose); // quat = q0*relpose
mju_negQuat(quat1, d->xquat+4*id[1]); // quat1 = neg(q1)
}
// orientation, site semantic
else {
mjtNum quat_site1[4];
mju_mulQuat(quat, d->xquat+4*body_id[0], m->site_quat+4*id[0]);
mju_mulQuat(quat_site1, d->xquat+4*body_id[1], m->site_quat+4*id[1]);
mju_negQuat(quat1, quat_site1);
}
mju_mulQuat(quat2, quat1, quat);
mju_scl3(cpos+3, quat2+1, torquescale); // scale axis components by torquescale
// correct rotation Jacobian: 0.5 * neg(q1) * (jac0-jac1) * q0 * relpose
for (int j=0; j < NV; j++) {
// axis = [jac0-jac1]_col(j)
axis[0] = jac[0][3*NV+j];
axis[1] = jac[0][4*NV+j];
axis[2] = jac[0][5*NV+j];
// apply formula
mju_mulQuatAxis(quat2, quat1, axis); // quat2 = neg(q1)*(jac0-jac1)
mju_mulQuat(quat3, quat2, quat); // quat3 = neg(q1)*(jac0-jac1)*q0*relpose
// correct Jacobian
jac[0][3*NV+j] = 0.5*quat3[1];
jac[0][4*NV+j] = 0.5*quat3[2];
jac[0][5*NV+j] = 0.5*quat3[3];
}
// scale rotational jacobian by torquescale
mju_scl(jac[0]+3*NV, jac[0]+3*NV, torquescale, 3*NV);
size = 6;
break;
case mjEQ_JOINT: // couple joint values with cubic
case mjEQ_TENDON: // couple tendon lengths with cubic
// get scalar positions and their Jacobians
for (int j=0; j < 1+(id[1] >= 0); j++) {
if (m->eq_type[i] == mjEQ_JOINT) { // joint object
pos[j][0] = d->qpos[m->jnt_qposadr[id[j]]];
ref[j] = m->qpos0[m->jnt_qposadr[id[j]]];
// make Jacobian: sparse or dense
if (issparse) {
// add first or second joint
if (j == 0) {
NV = 1;
chain[0] = m->jnt_dofadr[id[j]];
jac[j][0] = 1;
} else {
NV2 = 1;
chain2[0] = m->jnt_dofadr[id[j]];
jac[j][0] = 1;
}
} else {
mju_zero(jac[j], nv);
jac[j][m->jnt_dofadr[id[j]]] = 1;
}
} else { // tendon object
pos[j][0] = d->ten_length[id[j]];
ref[j] = m->tendon_length0[id[j]];
// set tendon_efcadr
if (d->tendon_efcadr[id[j]] == -1) {
d->tendon_efcadr[id[j]] = i;
}
// copy Jacobian: sparse or dense
if (issparse) {
// add first or second chain
if (j == 0) {
NV = d->ten_J_rownnz[id[j]];
mju_copyInt(chain, d->ten_J_colind+d->ten_J_rowadr[id[j]], NV);
mju_copy(jac[j], d->ten_J+d->ten_J_rowadr[id[j]], NV);
} else {
NV2 = d->ten_J_rownnz[id[j]];
mju_copyInt(chain2, d->ten_J_colind+d->ten_J_rowadr[id[j]], NV2);
mju_copy(jac[j], d->ten_J+d->ten_J_rowadr[id[j]], NV2);
}
} else {
mju_copy(jac[j], d->ten_J+id[j]*nv, nv);
}
}
}
// both objects defined
if (id[1] >= 0) {
// compute position error
dif = pos[1][0] - ref[1];
cpos[0] = pos[0][0] - ref[0] - data[0] -
(data[1]*dif + data[2]*dif*dif + data[3]*dif*dif*dif + data[4]*dif*dif*dif*dif);
// compute derivative
deriv = data[1] + 2*data[2]*dif + 3*data[3]*dif*dif + 4*data[4]*dif*dif*dif;
// compute Jacobian: sparse or dense
if (issparse) {
NV = mju_combineSparse(jac[0], jac[1], 1, -deriv, NV, NV2, chain,
chain2, sparse_buf, buf_ind);
} else {
mju_addToScl(jac[0], jac[1], -deriv, nv);
}
}
// only one object defined
else {
// compute position error
cpos[0] = pos[0][0] - ref[0] - data[0];
// jac[0] already has the correct Jacobian
}
size = 1;
break;
case mjEQ_FLEX:
flex_edgeadr = m->flex_edgeadr[id[0]];
flex_edgenum = m->flex_edgenum[id[0]];
// add one constraint per non-rigid edge
for (int e=flex_edgeadr; e < flex_edgeadr+flex_edgenum; e++) {
// skip rigid
if (m->flexedge_rigid[e]) {
continue;
}
// position error
cpos[0] = d->flexedge_length[e] - m->flexedge_length0[e];
// add constraint: sparse or dense
if (issparse) {
mj_addConstraint(m, d, d->flexedge_J+d->flexedge_J_rowadr[e], cpos, 0, 0,
1, mjCNSTR_EQUALITY, i,
d->flexedge_J_rownnz[e],
d->flexedge_J_colind+d->flexedge_J_rowadr[e]);
} else {
mj_addConstraint(m, d, d->flexedge_J+e*nv, cpos, 0, 0,
1, mjCNSTR_EQUALITY, i,
0, NULL);
}
}
break;
default: // SHOULD NOT OCCUR
mjERROR("invalid equality constraint type %d", m->eq_type[i]);
}
// add constraint
if (size) {
mj_addConstraint(m, d, jac[0], cpos, 0, 0,
size, mjCNSTR_EQUALITY, i,
issparse ? NV : 0,
issparse ? chain : NULL);
}
}
@@ -1388,7 +1390,7 @@ static void getimpedance(const mjtNum* solimp, mjtNum pos, mjtNum margin,
yP = solimp[4] * a*power(x, solimp[4]-1);
}
// y(x) = 1-b*(1-x)^p is x>midpoint
// y(x) = 1-b*(1-x)^p if x>midpoint
else {
mjtNum b = 1/power(1-solimp[3], solimp[4]-1);
y = 1-b*power(1-x, solimp[4]);
@@ -2193,12 +2195,9 @@ void mj_projectConstraint(const mjModel* m, mjData* d) {
}
// pre-count A nonzeros (compute AR_rownnz, AR_rowadr)
mju_sqrMatTDSparseCount(d->efc_AR_rownnz, d->efc_AR_rowadr, nefc,
BT_rownnz, BT_rowadr, BT_colind,
B_rownnz, B_rowadr, B_colind, B_rowsuper, d, /*flg_upper=*/1);
// nA = total number of nonzeros in A
d->nA = d->efc_AR_rownnz[nefc - 1] + d->efc_AR_rowadr[nefc - 1];
d->nA = mju_sqrMatTDSparseCount(d->efc_AR_rownnz, d->efc_AR_rowadr, nefc,
BT_rownnz, BT_rowadr, BT_colind,
B_rownnz, B_rowadr, B_colind, B_rowsuper, d, /*flg_upper=*/1);
// allocate A values and column indices on arena
d->efc_AR = mj_arenaAllocByte(d, sizeof(mjtNum) * d->nA, _Alignof(mjtNum));
+41 -35
View File
@@ -1469,9 +1469,12 @@ void mj_transmission(const mjModel* m, mjData* d) {
//-------------------------- inertia ---------------------------------------------------------------
// add tendon armature to qM
// add tendon armature to M
void mj_tendonArmature(const mjModel* m, mjData* d) {
int nv = m->nv, ntendon = m->ntendon, issparse = mj_isSparse(m);
const int* M_rownnz = d->M_rownnz;
const int* M_rowadr = d->M_rowadr;
const int* M_colind = d->M_colind;
for (int k=0; k < ntendon; k++) {
mjtNum armature = m->tendon_armature[k];
@@ -1482,11 +1485,19 @@ void mj_tendonArmature(const mjModel* m, mjData* d) {
// dense
if (!issparse) {
// M += armature * ten_J' * ten_J
mjtNum* ten_J = d->ten_J + nv*k;
for (int i=0; i < m->nv; i++) {
int Madr = m->dof_Madr[i];
for (int j = i; j >= 0; j = m->dof_parentid[j]) {
d->qM[Madr++] += armature * ten_J[j] * ten_J[i];
for (int i=0; i < nv; i++) {
mjtNum ten_J_i = ten_J[i];
if (!ten_J_i) {
continue;
}
// M[i,:] += armature * ten_J[i] * ten_J
int start = M_rowadr[i];
int end = start + M_rownnz[i];
for (int adr = start; adr < end; adr++) {
d->M[adr] += armature * ten_J_i * ten_J[M_colind[adr]];
}
}
}
@@ -1494,29 +1505,24 @@ void mj_tendonArmature(const mjModel* m, mjData* d) {
// sparse
else {
// get sparse info for tendon k
int rowadr = d->ten_J_rowadr[k];
int rownnz = d->ten_J_rownnz[k];
const int* colind = d->ten_J_colind + rowadr;
mjtNum* ten_J = d->ten_J + rowadr;
int J_rowadr = d->ten_J_rowadr[k];
int J_rownnz = d->ten_J_rownnz[k];
const int* J_colind = d->ten_J_colind + J_rowadr;
mjtNum* ten_J = d->ten_J + J_rowadr;
// iterate forward on nonzero rows i
for (int adr_i=0; adr_i < rownnz; adr_i++) {
int i = colind[adr_i];
int Madr = m->dof_Madr[i];
int adr_j = rownnz - 1;
// iterate backward on ancestors of i, find matching column j
for (int j = i; j >= 0; j = m->dof_parentid[j]) {
// reduce adr_j until column index is no bigger than j
while (colind[adr_j] > j && adr_j >= 0) {
adr_j--;
}
// found match, update qM
if (colind[adr_j] == j) {
d->qM[Madr++] += armature * ten_J[adr_j] * ten_J[adr_i];
}
// M += armature * ten_J' * ten_J
for (int j=0; j < J_rownnz; j++) {
mjtNum ten_J_i = ten_J[j];
if (!ten_J_i) {
continue;
}
// M[i,:] += armature * ten_J[i] * ten_J
int i = J_colind[j];
int M_adr = M_rowadr[i];
mju_addToSclSparseInc(d->M + M_adr, ten_J,
M_rownnz[i], M_colind + M_adr,
J_rownnz, J_colind, armature * ten_J_i);
}
}
}
@@ -1526,22 +1532,22 @@ void mj_tendonArmature(const mjModel* m, mjData* d) {
// composite rigid body inertia algorithm
void mj_crb(const mjModel* m, mjData* d) {
int nv = m->nv;
mjtNum buf[6];
mjtNum* crb = d->crb;
int last_body = m->nbody - 1, nv = m->nv;
// crb = cinert
mju_copy(crb, d->cinert, 10*m->nbody);
// backward pass over bodies, accumulate composite inertias
for (int i=last_body; i > 0; i--) {
for (int i=m->nbody - 1; i > 0; i--) {
if (m->body_parentid[i] > 0) {
mju_addTo(crb+10*m->body_parentid[i], crb+10*i, 10);
}
}
// clear qM
mju_zero(d->qM, m->nM);
// clear M
mju_zero(d->M, m->nC);
// dense forward pass over dofs
for (int i=0; i < nv; i++) {
@@ -1549,7 +1555,7 @@ void mj_crb(const mjModel* m, mjData* d) {
if (m->dof_simplenum[i]) {
int n = i + m->dof_simplenum[i];
for (; i < n; i++) {
d->qM[m->dof_Madr[i]] = m->dof_M0[i];
d->M[d->M_rowadr[i]] = m->dof_M0[i];
}
// finish or else fall through with next row
@@ -1559,8 +1565,8 @@ void mj_crb(const mjModel* m, mjData* d) {
}
// init M(i,i) with armature inertia
int Madr_ij = m->dof_Madr[i];
d->qM[Madr_ij] = m->dof_armature[i];
int Madr_ij = d->M_rowadr[i] + d->M_rownnz[i] - 1;
d->M[Madr_ij] = m->dof_armature[i];
// precompute buf = crb_body_i * cdof_i
mju_mulInertVec(buf, crb+10*m->dof_bodyid[i], d->cdof+6*i);
@@ -1568,7 +1574,7 @@ void mj_crb(const mjModel* m, mjData* d) {
// sparse backward pass over ancestors
for (int j=i; j >= 0; j = m->dof_parentid[j]) {
// M(i,j) += cdof_j * (crb_body_i * cdof_i)
d->qM[Madr_ij++] += mju_dot(d->cdof+6*j, buf, 6);
d->M[Madr_ij--] += mju_dot(d->cdof+6*j, buf, 6);
}
}
}
@@ -1579,7 +1585,7 @@ void mj_makeM(const mjModel* m, mjData* d) {
TM_START;
mj_crb(m, d);
mj_tendonArmature(m, d);
mju_gather(d->M, d->qM, d->mapM2M, m->nC);
mju_scatter(d->qM, d->M, d->mapM2M, m->nC);
TM_END(mjTIMER_POS_INERTIA);
}
+1 -3
View File
@@ -173,9 +173,7 @@ void mj_fwdPosition(const mjModel* m, mjData* d) {
TM_RESTART;
mj_makeConstraint(m, d);
if (mjENABLED(mjENBL_ISLAND)) {
mj_island(m, d);
}
mj_island(m, d);
TM_END(mjTIMER_POS_MAKE);
TM_RESTART;
+13 -6
View File
@@ -46,8 +46,8 @@ void mj_invPosition(const mjModel* m, mjData* d) {
mj_tendon(m, d);
TM_END(mjTIMER_POS_KINEMATICS);
mj_makeM(m, d); // timed internally (POS_INERTIA)
mj_factorM(m, d); // timed internally (POS_INERTIA)
mj_makeM(m, d); // timed internally (POS_INERTIA)
mj_factorM(m, d); // timed internally (POS_INERTIA)
mj_collision(m, d); // timed internally (POS_COLLISION)
@@ -226,15 +226,22 @@ void mj_inverseSkip(const mjModel* m, mjData* d,
// acceleration-dependent
mj_invConstraint(m, d);
mj_rne(m, d, 1, d->qfrc_inverse);
// sum of bias forces in qfrc_inverse = centripetal + Coriolis + tendon bias
mj_rne(m, d, 0, d->qfrc_inverse);
mj_tendonBias(m, d, d->qfrc_inverse);
if (!skipsensor) {
mj_sensorAcc(m, d);
}
// qfrc_inverse += armature*qacc - qfrc_passive - qfrc_constraint
// compute Ma = M*qacc
mjtNum* Ma = mjSTACKALLOC(d, nv, mjtNum);
mj_mulM(m, d, Ma, d->qacc);
// qfrc_inverse += Ma - qfrc_passive - qfrc_constraint
for (int i=0; i < nv; i++) {
d->qfrc_inverse[i] += m->dof_armature[i]*d->qacc[i]
- d->qfrc_passive[i] - d->qfrc_constraint[i];
d->qfrc_inverse[i] += Ma[i] - d->qfrc_passive[i] - d->qfrc_constraint[i];
}
if (mjENABLED(mjENBL_INVDISCRETE)) {
+8 -3
View File
@@ -183,6 +183,7 @@ static void setf4(float* rgba, float r, float g, float b, float a) {
// set visual options to default values
void mj_defaultVisual(mjVisual* vis) {
// global
vis->global.cameraid = -1;
vis->global.orthographic = 0;
vis->global.fovy = 45;
vis->global.ipd = 0.068;
@@ -459,7 +460,7 @@ static void freeModelBuffers(mjModel* m) {
// allocate and initialize mjModel structure
void mj_makeModel(mjModel** dest,
int nq, int nv, int nu, int na, int nbody, int nbvh,
int nbvhstatic, int nbvhdynamic, int njnt, int ngeom, int nsite, int ncam,
int nbvhstatic, int nbvhdynamic, int noct, int njnt, int ngeom, int nsite, int ncam,
int nlight, int nflex, int nflexnode, int nflexvert, int nflexedge, int nflexelem,
int nflexelemdata, int nflexelemedge, int nflexshelldata, int nflexevpair, int nflextexcoord,
int nmesh, int nmeshvert, int nmeshnormal, int nmeshtexcoord, int nmeshface,
@@ -498,6 +499,7 @@ void mj_makeModel(mjModel** dest,
m->nbvh = nbvh;
m->nbvhstatic = nbvhstatic;
m->nbvhdynamic = nbvhdynamic;
m->noct = noct;
m->njnt = njnt;
m->ngeom = ngeom;
m->nsite = nsite;
@@ -638,7 +640,7 @@ mjModel* mj_copyModel(mjModel* dest, const mjModel* src) {
if (!dest) {
mj_makeModel(&dest,
src->nq, src->nv, src->nu, src->na, src->nbody, src->nbvh,
src->nbvhstatic, src->nbvhdynamic, src->njnt, src->ngeom, src->nsite,
src->nbvhstatic, src->nbvhdynamic, src->noct, src->njnt, src->ngeom, src->nsite,
src->ncam, src->nlight, src->nflex, src->nflexnode, src->nflexvert, src->nflexedge,
src->nflexelem, src->nflexelemdata, src->nflexelemedge, src->nflexshelldata,
src->nflexevpair, src->nflextexcoord, src->nmesh, src->nmeshvert,
@@ -834,7 +836,7 @@ mjModel* mj_loadModelBuffer(const void* buffer, int buffer_sz) {
ints[42], ints[43], ints[44], ints[45], ints[46], ints[47], ints[48],
ints[49], ints[50], ints[51], ints[52], ints[53], ints[54], ints[55],
ints[56], ints[57], ints[58], ints[59], ints[60], ints[61], ints[62],
ints[63], ints[64], ints[65], ints[66], ints[67], ints[68]);
ints[63], ints[64], ints[65], ints[66], ints[67], ints[68], ints[69]);
if (!m || m->nbuffer != sizes[getnsize()-1]) {
mju_warning("Corrupted model, wrong size parameters");
mj_deleteModel(m);
@@ -1989,6 +1991,9 @@ static void _resetData(const mjModel* m, mjData* d, unsigned char debug_value) {
mju_zero(d->mocap_pos, 3*m->nmocap);
mju_zero(d->mocap_quat, 4*m->nmocap);
// zero out qM, special case because scattering from M skips simple body off-diagonals
mju_zero(d->qM, m->nM);
// copy qpos0 from model
if (m->qpos0) {
memcpy(d->qpos, m->qpos0, m->nq*sizeof(mjtNum));
+1 -1
View File
@@ -52,7 +52,7 @@ void mj_defaultStatistic(mjStatistic* stat);
// allocate mjModel
void mj_makeModel(mjModel** dest,
int nq, int nv, int nu, int na, int nbody, int nbvh, int nbvhstatic, int nbvhdynamic,
int nq, int nv, int nu, int na, int nbody, int nbvh, int nbvhstatic, int nbvhdynamic, int noct,
int njnt, int ngeom, int nsite, int ncam, int nlight, int nflex, int nflexnode, int nflexvert,
int nflexedge, int nflexelem, int nflexelemdata, int nflexelemedge, int nflexshelldata,
int nflexevpair, int nflextexcoord, int nmesh, int nmeshvert, int nmeshnormal,
+28 -44
View File
@@ -141,29 +141,6 @@ int mj_floodFill(int* island, int nr, const int* rownnz, const int* rowadr, cons
// return upper bound on number of tree-tree edges
static int countMaxEdge(const mjModel* m, const mjData* d) {
int nedge_max = 0;
nedge_max += 2*d->ncon; // contact: 2 edges
nedge_max += 2*d->ne; // equality: 2 edges
nedge_max += d->nl; // limit: 1 edges (always within same tree)
nedge_max += d->nf; // joint friction: 1 edge (always within same tree)
// tendon limits and friction add up to tendon_num edges
for (int i=0; i < m->ntendon; i++) {
if (m->tendon_frictionloss[i]) {
nedge_max += m->tendon_num[i];
}
if (m->tendon_limited[i]) {
nedge_max += m->tendon_num[i];
}
}
return nedge_max;
}
// return id of next tree in Jacobian row i that is different from tree, -1 if not found
// start search from *index
// write the index of the found tree to *index
@@ -242,21 +219,27 @@ static int treeFirst(const mjModel* m, const mjData* d, int tree[2], int i) {
if (efc_type == mjCNSTR_CONTACT_FRICTIONLESS ||
efc_type == mjCNSTR_CONTACT_PYRAMIDAL ||
efc_type == mjCNSTR_CONTACT_ELLIPTIC) {
tree[0] = m->body_treeid[m->geom_bodyid[d->contact[efc_id].geom[0]]];
tree[1] = m->body_treeid[m->geom_bodyid[d->contact[efc_id].geom[1]]];
int g1 = d->contact[efc_id].geom[0];
int g2 = d->contact[efc_id].geom[1];
// handle static bodies
if (tree[0] < 0) {
if (tree[1] < 0) {
mjERROR("contact %d is between two static bodies", efc_id); // SHOULD NOT OCCUR
} else {
int tmp = tree[0];
tree[0] = tree[1];
tree[1] = tmp;
// no shortcut for flex contacts (handled in the generic case)
if (g1 >=0 && g2 >= 0) {
tree[0] = m->body_treeid[m->geom_bodyid[g1]];
tree[1] = m->body_treeid[m->geom_bodyid[g2]];
// handle static bodies
if (tree[0] < 0) {
if (tree[1] < 0) {
mjERROR("contact %d is between two static bodies", efc_id); // SHOULD NOT OCCUR
} else {
int tmp = tree[0];
tree[0] = tree[1];
tree[1] = tmp;
}
}
}
return -1;
return -1;
}
}
// connect or weld constraints
@@ -373,9 +356,12 @@ static int findEdges(const mjModel* m, const mjData* d, int* treenedge, int* edg
int nedge = 0;
for (int i=0; i < nefc; i++) {
// row i is still in the same constraint: skip
// row i is still in the same constraint: skip it,
if (efc_type == d->efc_type[i] && efc_id == d->efc_id[i]) {
continue;
// unless it is a flex equality, where the tree pattern changes per dof
if (!(efc_type == mjCNSTR_EQUALITY && m->eq_type[efc_id] == mjEQ_FLEX)) {
continue;
}
}
efc_type = d->efc_type[i];
efc_id = d->efc_id[i];
@@ -425,21 +411,19 @@ void mj_island(const mjModel* m, mjData* d) {
int nv = m->nv, nefc = d->nefc, ntree=m->ntree;
// no constraints: quick return
if (!nefc || m->nflex) { // TODO: add flex support to island discovery
d->nisland = 0;
d->nidof = 0;
if (!mjENABLED(mjENBL_ISLAND) || !nefc) {
d->nisland = d->nidof = 0;
return;
}
mj_markStack(d);
// allocate edge array
int nedge_max = countMaxEdge(m, d);
int* edge = mjSTACKALLOC(d, 2*nedge_max, int);
// allocate edge array, nJ is an upper bound
int* edge = mjSTACKALLOC(d, 2*d->nJ, int);
// get tree-tree edges and rownnz counts from efc arrays
int* rownnz = mjSTACKALLOC(d, ntree, int); // number of edges per tree
int nedge = findEdges(m, d, rownnz, edge, nedge_max);
int nedge = findEdges(m, d, rownnz, edge, d->nJ);
// compute starting address of tree's column indices while resetting rownnz
int* rowadr = mjSTACKALLOC(d, ntree, int);
+7 -8
View File
@@ -1533,15 +1533,14 @@ static void MakeHessian(mjData* d, mjCGContext* ctx) {
// sparse
if (ctx->is_sparse) {
// initialize Hessian rowadr, rownnz
mju_sqrMatTDSparseCount(ctx->H_rownnz, ctx->H_rowadr, nv,
ctx->J_rownnz, ctx->J_rowadr, ctx->J_colind,
ctx->JT_rownnz, ctx->JT_rowadr, ctx->JT_colind,
ctx->JT_rowsuper, d, /*flg_upper=*/0);
// initialize Hessian rowadr, rownnz; get total nonzeros
ctx->nH = mju_sqrMatTDSparseCount(ctx->H_rownnz, ctx->H_rowadr, nv,
ctx->J_rownnz, ctx->J_rowadr, ctx->J_colind,
ctx->JT_rownnz, ctx->JT_rowadr, ctx->JT_colind,
ctx->JT_rowsuper, d, /*flg_upper=*/0);
// add nC to Hessian total nonzeros (unavoidable overcounting since H_colind is still unknown)
ctx->nH = ctx->M_rowadr[nv - 1] + ctx->M_rownnz[nv - 1] +
ctx->H_rowadr[nv - 1] + ctx->H_rownnz[nv - 1];
// add M nonzeros to Hessian total (unavoidable overcounting since H_colind is still unknown)
ctx->nH += ctx->M_rowadr[nv - 1] + ctx->M_rownnz[nv - 1];
// shift H row addresses to make room for C
int shift = 0;
+2 -2
View File
@@ -41,8 +41,8 @@
//-------------------------- Constants -------------------------------------------------------------
#define mjVERSION 333
#define mjVERSIONSTRING "3.3.3"
#define mjVERSION 334
#define mjVERSIONSTRING "3.3.4"
// names of disable flags
const char* mjDISABLESTRING[mjNDISABLE] = {
+7 -5
View File
@@ -636,11 +636,11 @@ void mju_superSparse(int nr, int* rowsuper,
}
// precount res_rownnz and precompute res_rowadr for mju_sqrMatTDSparse
void mju_sqrMatTDSparseCount(int* res_rownnz, int* res_rowadr, int nr,
const int* rownnz, const int* rowadr, const int* colind,
const int* rownnzT, const int* rowadrT, const int* colindT,
const int* rowsuperT, mjData* d, int flg_upper) {
// precount res_rownnz and precompute res_rowadr for mju_sqrMatTDSparse, return total non-zeros
int mju_sqrMatTDSparseCount(int* res_rownnz, int* res_rowadr, int nr,
const int* rownnz, const int* rowadr, const int* colind,
const int* rownnzT, const int* rowadrT, const int* colindT,
const int* rowsuperT, mjData* d, int flg_upper) {
mj_markStack(d);
int* chain = mjSTACKALLOC(d, 2*nr, int);
int nchain = 0;
@@ -726,6 +726,8 @@ void mju_sqrMatTDSparseCount(int* res_rownnz, int* res_rowadr, int nr,
}
mj_freeStack(d);
return res_rowadr[nr-1] + res_rownnz[nr-1];
}
+5 -5
View File
@@ -120,11 +120,11 @@ MJAPI void mju_sqrMatTDSparse_row(mjtNum* res, const mjtNum* mat, const mjtNum*
const int* colindT, const int* rowsuperT,
mjData* d, int* diagind);
// precount res_rownnz and precompute res_rowadr for mju_sqrMatTDSparse
MJAPI void mju_sqrMatTDSparseCount(int* res_rownnz, int* res_rowadr, int nr,
const int* rownnz, const int* rowadr, const int* colind,
const int* rownnzT, const int* rowadrT, const int* colindT,
const int* rowsuperT, mjData* d, int flg_upper);
// precount res_rownnz and precompute res_rowadr for mju_sqrMatTDSparse, return total non-zeros
MJAPI int mju_sqrMatTDSparseCount(int* res_rownnz, int* res_rowadr, int nr,
const int* rownnz, const int* rowadr, const int* colind,
const int* rownnzT, const int* rowadrT, const int* colindT,
const int* rowsuperT, mjData* d, int flg_upper);
// precompute res_rowadr for mju_sqrMatTDSparse using uncompressed memory
MJAPI void mju_sqrMatTDUncompressedInit(int* res_rowadr, int nc);
+38 -1
View File
@@ -763,7 +763,7 @@ void mjv_addGeoms(const mjModel* m, mjData* d, const mjvOption* vopt,
if (vopt->flags[mjVIS_MESHBVH]) {
for (int geomid = 0; geomid < m->ngeom; geomid++) {
int meshid = m->geom_dataid[geomid];
if (meshid == -1) {
if (m->geom_type[geomid] == mjGEOM_SDF || meshid == -1) {
continue;
}
@@ -806,6 +806,43 @@ void mjv_addGeoms(const mjModel* m, mjData* d, const mjvOption* vopt,
}
}
// mesh octree
category = mjCAT_DECOR;
objtype = mjOBJ_UNKNOWN;
if (vopt->flags[mjVIS_MESHBVH]) {
for (int geomid = 0; geomid < m->ngeom; geomid++) {
int meshid = m->geom_dataid[geomid];
if (m->geom_type[geomid] != mjGEOM_SDF || meshid == -1) {
continue;
}
for (int b = 0; b < m->mesh_octnum[meshid]; b++) {
int i = b + m->mesh_octadr[meshid];
START
if (m->oct_depth[i] != vopt->bvh_depth) {
continue;
}
// box color
const float* rgba = m->vis.rgba.bv;
// get xpos, xmat, size
const mjtNum* xpos = d->geom_xpos + 3 * geomid;
const mjtNum* xmat = d->geom_xmat + 9 * geomid;
const mjtNum *size = m->oct_aabb + 6*i + 3;
// offset xpos with aabb center (not always at geom origin)
const mjtNum *center = m->oct_aabb + 6*i;
mjtNum pos[3];
mju_mulMatVec3(pos, xmat, center);
mju_addTo3(pos, xpos);
mjv_initGeom(thisgeom, mjGEOM_LINEBOX, size, pos, xmat, rgba);
FINISH
}
}
}
// inertia
objtype = mjOBJ_BODY;
if (vopt->flags[mjVIS_INERTIA]) {
+270
View File
@@ -0,0 +1,270 @@
# Copyright 2025 DeepMind Technologies Limited
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# --- Global Configuration ---
# Plugin target names (used for library and plugInfo.json)
set(MJCF_PLUGIN_TARGET_NAME usdMjcf)
set(MJC_PHYSICS_PLUGIN_TARGET_NAME mjcPhysics)
set(MJ_USD_TARGET_NAME mj_usd)
# Installation directory for USD plugins
set(MJ_USD_INSTALL_DIR_LIB ${CMAKE_INSTALL_LIBDIR}/mujocoUsd)
# Common OpenUSD libraries
set(OPENUSD_CORE_LIBS
usd ar kind tf gf vt usdShade usdLux usdGeom usdImaging usdPhysics
)
# We need to set the visibility to default until core type symbol visibility
# is resolved in OpenUSD https://github.com/PixarAnimationStudios/OpenUSD/issues/1475
set(DEFAULT_CXX_VISIBILITY_PROPS
CXX_VISIBILITY_PRESET default
)
# --- Helper Functions ---
function(configure_and_install_usd_plugin_info plugin_name source_subpath install_base_dir)
# Determine shared library prefix
if(CMAKE_SHARED_LIBRARY_PREFIX)
set(LIB_PREFIX ${CMAKE_SHARED_LIBRARY_PREFIX})
else()
set(LIB_PREFIX "")
endif()
set(PLUG_INFO_LIBRARY_PATH "../../${LIB_PREFIX}${plugin_name}${CMAKE_SHARED_LIBRARY_SUFFIX}")
set(OUTPUT_DIR "${CMAKE_BINARY_DIR}/${install_base_dir}/resources/${plugin_name}")
set(OUTPUT_FILE "${OUTPUT_DIR}/plugInfo.json")
# Create the output directory if it doesn't exist
file(MAKE_DIRECTORY "${OUTPUT_DIR}")
# Configure the plugInfo.json file to replace @PLUG_INFO_LIBRARY_PATH@ instances.
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/${source_subpath}/plugInfo.json"
"${OUTPUT_FILE}"
@ONLY
)
install(FILES "${OUTPUT_FILE}"
DESTINATION "${install_base_dir}/resources/${plugin_name}"
)
endfunction()
# Function to apply Houdini-specific link directories and include paths
function(configure_houdini_target target_name link_scope include_scope)
set(HOUDINI_LIBS_DIR "${HOUDINI_HFS_DIR}/../Libraries")
get_filename_component(HOUDINI_LIBS_DIR "${HOUDINI_LIBS_DIR}" ABSOLUTE) # Normalize the path
# Assume everyone using Houdini on 3.11 for now.
set(USD_MJCF_PYTHON_LIB python3.11)
set(USD_MJCF_PYTHON_LIB_NUMBER python311) # For boost library names
set(PYTHON_LIB "${HOUDINI_HFS_DIR}/Frameworks/Python.framework/Versions/3.11/Python")
set(PXR_LIB_PREFIX "pxr_")
# Houdini-specific PXR and other libraries
set(HOUDINI_SPECIFIC_LIBS
${PXR_LIB_PREFIX}usd
${PXR_LIB_PREFIX}ar
${PXR_LIB_PREFIX}kind
${PXR_LIB_PREFIX}tf
${PXR_LIB_PREFIX}gf
${PXR_LIB_PREFIX}vt
${PXR_LIB_PREFIX}sdf
${PXR_LIB_PREFIX}usdShade
${PXR_LIB_PREFIX}usdLux
${PXR_LIB_PREFIX}usdGeom
${PXR_LIB_PREFIX}usdImaging
${PXR_LIB_PREFIX}usdPhysics
tbb
hboost_${USD_MJCF_PYTHON_LIB_NUMBER}
${PYTHON_LIB}
)
target_link_directories(${target_name} ${link_scope} ${HOUDINI_LIBS_DIR})
target_include_directories(${target_name} ${include_scope}
"${HOUDINI_HFS_DIR}/toolkit/include"
"${HOUDINI_HFS_DIR}/toolkit/include/python3.11"
)
target_link_libraries(${target_name} ${link_scope}
${HOUDINI_SPECIFIC_LIBS}
mujoco
tinyxml2
)
endfunction()
## usdMjcf
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
# The list of RPATHs:
# 1. $ORIGIN: For libmjcPhysics.so (since it's in the same install dir)
# 2. Path to OpenUSD libraries: Typically ${USD_DIR}/lib
# 3. Path to MuJoCo library: Assuming it's in ${MUJOCO_INSTALL_DIR}/lib
if(APPLE)
set(RPATH_LIST "@loader_path")
# Add OpenUSD lib to RPATH for macOS
if(DEFINED USD_DIR AND EXISTS "${USD_DIR}/lib")
list(APPEND RPATH_LIST "${USD_DIR}/lib")
endif()
# Add MuJoCo lib to RPATH for macOS
if(DEFINED MUJOCO_INSTALL_DIR AND EXISTS "${MUJOCO_INSTALL_DIR}/lib")
list(APPEND RPATH_LIST "${MUJOCO_INSTALL_DIR}/lib")
endif()
string(JOIN ":" CMAKE_INSTALL_RPATH "${RPATH_LIST}") # macOS uses colon for RPATH separator
message(STATUS "Final CMAKE_INSTALL_RPATH: ${CMAKE_INSTALL_RPATH}")
elseif(UNIX)
# For adjacent libs.
set(RPATH_LIST "\$ORIGIN")
# Add OpenUSD library directory
if(DEFINED USD_DIR AND EXISTS "${USD_DIR}/lib")
list(APPEND RPATH_LIST "${USD_DIR}/lib")
message(STATUS "Adding OpenUSD lib to RPATH: ${USD_DIR}/lib")
else()
message(WARNING "USD_DIR is not defined or ${USD_DIR}/lib does not exist. OpenUSD libraries might not be found via RPATH.")
endif()
# Add MuJoCo library directory
if(DEFINED MUJOCO_INSTALL_DIR AND EXISTS "${MUJOCO_INSTALL_DIR}/lib")
list(APPEND RPATH_LIST "${MUJOCO_INSTALL_DIR}/lib")
message(STATUS "Adding MuJoCo lib to RPATH: ${MUJOCO_INSTALL_DIR}/lib")
elseif(DEFINED MUJOCO_INSTALL_DIR AND EXISTS "${MUJOCO_INSTALL_DIR}/bin")
list(APPEND RPATH_LIST "${MUJOCO_INSTALL_DIR}/bin")
message(STATUS "Adding MuJoCo bin to RPATH: ${MUJOCO_INSTALL_DIR}/bin")
endif()
# Set the final RPATH for installation
string(JOIN ";" CMAKE_INSTALL_RPATH "${RPATH_LIST}")
message(STATUS "Final CMAKE_INSTALL_RPATH: ${CMAKE_INSTALL_RPATH}")
endif()
set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
add_library(${MJCF_PLUGIN_TARGET_NAME} SHARED)
add_library(mujoco::usd::mjcf ALIAS ${MJCF_PLUGIN_TARGET_NAME})
target_sources(${MJCF_PLUGIN_TARGET_NAME} PRIVATE
plugins/mjcf/mjcf_file_format.cc
plugins/mjcf/mjcf_file_format.h
plugins/mjcf/mujoco_to_usd.cc
plugins/mjcf/mujoco_to_usd.h
plugins/mjcf/utils.cc
plugins/mjcf/utils.h
)
set_target_properties(${MJCF_PLUGIN_TARGET_NAME} PROPERTIES
OUTPUT_NAME ${MJCF_PLUGIN_TARGET_NAME}
${DEFAULT_CXX_VISIBILITY_PROPS}
)
target_include_directories(${MJCF_PLUGIN_TARGET_NAME} PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/plugins"
)
## mjcPhysics
add_library(${MJC_PHYSICS_PLUGIN_TARGET_NAME} SHARED)
target_sources(${MJC_PHYSICS_PLUGIN_TARGET_NAME} PRIVATE
mjcPhysics/actuatorAPI.cpp
mjcPhysics/collisionAPI.cpp
mjcPhysics/meshCollisionAPI.cpp
mjcPhysics/sceneAPI.cpp
mjcPhysics/siteAPI.cpp
mjcPhysics/tokens.cpp
)
set_target_properties(${MJC_PHYSICS_PLUGIN_TARGET_NAME} PROPERTIES
OUTPUT_NAME ${MJC_PHYSICS_PLUGIN_TARGET_NAME}
${DEFAULT_CXX_VISIBILITY_PROPS}
)
target_include_directories(${MJC_PHYSICS_PLUGIN_TARGET_NAME} PUBLIC
"${CMAKE_CURRENT_SOURCE_DIR}/mjcPhysics"
)
## mj_usd
add_library(${MJ_USD_TARGET_NAME} SHARED)
add_library(mujoco::usd ALIAS ${MJ_USD_TARGET_NAME})
target_sources(${MJ_USD_TARGET_NAME} PRIVATE
writer.cc
layer_sink.cc
usd_to_mjspec.cc
utils.cc
)
if (USD_DIR)
find_package(pxr REQUIRED)
target_link_libraries(${MJC_PHYSICS_PLUGIN_TARGET_NAME} PRIVATE
${OPENUSD_CORE_LIBS}
mujoco
tinyxml2
)
target_link_libraries(${MJCF_PLUGIN_TARGET_NAME} PRIVATE
${OPENUSD_CORE_LIBS}
mujoco
tinyxml2
${MJC_PHYSICS_PLUGIN_TARGET_NAME}
)
target_link_libraries(${MJ_USD_TARGET_NAME} PUBLIC
${OPENUSD_CORE_LIBS}
mujoco
tinyxml2
${MJC_PHYSICS_PLUGIN_TARGET_NAME}
)
elseif (HOUDINI_HFS_DIR)
message(STATUS "Configuring for Houdini target...")
# In Houdini, the Houdini package we would typically use via find_package
# does not have all the USD dependencies that we need (namely UsdPhysics)
# so we need to manually link all the required libraries.
configure_houdini_target(${MJC_PHYSICS_PLUGIN_TARGET_NAME} PRIVATE PRIVATE)
configure_houdini_target(${MJCF_PLUGIN_TARGET_NAME} PRIVATE PRIVATE)
configure_houdini_target(${MJ_USD_TARGET_NAME} PUBLIC PUBLIC)
# Re-add inter-plugin dependencies after Houdini-specific linking
target_link_libraries(${MJCF_PLUGIN_TARGET_NAME} PRIVATE ${MJC_PHYSICS_PLUGIN_TARGET_NAME})
target_link_libraries(${MJ_USD_TARGET_NAME} PUBLIC ${MJC_PHYSICS_PLUGIN_TARGET_NAME})
endif()
## Installation
message(STATUS "Mujoco USD plugins will be installed to: ${CMAKE_INSTALL_PREFIX}/${MJ_USD_INSTALL_DIR_LIB}")
message(STATUS "Make sure PXR_PLUGINPATH_NAME includes: ${CMAKE_INSTALL_PREFIX}/${MJ_USD_INSTALL_DIR_LIB}/*/resources")
# Generate and install plugInfo.json for each plugin
configure_and_install_usd_plugin_info(
${MJCF_PLUGIN_TARGET_NAME}
plugins/mjcf
${MJ_USD_INSTALL_DIR_LIB}
)
configure_and_install_usd_plugin_info(
${MJC_PHYSICS_PLUGIN_TARGET_NAME}
mjcPhysics
${MJ_USD_INSTALL_DIR_LIB}
)
# Install specific resources that are not plugInfo.json (like generatedSchema.usda)
install(FILES
mjcPhysics/generatedSchema.usda
DESTINATION ${MJ_USD_INSTALL_DIR_LIB}/resources/mjcPhysics
)
# Install shared libraries
install(TARGETS
${MJCF_PLUGIN_TARGET_NAME}
${MJC_PHYSICS_PLUGIN_TARGET_NAME}
${MJ_USD_TARGET_NAME}
LIBRARY DESTINATION ${MJ_USD_INSTALL_DIR_LIB}
)
+132
View File
@@ -0,0 +1,132 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <mujoco/experimental/usd/layer_sink.h>
#include <vector>
#include <mujoco/mujoco.h>
#include <pxr/base/gf/declare.h>
#include <pxr/base/gf/matrix4d.h>
#include <pxr/base/gf/quaternion.h>
#include <pxr/base/gf/rotation.h>
#include <pxr/base/gf/transform.h>
#include <pxr/base/tf/staticTokens.h>
#include <pxr/base/tf/token.h>
#include <pxr/base/vt/types.h>
#include <pxr/base/vt/value.h>
#include <pxr/pxr.h>
#include <pxr/usd/sdf/attributeSpec.h>
#include <pxr/usd/sdf/changeBlock.h>
#include <pxr/usd/sdf/childrenPolicies.h>
#include <pxr/usd/sdf/primSpec.h>
#include <pxr/usd/sdf/types.h>
#include <pxr/usd/sdf/valueTypeName.h>
#include <pxr/usd/usd/common.h>
#include <pxr/usd/usd/stage.h>
#include <pxr/usd/usd/timeCode.h>
#include <pxr/usd/usdGeom/tokens.h>
#include <pxr/usd/usdGeom/xformCache.h>
PXR_NAMESPACE_OPEN_SCOPE
TF_DEFINE_PRIVATE_TOKENS(_layer_sink_tokens,
((xformOpTransform,
"xformOp:transform:mujoco"))(Xform));
PXR_NAMESPACE_CLOSE_SCOPE
namespace {
pxr::SdfAttributeSpecHandle GetOrCreateAttribute(
pxr::SdfLayerHandle layer, pxr::SdfPrimSpecHandle prim_spec,
const pxr::SdfPath& path, const pxr::TfToken& token,
const pxr::SdfValueTypeName& type, pxr::SdfVariability variability) {
// Find or create transform attribute.
pxr::SdfAttributeSpecHandle attr =
layer->GetAttributeAtPath(path.AppendProperty(token));
if (!attr) {
attr = pxr::SdfAttributeSpec::New(prim_spec, token, type, variability);
}
return attr;
}
} // namespace
namespace mujoco {
namespace usd {
LayerSink::LayerSink(pxr::UsdStageRefPtr stage) : stage_(stage) {}
void LayerSink::Update(const mjData* const data,
std::vector<pxr::SdfPath> body_paths) {
pxr::SdfLayerHandle layer = stage_->GetEditTarget().GetLayer();
pxr::UsdTimeCode timecode = data->time * layer->GetTimeCodesPerSecond();
pxr::UsdGeomXformCache xform_cache(timecode);
pxr::SdfChangeBlock change_block;
// TODO(shaves) this can be parallelised.
for (int i = 0; i < body_paths.size(); i++) {
const pxr::SdfPath& path = body_paths[i];
// Author transform timesample
// Convert pose data.
mjtNum* xpos = &data->xpos[i * 3];
mjtNum* xquat = &data->xquat[i * 4];
pxr::GfVec3d translation(xpos[0], xpos[1], xpos[2]);
pxr::GfVec3d imag_part(xquat[1], xquat[2], xquat[3]);
// Some bodies such as the world body don't have mapped USD prims to
// transform.
if (path.IsEmpty()) {
continue;
}
pxr::SdfPrimSpecHandle prim_spec = pxr::SdfCreatePrimInLayer(layer, path);
// Find or create transform attribute.
pxr::SdfAttributeSpecHandle xform_attr = GetOrCreateAttribute(
layer, prim_spec, path, pxr::_layer_sink_tokens->xformOpTransform,
pxr::SdfValueTypeNames->Matrix4d, pxr::SdfVariabilityVarying);
pxr::GfQuaternion quat(xquat[0], imag_part);
pxr::GfRotation rotation(quat);
// Identities
pxr::GfVec3d scale(1.0);
pxr::GfVec3d pivot_position(0);
pxr::GfRotation pivot_rotation(pxr::GfQuaternion::GetIdentity());
// Set time sample
pxr::GfTransform xform(translation, rotation, scale, pivot_position,
pivot_rotation);
pxr::GfMatrix4d parent_xform =
xform_cache.GetParentToWorldTransform(stage_->GetPrimAtPath(path));
pxr::GfMatrix4d relative_xform =
xform.GetMatrix() * parent_xform.GetInverse();
layer->SetTimeSample(xform_attr->GetPath(), timecode.GetValue(),
relative_xform);
// Create xformop_order attribute if missing.
pxr::SdfAttributeSpecHandle op_order_attr = GetOrCreateAttribute(
layer, prim_spec, path, pxr::UsdGeomTokens->xformOpOrder,
pxr::SdfValueTypeNames->TokenArray, pxr::SdfVariabilityUniform);
// Override all local transforms so that this is the absolute position.
// We don't reset the xform stack here, so parent transforms will still
// affect the world pose.
const pxr::VtTokenArray xformop_order(
{pxr::_layer_sink_tokens->xformOpTransform});
op_order_attr->SetDefaultValue(pxr::VtValue(xformop_order));
}
}
} // namespace usd
} // namespace mujoco
@@ -12,12 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include "./actuatorAPI.h"
#include <mujoco/experimental/usd/mjcPhysics/actuatorAPI.h>
#include "pxr/usd/sdf/assetPath.h"
#include "pxr/usd/sdf/types.h"
#include "pxr/usd/usd/schemaRegistry.h"
#include "pxr/usd/usd/typed.h"
#include <pxr/usd/sdf/assetPath.h>
#include <pxr/usd/sdf/types.h>
#include <pxr/usd/usd/schemaRegistry.h>
#include <pxr/usd/usd/typed.h>
PXR_NAMESPACE_OPEN_SCOPE
@@ -325,15 +325,6 @@ UsdRelationship MjcPhysicsActuatorAPI::CreateMjcRefSiteRel() const {
/* custom = */ false);
}
UsdRelationship MjcPhysicsActuatorAPI::GetMjcCrankSiteRel() const {
return GetPrim().GetRelationship(MjcPhysicsTokens->mjcCrankSite);
}
UsdRelationship MjcPhysicsActuatorAPI::CreateMjcCrankSiteRel() const {
return GetPrim().CreateRelationship(MjcPhysicsTokens->mjcCrankSite,
/* custom = */ false);
}
UsdRelationship MjcPhysicsActuatorAPI::GetMjcSliderSiteRel() const {
return GetPrim().GetRelationship(MjcPhysicsTokens->mjcSliderSite);
}
@@ -12,12 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include "./collisionAPI.h"
#include <mujoco/experimental/usd/mjcPhysics/collisionAPI.h>
#include "pxr/usd/sdf/assetPath.h"
#include "pxr/usd/sdf/types.h"
#include "pxr/usd/usd/schemaRegistry.h"
#include "pxr/usd/usd/typed.h"
#include <pxr/usd/sdf/assetPath.h>
#include <pxr/usd/sdf/types.h>
#include <pxr/usd/usd/schemaRegistry.h>
#include <pxr/usd/usd/typed.h>
PXR_NAMESPACE_OPEN_SCOPE
@@ -277,9 +277,6 @@ class "PhysicsActuatorAPI" (
uniform double mjc:crankLength = 0 (
doc = "Used only for the slider-crank transmission type. Specifies the length of the connecting rod. The compiler expects this value to be positive when a slider-crank transmission is present."
)
rel mjc:crankSite (
doc = "If specified, the actuator acts on a slider-crank mechanism which is implicitly determined by the actuator (i.e., it is not a separate model element). The target site corresponds to the pin joining the crank and the connecting rod."
)
uniform token mjc:ctrlLimited = "auto" (
allowedTokens = ["false", "true", "auto"]
doc = "If true, the control input to this actuator is automatically clamped to ctrlrange at runtime. If false, control input clamping is disabled. If 'auto' and autolimits is set in compiler, control clamping will automatically be set to true if ctrlrange is defined without explicitly setting this attribute to 'true'. Note that control input clamping can also be globally disabled with the clampctrl attribute of option/flag."
@@ -334,3 +331,82 @@ class "PhysicsActuatorAPI" (
)
}
class Keyframe "Keyframe" (
doc = "Represents time independent keyframe values."
)
{
double[] mjc:act (
doc = "Vector of actuator activations, copied into mjData.act when the simulation state is set to this keyframe."
)
double[] mjc:ctrl (
doc = "Vector of controls, copied into mjData.ctrl when the simulation state is set to this keyframe."
)
double[] mjc:mpos (
doc = "Vector of mocap body positions, copied into mjData.mocap_pos when the simulation state is set to this keyframe."
)
double[] mjc:mquat (
doc = "Vector of mocap body quaternions, copied into mjData.mocap_quat when the simulation state is set to this keyframe."
)
double[] mjc:qpos (
doc = "Vector of joint positions, copied into mjData.qpos when the simulation state is set to this keyframe."
)
double[] mjc:qvel (
doc = "Vector of joint velocities, copied into mjData.qvel when the simulation state is set to this keyframe."
)
}
class "PhysicsJointsAPI" (
doc = "API describing a Mujoco joint."
)
{
uniform token mjc:actuatorfrclimited = "auto" (
allowedTokens = ["false", "true", "auto"]
doc = "This attribute specifies whether actuator forces acting on the joint should be clamped. See Force limits for details. It is available only for scalar joints (hinge and slider) and ignored for ball and free joints. This attribute interacts with the actuatorfrcrange attribute. If this attribute is 'false', actuator force clamping is disabled. If it is 'true', actuator force clamping is enabled. If this attribute is 'auto', and autolimits is set in compiler, actuator force clamping will be enabled if actuatorfrcrange is defined."
)
uniform double mjc:actuatorfrcrange:max = 0 (
doc = "Maximum range for clamping total actuator forces acting on this joint. See Force limits for details. It is available only for scalar joints (hinge and slider) and ignored for ball and free joints. The compiler expects the first value to be smaller than the second value. Setting this attribute without specifying actuatorfrclimited is an error if compiler-autolimits is 'false'."
)
uniform double mjc:actuatorfrcrange:min = 0 (
doc = "Minimum range for clamping total actuator forces acting on this joint. See Force limits for details. It is available only for scalar joints (hinge and slider) and ignored for ball and free joints. The compiler expects the first value to be smaller than the second value. Setting this attribute without specifying actuatorfrclimited is an error if compiler-autolimits is 'false'."
)
uniform bool mjc:actuatorgravcomp = 0 (
doc = "If this flag is enabled, gravity compensation applied to this joint is added to actuator forces (mjData.qfrc_actuator) rather than passive forces (mjData.qfrc_passive). Notionally, this means that gravity compensation is the result of a control system rather than natural buoyancy. In practice, enabling this flag is useful when joint-level actuator force clamping is used. In this case, the total actuation force applied on a joint, including gravity compensation, is guaranteed to not exceed the specified limits. See Force limits and actuatorfrcrange for more details on this type of force limit."
)
uniform double mjc:armature = 0 (
doc = "Additional inertia associated with movement of the joint that is not due to body mass. This added inertia is usually due to a rotor (a.k.a armature) spinning faster than the joint itself due to a geared transmission. The value applies to all degrees of freedom created by this joint. Besides increasing the realism of joints with geared transmission, positive armature significantly improves simulation stability, even for small values, and is a recommended possible fix when encountering stability issues."
)
uniform double mjc:damping = 0 (
doc = "Damping applied to all degrees of freedom created by this joint. Unlike friction loss which is computed by the constraint solver, damping is simply a force linear in velocity. It is included in the passive forces. Despite this simplicity, larger damping values can make numerical integrators unstable, which is why our Euler integrator handles damping implicitly. See Integration in the Computation chapter."
)
uniform double mjc:frictionloss = 0 (
doc = "Friction loss due to dry friction. This value is the same for all degrees of freedom created by this joint. Semantically friction loss does not make sense for free joints, but the compiler allows it. To enable friction loss, set this attribute to a positive value."
)
uniform double mjc:margin = 0 (
doc = "The distance threshold below which limits become active. Recall that the Constraint solver normally generates forces as soon as a constraint becomes active, even if the margin parameter makes that happen at a distance. This attribute together with solreflimit and solimplimit can be used to model a soft joint limit."
)
uniform double mjc:ref = 0 (
doc = "The reference position or angle of the joint. This attribute is only used for slide and hinge joints. It defines the joint value corresponding to the initial model configuration. The amount of spatial transformation that the joint applies at runtime equals the current joint value stored in mjData.qpos minus this reference value stored in mjModel.qpos0. The meaning of these vectors was discussed in the Stand-alone section in the Overview chapter."
)
uniform double[] mjc:solimpfriction = [0.9, 0.95, 0.001, 0.5, 2] (
doc = "Constraint solver parameters for simulating dry friction."
)
uniform double[] mjc:solimplimit = [0.9, 0.95, 0.001, 0.5, 2] (
doc = "Constraint solver parameters for simulating joint limits."
)
uniform double[] mjc:solreffriction = [0.02, 1] (
doc = "Constraint solver parameters for simulating dry friction."
)
uniform double[] mjc:solreflimit = [0.02, 1] (
doc = "Constraint solver parameters for simulating joint limits."
)
uniform double[] mjc:springdamper = [0, 0] (
doc = "When both numbers are positive, the compiler will override any stiffness and damping values specified with the attributes below, and will instead set them automatically so that the resulting mass-spring-damper for this joint has the desired time constant (first value) and damping ratio (second value). This is done by taking into account the joint inertia in the model reference configuration. Note that the format is the same as the solref parameter of the constraint solver."
)
uniform double mjc:springref = 0 (
doc = "The joint position or angle in which the joint spring (if any) achieves equilibrium. Similar to the vector mjModel.qpos0 which stores all joint reference values specified with the ref attribute above, all spring reference values specified with this attribute are stored in the vector mjModel.qpos_spring. The model configuration corresponding to mjModel.qpos_spring is also used to compute the spring reference lengths of all tendons, stored in mjModel.tendon_lengthspring. This is because tendons can also have springs."
)
uniform double mjc:stiffness = 0 (
doc = "Joint stiffness. If this value is positive, a spring will be created with equilibrium position given by springref below. The spring force is computed along with the other passive forces."
)
}
@@ -0,0 +1,303 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <mujoco/experimental/usd/mjcPhysics/jointAPI.h>
#include <pxr/usd/sdf/assetPath.h>
#include <pxr/usd/sdf/types.h>
#include <pxr/usd/usd/schemaRegistry.h>
#include <pxr/usd/usd/typed.h>
PXR_NAMESPACE_OPEN_SCOPE
// Register the schema with the TfType system.
TF_REGISTRY_FUNCTION(TfType) {
TfType::Define<MjcPhysicsJointAPI, TfType::Bases<UsdAPISchemaBase> >();
}
/* virtual */
MjcPhysicsJointAPI::~MjcPhysicsJointAPI() {}
/* static */
MjcPhysicsJointAPI MjcPhysicsJointAPI::Get(const UsdStagePtr &stage,
const SdfPath &path) {
if (!stage) {
TF_CODING_ERROR("Invalid stage");
return MjcPhysicsJointAPI();
}
return MjcPhysicsJointAPI(stage->GetPrimAtPath(path));
}
/* virtual */
UsdSchemaKind MjcPhysicsJointAPI::_GetSchemaKind() const {
return MjcPhysicsJointAPI::schemaKind;
}
/* static */
bool MjcPhysicsJointAPI::CanApply(const UsdPrim &prim, std::string *whyNot) {
return prim.CanApplyAPI<MjcPhysicsJointAPI>(whyNot);
}
/* static */
MjcPhysicsJointAPI MjcPhysicsJointAPI::Apply(const UsdPrim &prim) {
if (prim.ApplyAPI<MjcPhysicsJointAPI>()) {
return MjcPhysicsJointAPI(prim);
}
return MjcPhysicsJointAPI();
}
/* static */
const TfType &MjcPhysicsJointAPI::_GetStaticTfType() {
static TfType tfType = TfType::Find<MjcPhysicsJointAPI>();
return tfType;
}
/* static */
bool MjcPhysicsJointAPI::_IsTypedSchema() {
static bool isTyped = _GetStaticTfType().IsA<UsdTyped>();
return isTyped;
}
/* virtual */
const TfType &MjcPhysicsJointAPI::_GetTfType() const {
return _GetStaticTfType();
}
UsdAttribute MjcPhysicsJointAPI::GetMjcSpringdamperAttr() const {
return GetPrim().GetAttribute(MjcPhysicsTokens->mjcSpringdamper);
}
UsdAttribute MjcPhysicsJointAPI::CreateMjcSpringdamperAttr(
VtValue const &defaultValue, bool writeSparsely) const {
return UsdSchemaBase::_CreateAttr(
MjcPhysicsTokens->mjcSpringdamper, SdfValueTypeNames->DoubleArray,
/* custom = */ false, SdfVariabilityUniform, defaultValue, writeSparsely);
}
UsdAttribute MjcPhysicsJointAPI::GetMjcSolreflimitAttr() const {
return GetPrim().GetAttribute(MjcPhysicsTokens->mjcSolreflimit);
}
UsdAttribute MjcPhysicsJointAPI::CreateMjcSolreflimitAttr(
VtValue const &defaultValue, bool writeSparsely) const {
return UsdSchemaBase::_CreateAttr(
MjcPhysicsTokens->mjcSolreflimit, SdfValueTypeNames->DoubleArray,
/* custom = */ false, SdfVariabilityUniform, defaultValue, writeSparsely);
}
UsdAttribute MjcPhysicsJointAPI::GetMjcSolimplimitAttr() const {
return GetPrim().GetAttribute(MjcPhysicsTokens->mjcSolimplimit);
}
UsdAttribute MjcPhysicsJointAPI::CreateMjcSolimplimitAttr(
VtValue const &defaultValue, bool writeSparsely) const {
return UsdSchemaBase::_CreateAttr(
MjcPhysicsTokens->mjcSolimplimit, SdfValueTypeNames->DoubleArray,
/* custom = */ false, SdfVariabilityUniform, defaultValue, writeSparsely);
}
UsdAttribute MjcPhysicsJointAPI::GetMjcSolreffrictionAttr() const {
return GetPrim().GetAttribute(MjcPhysicsTokens->mjcSolreffriction);
}
UsdAttribute MjcPhysicsJointAPI::CreateMjcSolreffrictionAttr(
VtValue const &defaultValue, bool writeSparsely) const {
return UsdSchemaBase::_CreateAttr(
MjcPhysicsTokens->mjcSolreffriction, SdfValueTypeNames->DoubleArray,
/* custom = */ false, SdfVariabilityUniform, defaultValue, writeSparsely);
}
UsdAttribute MjcPhysicsJointAPI::GetMjcSolimpfrictionAttr() const {
return GetPrim().GetAttribute(MjcPhysicsTokens->mjcSolimpfriction);
}
UsdAttribute MjcPhysicsJointAPI::CreateMjcSolimpfrictionAttr(
VtValue const &defaultValue, bool writeSparsely) const {
return UsdSchemaBase::_CreateAttr(
MjcPhysicsTokens->mjcSolimpfriction, SdfValueTypeNames->DoubleArray,
/* custom = */ false, SdfVariabilityUniform, defaultValue, writeSparsely);
}
UsdAttribute MjcPhysicsJointAPI::GetMjcStiffnessAttr() const {
return GetPrim().GetAttribute(MjcPhysicsTokens->mjcStiffness);
}
UsdAttribute MjcPhysicsJointAPI::CreateMjcStiffnessAttr(
VtValue const &defaultValue, bool writeSparsely) const {
return UsdSchemaBase::_CreateAttr(
MjcPhysicsTokens->mjcStiffness, SdfValueTypeNames->Double,
/* custom = */ false, SdfVariabilityUniform, defaultValue, writeSparsely);
}
UsdAttribute MjcPhysicsJointAPI::GetMjcActuatorfrcrangeMinAttr() const {
return GetPrim().GetAttribute(MjcPhysicsTokens->mjcActuatorfrcrangeMin);
}
UsdAttribute MjcPhysicsJointAPI::CreateMjcActuatorfrcrangeMinAttr(
VtValue const &defaultValue, bool writeSparsely) const {
return UsdSchemaBase::_CreateAttr(
MjcPhysicsTokens->mjcActuatorfrcrangeMin, SdfValueTypeNames->Double,
/* custom = */ false, SdfVariabilityUniform, defaultValue, writeSparsely);
}
UsdAttribute MjcPhysicsJointAPI::GetMjcActuatorfrcrangeMaxAttr() const {
return GetPrim().GetAttribute(MjcPhysicsTokens->mjcActuatorfrcrangeMax);
}
UsdAttribute MjcPhysicsJointAPI::CreateMjcActuatorfrcrangeMaxAttr(
VtValue const &defaultValue, bool writeSparsely) const {
return UsdSchemaBase::_CreateAttr(
MjcPhysicsTokens->mjcActuatorfrcrangeMax, SdfValueTypeNames->Double,
/* custom = */ false, SdfVariabilityUniform, defaultValue, writeSparsely);
}
UsdAttribute MjcPhysicsJointAPI::GetMjcActuatorfrclimitedAttr() const {
return GetPrim().GetAttribute(MjcPhysicsTokens->mjcActuatorfrclimited);
}
UsdAttribute MjcPhysicsJointAPI::CreateMjcActuatorfrclimitedAttr(
VtValue const &defaultValue, bool writeSparsely) const {
return UsdSchemaBase::_CreateAttr(
MjcPhysicsTokens->mjcActuatorfrclimited, SdfValueTypeNames->Token,
/* custom = */ false, SdfVariabilityUniform, defaultValue, writeSparsely);
}
UsdAttribute MjcPhysicsJointAPI::GetMjcActuatorgravcompAttr() const {
return GetPrim().GetAttribute(MjcPhysicsTokens->mjcActuatorgravcomp);
}
UsdAttribute MjcPhysicsJointAPI::CreateMjcActuatorgravcompAttr(
VtValue const &defaultValue, bool writeSparsely) const {
return UsdSchemaBase::_CreateAttr(
MjcPhysicsTokens->mjcActuatorgravcomp, SdfValueTypeNames->Bool,
/* custom = */ false, SdfVariabilityUniform, defaultValue, writeSparsely);
}
UsdAttribute MjcPhysicsJointAPI::GetMjcMarginAttr() const {
return GetPrim().GetAttribute(MjcPhysicsTokens->mjcMargin);
}
UsdAttribute MjcPhysicsJointAPI::CreateMjcMarginAttr(
VtValue const &defaultValue, bool writeSparsely) const {
return UsdSchemaBase::_CreateAttr(
MjcPhysicsTokens->mjcMargin, SdfValueTypeNames->Double,
/* custom = */ false, SdfVariabilityUniform, defaultValue, writeSparsely);
}
UsdAttribute MjcPhysicsJointAPI::GetMjcRefAttr() const {
return GetPrim().GetAttribute(MjcPhysicsTokens->mjcRef);
}
UsdAttribute MjcPhysicsJointAPI::CreateMjcRefAttr(VtValue const &defaultValue,
bool writeSparsely) const {
return UsdSchemaBase::_CreateAttr(
MjcPhysicsTokens->mjcRef, SdfValueTypeNames->Double,
/* custom = */ false, SdfVariabilityUniform, defaultValue, writeSparsely);
}
UsdAttribute MjcPhysicsJointAPI::GetMjcSpringrefAttr() const {
return GetPrim().GetAttribute(MjcPhysicsTokens->mjcSpringref);
}
UsdAttribute MjcPhysicsJointAPI::CreateMjcSpringrefAttr(
VtValue const &defaultValue, bool writeSparsely) const {
return UsdSchemaBase::_CreateAttr(
MjcPhysicsTokens->mjcSpringref, SdfValueTypeNames->Double,
/* custom = */ false, SdfVariabilityUniform, defaultValue, writeSparsely);
}
UsdAttribute MjcPhysicsJointAPI::GetMjcArmatureAttr() const {
return GetPrim().GetAttribute(MjcPhysicsTokens->mjcArmature);
}
UsdAttribute MjcPhysicsJointAPI::CreateMjcArmatureAttr(
VtValue const &defaultValue, bool writeSparsely) const {
return UsdSchemaBase::_CreateAttr(
MjcPhysicsTokens->mjcArmature, SdfValueTypeNames->Double,
/* custom = */ false, SdfVariabilityUniform, defaultValue, writeSparsely);
}
UsdAttribute MjcPhysicsJointAPI::GetMjcDampingAttr() const {
return GetPrim().GetAttribute(MjcPhysicsTokens->mjcDamping);
}
UsdAttribute MjcPhysicsJointAPI::CreateMjcDampingAttr(
VtValue const &defaultValue, bool writeSparsely) const {
return UsdSchemaBase::_CreateAttr(
MjcPhysicsTokens->mjcDamping, SdfValueTypeNames->Double,
/* custom = */ false, SdfVariabilityUniform, defaultValue, writeSparsely);
}
UsdAttribute MjcPhysicsJointAPI::GetMjcFrictionlossAttr() const {
return GetPrim().GetAttribute(MjcPhysicsTokens->mjcFrictionloss);
}
UsdAttribute MjcPhysicsJointAPI::CreateMjcFrictionlossAttr(
VtValue const &defaultValue, bool writeSparsely) const {
return UsdSchemaBase::_CreateAttr(
MjcPhysicsTokens->mjcFrictionloss, SdfValueTypeNames->Double,
/* custom = */ false, SdfVariabilityUniform, defaultValue, writeSparsely);
}
namespace {
static inline TfTokenVector _ConcatenateAttributeNames(
const TfTokenVector &left, const TfTokenVector &right) {
TfTokenVector result;
result.reserve(left.size() + right.size());
result.insert(result.end(), left.begin(), left.end());
result.insert(result.end(), right.begin(), right.end());
return result;
}
} // namespace
/*static*/
const TfTokenVector &MjcPhysicsJointAPI::GetSchemaAttributeNames(
bool includeInherited) {
static TfTokenVector localNames = {
MjcPhysicsTokens->mjcSpringdamper,
MjcPhysicsTokens->mjcSolreflimit,
MjcPhysicsTokens->mjcSolimplimit,
MjcPhysicsTokens->mjcSolreffriction,
MjcPhysicsTokens->mjcSolimpfriction,
MjcPhysicsTokens->mjcStiffness,
MjcPhysicsTokens->mjcActuatorfrcrangeMin,
MjcPhysicsTokens->mjcActuatorfrcrangeMax,
MjcPhysicsTokens->mjcActuatorfrclimited,
MjcPhysicsTokens->mjcActuatorgravcomp,
MjcPhysicsTokens->mjcMargin,
MjcPhysicsTokens->mjcRef,
MjcPhysicsTokens->mjcSpringref,
MjcPhysicsTokens->mjcArmature,
MjcPhysicsTokens->mjcDamping,
MjcPhysicsTokens->mjcFrictionloss,
};
static TfTokenVector allNames = _ConcatenateAttributeNames(
UsdAPISchemaBase::GetSchemaAttributeNames(true), localNames);
if (includeInherited)
return allNames;
else
return localNames;
}
PXR_NAMESPACE_CLOSE_SCOPE
// ===================================================================== //
// Feel free to add custom code below this line. It will be preserved by
// the code generator.
//
// Just remember to wrap code in the appropriate delimiters:
// 'PXR_NAMESPACE_OPEN_SCOPE', 'PXR_NAMESPACE_CLOSE_SCOPE'.
// ===================================================================== //
// --(BEGIN CUSTOM CODE)--
@@ -0,0 +1,185 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <mujoco/experimental/usd/mjcPhysics/keyframe.h>
#include <pxr/usd/sdf/assetPath.h>
#include <pxr/usd/sdf/types.h>
#include <pxr/usd/usd/schemaRegistry.h>
#include <pxr/usd/usd/typed.h>
PXR_NAMESPACE_OPEN_SCOPE
// Register the schema with the TfType system.
TF_REGISTRY_FUNCTION(TfType) {
TfType::Define<MjcPhysicsKeyframe, TfType::Bases<UsdTyped> >();
// Register the usd prim typename as an alias under UsdSchemaBase. This
// enables one to call
// TfType::Find<UsdSchemaBase>().FindDerivedByName("Keyframe")
// to find TfType<MjcPhysicsKeyframe>, which is how IsA queries are
// answered.
TfType::AddAlias<UsdSchemaBase, MjcPhysicsKeyframe>("Keyframe");
}
/* virtual */
MjcPhysicsKeyframe::~MjcPhysicsKeyframe() {}
/* static */
MjcPhysicsKeyframe MjcPhysicsKeyframe::Get(const UsdStagePtr &stage,
const SdfPath &path) {
if (!stage) {
TF_CODING_ERROR("Invalid stage");
return MjcPhysicsKeyframe();
}
return MjcPhysicsKeyframe(stage->GetPrimAtPath(path));
}
/* static */
MjcPhysicsKeyframe MjcPhysicsKeyframe::Define(const UsdStagePtr &stage,
const SdfPath &path) {
static TfToken usdPrimTypeName("Keyframe");
if (!stage) {
TF_CODING_ERROR("Invalid stage");
return MjcPhysicsKeyframe();
}
return MjcPhysicsKeyframe(stage->DefinePrim(path, usdPrimTypeName));
}
/* virtual */
UsdSchemaKind MjcPhysicsKeyframe::_GetSchemaKind() const {
return MjcPhysicsKeyframe::schemaKind;
}
/* static */
const TfType &MjcPhysicsKeyframe::_GetStaticTfType() {
static TfType tfType = TfType::Find<MjcPhysicsKeyframe>();
return tfType;
}
/* static */
bool MjcPhysicsKeyframe::_IsTypedSchema() {
static bool isTyped = _GetStaticTfType().IsA<UsdTyped>();
return isTyped;
}
/* virtual */
const TfType &MjcPhysicsKeyframe::_GetTfType() const {
return _GetStaticTfType();
}
UsdAttribute MjcPhysicsKeyframe::GetMjcQposAttr() const {
return GetPrim().GetAttribute(MjcPhysicsTokens->mjcQpos);
}
UsdAttribute MjcPhysicsKeyframe::CreateMjcQposAttr(VtValue const &defaultValue,
bool writeSparsely) const {
return UsdSchemaBase::_CreateAttr(
MjcPhysicsTokens->mjcQpos, SdfValueTypeNames->DoubleArray,
/* custom = */ false, SdfVariabilityVarying, defaultValue, writeSparsely);
}
UsdAttribute MjcPhysicsKeyframe::GetMjcQvelAttr() const {
return GetPrim().GetAttribute(MjcPhysicsTokens->mjcQvel);
}
UsdAttribute MjcPhysicsKeyframe::CreateMjcQvelAttr(VtValue const &defaultValue,
bool writeSparsely) const {
return UsdSchemaBase::_CreateAttr(
MjcPhysicsTokens->mjcQvel, SdfValueTypeNames->DoubleArray,
/* custom = */ false, SdfVariabilityVarying, defaultValue, writeSparsely);
}
UsdAttribute MjcPhysicsKeyframe::GetMjcActAttr() const {
return GetPrim().GetAttribute(MjcPhysicsTokens->mjcAct);
}
UsdAttribute MjcPhysicsKeyframe::CreateMjcActAttr(VtValue const &defaultValue,
bool writeSparsely) const {
return UsdSchemaBase::_CreateAttr(
MjcPhysicsTokens->mjcAct, SdfValueTypeNames->DoubleArray,
/* custom = */ false, SdfVariabilityVarying, defaultValue, writeSparsely);
}
UsdAttribute MjcPhysicsKeyframe::GetMjcCtrlAttr() const {
return GetPrim().GetAttribute(MjcPhysicsTokens->mjcCtrl);
}
UsdAttribute MjcPhysicsKeyframe::CreateMjcCtrlAttr(VtValue const &defaultValue,
bool writeSparsely) const {
return UsdSchemaBase::_CreateAttr(
MjcPhysicsTokens->mjcCtrl, SdfValueTypeNames->DoubleArray,
/* custom = */ false, SdfVariabilityVarying, defaultValue, writeSparsely);
}
UsdAttribute MjcPhysicsKeyframe::GetMjcMposAttr() const {
return GetPrim().GetAttribute(MjcPhysicsTokens->mjcMpos);
}
UsdAttribute MjcPhysicsKeyframe::CreateMjcMposAttr(VtValue const &defaultValue,
bool writeSparsely) const {
return UsdSchemaBase::_CreateAttr(
MjcPhysicsTokens->mjcMpos, SdfValueTypeNames->DoubleArray,
/* custom = */ false, SdfVariabilityVarying, defaultValue, writeSparsely);
}
UsdAttribute MjcPhysicsKeyframe::GetMjcMquatAttr() const {
return GetPrim().GetAttribute(MjcPhysicsTokens->mjcMquat);
}
UsdAttribute MjcPhysicsKeyframe::CreateMjcMquatAttr(VtValue const &defaultValue,
bool writeSparsely) const {
return UsdSchemaBase::_CreateAttr(
MjcPhysicsTokens->mjcMquat, SdfValueTypeNames->DoubleArray,
/* custom = */ false, SdfVariabilityVarying, defaultValue, writeSparsely);
}
namespace {
static inline TfTokenVector _ConcatenateAttributeNames(
const TfTokenVector &left, const TfTokenVector &right) {
TfTokenVector result;
result.reserve(left.size() + right.size());
result.insert(result.end(), left.begin(), left.end());
result.insert(result.end(), right.begin(), right.end());
return result;
}
} // namespace
/*static*/
const TfTokenVector &MjcPhysicsKeyframe::GetSchemaAttributeNames(
bool includeInherited) {
static TfTokenVector localNames = {
MjcPhysicsTokens->mjcQpos, MjcPhysicsTokens->mjcQvel,
MjcPhysicsTokens->mjcAct, MjcPhysicsTokens->mjcCtrl,
MjcPhysicsTokens->mjcMpos, MjcPhysicsTokens->mjcMquat,
};
static TfTokenVector allNames = _ConcatenateAttributeNames(
UsdTyped::GetSchemaAttributeNames(true), localNames);
if (includeInherited)
return allNames;
else
return localNames;
}
PXR_NAMESPACE_CLOSE_SCOPE
// ===================================================================== //
// Feel free to add custom code below this line. It will be preserved by
// the code generator.
//
// Just remember to wrap code in the appropriate delimiters:
// 'PXR_NAMESPACE_OPEN_SCOPE', 'PXR_NAMESPACE_CLOSE_SCOPE'.
// ===================================================================== //
// --(BEGIN CUSTOM CODE)--
@@ -12,12 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include "./meshCollisionAPI.h"
#include <mujoco/experimental/usd/mjcPhysics/meshCollisionAPI.h>
#include "pxr/usd/sdf/assetPath.h"
#include "pxr/usd/sdf/types.h"
#include "pxr/usd/usd/schemaRegistry.h"
#include "pxr/usd/usd/typed.h"
#include <pxr/usd/sdf/assetPath.h>
#include <pxr/usd/sdf/types.h>
#include <pxr/usd/usd/schemaRegistry.h>
#include <pxr/usd/usd/typed.h>
PXR_NAMESPACE_OPEN_SCOPE
+21 -4
View File
@@ -1,6 +1,3 @@
#Portions of this file auto - generated by usdGenSchema.
#Edits will survive regeneration except for comments and
#changes to types with autoGenerated = true.
{
"Plugins": [
{
@@ -26,6 +23,26 @@
],
"schemaKind": "singleApplyAPI"
},
"MjcPhysicsJointAPI": {
"alias": {
"UsdSchemaBase": "PhysicsJointsAPI"
},
"autoGenerated": true,
"bases": [
"UsdAPISchemaBase"
],
"schemaKind": "singleApplyAPI"
},
"MjcPhysicsKeyframe": {
"alias": {
"UsdSchemaBase": "Keyframe"
},
"autoGenerated": true,
"bases": [
"UsdTyped"
],
"schemaKind": "concreteTyped"
},
"MjcPhysicsMeshCollisionAPI": {
"alias": {
"UsdSchemaBase": "MeshCollisionAPI"
@@ -58,7 +75,7 @@
}
}
},
"LibraryPath": "",
"LibraryPath": "@PLUG_INFO_LIBRARY_PATH@",
"Name": "mjcPhysics",
"ResourcePath": "",
"Root": ".",
+5 -5
View File
@@ -12,12 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include "./sceneAPI.h"
#include <mujoco/experimental/usd/mjcPhysics/sceneAPI.h>
#include "pxr/usd/sdf/assetPath.h"
#include "pxr/usd/sdf/types.h"
#include "pxr/usd/usd/schemaRegistry.h"
#include "pxr/usd/usd/typed.h"
#include <pxr/usd/sdf/assetPath.h>
#include <pxr/usd/sdf/types.h>
#include <pxr/usd/usd/schemaRegistry.h>
#include <pxr/usd/usd/typed.h>
PXR_NAMESPACE_OPEN_SCOPE
+107
View File
@@ -663,3 +663,110 @@ class "PhysicsActuatorAPI"
}
class Keyframe "Keyframe"
(
doc = """Represents time independent keyframe values."""
inherits = </Typed>
)
{
double[] mjc:qpos (
doc = "Vector of joint positions, copied into mjData.qpos when the simulation state is set to this keyframe."
)
double[] mjc:qvel (
doc = "Vector of joint velocities, copied into mjData.qvel when the simulation state is set to this keyframe."
)
double[] mjc:act (
doc = "Vector of actuator activations, copied into mjData.act when the simulation state is set to this keyframe."
)
double[] mjc:ctrl (
doc = "Vector of controls, copied into mjData.ctrl when the simulation state is set to this keyframe."
)
double[] mjc:mpos (
doc = "Vector of mocap body positions, copied into mjData.mocap_pos when the simulation state is set to this keyframe."
)
double[] mjc:mquat (
doc = "Vector of mocap body quaternions, copied into mjData.mocap_quat when the simulation state is set to this keyframe."
)
}
class "PhysicsJointsAPI"
(
customData = {
string className = "JointAPI"
}
doc = """API describing a Mujoco joint."""
inherits = </APISchemaBase>
)
{
uniform double[] mjc:springdamper = [0, 0] (
doc = "When both numbers are positive, the compiler will override any stiffness and damping values specified with the attributes below, and will instead set them automatically so that the resulting mass-spring-damper for this joint has the desired time constant (first value) and damping ratio (second value). This is done by taking into account the joint inertia in the model reference configuration. Note that the format is the same as the solref parameter of the constraint solver."
)
uniform double[] mjc:solreflimit = [0.02, 1.0] (
doc = "Constraint solver parameters for simulating joint limits."
)
uniform double[] mjc:solimplimit = [0.9, 0.95, 0.001, 0.5, 2.0] (
doc = "Constraint solver parameters for simulating joint limits."
)
uniform double[] mjc:solreffriction = [0.02, 1.0] (
doc = "Constraint solver parameters for simulating dry friction."
)
uniform double[] mjc:solimpfriction = [0.9, 0.95, 0.001, 0.5, 2.0] (
doc = "Constraint solver parameters for simulating dry friction."
)
uniform double mjc:stiffness = 0 (
doc = "Joint stiffness. If this value is positive, a spring will be created with equilibrium position given by springref below. The spring force is computed along with the other passive forces."
)
uniform double mjc:actuatorfrcrange:min = 0 (
doc = "Minimum range for clamping total actuator forces acting on this joint. See Force limits for details. It is available only for scalar joints (hinge and slider) and ignored for ball and free joints. The compiler expects the first value to be smaller than the second value. Setting this attribute without specifying actuatorfrclimited is an error if compiler-autolimits is 'false'."
)
uniform double mjc:actuatorfrcrange:max = 0 (
doc = "Maximum range for clamping total actuator forces acting on this joint. See Force limits for details. It is available only for scalar joints (hinge and slider) and ignored for ball and free joints. The compiler expects the first value to be smaller than the second value. Setting this attribute without specifying actuatorfrclimited is an error if compiler-autolimits is 'false'."
)
uniform token mjc:actuatorfrclimited = "auto" (
doc = "This attribute specifies whether actuator forces acting on the joint should be clamped. See Force limits for details. It is available only for scalar joints (hinge and slider) and ignored for ball and free joints. This attribute interacts with the actuatorfrcrange attribute. If this attribute is 'false', actuator force clamping is disabled. If it is 'true', actuator force clamping is enabled. If this attribute is 'auto', and autolimits is set in compiler, actuator force clamping will be enabled if actuatorfrcrange is defined."
allowedTokens = ["false", "true", "auto"]
)
uniform bool mjc:actuatorgravcomp = false (
doc = "If this flag is enabled, gravity compensation applied to this joint is added to actuator forces (mjData.qfrc_actuator) rather than passive forces (mjData.qfrc_passive). Notionally, this means that gravity compensation is the result of a control system rather than natural buoyancy. In practice, enabling this flag is useful when joint-level actuator force clamping is used. In this case, the total actuation force applied on a joint, including gravity compensation, is guaranteed to not exceed the specified limits. See Force limits and actuatorfrcrange for more details on this type of force limit."
)
uniform double mjc:margin = 0 (
doc = "The distance threshold below which limits become active. Recall that the Constraint solver normally generates forces as soon as a constraint becomes active, even if the margin parameter makes that happen at a distance. This attribute together with solreflimit and solimplimit can be used to model a soft joint limit."
)
uniform double mjc:ref = 0 (
doc = "The reference position or angle of the joint. This attribute is only used for slide and hinge joints. It defines the joint value corresponding to the initial model configuration. The amount of spatial transformation that the joint applies at runtime equals the current joint value stored in mjData.qpos minus this reference value stored in mjModel.qpos0. The meaning of these vectors was discussed in the Stand-alone section in the Overview chapter."
)
uniform double mjc:springref = 0 (
doc = "The joint position or angle in which the joint spring (if any) achieves equilibrium. Similar to the vector mjModel.qpos0 which stores all joint reference values specified with the ref attribute above, all spring reference values specified with this attribute are stored in the vector mjModel.qpos_spring. The model configuration corresponding to mjModel.qpos_spring is also used to compute the spring reference lengths of all tendons, stored in mjModel.tendon_lengthspring. This is because tendons can also have springs."
)
uniform double mjc:armature = 0 (
doc = "Additional inertia associated with movement of the joint that is not due to body mass. This added inertia is usually due to a rotor (a.k.a armature) spinning faster than the joint itself due to a geared transmission. The value applies to all degrees of freedom created by this joint. Besides increasing the realism of joints with geared transmission, positive armature significantly improves simulation stability, even for small values, and is a recommended possible fix when encountering stability issues."
)
uniform double mjc:damping = 0 (
doc = "Damping applied to all degrees of freedom created by this joint. Unlike friction loss which is computed by the constraint solver, damping is simply a force linear in velocity. It is included in the passive forces. Despite this simplicity, larger damping values can make numerical integrators unstable, which is why our Euler integrator handles damping implicitly. See Integration in the Computation chapter."
)
uniform double mjc:frictionloss = 0 (
doc = "Friction loss due to dry friction. This value is the same for all degrees of freedom created by this joint. Semantically friction loss does not make sense for free joints, but the compiler allows it. To enable friction loss, set this attribute to a positive value."
)
}
+5 -5
View File
@@ -12,12 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include "./siteAPI.h"
#include <mujoco/experimental/usd/mjcPhysics/siteAPI.h>
#include "pxr/usd/sdf/assetPath.h"
#include "pxr/usd/sdf/types.h"
#include "pxr/usd/usd/schemaRegistry.h"
#include "pxr/usd/usd/typed.h"
#include <pxr/usd/sdf/assetPath.h>
#include <pxr/usd/sdf/types.h>
#include <pxr/usd/usd/schemaRegistry.h>
#include <pxr/usd/usd/typed.h>
PXR_NAMESPACE_OPEN_SCOPE
+49 -3
View File
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include "./tokens.h"
#include <mujoco/experimental/usd/mjcPhysics/tokens.h>
PXR_NAMESPACE_OPEN_SCOPE
@@ -33,18 +33,25 @@ MjcPhysicsTokensType::MjcPhysicsTokensType()
implicitfast("implicitfast", TfToken::Immortal),
integrator("integrator", TfToken::Immortal),
legacy("legacy", TfToken::Immortal),
mjcAct("mjc:act", TfToken::Immortal),
mjcActDim("mjc:actDim", TfToken::Immortal),
mjcActEarly("mjc:actEarly", TfToken::Immortal),
mjcActLimited("mjc:actLimited", TfToken::Immortal),
mjcActRangeMax("mjc:actRange:max", TfToken::Immortal),
mjcActRangeMin("mjc:actRange:min", TfToken::Immortal),
mjcActuatorfrclimited("mjc:actuatorfrclimited", TfToken::Immortal),
mjcActuatorfrcrangeMax("mjc:actuatorfrcrange:max", TfToken::Immortal),
mjcActuatorfrcrangeMin("mjc:actuatorfrcrange:min", TfToken::Immortal),
mjcActuatorgravcomp("mjc:actuatorgravcomp", TfToken::Immortal),
mjcArmature("mjc:armature", TfToken::Immortal),
mjcBiasPrm("mjc:biasPrm", TfToken::Immortal),
mjcBiasType("mjc:biasType", TfToken::Immortal),
mjcCrankLength("mjc:crankLength", TfToken::Immortal),
mjcCrankSite("mjc:crankSite", TfToken::Immortal),
mjcCtrl("mjc:ctrl", TfToken::Immortal),
mjcCtrlLimited("mjc:ctrlLimited", TfToken::Immortal),
mjcCtrlRangeMax("mjc:ctrlRange:max", TfToken::Immortal),
mjcCtrlRangeMin("mjc:ctrlRange:min", TfToken::Immortal),
mjcDamping("mjc:damping", TfToken::Immortal),
mjcDynPrm("mjc:dynPrm", TfToken::Immortal),
mjcDynType("mjc:dynType", TfToken::Immortal),
mjcFlagActuation("mjc:flag:actuation", TfToken::Immortal),
@@ -73,6 +80,7 @@ MjcPhysicsTokensType::MjcPhysicsTokensType()
mjcForceLimited("mjc:forceLimited", TfToken::Immortal),
mjcForceRangeMax("mjc:forceRange:max", TfToken::Immortal),
mjcForceRangeMin("mjc:forceRange:min", TfToken::Immortal),
mjcFrictionloss("mjc:frictionloss", TfToken::Immortal),
mjcGainPrm("mjc:gainPrm", TfToken::Immortal),
mjcGainType("mjc:gainType", TfToken::Immortal),
mjcGear("mjc:gear", TfToken::Immortal),
@@ -80,6 +88,9 @@ MjcPhysicsTokensType::MjcPhysicsTokensType()
mjcJointInParent("mjc:jointInParent", TfToken::Immortal),
mjcLengthRangeMax("mjc:lengthRange:max", TfToken::Immortal),
mjcLengthRangeMin("mjc:lengthRange:min", TfToken::Immortal),
mjcMargin("mjc:margin", TfToken::Immortal),
mjcMpos("mjc:mpos", TfToken::Immortal),
mjcMquat("mjc:mquat", TfToken::Immortal),
mjcOptionActuatorgroupdisable("mjc:option:actuatorgroupdisable",
TfToken::Immortal),
mjcOptionApirate("mjc:option:apirate", TfToken::Immortal),
@@ -109,9 +120,19 @@ MjcPhysicsTokensType::MjcPhysicsTokensType()
mjcOptionTolerance("mjc:option:tolerance", TfToken::Immortal),
mjcOptionViscosity("mjc:option:viscosity", TfToken::Immortal),
mjcOptionWind("mjc:option:wind", TfToken::Immortal),
mjcQpos("mjc:qpos", TfToken::Immortal),
mjcQvel("mjc:qvel", TfToken::Immortal),
mjcRef("mjc:ref", TfToken::Immortal),
mjcRefSite("mjc:refSite", TfToken::Immortal),
mjcShellinertia("mjc:shellinertia", TfToken::Immortal),
mjcSliderSite("mjc:sliderSite", TfToken::Immortal),
mjcSolimpfriction("mjc:solimpfriction", TfToken::Immortal),
mjcSolimplimit("mjc:solimplimit", TfToken::Immortal),
mjcSolreffriction("mjc:solreffriction", TfToken::Immortal),
mjcSolreflimit("mjc:solreflimit", TfToken::Immortal),
mjcSpringdamper("mjc:springdamper", TfToken::Immortal),
mjcSpringref("mjc:springref", TfToken::Immortal),
mjcStiffness("mjc:stiffness", TfToken::Immortal),
muscle("muscle", TfToken::Immortal),
newton("newton", TfToken::Immortal),
none("none", TfToken::Immortal),
@@ -123,8 +144,10 @@ MjcPhysicsTokensType::MjcPhysicsTokensType()
true_("true", TfToken::Immortal),
user("user", TfToken::Immortal),
CollisionAPI("CollisionAPI", TfToken::Immortal),
Keyframe("Keyframe", TfToken::Immortal),
MeshCollisionAPI("MeshCollisionAPI", TfToken::Immortal),
PhysicsActuatorAPI("PhysicsActuatorAPI", TfToken::Immortal),
PhysicsJointsAPI("PhysicsJointsAPI", TfToken::Immortal),
SceneAPI("SceneAPI", TfToken::Immortal),
SiteAPI("SiteAPI", TfToken::Immortal),
allTokens({affine,
@@ -143,18 +166,25 @@ MjcPhysicsTokensType::MjcPhysicsTokensType()
implicitfast,
integrator,
legacy,
mjcAct,
mjcActDim,
mjcActEarly,
mjcActLimited,
mjcActRangeMax,
mjcActRangeMin,
mjcActuatorfrclimited,
mjcActuatorfrcrangeMax,
mjcActuatorfrcrangeMin,
mjcActuatorgravcomp,
mjcArmature,
mjcBiasPrm,
mjcBiasType,
mjcCrankLength,
mjcCrankSite,
mjcCtrl,
mjcCtrlLimited,
mjcCtrlRangeMax,
mjcCtrlRangeMin,
mjcDamping,
mjcDynPrm,
mjcDynType,
mjcFlagActuation,
@@ -183,6 +213,7 @@ MjcPhysicsTokensType::MjcPhysicsTokensType()
mjcForceLimited,
mjcForceRangeMax,
mjcForceRangeMin,
mjcFrictionloss,
mjcGainPrm,
mjcGainType,
mjcGear,
@@ -190,6 +221,9 @@ MjcPhysicsTokensType::MjcPhysicsTokensType()
mjcJointInParent,
mjcLengthRangeMax,
mjcLengthRangeMin,
mjcMargin,
mjcMpos,
mjcMquat,
mjcOptionActuatorgroupdisable,
mjcOptionApirate,
mjcOptionCcd_iterations,
@@ -216,9 +250,19 @@ MjcPhysicsTokensType::MjcPhysicsTokensType()
mjcOptionTolerance,
mjcOptionViscosity,
mjcOptionWind,
mjcQpos,
mjcQvel,
mjcRef,
mjcRefSite,
mjcShellinertia,
mjcSliderSite,
mjcSolimpfriction,
mjcSolimplimit,
mjcSolreffriction,
mjcSolreflimit,
mjcSpringdamper,
mjcSpringref,
mjcStiffness,
muscle,
newton,
none,
@@ -230,8 +274,10 @@ MjcPhysicsTokensType::MjcPhysicsTokensType()
true_,
user,
CollisionAPI,
Keyframe,
MeshCollisionAPI,
PhysicsActuatorAPI,
PhysicsJointsAPI,
SceneAPI,
SiteAPI}) {}
-137
View File
@@ -1,137 +0,0 @@
# Copyright 2025 DeepMind Technologies Limited
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Plugin target name (used for library and plugInfo.json)
set(MJCF_PLUGIN_TARGET_NAME usdMjcf)
add_library(${MJCF_PLUGIN_TARGET_NAME} SHARED
mjcf/mjcf_file_format.cc
mjcf/mjcf_file_format.h
mjcf/mujoco_to_usd.cc
mjcf/mujoco_to_usd.h
mjcf/utils.cc
mjcf/utils.h
)
# We need to set the visibility to default until core type symbol visibility
# is resolved in OpenUSD https://github.com/PixarAnimationStudios/OpenUSD/issues/1475
# Otherwise we will run into issues during composition on MacOS due to std::type_info
# comparisons failing for pxr::TfTokenVector and the like that we place in SdfAbstractData.
set_target_properties(${MJCF_PLUGIN_TARGET_NAME} PROPERTIES
OUTPUT_NAME ${MJCF_PLUGIN_TARGET_NAME}
CXX_VISIBILITY_PRESET default
)
target_include_directories(${MJCF_PLUGIN_TARGET_NAME} PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}"
)
if (MUJOCO_USD_TARGET STREQUAL "USD")
find_package(pxr REQUIRED)
# --- Link Dependencies ---
# Link against the necessary OpenUSD components
target_link_libraries(${MJCF_PLUGIN_TARGET_NAME} PRIVATE
usd
ar
kind
tf
gf
vt
usdShade
usdLux
usdGeom
usdImaging
usdPhysics
mujoco
tinyxml2
)
elseif (MUJOCO_USD_TARGET STREQUAL "Houdini")
if (NOT DEFINED ENV{HFS})
message(FATAL_ERROR "Environment variable 'HFS' is not defined: $ENV{HFS}. Please run houdini_setup.")
endif()
# In Houdini, the Houdini package we would typically use via find_package
# does not have all the USD dependencies that we need (namely UsdPhysics)
# so we need to manually link all the required libraries.
set(HFS_ENV "$ENV{HFS}")
set(HOUDINI_LIBS "${HFS_ENV}/../Libraries")
get_filename_component(HOUDINI_LIBS "${HOUDINI_LIBS}" ABSOLUTE) # Normalize the path
target_link_directories(${MJCF_PLUGIN_TARGET_NAME} PRIVATE ${HOUDINI_LIBS})
target_include_directories(${MJCF_PLUGIN_TARGET_NAME} PRIVATE
"${HFS_ENV}/toolkit/include"
"${HFS_ENV}/toolkit/include/python3.11"
)
# Assume everyone using Houdini on 3.11 for now.
set(USD_MJCF_PYTHON_LIB python3.11)
set(USD_MJCF_PYTHON_LIB_NUMBER python311)
set(PYTHON_LIB "${HFS_ENV}/Frameworks/Python.framework/Versions/3.11/Python")
set(PXR_LIB_PREFIX "pxr_")
# --- Link Dependencies ---
# Link against the necessary OpenUSD components
target_link_libraries(${MJCF_PLUGIN_TARGET_NAME} PRIVATE
${PXR_LIB_PREFIX}usd
${PXR_LIB_PREFIX}ar
${PXR_LIB_PREFIX}kind
${PXR_LIB_PREFIX}tf
${PXR_LIB_PREFIX}gf
${PXR_LIB_PREFIX}vt
${PXR_LIB_PREFIX}sdf
${PXR_LIB_PREFIX}usdShade
${PXR_LIB_PREFIX}usdLux
${PXR_LIB_PREFIX}usdGeom
${PXR_LIB_PREFIX}usdImaging
${PXR_LIB_PREFIX}usdPhysics
tbb
hboost_${USD_MJCF_PYTHON_LIB_NUMBER}
${PYTHON_LIB}
mujoco
tinyxml2
)
endif()
# --- Generate plugInfo.json ---
if(CMAKE_SHARED_LIBRARY_PREFIX)
set(LIB_PREFIX ${CMAKE_SHARED_LIBRARY_PREFIX}) # Usually "lib" on Unix
else()
set(LIB_PREFIX "")
endif()
set(PLUG_INFO_LIBRARY_PATH "${LIB_PREFIX}${MJCF_PLUGIN_TARGET_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}")
# --- Installation ---
set(USD_PLUGIN_INSTALL_DIR_LIB ${CMAKE_INSTALL_LIBDIR}/usdMjcf)
message(STATUS "Copying plugInfo.json to ${CMAKE_BINARY_DIR}/${USD_PLUGIN_INSTALL_DIR_LIB}/plugInfo.json")
configure_file(
mjcf/plugInfo.json
${CMAKE_BINARY_DIR}/${USD_PLUGIN_INSTALL_DIR_LIB}/plugInfo.json
)
install(FILES ${CMAKE_BINARY_DIR}/${USD_PLUGIN_INSTALL_DIR_LIB}/plugInfo.json DESTINATION ${USD_PLUGIN_INSTALL_DIR_LIB})
# Install shared lib and plugInfo to same location for simplicity.
install(TARGETS ${MJCF_PLUGIN_TARGET_NAME}
LIBRARY DESTINATION ${USD_PLUGIN_INSTALL_DIR_LIB}
)
message(STATUS "USD MJCF Plugin will be installed to: ${CMAKE_INSTALL_PREFIX}/${USD_PLUGIN_INSTALL_DIR_LIB}")
message(STATUS "Make sure PXR_PLUGINPATH_NAME includes: ${CMAKE_INSTALL_PREFIX}/${USD_PLUGIN_INSTALL_DIR_LIB}")
@@ -21,6 +21,7 @@
#include <mujoco/mujoco.h>
#include "mjcf/mujoco_to_usd.h"
#include "tinyxml2.h"
#include <pxr/base/tf/diagnostic.h>
#include <pxr/base/tf/enum.h>
#include <pxr/base/tf/pathUtils.h>
@@ -37,7 +38,6 @@
#include <pxr/usd/sdf/fileFormat.h>
#include <pxr/usd/sdf/layer.h>
#include <pxr/usd/usd/usdaFileFormat.h>
#include "tinyxml2.h"
PXR_NAMESPACE_OPEN_SCOPE
@@ -21,8 +21,9 @@
#include <utility>
#include <vector>
#include <mujoco/experimental/usd/mjcPhysics/tokens.h>
#include <mujoco/mjspec.h>
#include <mujoco/mujoco.h>
#include "third_party/mujoco/src/experimental/usd/mjcPhysics/tokens.h"
#include "mjcf/utils.h"
#include <pxr/base/arch/attributes.h>
#include <pxr/base/gf/matrix4d.h>
@@ -54,6 +55,11 @@
#include <pxr/usd/usdGeom/metrics.h>
#include <pxr/usd/usdGeom/tokens.h>
#include <pxr/usd/usdLux/tokens.h>
#include <pxr/usd/usdPhysics/fixedJoint.h>
#include <pxr/usd/usdPhysics/joint.h>
#include <pxr/usd/usdPhysics/prismaticJoint.h>
#include <pxr/usd/usdPhysics/revoluteJoint.h>
#include <pxr/usd/usdPhysics/sphericalJoint.h>
#include <pxr/usd/usdPhysics/tokens.h>
#include <pxr/usd/usdShade/tokens.h>
#include <pxr/usdImaging/usdImaging/tokens.h>
@@ -77,7 +83,7 @@ TF_DEFINE_PRIVATE_TOKENS(kTokens,
((light, "Light"))
((meshScope, "MeshSources"))
((materialsScope, "Materials"))
((surface, "PreviewSurface"))
((previewSurface, "PreviewSurface"))
((world, "World"))
((xformOpTransform, "xformOp:transform"))
((xformOpScale, "xformOp:scale"))
@@ -90,10 +96,18 @@ TF_DEFINE_PRIVATE_TOKENS(kTokens,
((inputsWrapS, "inputs:wrapS"))
((inputsWrapT, "inputs:wrapT"))
((inputsDiffuseColor, "inputs:diffuseColor"))
((inputsEmissiveColor, "inputs:emissiveColor"))
((outputsRgb, "outputs:rgb"))
((outputsR, "outputs:r"))
((outputsG, "outputs:g"))
((outputsB, "outputs:b"))
((inputsMetallic, "inputs:metallic"))
((inputsOcclusion, "inputs:occlusion"))
((inputsRoughness, "inputs:roughness"))
(repeat)
((sourceMesh, pxr::UsdGeomTokens->Mesh))
((inputsNormal, "inputs:normal"))
((joint, "Joint"))
);
// Using to satisfy TF_REGISTRY_FUNCTION macro below and avoid operating in PXR_NS.
@@ -157,10 +171,14 @@ class ModelWriter {
: spec_(spec), model_(model), data_(data), class_path_("/Bad_Path") {
body_paths_ = std::vector<pxr::SdfPath>(model->nbody);
site_paths_ = std::vector<pxr::SdfPath>(model->nsite);
joint_paths_ = std::vector<pxr::SdfPath>(model->njnt);
}
~ModelWriter() { mj_deleteModel(model_); }
void Write(bool write_physics) {
// Set working parameters.
write_physics_ = write_physics;
// Create top level class holder.
class_path_ = CreateClassSpec(data_, pxr::SdfPath::AbsoluteRootPath(),
pxr::TfToken("__class__"));
@@ -168,9 +186,6 @@ class ModelWriter {
// Create the world body.
body_paths_[kWorldIndex] = WriteWorldBody(kWorldIndex);
// Set working parameters.
write_physics_ = write_physics;
SetLayerMetadata(data_, pxr::SdfFieldKeys->Documentation,
"Generated by mujoco model writer.");
// Mujoco is Z up by default.
@@ -207,6 +222,8 @@ class ModelWriter {
std::vector<pxr::SdfPath> body_paths_;
// Mapping from Mujoco site id to SdfPath.
std::vector<pxr::SdfPath> site_paths_;
// Mapping from Mujoco joint id to SdfPath.
std::vector<pxr::SdfPath> joint_paths_;
// Mapping from mesh names to Mesh prim path.
std::unordered_map<std::string, pxr::SdfPath> mesh_paths_;
// Whether to write physics data.
@@ -540,13 +557,13 @@ class ModelWriter {
MjcPhysicsTokens->mjcOptionMagnetic, magnetic);
pxr::VtArray<double> o_solref(spec_->option.o_solref,
spec_->option.o_solref + 2);
spec_->option.o_solref + mjNREF);
WriteUniformAttribute(physics_scene_path,
pxr::SdfValueTypeNames->DoubleArray,
MjcPhysicsTokens->mjcOptionO_solref, o_solref);
pxr::VtArray<double> o_solimp(spec_->option.o_solimp,
spec_->option.o_solimp + 5);
spec_->option.o_solimp + mjNIMP);
WriteUniformAttribute(physics_scene_path,
pxr::SdfValueTypeNames->DoubleArray,
MjcPhysicsTokens->mjcOptionO_solimp, o_solimp);
@@ -630,12 +647,10 @@ class ModelWriter {
}
}
pxr::SdfPath AddTextureShader(const pxr::SdfPath &material_path,
const char *texture_file) {
// Shader "uvmap"
pxr::SdfPath AddUVTextureShader(const pxr::SdfPath &material_path,
const pxr::TfToken &name) {
pxr::SdfPath uvmap_shader_path =
CreatePrimSpec(data_, material_path, pxr::TfToken("uvmap"),
pxr::UsdShadeTokens->Shader);
CreatePrimSpec(data_, material_path, name, pxr::UsdShadeTokens->Shader);
pxr::SdfPath uvmap_info_id_attr = CreateAttributeSpec(
data_, uvmap_shader_path, pxr::UsdShadeTokens->infoId,
@@ -652,10 +667,15 @@ class ModelWriter {
CreateAttributeSpec(data_, uvmap_shader_path, kTokens->outputsSt,
pxr::SdfValueTypeNames->Float2);
// Shader "texture"
return uvmap_st_output_attr;
}
std::vector<pxr::SdfPath> AddTextureShader(
const pxr::SdfPath &material_path, const char *texture_file,
const pxr::TfToken &name, const pxr::SdfPath &uvmap_st_output_attr,
const std::vector<pxr::TfToken> &output_channels) {
pxr::SdfPath texture_shader_path =
CreatePrimSpec(data_, material_path, pxr::TfToken("texture"),
pxr::UsdShadeTokens->Shader);
CreatePrimSpec(data_, material_path, name, pxr::UsdShadeTokens->Shader);
pxr::SdfPath texture_info_id_attr = CreateAttributeSpec(
data_, texture_shader_path, pxr::UsdShadeTokens->infoId,
pxr::SdfValueTypeNames->Token, pxr::SdfVariabilityUniform);
@@ -683,78 +703,216 @@ class ModelWriter {
pxr::SdfValueTypeNames->Token);
SetAttributeDefault(data_, texture_wrap_t_attr, kTokens->repeat);
pxr::SdfPath texture_rgb_output_attr =
CreateAttributeSpec(data_, texture_shader_path, kTokens->outputsRgb,
pxr::SdfValueTypeNames->Float3);
return texture_rgb_output_attr;
std::vector<pxr::SdfPath> texture_output_attrs;
for (const auto &output_channel : output_channels) {
pxr::SdfValueTypeName value_type;
if (output_channel == kTokens->outputsRgb) {
value_type = pxr::SdfValueTypeNames->Float3;
} else {
// Assume the other specified channels are outputR, outputG, outputB.
value_type = pxr::SdfValueTypeNames->Float;
}
texture_output_attrs.push_back(CreateAttributeSpec(
data_, texture_shader_path, output_channel, value_type));
}
return texture_output_attrs;
}
void WriteMaterial(mjsMaterial *material, const pxr::SdfPath &parent_path) {
// Create a Material prim.
auto name = GetAvailablePrimName(
*material->name, pxr::UsdShadeTokens->Material, parent_path);
pxr::SdfPath material_path =
CreatePrimSpec(data_, parent_path, name, pxr::UsdShadeTokens->Material);
// Shader "PreviewSurface"
pxr::SdfPath preview_surface_shader_path = CreatePrimSpec(
data_, material_path, kTokens->surface, pxr::UsdShadeTokens->Shader);
// Create a Shader prim "PreviewSurface" under the Material prim.
pxr::SdfPath preview_surface_shader_path =
CreatePrimSpec(data_, material_path, kTokens->previewSurface,
pxr::UsdShadeTokens->Shader);
// Set the Shader'sinfoId attribute to UsdPreviewSurface, a standard surface
// shader.
pxr::SdfPath info_id_attr = CreateAttributeSpec(
data_, preview_surface_shader_path, pxr::UsdShadeTokens->infoId,
pxr::SdfValueTypeNames->Token, pxr::SdfVariabilityUniform);
SetAttributeDefault(data_, info_id_attr,
pxr::UsdImagingTokens->UsdPreviewSurface);
// Connect material's surface output to the preview surface's surface
// output.
pxr::SdfPath surface_output_attr = CreateAttributeSpec(
data_, preview_surface_shader_path, pxr::UsdShadeTokens->outputsSurface,
pxr::SdfValueTypeNames->Token);
pxr::SdfPath material_surface_output_attr = CreateAttributeSpec(
data_, material_path, pxr::UsdShadeTokens->outputsSurface,
pxr::SdfValueTypeNames->Token);
AddAttributeConnection(data_, material_surface_output_attr,
surface_output_attr);
// Connect material's displacement output to the preview surface's
// displacement output.
pxr::SdfPath displacement_output_attr =
CreateAttributeSpec(data_, preview_surface_shader_path,
pxr::UsdShadeTokens->outputsDisplacement,
pxr::SdfValueTypeNames->Token);
pxr::SdfPath material_displacement_output_attr = CreateAttributeSpec(
data_, material_path, pxr::UsdShadeTokens->outputsDisplacement,
pxr::SdfValueTypeNames->Token);
AddAttributeConnection(data_, material_displacement_output_attr,
displacement_output_attr);
// Add an st (uv) Shader, a prim var reader for the UV coordinates.
const pxr::SdfPath &uvmap_st_output_attr =
AddUVTextureShader(material_path, pxr::TfToken("uvmap"));
const mjStringVec &textures = *(material->textures);
// Set the values of metallic, roughness and occlusion. These can come from
// an ORM packed texture, as individual textures, or as a values defined in
// mjsMaterial_ (with the exception of occlusion).
pxr::SdfPath metallic_attr = CreateAttributeSpec(
data_, preview_surface_shader_path, kTokens->inputsMetallic,
pxr::SdfValueTypeNames->Float);
pxr::SdfPath roughness_attr = CreateAttributeSpec(
data_, preview_surface_shader_path, kTokens->inputsRoughness,
pxr::SdfValueTypeNames->Float);
// Find the occlusion, roughness, and metallic textures.
if (mjTEXROLE_ORM < textures.size()) {
std::string orm_texture_name = textures[mjTEXROLE_ORM];
mjsTexture *orm_texture = mjs_asTexture(
mjs_findElement(spec_, mjOBJ_TEXTURE, orm_texture_name.c_str()));
std::string occlusion_texture_name = textures[mjTEXROLE_OCCLUSION];
mjsTexture *occlusion_texture = mjs_asTexture(mjs_findElement(
spec_, mjOBJ_TEXTURE, occlusion_texture_name.c_str()));
std::string roughness_texture_name = textures[mjTEXROLE_ROUGHNESS];
mjsTexture *roughness_texture = mjs_asTexture(mjs_findElement(
spec_, mjOBJ_TEXTURE, roughness_texture_name.c_str()));
std::string metallic_texture_name = textures[mjTEXROLE_METALLIC];
mjsTexture *metallic_texture = mjs_asTexture(
mjs_findElement(spec_, mjOBJ_TEXTURE, metallic_texture_name.c_str()));
if (orm_texture) {
// Create the ORM shader and connect its output to the preview
// surface ORM attrs.
const std::vector<pxr::SdfPath> orm_output_attrs = AddTextureShader(
material_path, orm_texture->file->c_str(),
pxr::TfToken("orm_packed"), uvmap_st_output_attr,
{kTokens->outputsR, kTokens->outputsG, kTokens->outputsB});
if (orm_output_attrs.size() == 3) {
pxr::SdfPath occlusion_attr = CreateAttributeSpec(
data_, preview_surface_shader_path, kTokens->inputsOcclusion,
pxr::SdfValueTypeNames->Float);
AddAttributeConnection(data_, occlusion_attr, orm_output_attrs[0]);
AddAttributeConnection(data_, roughness_attr, orm_output_attrs[1]);
AddAttributeConnection(data_, metallic_attr, orm_output_attrs[2]);
}
} else {
if (metallic_texture) {
const std::vector<pxr::SdfPath> metallic_output_attrs =
AddTextureShader(material_path, metallic_texture->file->c_str(),
pxr::TfToken("metallic"), uvmap_st_output_attr,
{kTokens->outputsRgb});
if (metallic_output_attrs.size() == 1) {
AddAttributeConnection(data_, metallic_attr,
metallic_output_attrs[0]);
}
} else {
SetAttributeDefault(data_, metallic_attr, material->metallic);
}
if (roughness_texture) {
const std::vector<pxr::SdfPath> roughness_output_attrs =
AddTextureShader(material_path, roughness_texture->file->c_str(),
pxr::TfToken("roughness"), uvmap_st_output_attr,
{kTokens->outputsRgb});
if (roughness_output_attrs.size() == 1) {
AddAttributeConnection(data_, roughness_attr,
roughness_output_attrs[0]);
}
} else {
SetAttributeDefault(data_, roughness_attr, material->roughness);
}
if (occlusion_texture) {
pxr::SdfPath occlusion_attr = CreateAttributeSpec(
data_, preview_surface_shader_path, kTokens->inputsOcclusion,
pxr::SdfValueTypeNames->Float);
const std::vector<pxr::SdfPath> occlusion_output_attrs =
AddTextureShader(material_path, occlusion_texture->file->c_str(),
pxr::TfToken("occlusion"), uvmap_st_output_attr,
{kTokens->outputsRgb});
if (occlusion_output_attrs.size() == 1) {
AddAttributeConnection(data_, occlusion_attr,
occlusion_output_attrs[0]);
}
}
}
}
// Find the normal texture if specified.
if (mjTEXROLE_NORMAL < textures.size()) {
std::string normal_texture_name = textures[mjTEXROLE_NORMAL];
mjsTexture *normal_texture = mjs_asTexture(
mjs_findElement(spec_, mjOBJ_TEXTURE, normal_texture_name.c_str()));
if (normal_texture) {
pxr::SdfPath normal_attr = CreateAttributeSpec(
data_, preview_surface_shader_path, kTokens->inputsNormal,
pxr::SdfValueTypeNames->Normal3f);
// Create the normal map shader and connect its output to the preview
// surface normal attr.
const std::vector<pxr::SdfPath> normal_map_output_attrs =
AddTextureShader(material_path, normal_texture->file->c_str(),
pxr::TfToken("normal"), uvmap_st_output_attr,
{kTokens->outputsRgb});
if (normal_map_output_attrs.size() == 1) {
AddAttributeConnection(data_, normal_attr,
normal_map_output_attrs[0]);
}
}
}
// Connect an emissive texture if specified.
if (mjTEXROLE_EMISSIVE < textures.size()) {
std::string emissive_texture_name = textures[mjTEXROLE_EMISSIVE];
mjsTexture *emissive_texture = mjs_asTexture(
mjs_findElement(spec_, mjOBJ_TEXTURE, emissive_texture_name.c_str()));
if (emissive_texture) {
pxr::SdfPath emissive_attr = CreateAttributeSpec(
data_, preview_surface_shader_path, kTokens->inputsEmissiveColor,
pxr::SdfValueTypeNames->Color3f);
const std::vector<pxr::SdfPath> emissive_map_output_attrs =
AddTextureShader(material_path, emissive_texture->file->c_str(),
pxr::TfToken("emissive"), uvmap_st_output_attr,
{kTokens->outputsRgb});
if (emissive_map_output_attrs.size() == 1) {
AddAttributeConnection(data_, emissive_attr,
emissive_map_output_attrs[0]);
}
}
}
// Set the value of diffuse color. This can come from a diffuse texture
// or as a value defined in mjsMaterial_.
pxr::SdfPath diffuse_color_attr = CreateAttributeSpec(
data_, preview_surface_shader_path, kTokens->inputsDiffuseColor,
pxr::SdfValueTypeNames->Color3f);
// Find the main texture if specified.
std::string main_texture_name = (*material->textures)[mjTEXROLE_RGB];
std::string main_texture_name = textures[mjTEXROLE_RGB];
mjsTexture *main_texture = mjs_asTexture(
mjs_findElement(spec_, mjOBJ_TEXTURE, main_texture_name.c_str()));
if (main_texture) {
// Create the texture shader and connect it to the diffuse color
// attribute.
pxr::SdfPath texture_rgb_output_attr =
AddTextureShader(material_path, main_texture->file->c_str());
AddAttributeConnection(data_, diffuse_color_attr,
texture_rgb_output_attr);
const std::vector<pxr::SdfPath> texture_diffuse_output_attrs =
AddTextureShader(material_path, main_texture->file->c_str(),
pxr::TfToken("diffuse"), uvmap_st_output_attr,
{kTokens->outputsRgb});
if (texture_diffuse_output_attrs.size() == 1) {
AddAttributeConnection(data_, diffuse_color_attr,
texture_diffuse_output_attrs[0]);
}
} else {
// If no texture is specified, use the rgba diffuse color.
SetAttributeDefault(data_, diffuse_color_attr,
pxr::GfVec3f(material->rgba[0], material->rgba[1],
material->rgba[2]));
}
pxr::SdfPath metallic_attr = CreateAttributeSpec(
data_, preview_surface_shader_path, kTokens->inputsMetallic,
pxr::SdfValueTypeNames->Float);
SetAttributeDefault(data_, metallic_attr, material->metallic);
pxr::SdfPath material_surface_output_attr = CreateAttributeSpec(
data_, material_path, pxr::UsdShadeTokens->outputsSurface,
pxr::SdfValueTypeNames->Token);
AddAttributeConnection(data_, material_surface_output_attr,
surface_output_attr);
pxr::SdfPath material_displacement_output_attr = CreateAttributeSpec(
data_, material_path, pxr::UsdShadeTokens->outputsDisplacement,
pxr::SdfValueTypeNames->Token);
AddAttributeConnection(data_, material_displacement_output_attr,
displacement_output_attr);
}
void WriteMaterials() {
@@ -780,6 +938,9 @@ class ModelWriter {
actuator->trntype == mjtTrn::mjTRN_SLIDERCRANK) {
int site_id = mj_name2id(model_, mjOBJ_SITE, actuator->target->c_str());
transmission_path = site_paths_[site_id];
} else if (actuator->trntype == mjtTrn::mjTRN_JOINT) {
int joint_id = mj_name2id(model_, mjOBJ_JOINT, actuator->target->c_str());
transmission_path = joint_paths_[joint_id];
} else {
TF_WARN(UnsupportedActuatorTypeError,
"Unsupported actuator type for actuator %d",
@@ -791,22 +952,23 @@ class ModelWriter {
MjcPhysicsTokens->PhysicsActuatorAPI);
if (!actuator->refsite->empty()) {
int refsite_id = mj_name2id(model_, mjOBJ_SITE, actuator->refsite->c_str());
int refsite_id =
mj_name2id(model_, mjOBJ_SITE, actuator->refsite->c_str());
pxr::SdfPath refsite_path = site_paths_[refsite_id];
CreateRelationshipSpec(data_, transmission_path,
MjcPhysicsTokens->mjcRefSite,
refsite_path, pxr::SdfVariabilityUniform);
MjcPhysicsTokens->mjcRefSite, refsite_path,
pxr::SdfVariabilityUniform);
}
if (!actuator->slidersite->empty()) {
int slidersite_id = mj_name2id(model_, mjOBJ_SITE, actuator->slidersite->c_str());
int slidersite_id =
mj_name2id(model_, mjOBJ_SITE, actuator->slidersite->c_str());
pxr::SdfPath slidersite_path = site_paths_[slidersite_id];
CreateRelationshipSpec(data_, transmission_path,
MjcPhysicsTokens->mjcSliderSite,
slidersite_path, pxr::SdfVariabilityUniform);
MjcPhysicsTokens->mjcSliderSite, slidersite_path,
pxr::SdfVariabilityUniform);
}
const std::vector<std::pair<pxr::TfToken, int>> limited_attributes = {
{MjcPhysicsTokens->mjcCtrlLimited, actuator->ctrllimited},
{MjcPhysicsTokens->mjcForceLimited, actuator->forcelimited},
@@ -872,7 +1034,6 @@ class ModelWriter {
MjcPhysicsTokens->mjcDynPrm,
pxr::VtDoubleArray(actuator->dynprm, actuator->dynprm + 10));
pxr::TfToken gain_type;
if (actuator->gaintype == mjtGain::mjGAIN_FIXED) {
gain_type = MjcPhysicsTokens->fixed;
@@ -1345,6 +1506,296 @@ class ModelWriter {
}
}
void WriteJoints(mjsBody *body) {
if (!write_physics_) return;
int body_id = mjs_getId(body->element);
if (body_id == kWorldIndex) return;
mjsJoint *joint = mjs_asJoint(mjs_firstChild(body, mjOBJ_JOINT, false));
if (!joint) {
// If no joint is found, then we pass nullptr to create a FixedJoint.
// WriteJoint properly handles the case where the parent is the worldbody.
WriteJoint(nullptr, body);
} else {
WriteJoint(joint, body);
if (mjs_asJoint(mjs_nextChild(body, joint->element, false))) {
TF_WARN(
"Multiple joints found for body %d. Only writing the first one.",
body_id);
}
}
}
// Write the joint. If null, then a FixedJoint is created.
void WriteJoint(mjsJoint *joint, const mjsBody *parent_mj_body) {
// Default to fixed joint if joint is null.
pxr::TfToken joint_prim_type = pxr::UsdPhysicsTokens->PhysicsFixedJoint;
int joint_id = -1;
if (joint) {
joint_id = mjs_getId(joint->element);
mjtJoint type = (mjtJoint)model_->jnt_type[joint_id];
switch (type) {
case mjJNT_FREE:
// Free joints are guaranteed to only ever be on the top-level body so
// we just write no joint. As a top-level body with no joint it will
// be considered a floating-base body.
return;
case mjJNT_HINGE:
joint_prim_type = pxr::UsdPhysicsTokens->PhysicsRevoluteJoint;
break;
case mjJNT_SLIDE:
joint_prim_type = pxr::UsdPhysicsTokens->PhysicsPrismaticJoint;
break;
default:
TF_WARN("Unsupported joint type '%d' for joint '%s'. Skipping.",
(int)type, joint->name->c_str());
return;
}
}
int body_id = mjs_getId(parent_mj_body->element);
// the joint connects the current body as body1, to its parent body as
// body0.
int body1_id_usd = body_id;
int body0_id_usd = model_->body_parentid[body_id];
const pxr::SdfPath &body1_path_usd = body_paths_[body1_id_usd];
auto joint_name = joint ? *joint->name : "FixedJoint";
pxr::TfToken joint_name_token =
GetAvailablePrimName(joint_name, kTokens->joint, body1_path_usd);
pxr::SdfPath joint_path = CreatePrimSpec(data_, body1_path_usd,
joint_name_token, joint_prim_type);
// Set body0 and body1 relationships
// For the initial joints that connect to the world, we signal this by
// keeping the body0 relationship empty.
if (body0_id_usd != kWorldIndex) {
const pxr::SdfPath &body0_path_usd = body_paths_[body0_id_usd];
CreateRelationshipSpec(data_, joint_path,
pxr::UsdPhysicsTokens->physicsBody0,
body0_path_usd, pxr::SdfVariabilityUniform);
}
CreateRelationshipSpec(data_, joint_path,
pxr::UsdPhysicsTokens->physicsBody1, body1_path_usd,
pxr::SdfVariabilityUniform);
// Joint frame in MuJoCo is defined by jnt_pos and jnt_axis in body1's frame
// For FixedJoint, these are both unity.
pxr::GfVec3d mj_jnt_pos = pxr::GfVec3d(0.0);
pxr::GfVec3d mj_jnt_axis = pxr::GfVec3d(0.0, 0.0, 1.0);
if (joint) {
mj_jnt_pos = pxr::GfVec3d(&model_->jnt_pos[joint_id * 3]);
mj_jnt_axis = pxr::GfVec3d(&model_->jnt_axis[joint_id * 3]);
}
// Local joint frame for body1
pxr::GfVec3f local_pos1(mj_jnt_pos);
pxr::GfRotation().SetRotateInto(pxr::GfVec3f::ZAxis(), mj_jnt_axis);
pxr::GfQuatf local_rot1(
pxr::GfRotation()
.SetRotateInto(pxr::GfVec3f::ZAxis(), mj_jnt_axis)
.GetQuat());
SetAttributeDefault(
data_,
CreateAttributeSpec(data_, joint_path,
pxr::UsdPhysicsTokens->physicsLocalPos1,
pxr::SdfValueTypeNames->Float3),
local_pos1);
if (joint_prim_type == pxr::UsdPhysicsTokens->PhysicsRevoluteJoint ||
joint_prim_type == pxr::UsdPhysicsTokens->PhysicsPrismaticJoint) {
SetAttributeDefault(
data_,
CreateAttributeSpec(data_, joint_path,
pxr::UsdPhysicsTokens->physicsLocalRot1,
pxr::SdfValueTypeNames->Quatf),
local_rot1);
} else {
SetAttributeDefault(
data_,
CreateAttributeSpec(data_, joint_path,
pxr::UsdPhysicsTokens->physicsLocalRot1,
pxr::SdfValueTypeNames->Quatf),
pxr::GfQuatf::GetIdentity());
}
// Calculate local joint frame for body0
pxr::GfMatrix4d body1_transform_local =
MujocoPosQuatToTransform(&model_->body_pos[body1_id_usd * 3],
&model_->body_quat[body1_id_usd * 4]);
pxr::GfVec3d jnt_pos_parent_local =
body1_transform_local.Transform(mj_jnt_pos);
pxr::GfVec3d jnt_axis_parent_local =
body1_transform_local.TransformDir(mj_jnt_axis);
pxr::GfVec3f local_pos0(jnt_pos_parent_local);
SetAttributeDefault(
data_,
CreateAttributeSpec(data_, joint_path,
pxr::UsdPhysicsTokens->physicsLocalPos0,
pxr::SdfValueTypeNames->Float3),
local_pos0);
if (joint_prim_type == pxr::UsdPhysicsTokens->PhysicsRevoluteJoint ||
joint_prim_type == pxr::UsdPhysicsTokens->PhysicsPrismaticJoint) {
pxr::GfQuatf other_rot0(
pxr::GfRotation()
.SetRotateInto(pxr::GfVec3f::ZAxis(), jnt_axis_parent_local)
.GetQuat());
SetAttributeDefault(
data_,
CreateAttributeSpec(data_, joint_path,
pxr::UsdPhysicsTokens->physicsLocalRot0,
pxr::SdfValueTypeNames->Quatf),
other_rot0);
} else {
// Fixed joints have no frame and no axis per se. We simply need the
// rotation quaternion of the body its on.
SetAttributeDefault(
data_,
CreateAttributeSpec(data_, joint_path,
pxr::UsdPhysicsTokens->physicsLocalRot0,
pxr::SdfValueTypeNames->Quatf),
body1_transform_local.ExtractRotationQuat());
}
if (joint) {
mjtJoint type = (mjtJoint)model_->jnt_type[joint_id];
// Joint-specific attributes
if (type == mjJNT_HINGE || type == mjJNT_SLIDE) {
// The joint motion occurs around/along the Z-axis of the joint frame
// established by localRot0/1.
SetAttributeDefault(
data_,
CreateAttributeSpec(data_, joint_path,
pxr::UsdPhysicsTokens->physicsAxis,
pxr::SdfValueTypeNames->Token),
pxr::UsdPhysicsTokens->z); // "Z" axis
}
if (model_->jnt_limited[joint_id]) {
float lower_limit = model_->jnt_range[joint_id * 2];
float upper_limit = model_->jnt_range[joint_id * 2 + 1];
if (type == mjJNT_HINGE) {
// Convert radians to degrees for USD
// As per the XML Reference, "mjModel always uses radians"
lower_limit *= (180.0 / mjPI);
upper_limit *= (180.0 / mjPI);
SetAttributeDefault(
data_,
CreateAttributeSpec(data_, joint_path,
pxr::UsdPhysicsTokens->physicsLowerLimit,
pxr::SdfValueTypeNames->Float),
lower_limit);
SetAttributeDefault(
data_,
CreateAttributeSpec(data_, joint_path,
pxr::UsdPhysicsTokens->physicsUpperLimit,
pxr::SdfValueTypeNames->Float),
upper_limit);
} else if (type == mjJNT_SLIDE) {
SetAttributeDefault(
data_,
CreateAttributeSpec(data_, joint_path,
pxr::UsdPhysicsTokens->physicsLowerLimit,
pxr::SdfValueTypeNames->Float),
lower_limit);
SetAttributeDefault(
data_,
CreateAttributeSpec(data_, joint_path,
pxr::UsdPhysicsTokens->physicsUpperLimit,
pxr::SdfValueTypeNames->Float),
upper_limit);
}
}
// Finally write the mjcPhysicsJointAPI attributes.
ApplyApiSchema(data_, joint_path, MjcPhysicsTokens->PhysicsJointsAPI);
WriteUniformAttribute(
joint_path, pxr::SdfValueTypeNames->DoubleArray,
MjcPhysicsTokens->mjcSpringdamper,
pxr::VtArray<double>(joint->springdamper, joint->springdamper + 2));
WriteUniformAttribute(joint_path, pxr::SdfValueTypeNames->DoubleArray,
MjcPhysicsTokens->mjcSolreflimit,
pxr::VtArray<double>(joint->solref_limit,
joint->solref_limit + mjNREF));
WriteUniformAttribute(joint_path, pxr::SdfValueTypeNames->DoubleArray,
MjcPhysicsTokens->mjcSolimplimit,
pxr::VtArray<double>(joint->solimp_limit,
joint->solimp_limit + mjNIMP));
WriteUniformAttribute(
joint_path, pxr::SdfValueTypeNames->DoubleArray,
MjcPhysicsTokens->mjcSolreffriction,
pxr::VtArray<double>(joint->solref_friction,
joint->solref_friction + mjNREF));
WriteUniformAttribute(
joint_path, pxr::SdfValueTypeNames->DoubleArray,
MjcPhysicsTokens->mjcSolimpfriction,
pxr::VtArray<double>(joint->solimp_friction,
joint->solimp_friction + mjNIMP));
WriteUniformAttribute(joint_path, pxr::SdfValueTypeNames->Double,
MjcPhysicsTokens->mjcStiffness, joint->stiffness);
WriteUniformAttribute(joint_path, pxr::SdfValueTypeNames->Double,
MjcPhysicsTokens->mjcActuatorfrcrangeMin,
joint->actfrcrange[0]);
WriteUniformAttribute(joint_path, pxr::SdfValueTypeNames->Double,
MjcPhysicsTokens->mjcActuatorfrcrangeMax,
joint->actfrcrange[1]);
pxr::TfToken actuatorfrclimited_token = MjcPhysicsTokens->auto_;
if (joint->actfrclimited == mjLIMITED_TRUE) {
actuatorfrclimited_token = MjcPhysicsTokens->true_;
} else if (joint->actfrclimited == mjLIMITED_FALSE) {
actuatorfrclimited_token = MjcPhysicsTokens->false_;
}
WriteUniformAttribute(joint_path, pxr::SdfValueTypeNames->Token,
MjcPhysicsTokens->mjcActuatorfrclimited,
actuatorfrclimited_token);
WriteUniformAttribute(joint_path, pxr::SdfValueTypeNames->Bool,
MjcPhysicsTokens->mjcActuatorgravcomp,
static_cast<bool>(joint->actgravcomp));
WriteUniformAttribute(joint_path, pxr::SdfValueTypeNames->Double,
MjcPhysicsTokens->mjcMargin, joint->margin);
WriteUniformAttribute(joint_path, pxr::SdfValueTypeNames->Double,
MjcPhysicsTokens->mjcRef, joint->ref);
WriteUniformAttribute(joint_path, pxr::SdfValueTypeNames->Double,
MjcPhysicsTokens->mjcSpringref, joint->springref);
WriteUniformAttribute(joint_path, pxr::SdfValueTypeNames->Double,
MjcPhysicsTokens->mjcArmature, joint->armature);
WriteUniformAttribute(joint_path, pxr::SdfValueTypeNames->Double,
MjcPhysicsTokens->mjcDamping, joint->damping);
WriteUniformAttribute(joint_path, pxr::SdfValueTypeNames->Double,
MjcPhysicsTokens->mjcFrictionloss,
joint->frictionloss);
}
if (joint_id >= 0) {
joint_paths_[joint_id] = joint_path;
}
}
void WriteCamera(mjsCamera *spec_cam, const mjsBody *body) {
const auto &body_path = body_paths_[mjs_getId(body->element)];
auto name = GetAvailablePrimName(*spec_cam->name,
@@ -1444,6 +1895,16 @@ class ModelWriter {
: pxr::KindTokens->subcomponent;
SetPrimKind(data_, body_path, kind);
// If the parent is not the world body, but is child of the world body
// then we need to apply the articulation root API.
if (parent_id != kWorldIndex) {
int parent_parent_id = mjs_getId(mjs_getParent(parent->element)->element);
if (parent_parent_id == kWorldIndex) {
ApplyApiSchema(data_, parent_path,
pxr::UsdPhysicsTokens->PhysicsArticulationRootAPI);
}
}
// Apply the PhysicsRigidBodyAPI schema if we are writing physics.
if (write_physics_) {
// If the body had a mass specified then it must have either inertia or
@@ -1486,17 +1947,6 @@ class ModelWriter {
ApplyApiSchema(data_, body_path,
pxr::UsdPhysicsTokens->PhysicsRigidBodyAPI);
// If the parent is not the world body, but is child of the world body
// then we need to apply the articulation root API.
if (parent_id != kWorldIndex) {
int parent_parent_id =
mjs_getId(mjs_getParent(parent->element)->element);
if (parent_parent_id == kWorldIndex) {
ApplyApiSchema(data_, parent_path,
pxr::UsdPhysicsTokens->PhysicsArticulationRootAPI);
}
}
}
// Create classes if necessary
@@ -1543,6 +1993,7 @@ class ModelWriter {
}
WriteSites(body);
WriteGeoms(body);
WriteJoints(body);
WriteCameras(body);
WriteLights(body);
body = mjs_asBody(mjs_nextElement(spec_, body->element));
@@ -1558,6 +2009,7 @@ class ModelWriter {
CreatePrimSpec(data_, pxr::SdfPath::AbsoluteRootPath(), name,
pxr::UsdGeomTokens->Xform);
SetPrimKind(data_, world_group_path, pxr::KindTokens->group);
return world_group_path;
}
};
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include "third_party/mujoco/src/experimental/usd/utils.h"
#include <mujoco/experimental/usd/utils.h>
#include <mujoco/mujoco.h>
#include <pxr/usd/sdf/path.h>

Some files were not shown because too many files have changed in this diff Show More