From 14dc7c2a60243b1c70c2b3ae3cfd6248d899a57c Mon Sep 17 00:00:00 2001 From: Yuval Tassa Date: Fri, 20 Jun 2025 11:39:59 -0700 Subject: [PATCH] Add "cameraid" attribute to `visual/global` specifying the default camera to use at model load. PiperOrigin-RevId: 773783069 Change-Id: I2fff2abdceedc982d89cee8d14b95abceea1c113 --- doc/APIreference/functions.rst | 4 ++-- doc/APIreference/functions_override.rst | 8 ++++++++ doc/XMLreference.rst | 6 ++++++ doc/XMLschema.rst | 8 +++++--- doc/changelog.rst | 5 +++++ doc/includes/references.h | 7 ++++--- include/mujoco/mjmodel.h | 7 ++++--- python/mujoco/introspect/structs.py | 5 +++++ python/mujoco/structs.cc | 1 + simulate/main.cc | 2 +- simulate/simulate.cc | 12 ++++++++++-- src/engine/engine_io.c | 1 + src/xml/xml_native_reader.cc | 6 ++++-- src/xml/xml_native_writer.cc | 1 + unity/Runtime/Bindings/MjBindings.cs | 1 + 15 files changed, 58 insertions(+), 16 deletions(-) diff --git a/doc/APIreference/functions.rst b/doc/APIreference/functions.rst index 8c252a0f..2205384a 100644 --- a/doc/APIreference/functions.rst +++ b/doc/APIreference/functions.rst @@ -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: diff --git a/doc/APIreference/functions_override.rst b/doc/APIreference/functions_override.rst index 5d00db0a..da9c913a 100644 --- a/doc/APIreference/functions_override.rst +++ b/doc/APIreference/functions_override.rst @@ -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) 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``. diff --git a/doc/XMLreference.rst b/doc/XMLreference.rst index 4e867448..b36cd054 100644 --- a/doc/XMLreference.rst +++ b/doc/XMLreference.rst @@ -7534,6 +7534,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`, use the camera's id as given by :ref:`mj_name2id`. + .. _visual-global-orthographic: :at:`orthographic`: :at-val:`[false, true], "false"` diff --git a/doc/XMLschema.rst b/doc/XMLschema.rst index 21272c90..b0ba5dd0 100644 --- a/doc/XMLschema.rst +++ b/doc/XMLschema.rst @@ -1298,11 +1298,13 @@ | :ref:`global | ? | :class: mjcf-attributes | | ` | | | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | -| | | | :ref:`orthographic` | :ref:`fovy` | :ref:`ipd` | :ref:`azimuth` | | +| | | | :ref:`cameraid` | :ref:`orthographic` | :ref:`fovy` | :ref:`ipd` | | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | -| | | | :ref:`elevation` | :ref:`linewidth` | :ref:`glow` | :ref:`offwidth` | | +| | | | :ref:`azimuth` | :ref:`elevation` | :ref:`linewidth` | :ref:`glow` | | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | -| | | | :ref:`offheight` | :ref:`realtime` | :ref:`ellipsoidinertia` | :ref:`bvactive` | | +| | | | :ref:`offwidth` | :ref:`offheight` | :ref:`realtime` | :ref:`ellipsoidinertia` | | +| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | +| | | | :ref:`bvactive` | | | | | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | +------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |_| visual |br| |_| |L| | | .. table:: | diff --git a/doc/changelog.rst b/doc/changelog.rst index 02d2cacf..d2b5ce53 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -12,6 +12,11 @@ Upcoming version (not yet released) `:ref:mjs_detach` was added in order to remove an element from the spec without deleting it. - The Python functions ``element.delete`` have been replaced by ``spec.delete(element)``. +General +^^^^^^^ +- Added support for setting the initial camera in the viewer using + :ref:`visual/global/cameraid`. + Bug fixes ^^^^^^^^^ - Inverse dynamics were not being computed correctly when :ref:`tendon armature` was present, diff --git a/doc/includes/references.h b/doc/includes/references.h index 2e1ef149..1ef080b9 100644 --- a/doc/includes/references.h +++ b/doc/includes/references.h @@ -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) diff --git a/include/mujoco/mjmodel.h b/include/mujoco/mjmodel.h index 75eb3cd0..f0a80d79 100644 --- a/include/mujoco/mjmodel.h +++ b/include/mujoco/mjmodel.h @@ -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) diff --git a/python/mujoco/introspect/structs.py b/python/mujoco/introspect/structs.py index 49b8752b..c12bab47 100644 --- a/python/mujoco/introspect/structs.py +++ b/python/mujoco/introspect/structs.py @@ -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'), diff --git a/python/mujoco/structs.cc b/python/mujoco/structs.cc index 1b92c61a..4501d593 100644 --- a/python/mujoco/structs.cc +++ b/python/mujoco/structs.cc @@ -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); diff --git a/simulate/main.cc b/simulate/main.cc index 8d70e6e8..1b5ccb65 100644 --- a/simulate/main.cc +++ b/simulate/main.cc @@ -353,7 +353,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; diff --git a/simulate/simulate.cc b/simulate/simulate.cc index 273b7f7a..b692302e 100644 --- a/simulate/simulate.cc +++ b/simulate/simulate.cc @@ -1178,8 +1178,16 @@ void MakeUiSections(mj::Simulate* sim, const mjModel* m, const mjData* d) { // 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); + } } diff --git a/src/engine/engine_io.c b/src/engine/engine_io.c index 9a9ad366..54262a18 100644 --- a/src/engine/engine_io.c +++ b/src/engine/engine_io.c @@ -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; diff --git a/src/xml/xml_native_reader.cc b/src/xml/xml_native_reader.cc index f5994e0a..55d3c4ce 100644 --- a/src/xml/xml_native_reader.cc +++ b/src/xml/xml_native_reader.cc @@ -127,8 +127,9 @@ const char* MJCF[nMJCF][mjXATTRNUM] = { {"visual", "*", "0"}, {"<"}, - {"global", "?", "12", "orthographic", "fovy", "ipd", "azimuth", "elevation", "linewidth", - "glow", "offwidth", "offheight", "realtime", "ellipsoidinertia", "bvactive"}, + {"global", "?", "13", "cameraid", "orthographic", "fovy", "ipd", "azimuth", "elevation", + "linewidth", "glow", "offwidth", "offheight", "realtime", "ellipsoidinertia", + "bvactive"}, {"quality", "?", "5", "shadowsize", "offsamples", "numslices", "numstacks", "numquads"}, {"headlight", "?", "4", "ambient", "diffuse", "specular", "active"}, @@ -2998,6 +2999,7 @@ void mjXReader::Visual(XMLElement* section) { // global sub-element if (name == "global") { + ReadAttrInt(elem, "cameraid", &vis->global.cameraid); if (MapValue(elem, "orthographic", &n, bool_map, 2)) { vis->global.orthographic = (n == 1); } diff --git a/src/xml/xml_native_writer.cc b/src/xml/xml_native_writer.cc index 1b82b9fb..6b27bbc3 100644 --- a/src/xml/xml_native_writer.cc +++ b/src/xml/xml_native_writer.cc @@ -1130,6 +1130,7 @@ void mjXWriter::Visual(XMLElement* root) { // global elem = InsertEnd(section, "global"); + WriteAttrInt(elem, "cameraid", vis->global.cameraid, visdef.global.cameraid); WriteAttrKey(elem, "orthographic", bool_map, 2, vis->global.orthographic, visdef.global.orthographic); WriteAttr(elem, "fovy", 1, &vis->global.fovy, &visdef.global.fovy); diff --git a/unity/Runtime/Bindings/MjBindings.cs b/unity/Runtime/Bindings/MjBindings.cs index b7448e56..23006d1d 100644 --- a/unity/Runtime/Bindings/MjBindings.cs +++ b/unity/Runtime/Bindings/MjBindings.cs @@ -5114,6 +5114,7 @@ public unsafe struct mjOption_ { [StructLayout(LayoutKind.Sequential)] public unsafe struct global { + public int cameraid; public int orthographic; public float fovy; public float ipd;