From 96dda6ea75f63bb484df15a6a1633d0c7ed56997 Mon Sep 17 00:00:00 2001 From: Taylor Howell Date: Tue, 8 Apr 2025 05:15:27 -0700 Subject: [PATCH] Add tendon actuator force limits and tendon actuator force sensor. PiperOrigin-RevId: 745096883 Change-Id: Ib9acb727fbbfc6b0b0323ee6a889053a7a878056 --- doc/XMLreference.rst | 51 ++++++++++++++++ doc/XMLschema.rst | 27 +++++--- doc/changelog.rst | 2 + doc/includes/references.h | 7 +++ doc/modeling.rst | 5 +- include/mujoco/mjmodel.h | 3 + include/mujoco/mjspec.h | 2 + include/mujoco/mjvisualize.h | 2 + include/mujoco/mjxmacro.h | 2 + mjx/mujoco/mjx/_src/support_test.py | 2 +- mjx/mujoco/mjx/_src/types.py | 4 ++ python/mujoco/introspect/enums.py | 57 ++++++++--------- python/mujoco/introspect/structs.py | 43 +++++++++++++ src/engine/engine_forward.c | 42 ++++++++++++- src/engine/engine_io.c | 1 + src/engine/engine_sensor.c | 14 ++++- src/user/user_model.cc | 4 ++ src/user/user_objects.cc | 31 +++++++++- src/user/user_objects.h | 1 + src/xml/xml_native_reader.cc | 15 +++-- src/xml/xml_native_reader.h | 2 +- src/xml/xml_native_writer.cc | 6 ++ test/engine/engine_forward_test.cc | 54 ++++++++++++++++ .../testdata/actuation/tendon_force_clamp.xml | 55 +++++++++++++++++ test/user/user_objects_test.cc | 45 ++++++++++++++ unity/Runtime/Bindings/MjBindings.cs | 61 ++++++++++--------- 26 files changed, 464 insertions(+), 74 deletions(-) create mode 100644 test/engine/testdata/actuation/tendon_force_clamp.xml diff --git a/doc/XMLreference.rst b/doc/XMLreference.rst index 6166691b..647a731b 100644 --- a/doc/XMLreference.rst +++ b/doc/XMLreference.rst @@ -4630,12 +4630,28 @@ length X, as in the clip on the right of `this example model solver. If this attribute is "auto", and :at:`autolimits` is set in :ref:`compiler `, length limits will be enabled if range is defined. +.. _tendon-spatial-actuatorfrclimited: + +:at:`actuatorfrclimited`: :at-val:`[false, true, auto], "auto"` + This attribute specifies whether actuator forces acting on the tendon should be clamped. See :ref:`CForceRange` for + details. This attribute interacts with the :ref:`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 :at:`autolimits` is set in :ref:`compiler `, actuator force clamping will + be enabled if :at:`actuatorfrcrange` is defined. + .. _tendon-spatial-range: :at:`range`: :at-val:`real(2), "0 0"` Range of allowed tendon lengths. Setting this attribute without specifying :at:`limited` is an error, unless :at:`autolimits` is set in :ref:`compiler `. +.. _tendon-spatial-actuatorfrcrange: + +:at:`actuatorfrcrange`: :at-val:`real(2), "0 0"` + Range for clamping total actuator forces acting on this tendon. See :ref:`CForceRange` for details. The compiler + expects the lower bound to be nonpositive and the upper bound to be nonnegative. |br| Setting this attribute without + specifying :at:`actuatorfrclimited` is an error if :at:`compiler-autolimits` is "false". + .. _tendon-spatial-solreflimit: .. _tendon-spatial-solimplimit: @@ -4820,8 +4836,12 @@ as above. .. _tendon-fixed-limited: +.. _tendon-fixed-actuatorfrclimited: + .. _tendon-fixed-range: +.. _tendon-fixed-actuatorfrcrange: + .. _tendon-fixed-solreflimit: .. _tendon-fixed-solimplimit: @@ -6385,6 +6405,33 @@ joint or when a single actuator acts on multiple joints. See :ref:`CForceRange` The joint where actuator forces will be sensed. The sensor output is copied from ``mjData.qfrc_actuator``. +.. _sensor-tendonactuatorfrc: + +:el-prefix:`sensor/` |-| **tendonactuatorfrc** (*) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This element creates an actuator force sensor, measured at a tendon. The quantity being sensed is the total force +contributed by all actuators to a single tendon. This type of sensor is important when multiple actuators act on a +single tendon. See :ref:`CForceRange` for details. + + +.. _sensor-tendonactuatorfrc-name: + +.. _sensor-tendonactuatorfrc-noise: + +.. _sensor-tendonactuatorfrc-cutoff: + +.. _sensor-tendonactuatorfrc-user: + +:at:`name`, :at:`noise`, :at:`cutoff`, :at:`user` + See :ref:`CSensor`. + +.. _sensor-tendonactuatorfrc-tendon: + +:at:`tendon`: :at-val:`string, required` + The tendon where actuator forces will be sensed. + + .. _sensor-ballquat: :el-prefix:`sensor/` |-| **ballquat** (*) @@ -8277,8 +8324,12 @@ if omitted. .. _default-tendon-limited: +.. _default-tendon-actuatorfrclimited: + .. _default-tendon-range: +.. _default-tendon-actuatorfrcrange: + .. _default-tendon-solreflimit: .. _default-tendon-solimplimit: diff --git a/doc/XMLschema.rst b/doc/XMLschema.rst index 56119cd7..9036d1c0 100644 --- a/doc/XMLschema.rst +++ b/doc/XMLschema.rst @@ -621,13 +621,15 @@ | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | | | | | :ref:`name` | :ref:`class` | :ref:`group` | :ref:`limited` | | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | -| | | | :ref:`range` | :ref:`solreflimit` | :ref:`solimplimit` | :ref:`solreffriction` | | +| | | | :ref:`actuatorfrclimited` | :ref:`range` | :ref:`actuatorfrcrange` | :ref:`solreflimit` | | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | -| | | | :ref:`solimpfriction` | :ref:`frictionloss` | :ref:`springlength` | :ref:`width` | | +| | | | :ref:`solimplimit` | :ref:`solreffriction` | :ref:`solimpfriction` | :ref:`frictionloss` | | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | -| | | | :ref:`material` | :ref:`margin` | :ref:`stiffness` | :ref:`damping` | | +| | | | :ref:`springlength` | :ref:`width` | :ref:`material` | :ref:`margin` | | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | -| | | | :ref:`armature` | :ref:`rgba` | :ref:`user` | | | +| | | | :ref:`stiffness` | :ref:`damping` | :ref:`armature` | :ref:`rgba` | | +| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | +| | | | :ref:`user` | | | | | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | +------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |_2| spatial |br| |_2| |L| | | .. table:: | @@ -657,11 +659,13 @@ | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | | | | | :ref:`name` | :ref:`class` | :ref:`group` | :ref:`limited` | | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | -| | | | :ref:`range` | :ref:`solreflimit` | :ref:`solimplimit` | :ref:`solreffriction` | | +| | | | :ref:`actuatorfrclimited` | :ref:`range` | :ref:`actuatorfrcrange` | :ref:`solreflimit` | | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | -| | | | :ref:`solimpfriction` | :ref:`frictionloss` | :ref:`springlength` | :ref:`margin` | | +| | | | :ref:`solimplimit` | :ref:`solreffriction` | :ref:`solimpfriction` | :ref:`frictionloss` | | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | -| | | | :ref:`stiffness` | :ref:`damping` | :ref:`armature` | :ref:`user` | | +| | | | :ref:`springlength` | :ref:`margin` | :ref:`stiffness` | :ref:`damping` | | +| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | +| | | | :ref:`armature` | :ref:`user` | | | | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | +------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |_2| fixed |br| |_2| |L| | | .. table:: | @@ -1004,6 +1008,15 @@ | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | +------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |_| sensor |br| |_| |L| | | .. table:: | +| :ref:`tendonactuatorfrc | \* | :class: mjcf-attributes | +| ` | | | +| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | +| | | | :ref:`name` | :ref:`tendon` | :ref:`cutoff` | :ref:`noise` | | +| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | +| | | | :ref:`user` | | | | | +| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | ++------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| |_| sensor |br| |_| |L| | | .. table:: | | :ref:`ballquat | \* | :class: mjcf-attributes | | ` | | | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | diff --git a/doc/changelog.rst b/doc/changelog.rst index 2c5cbe8b..deaba59e 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -20,6 +20,8 @@ General bodies when saving to XML. - Added :ref:`orientation` attribute to :ref:`composite`. Moreover, allow the composite to be the direct child of a frame. +- Added :ref:`tendon actuator force limits` and + :ref:`tendon actuator force sensor`. Bug fixes ^^^^^^^^^ diff --git a/doc/includes/references.h b/doc/includes/references.h index 846f0c75..b97f2b93 100644 --- a/doc/includes/references.h +++ b/doc/includes/references.h @@ -649,6 +649,7 @@ typedef enum mjtSensor_ { // type of sensor mjSENS_ACTUATORVEL, // scalar actuator velocity mjSENS_ACTUATORFRC, // scalar actuator force mjSENS_JOINTACTFRC, // scalar actuator force, measured at the joint + mjSENS_TENDONACTFRC, // scalar actuator force, measured at the tendon // sensors related to ball joints mjSENS_BALLQUAT, // 4D ball joint quaternion @@ -1330,12 +1331,14 @@ struct mjModel_ { int* tendon_matid; // material id for rendering (ntendon x 1) int* tendon_group; // group for visibility (ntendon x 1) mjtByte* tendon_limited; // does tendon have length limits (ntendon x 1) + mjtByte* tendon_actfrclimited; // does tendon have actuator force limits (ntendon x 1) mjtNum* tendon_width; // width for rendering (ntendon x 1) mjtNum* tendon_solref_lim; // constraint solver reference: limit (ntendon x mjNREF) mjtNum* tendon_solimp_lim; // constraint solver impedance: limit (ntendon x mjNIMP) mjtNum* tendon_solref_fri; // constraint solver reference: friction (ntendon x mjNREF) mjtNum* tendon_solimp_fri; // constraint solver impedance: friction (ntendon x mjNIMP) mjtNum* tendon_range; // tendon length limits (ntendon x 2) + mjtNum* tendon_actfrcrange; // range of total actuator force (ntendon x 2) mjtNum* tendon_margin; // min distance for limit detection (ntendon x 1) mjtNum* tendon_stiffness; // stiffness coefficient (ntendon x 1) mjtNum* tendon_damping; // damping coefficient (ntendon x 1) @@ -2186,7 +2189,9 @@ typedef struct mjsTendon_ { // tendon specification // length range int limited; // does tendon have limits (mjtLimited) + int actfrclimited; // does tendon have actuator force limits double range[2]; // length limits + double actfrcrange[2]; // actuator force limits double margin; // margin value for tendon limit detection mjtNum solref_limit[mjNREF]; // solver reference: tendon limits mjtNum solimp_limit[mjNIMP]; // solver impedance: tendon limits @@ -3086,8 +3091,10 @@ struct mjvSceneState_ { int* tendon_matid; int* tendon_group; mjtByte* tendon_limited; + mjtByte* tendon_actfrclimited; mjtNum* tendon_width; mjtNum* tendon_range; + mjtNum* tendon_actfrcrange; mjtNum* tendon_stiffness; mjtNum* tendon_damping; mjtNum* tendon_frictionloss; diff --git a/doc/modeling.rst b/doc/modeling.rst index 4ef331a4..61d79c13 100644 --- a/doc/modeling.rst +++ b/doc/modeling.rst @@ -724,7 +724,10 @@ Force clamping at joint input with :ref:`joint/actuatorfrcrange` sensor to report the total actuator force acting on a joint. The standard :ref:`actuatorfrc` sensor will continue to report the pre-clamped actuator force. -The three clamping options above are non-exclusive and can be combined as required. +Force clamping at tendon input with :ref:`tendon/actuatorfrcrange`: + This tendon attribute clamps input forces from all actuators acting on the tendon. + +The clamping options above are non-exclusive and can be combined as required. .. _CLengthRange: diff --git a/include/mujoco/mjmodel.h b/include/mujoco/mjmodel.h index 1699ca0c..b6bdbfc6 100644 --- a/include/mujoco/mjmodel.h +++ b/include/mujoco/mjmodel.h @@ -317,6 +317,7 @@ typedef enum mjtSensor_ { // type of sensor mjSENS_ACTUATORVEL, // scalar actuator velocity mjSENS_ACTUATORFRC, // scalar actuator force mjSENS_JOINTACTFRC, // scalar actuator force, measured at the joint + mjSENS_TENDONACTFRC, // scalar actuator force, measured at the tendon // sensors related to ball joints mjSENS_BALLQUAT, // 4D ball joint quaternion @@ -1031,12 +1032,14 @@ struct mjModel_ { int* tendon_matid; // material id for rendering (ntendon x 1) int* tendon_group; // group for visibility (ntendon x 1) mjtByte* tendon_limited; // does tendon have length limits (ntendon x 1) + mjtByte* tendon_actfrclimited; // does tendon have actuator force limits (ntendon x 1) mjtNum* tendon_width; // width for rendering (ntendon x 1) mjtNum* tendon_solref_lim; // constraint solver reference: limit (ntendon x mjNREF) mjtNum* tendon_solimp_lim; // constraint solver impedance: limit (ntendon x mjNIMP) mjtNum* tendon_solref_fri; // constraint solver reference: friction (ntendon x mjNREF) mjtNum* tendon_solimp_fri; // constraint solver impedance: friction (ntendon x mjNIMP) mjtNum* tendon_range; // tendon length limits (ntendon x 2) + mjtNum* tendon_actfrcrange; // range of total actuator force (ntendon x 2) mjtNum* tendon_margin; // min distance for limit detection (ntendon x 1) mjtNum* tendon_stiffness; // stiffness coefficient (ntendon x 1) mjtNum* tendon_damping; // damping coefficient (ntendon x 1) diff --git a/include/mujoco/mjspec.h b/include/mujoco/mjspec.h index 42b8c03f..3202d9dc 100644 --- a/include/mujoco/mjspec.h +++ b/include/mujoco/mjspec.h @@ -628,7 +628,9 @@ typedef struct mjsTendon_ { // tendon specification // length range int limited; // does tendon have limits (mjtLimited) + int actfrclimited; // does tendon have actuator force limits double range[2]; // length limits + double actfrcrange[2]; // actuator force limits double margin; // margin value for tendon limit detection mjtNum solref_limit[mjNREF]; // solver reference: tendon limits mjtNum solimp_limit[mjNIMP]; // solver impedance: tendon limits diff --git a/include/mujoco/mjvisualize.h b/include/mujoco/mjvisualize.h index 19dad80c..0a757cf1 100644 --- a/include/mujoco/mjvisualize.h +++ b/include/mujoco/mjvisualize.h @@ -593,8 +593,10 @@ struct mjvSceneState_ { int* tendon_matid; int* tendon_group; mjtByte* tendon_limited; + mjtByte* tendon_actfrclimited; mjtNum* tendon_width; mjtNum* tendon_range; + mjtNum* tendon_actfrcrange; mjtNum* tendon_stiffness; mjtNum* tendon_damping; mjtNum* tendon_frictionloss; diff --git a/include/mujoco/mjxmacro.h b/include/mujoco/mjxmacro.h index 0aaf1af6..704ba353 100644 --- a/include/mujoco/mjxmacro.h +++ b/include/mujoco/mjxmacro.h @@ -475,12 +475,14 @@ XMJV( int, tendon_matid, ntendon, 1 ) \ XMJV( int, tendon_group, ntendon, 1 ) \ XMJV( mjtByte, tendon_limited, ntendon, 1 ) \ + XMJV( mjtByte, tendon_actfrclimited, ntendon, 1 ) \ XMJV( mjtNum, tendon_width, ntendon, 1 ) \ X ( mjtNum, tendon_solref_lim, ntendon, mjNREF ) \ X ( mjtNum, tendon_solimp_lim, ntendon, mjNIMP ) \ X ( mjtNum, tendon_solref_fri, ntendon, mjNREF ) \ X ( mjtNum, tendon_solimp_fri, ntendon, mjNIMP ) \ XMJV( mjtNum, tendon_range, ntendon, 2 ) \ + XMJV( mjtNum, tendon_actfrcrange, ntendon, 2 ) \ X ( mjtNum, tendon_margin, ntendon, 1 ) \ XMJV( mjtNum, tendon_stiffness, ntendon, 1 ) \ XMJV( mjtNum, tendon_damping, ntendon, 1 ) \ diff --git a/mjx/mujoco/mjx/_src/support_test.py b/mjx/mujoco/mjx/_src/support_test.py index 373b6a90..1a0c904c 100644 --- a/mjx/mujoco/mjx/_src/support_test.py +++ b/mjx/mujoco/mjx/_src/support_test.py @@ -349,7 +349,7 @@ class SupportTest(parameterized.TestCase): self.assertEqual( str(e.exception), 'mjSpec signature does not match mjx.Model signature:' - ' 4300287342280373816 != 9887180086914550999', + ' 17856615236057737915 != 12517827274439268436', ) _CONTACTS = """ diff --git a/mjx/mujoco/mjx/_src/types.py b/mjx/mujoco/mjx/_src/types.py index 9052043c..3ee10499 100644 --- a/mjx/mujoco/mjx/_src/types.py +++ b/mjx/mujoco/mjx/_src/types.py @@ -763,11 +763,13 @@ class Model(PyTreeNode): tendon_adr: address of first object in tendon's path (ntendon,) tendon_num: number of objects in tendon's path (ntendon,) tendon_limited: does tendon have length limits (ntendon,) + tendon_actfrclimited: tendon has actuator force limits (ntendon,) tendon_solref_lim: constraint solver reference: limit (ntendon, mjNREF) tendon_solimp_lim: constraint solver impedance: limit (ntendon, mjNIMP) tendon_solref_fri: constraint solver reference: friction (ntendon, mjNREF) tendon_solimp_fri: constraint solver impedance: friction (ntendon, mjNIMP) tendon_range: tendon length limits (ntendon, 2) + tendon_actfrcrange: tendon actuator force limits (ntendon, 2) tendon_margin: min distance for limit detection (ntendon,) tendon_stiffness: stiffness coefficient (ntendon,) tendon_damping: damping coefficient (ntendon,) @@ -1106,11 +1108,13 @@ class Model(PyTreeNode): tendon_adr: np.ndarray tendon_num: np.ndarray tendon_limited: np.ndarray + tendon_actfrclimited: np.ndarray tendon_solref_lim: jax.Array tendon_solimp_lim: jax.Array tendon_solref_fri: jax.Array tendon_solimp_fri: jax.Array tendon_range: jax.Array + tendon_actfrcrange: jax.Array tendon_margin: jax.Array tendon_stiffness: jax.Array tendon_damping: jax.Array diff --git a/python/mujoco/introspect/enums.py b/python/mujoco/introspect/enums.py index 9443566f..dd1a1fad 100644 --- a/python/mujoco/introspect/enums.py +++ b/python/mujoco/introspect/enums.py @@ -340,34 +340,35 @@ ENUMS: Mapping[str, EnumDecl] = dict([ ('mjSENS_ACTUATORVEL', 14), ('mjSENS_ACTUATORFRC', 15), ('mjSENS_JOINTACTFRC', 16), - ('mjSENS_BALLQUAT', 17), - ('mjSENS_BALLANGVEL', 18), - ('mjSENS_JOINTLIMITPOS', 19), - ('mjSENS_JOINTLIMITVEL', 20), - ('mjSENS_JOINTLIMITFRC', 21), - ('mjSENS_TENDONLIMITPOS', 22), - ('mjSENS_TENDONLIMITVEL', 23), - ('mjSENS_TENDONLIMITFRC', 24), - ('mjSENS_FRAMEPOS', 25), - ('mjSENS_FRAMEQUAT', 26), - ('mjSENS_FRAMEXAXIS', 27), - ('mjSENS_FRAMEYAXIS', 28), - ('mjSENS_FRAMEZAXIS', 29), - ('mjSENS_FRAMELINVEL', 30), - ('mjSENS_FRAMEANGVEL', 31), - ('mjSENS_FRAMELINACC', 32), - ('mjSENS_FRAMEANGACC', 33), - ('mjSENS_SUBTREECOM', 34), - ('mjSENS_SUBTREELINVEL', 35), - ('mjSENS_SUBTREEANGMOM', 36), - ('mjSENS_GEOMDIST', 37), - ('mjSENS_GEOMNORMAL', 38), - ('mjSENS_GEOMFROMTO', 39), - ('mjSENS_E_POTENTIAL', 40), - ('mjSENS_E_KINETIC', 41), - ('mjSENS_CLOCK', 42), - ('mjSENS_PLUGIN', 43), - ('mjSENS_USER', 44), + ('mjSENS_TENDONACTFRC', 17), + ('mjSENS_BALLQUAT', 18), + ('mjSENS_BALLANGVEL', 19), + ('mjSENS_JOINTLIMITPOS', 20), + ('mjSENS_JOINTLIMITVEL', 21), + ('mjSENS_JOINTLIMITFRC', 22), + ('mjSENS_TENDONLIMITPOS', 23), + ('mjSENS_TENDONLIMITVEL', 24), + ('mjSENS_TENDONLIMITFRC', 25), + ('mjSENS_FRAMEPOS', 26), + ('mjSENS_FRAMEQUAT', 27), + ('mjSENS_FRAMEXAXIS', 28), + ('mjSENS_FRAMEYAXIS', 29), + ('mjSENS_FRAMEZAXIS', 30), + ('mjSENS_FRAMELINVEL', 31), + ('mjSENS_FRAMEANGVEL', 32), + ('mjSENS_FRAMELINACC', 33), + ('mjSENS_FRAMEANGACC', 34), + ('mjSENS_SUBTREECOM', 35), + ('mjSENS_SUBTREELINVEL', 36), + ('mjSENS_SUBTREEANGMOM', 37), + ('mjSENS_GEOMDIST', 38), + ('mjSENS_GEOMNORMAL', 39), + ('mjSENS_GEOMFROMTO', 40), + ('mjSENS_E_POTENTIAL', 41), + ('mjSENS_E_KINETIC', 42), + ('mjSENS_CLOCK', 43), + ('mjSENS_PLUGIN', 44), + ('mjSENS_USER', 45), ]), )), ('mjtStage', diff --git a/python/mujoco/introspect/structs.py b/python/mujoco/introspect/structs.py index 59b21fb8..51799032 100644 --- a/python/mujoco/introspect/structs.py +++ b/python/mujoco/introspect/structs.py @@ -3615,6 +3615,14 @@ STRUCTS: Mapping[str, StructDecl] = dict([ doc='does tendon have length limits', array_extent=('ntendon',), ), + StructFieldDecl( + name='tendon_actfrclimited', + type=PointerType( + inner_type=ValueType(name='mjtByte'), + ), + doc='does tendon have actuator force limits', + array_extent=('ntendon',), + ), StructFieldDecl( name='tendon_width', type=PointerType( @@ -3663,6 +3671,14 @@ STRUCTS: Mapping[str, StructDecl] = dict([ doc='tendon length limits', array_extent=('ntendon', 2), ), + StructFieldDecl( + name='tendon_actfrcrange', + type=PointerType( + inner_type=ValueType(name='mjtNum'), + ), + doc='range of total actuator force', + array_extent=('ntendon', 2), + ), StructFieldDecl( name='tendon_margin', type=PointerType( @@ -8135,6 +8151,13 @@ STRUCTS: Mapping[str, StructDecl] = dict([ ), doc='', ), + StructFieldDecl( + name='tendon_actfrclimited', + type=PointerType( + inner_type=ValueType(name='mjtByte'), + ), + doc='', + ), StructFieldDecl( name='tendon_width', type=PointerType( @@ -8149,6 +8172,13 @@ STRUCTS: Mapping[str, StructDecl] = dict([ ), doc='', ), + StructFieldDecl( + name='tendon_actfrcrange', + type=PointerType( + inner_type=ValueType(name='mjtNum'), + ), + doc='', + ), StructFieldDecl( name='tendon_stiffness', type=PointerType( @@ -11342,6 +11372,11 @@ STRUCTS: Mapping[str, StructDecl] = dict([ type=ValueType(name='int'), doc='does tendon have limits (mjtLimited)', ), + StructFieldDecl( + name='actfrclimited', + type=ValueType(name='int'), + doc='does tendon have actuator force limits', + ), StructFieldDecl( name='range', type=ArrayType( @@ -11350,6 +11385,14 @@ STRUCTS: Mapping[str, StructDecl] = dict([ ), doc='length limits', ), + StructFieldDecl( + name='actfrcrange', + type=ArrayType( + inner_type=ValueType(name='double'), + extents=(2,), + ), + doc='actuator force limits', + ), StructFieldDecl( name='margin', type=ValueType(name='double'), diff --git a/src/engine/engine_forward.c b/src/engine/engine_forward.c index d55f857b..5771db32 100644 --- a/src/engine/engine_forward.c +++ b/src/engine/engine_forward.c @@ -276,7 +276,7 @@ static void clampVec(mjtNum* vec, const mjtNum* range, const mjtByte* limited, i // (qpos, qvel, ctrl, act) => (qfrc_actuator, actuator_force, act_dot) void mj_fwdActuation(const mjModel* m, mjData* d) { TM_START; - int nv = m->nv, nu = m->nu; + int nv = m->nv, nu = m->nu, ntendon = m->ntendon; mjtNum gain, bias, tau; mjtNum *prm, *force = d->actuator_force; @@ -289,6 +289,9 @@ void mj_fwdActuation(const mjModel* m, mjData* d) { return; } + // any tendon transmission targets with force limits + int tendon_frclimited = 0; + // local, clamped copy of ctrl mj_markStack(d); mjtNum *ctrl = mjSTACKALLOC(d, nu, mjtNum); @@ -384,6 +387,11 @@ void mj_fwdActuation(const mjModel* m, mjData* d) { continue; } + // check for tendon transmission with force limits + if (ntendon && !tendon_frclimited && m->actuator_trntype[i] == mjTRN_TENDON) { + tendon_frclimited = m->tendon_actfrclimited[m->actuator_trnid[2*i]]; + } + // extract gain info prm = m->actuator_gainprm + mjNGAIN*i; @@ -479,6 +487,38 @@ void mj_fwdActuation(const mjModel* m, mjData* d) { } } + // clamp tendon total actuator force + if (tendon_frclimited) { + // compute total force for each tendon + mjtNum* tendon_total_force = mjSTACKALLOC(d, ntendon, mjtNum); + mju_zero(tendon_total_force, ntendon); + for (int i=0; i < nu; i++) { + if (m->actuator_trntype[i] == mjTRN_TENDON) { + int tendon_id = m->actuator_trnid[2*i]; + if (m->tendon_actfrclimited[tendon_id]) { + tendon_total_force[tendon_id] += force[i]; + } + } + } + + // scale tendon actuator forces if limited and outside range + for (int i=0; i < nu; i++) { + if (m->actuator_trntype[i] != mjTRN_TENDON) { + continue; + } + int tendon_id = m->actuator_trnid[2*i]; + mjtNum tendon_force = tendon_total_force[tendon_id]; + if (m->tendon_actfrclimited[tendon_id] && tendon_force) { + const mjtNum* range = m->tendon_actfrcrange + 2 * tendon_id; + if (tendon_force < range[0]) { + force[i] *= range[0] / tendon_force; + } else if (tendon_force > range[1]) { + force[i] *= range[1] / tendon_force; + } + } + } + } + // clamp actuator_force clampVec(force, m->actuator_forcerange, m->actuator_forcelimited, nu, NULL); diff --git a/src/engine/engine_io.c b/src/engine/engine_io.c index f982941f..0f6878e1 100644 --- a/src/engine/engine_io.c +++ b/src/engine/engine_io.c @@ -2079,6 +2079,7 @@ static int sensorSize(mjtSensor sensor_type, int sensor_dim) { case mjSENS_ACTUATORVEL: case mjSENS_ACTUATORFRC: case mjSENS_JOINTACTFRC: + case mjSENS_TENDONACTFRC: case mjSENS_JOINTLIMITPOS: case mjSENS_JOINTLIMITVEL: case mjSENS_JOINTLIMITFRC: diff --git a/src/engine/engine_sensor.c b/src/engine/engine_sensor.c index baad81c4..936dc085 100644 --- a/src/engine/engine_sensor.c +++ b/src/engine/engine_sensor.c @@ -698,8 +698,8 @@ void mj_sensorVel(const mjModel* m, mjData* d) { // acceleration/force-dependent sensors void mj_sensorAcc(const mjModel* m, mjData* d) { int rootid, bodyid, objtype, objid, adr, nusersensor = 0; - int ne = d->ne, nf = d->nf, nefc = d->nefc; - mjtNum tmp[6], conforce[6], conray[3]; + int ne = d->ne, nf = d->nf, nefc = d->nefc, nu = m->nu; + mjtNum tmp[6], conforce[6], conray[3], frc; mjContact* con; // disabled sensors: return @@ -825,6 +825,16 @@ void mj_sensorAcc(const mjModel* m, mjData* d) { d->sensordata[adr] = d->qfrc_actuator[m->jnt_dofadr[objid]]; break; + case mjSENS_TENDONACTFRC: // tendonactfrc + frc = 0.0; + for (int j=0; j < nu; j++) { + if (m->actuator_trntype[j] == mjTRN_TENDON && m->actuator_trnid[2*j] == objid) { + frc += d->actuator_force[j]; + } + } + d->sensordata[adr] = frc; + break; + case mjSENS_JOINTLIMITFRC: // jointlimitfrc d->sensordata[adr] = 0; for (int j=ne+nf; j < nefc; j++) { diff --git a/src/user/user_model.cc b/src/user/user_model.cc index c6b181d0..ff78e190 100644 --- a/src/user/user_model.cc +++ b/src/user/user_model.cc @@ -3351,6 +3351,7 @@ void mjCModel::CopyObjects(mjModel* m) { m->tendon_matid[i] = pte->matid; m->tendon_group[i] = pte->group; m->tendon_limited[i] = (mjtByte)pte->is_limited(); + m->tendon_actfrclimited[i] = (mjtByte)pte->is_actfrclimited(); m->tendon_width[i] = (mjtNum)pte->width; mjuu_copyvec(m->tendon_solref_lim+mjNREF*i, pte->solref_limit, mjNREF); mjuu_copyvec(m->tendon_solimp_lim+mjNIMP*i, pte->solimp_limit, mjNIMP); @@ -3358,6 +3359,8 @@ void mjCModel::CopyObjects(mjModel* m) { mjuu_copyvec(m->tendon_solimp_fri+mjNIMP*i, pte->solimp_friction, mjNIMP); m->tendon_range[2*i] = (mjtNum)pte->range[0]; m->tendon_range[2*i+1] = (mjtNum)pte->range[1]; + m->tendon_actfrcrange[2*i] = (mjtNum)pte->actfrcrange[0]; + m->tendon_actfrcrange[2*i+1] = (mjtNum)pte->actfrcrange[1]; m->tendon_margin[i] = (mjtNum)pte->margin; m->tendon_stiffness[i] = (mjtNum)pte->stiffness; m->tendon_damping[i] = (mjtNum)pte->damping; @@ -5005,6 +5008,7 @@ bool mjCModel::CopyBack(const mjModel* m) { // tendons for (int i=0; i < ntendon; i++) { mjuu_copyvec(tendons_[i]->range, m->tendon_range+2*i, 2); + mjuu_copyvec(tendons_[i]->actfrcrange, m->tendon_actfrcrange+2*i, 2); mjuu_copyvec(tendons_[i]->solref_limit, m->tendon_solref_lim+mjNREF*i, mjNREF); mjuu_copyvec(tendons_[i]->solimp_limit, m->tendon_solimp_lim+mjNIMP*i, mjNIMP); mjuu_copyvec(tendons_[i]->solref_friction, m->tendon_solref_fri+mjNREF*i, mjNREF); diff --git a/src/user/user_objects.cc b/src/user/user_objects.cc index 827b5e48..d609621c 100644 --- a/src/user/user_objects.cc +++ b/src/user/user_objects.cc @@ -5388,7 +5388,9 @@ mjCTendon& mjCTendon::operator=(const mjCTendon& other) { bool mjCTendon::is_limited() const { return islimited(limited, range); } - +bool mjCTendon::is_actfrclimited() const { + return islimited(actfrclimited, actfrcrange); +} void mjCTendon::PointToLocal() { spec.element = static_cast(this); @@ -5686,6 +5688,21 @@ void mjCTendon::Compile(void) { throw mjCError(this, "invalid limits in tendon"); } + // if limited is auto, set to 1 if range is specified, otherwise unlimited + if (actfrclimited == mjLIMITED_AUTO) { + bool hasactfrcrange = !(actfrcrange[0] == 0 && actfrcrange[1] == 0); + checklimited(this, compiler->autolimits, "tendon", "", actfrclimited, + hasactfrcrange); + } + + // check actfrclimits + if (actfrcrange[0] >= actfrcrange[1] && is_actfrclimited()) { + throw mjCError(this, "invalid actuatorfrcrange in tendon"); + } + if ((actfrcrange[0] > 0 || actfrcrange[1] < 0) && is_actfrclimited()) { + throw mjCError(this, "invalid actuatorfrcrange in tendon"); + } + // check springlength if (springlength[0] > springlength[1]) { throw mjCError(this, "invalid springlength in tendon"); @@ -6479,6 +6496,18 @@ void mjCSensor::Compile(void) { } break; + case mjSENS_TENDONACTFRC: + // must be attached to tendon + if (objtype != mjOBJ_TENDON) { + throw mjCError(this, "sensor must be attached to tendon"); + } + + // set + dim = 1; + datatype = mjDATATYPE_REAL; + needstage = mjSTAGE_ACC; + break; + case mjSENS_TENDONPOS: case mjSENS_TENDONVEL: // must be attached to tendon diff --git a/src/user/user_objects.h b/src/user/user_objects.h index ce890610..16aa57af 100644 --- a/src/user/user_objects.h +++ b/src/user/user_objects.h @@ -1523,6 +1523,7 @@ class mjCTendon : public mjCTendon_, private mjsTendon { void SetModel(mjCModel* _model); bool is_limited() const; + bool is_actfrclimited() const; private: void Compile(void); // compiler diff --git a/src/xml/xml_native_reader.cc b/src/xml/xml_native_reader.cc index f3afd4a2..a5bf6824 100644 --- a/src/xml/xml_native_reader.cc +++ b/src/xml/xml_native_reader.cc @@ -363,8 +363,8 @@ const char* MJCF[nMJCF][mjXATTRNUM] = { {"tendon", "*", "0"}, {"<"}, - {"spatial", "*", "19", "name", "class", "group", "limited", "range", - "solreflimit", "solimplimit", "solreffriction", "solimpfriction", + {"spatial", "*", "21", "name", "class", "group", "limited", "actuatorfrclimited", "range", + "actuatorfrcrange", "solreflimit", "solimplimit", "solreffriction", "solimpfriction", "frictionloss", "springlength", "width", "material", "margin", "stiffness", "damping", "armature", "rgba", "user"}, {"<"}, @@ -372,8 +372,8 @@ const char* MJCF[nMJCF][mjXATTRNUM] = { {"geom", "*", "2", "geom", "sidesite"}, {"pulley", "*", "1", "divisor"}, {">"}, - {"fixed", "*", "16", "name", "class", "group", "limited", "range", - "solreflimit", "solimplimit", "solreffriction", "solimpfriction", + {"fixed", "*", "18", "name", "class", "group", "limited", "actuatorfrclimited", "range", + "actuatorfrcrange","solreflimit", "solimplimit", "solreffriction", "solimpfriction", "frictionloss", "springlength", "margin", "stiffness", "damping", "armature", "user"}, {"<"}, {"joint", "*", "2", "joint", "coef"}, @@ -455,6 +455,7 @@ const char* MJCF[nMJCF][mjXATTRNUM] = { {"actuatorvel", "*", "5", "name", "actuator", "cutoff", "noise", "user"}, {"actuatorfrc", "*", "5", "name", "actuator", "cutoff", "noise", "user"}, {"jointactuatorfrc", "*", "5", "name", "joint", "cutoff", "noise", "user"}, + {"tendonactuatorfrc", "*", "5", "name", "tendon", "cutoff", "noise", "user"}, {"ballquat", "*", "5", "name", "joint", "cutoff", "noise", "user"}, {"ballangvel", "*", "5", "name", "joint", "cutoff", "noise", "user"}, {"jointlimitpos", "*", "5", "name", "joint", "cutoff", "noise", "user"}, @@ -2052,12 +2053,14 @@ void mjXReader::OneTendon(XMLElement* elem, mjsTendon* tendon) { mjs_setString(tendon->material, material.c_str()); } MapValue(elem, "limited", &tendon->limited, TFAuto_map, 3); + MapValue(elem, "actuatorfrclimited", &tendon->actfrclimited, TFAuto_map, 3); ReadAttr(elem, "width", 1, &tendon->width, text); ReadAttr(elem, "solreflimit", mjNREF, tendon->solref_limit, text, false, false); ReadAttr(elem, "solimplimit", mjNIMP, tendon->solimp_limit, text, false, false); ReadAttr(elem, "solreffriction", mjNREF, tendon->solref_friction, text, false, false); ReadAttr(elem, "solimpfriction", mjNIMP, tendon->solimp_friction, text, false, false); ReadAttr(elem, "range", 2, tendon->range, text); + ReadAttr(elem, "actuatorfrcrange", 2, tendon->actfrcrange, text); ReadAttr(elem, "margin", 1, &tendon->margin, text); ReadAttr(elem, "stiffness", 1, &tendon->stiffness, text); ReadAttr(elem, "damping", 1, &tendon->damping, text); @@ -3973,6 +3976,10 @@ void mjXReader::Sensor(XMLElement* section) { sensor->type = mjSENS_JOINTACTFRC; sensor->objtype = mjOBJ_JOINT; ReadAttrTxt(elem, "joint", objname, true); + } else if (type=="tendonactuatorfrc") { + sensor->type = mjSENS_TENDONACTFRC; + sensor->objtype = mjOBJ_TENDON; + ReadAttrTxt(elem, "tendon", objname, true); } // sensors related to ball joints diff --git a/src/xml/xml_native_reader.h b/src/xml/xml_native_reader.h index 0e568f99..010ea2d5 100644 --- a/src/xml/xml_native_reader.h +++ b/src/xml/xml_native_reader.h @@ -102,7 +102,7 @@ class mjXReader : public mjXBase { }; // MJCF schema -#define nMJCF 237 +#define nMJCF 238 extern const char* MJCF[nMJCF][mjXATTRNUM]; #endif // MUJOCO_SRC_XML_XML_NATIVE_READER_H_ diff --git a/src/xml/xml_native_writer.cc b/src/xml/xml_native_writer.cc index 5f2d44cf..001ecf18 100644 --- a/src/xml/xml_native_writer.cc +++ b/src/xml/xml_native_writer.cc @@ -725,7 +725,9 @@ void mjXWriter::OneTendon(XMLElement* elem, const mjCTendon* tendon, mjCDef* def WriteAttr(elem, "solimpfriction", mjNIMP, tendon->solimp_friction, def->Tendon().solimp_friction, true); WriteAttrKey(elem, "limited", TFAuto_map, 3, tendon->limited, def->Tendon().limited); + WriteAttrKey(elem, "actuatorfrclimited", TFAuto_map, 3, tendon->actfrclimited, def->Tendon().actfrclimited); WriteAttr(elem, "range", 2, tendon->range, def->Tendon().range); + WriteAttr(elem, "actuatorfrcrange", 2, tendon->actfrcrange, def->Tendon().actfrcrange); WriteAttr(elem, "margin", 1, &tendon->margin, &def->Tendon().margin); WriteAttr(elem, "stiffness", 1, &tendon->stiffness, &def->Tendon().stiffness); WriteAttr(elem, "damping", 1, &tendon->damping, &def->Tendon().damping); @@ -2033,6 +2035,10 @@ void mjXWriter::Sensor(XMLElement* root) { elem = InsertEnd(section, "jointactuatorfrc"); WriteAttrTxt(elem, "joint", sensor->get_objname()); break; + case mjSENS_TENDONACTFRC: + elem = InsertEnd(section, "tendonactuatorfrc"); + WriteAttrTxt(elem, "tendon", sensor->get_objname()); + break; // sensors related to ball joints case mjSENS_BALLQUAT: diff --git a/test/engine/engine_forward_test.cc b/test/engine/engine_forward_test.cc index 16a52f43..d345b065 100644 --- a/test/engine/engine_forward_test.cc +++ b/test/engine/engine_forward_test.cc @@ -46,6 +46,8 @@ static const char* const kDampedActuatorsPath = "engine/testdata/derivative/damped_actuators.xml"; static const char* const kJointForceClamp = "engine/testdata/actuation/joint_force_clamp.xml"; +static const char* const kTendonForceClamp = + "engine/testdata/actuation/tendon_force_clamp.xml"; using ::testing::Pointwise; using ::testing::DoubleNear; @@ -1393,5 +1395,57 @@ TEST_F(ActuatorTest, DisableActuatorOutOfRange) { mj_deleteModel(model); } +TEST_F(ActuatorTest, TendonActuatorForceRange) { + const std::string xml_path = GetTestDataFilePath(kTendonForceClamp); + mjModel* model = mj_loadXML(xml_path.c_str(), nullptr, nullptr, 0); + mjData* data = mj_makeData(model); + + EXPECT_EQ(model->tendon_actfrclimited[0], 0); + EXPECT_EQ(model->tendon_actfrcrange[0], 0); + EXPECT_EQ(model->tendon_actfrcrange[1], 0); + + EXPECT_EQ(model->tendon_actfrclimited[1], 1); + EXPECT_EQ(model->tendon_actfrcrange[2], -1); + EXPECT_EQ(model->tendon_actfrcrange[3], 1); + + EXPECT_EQ(model->tendon_actfrclimited[2], 1); + EXPECT_EQ(model->tendon_actfrcrange[4], -10); + EXPECT_EQ(model->tendon_actfrcrange[5], 10); + + EXPECT_EQ(model->tendon_actfrclimited[3], 1); + EXPECT_EQ(model->tendon_actfrcrange[6], 0); + EXPECT_EQ(model->tendon_actfrcrange[7], 1); + + data->ctrl[0] = 1; + data->ctrl[1] = 1; + data->ctrl[2] = 1; + + data->ctrl[3] = -1; + data->ctrl[4] = 1; + + data->ctrl[5] = -20; + data->ctrl[6] = 5; + data->ctrl[7] = -5; + + mj_forward(model, data); + + EXPECT_NEAR(data->actuator_force[0], 1, 1e-6); + EXPECT_NEAR(data->actuator_force[1], 1, 1e-6); + EXPECT_NEAR(data->actuator_force[2], 1, 1e-6); + EXPECT_NEAR(data->actuator_force[3], -1, 1e-6); + EXPECT_NEAR(data->actuator_force[4], 1, 1e-6); + EXPECT_NEAR(data->actuator_force[5], -10, 1e-6); + EXPECT_NEAR(data->actuator_force[6], 5, 1e-6); + EXPECT_NEAR(data->actuator_force[7], -5, 1e-6); + + EXPECT_EQ(data->sensordata[0], 3); + EXPECT_EQ(data->sensordata[1], 0); + EXPECT_EQ(data->sensordata[2], -10); + EXPECT_EQ(data->sensordata[3], 0); + + mj_deleteData(data); + mj_deleteModel(model); +} + } // namespace } // namespace mujoco diff --git a/test/engine/testdata/actuation/tendon_force_clamp.xml b/test/engine/testdata/actuation/tendon_force_clamp.xml new file mode 100644 index 00000000..51952b3a --- /dev/null +++ b/test/engine/testdata/actuation/tendon_force_clamp.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/user/user_objects_test.cc b/test/user/user_objects_test.cc index b9f13cd4..37bec917 100644 --- a/test/user/user_objects_test.cc +++ b/test/user/user_objects_test.cc @@ -2070,6 +2070,51 @@ TEST_F(TendonTest, SiteBetweenPulleyNotAllowed) { EXPECT_THAT(error.data(), HasSubstr("line 9")); } +TEST_F(TendonTest, ActuatorForceRangeNotAllowed) { + std::string xml = R"( + + + + + + + + + + + + + + + + )"; + + std::array error; + std::string str_replace = "{}"; + size_t rng_ind = xml.find(str_replace); + + std::string xml0 = xml; + std::string range0 = "-2 -1"; + xml0.replace(rng_ind, str_replace.length(), range0); + mjModel* m0 = LoadModelFromString(xml0.c_str(), error.data(), error.size()); + EXPECT_THAT(m0, IsNull()); + EXPECT_THAT(error.data(), HasSubstr("invalid actuatorfrcrange in tendon")); + + std::string xml1 = xml; + std::string range1 = "1 2"; + xml1.replace(rng_ind, str_replace.length(), range1); + mjModel* m1 = LoadModelFromString(xml1.c_str(), error.data(), error.size()); + EXPECT_THAT(m1, IsNull()); + EXPECT_THAT(error.data(), HasSubstr("invalid actuatorfrcrange in tendon")); + + std::string xml2 = xml; + std::string range2 = "1 0"; + xml2.replace(rng_ind, str_replace.length(), range2); + mjModel* m2 = LoadModelFromString(xml2.c_str(), error.data(), error.size()); + EXPECT_THAT(m2, IsNull()); + EXPECT_THAT(error.data(), HasSubstr("invalid actuatorfrcrange in tendon")); +} + // ------------- tests for tendon springrange ---------------------------------- using SpringrangeTest = MujocoTest; diff --git a/unity/Runtime/Bindings/MjBindings.cs b/unity/Runtime/Bindings/MjBindings.cs index f7d6ac29..ca702ca0 100644 --- a/unity/Runtime/Bindings/MjBindings.cs +++ b/unity/Runtime/Bindings/MjBindings.cs @@ -357,34 +357,35 @@ public enum mjtSensor : int{ mjSENS_ACTUATORVEL = 14, mjSENS_ACTUATORFRC = 15, mjSENS_JOINTACTFRC = 16, - mjSENS_BALLQUAT = 17, - mjSENS_BALLANGVEL = 18, - mjSENS_JOINTLIMITPOS = 19, - mjSENS_JOINTLIMITVEL = 20, - mjSENS_JOINTLIMITFRC = 21, - mjSENS_TENDONLIMITPOS = 22, - mjSENS_TENDONLIMITVEL = 23, - mjSENS_TENDONLIMITFRC = 24, - mjSENS_FRAMEPOS = 25, - mjSENS_FRAMEQUAT = 26, - mjSENS_FRAMEXAXIS = 27, - mjSENS_FRAMEYAXIS = 28, - mjSENS_FRAMEZAXIS = 29, - mjSENS_FRAMELINVEL = 30, - mjSENS_FRAMEANGVEL = 31, - mjSENS_FRAMELINACC = 32, - mjSENS_FRAMEANGACC = 33, - mjSENS_SUBTREECOM = 34, - mjSENS_SUBTREELINVEL = 35, - mjSENS_SUBTREEANGMOM = 36, - mjSENS_GEOMDIST = 37, - mjSENS_GEOMNORMAL = 38, - mjSENS_GEOMFROMTO = 39, - mjSENS_E_POTENTIAL = 40, - mjSENS_E_KINETIC = 41, - mjSENS_CLOCK = 42, - mjSENS_PLUGIN = 43, - mjSENS_USER = 44, + mjSENS_TENDONACTFRC = 17, + mjSENS_BALLQUAT = 18, + mjSENS_BALLANGVEL = 19, + mjSENS_JOINTLIMITPOS = 20, + mjSENS_JOINTLIMITVEL = 21, + mjSENS_JOINTLIMITFRC = 22, + mjSENS_TENDONLIMITPOS = 23, + mjSENS_TENDONLIMITVEL = 24, + mjSENS_TENDONLIMITFRC = 25, + mjSENS_FRAMEPOS = 26, + mjSENS_FRAMEQUAT = 27, + mjSENS_FRAMEXAXIS = 28, + mjSENS_FRAMEYAXIS = 29, + mjSENS_FRAMEZAXIS = 30, + mjSENS_FRAMELINVEL = 31, + mjSENS_FRAMEANGVEL = 32, + mjSENS_FRAMELINACC = 33, + mjSENS_FRAMEANGACC = 34, + mjSENS_SUBTREECOM = 35, + mjSENS_SUBTREELINVEL = 36, + mjSENS_SUBTREEANGMOM = 37, + mjSENS_GEOMDIST = 38, + mjSENS_GEOMNORMAL = 39, + mjSENS_GEOMFROMTO = 40, + mjSENS_E_POTENTIAL = 41, + mjSENS_E_KINETIC = 42, + mjSENS_CLOCK = 43, + mjSENS_PLUGIN = 44, + mjSENS_USER = 45, } public enum mjtStage : int{ mjSTAGE_NONE = 0, @@ -5569,12 +5570,14 @@ public unsafe struct mjModel_ { public int* tendon_matid; public int* tendon_group; public byte* tendon_limited; + public byte* tendon_actfrclimited; public double* tendon_width; public double* tendon_solref_lim; public double* tendon_solimp_lim; public double* tendon_solref_fri; public double* tendon_solimp_fri; public double* tendon_range; + public double* tendon_actfrcrange; public double* tendon_margin; public double* tendon_stiffness; public double* tendon_damping; @@ -6398,8 +6401,10 @@ public unsafe struct model { public int* tendon_matid; public int* tendon_group; public byte* tendon_limited; + public byte* tendon_actfrclimited; public double* tendon_width; public double* tendon_range; + public double* tendon_actfrcrange; public double* tendon_stiffness; public double* tendon_damping; public double* tendon_frictionloss;