From af4be63cd8ac26eaf49ee5269097c8e6d050e8f0 Mon Sep 17 00:00:00 2001 From: Google DeepMind Date: Wed, 27 May 2026 09:10:43 -0700 Subject: [PATCH] Add xmacros for MjSpec. PiperOrigin-RevId: 922169069 Change-Id: I01426596e49196996dfa324d5ab3849a90de2e3a --- CMakeLists.txt | 1 + doc/APIreference/APIglobals.rst | 5 +- doc/programming/index.rst | 6 +- include/mujoco/mjspecmacro.h | 603 +++++++++++++++++++++++++++ test/CMakeLists.txt | 2 + test/mjspecmacro_test.cc | 145 +++++++ unity/Runtime/Bindings/MjBindings.cs | 1 + 7 files changed, 760 insertions(+), 3 deletions(-) create mode 100644 include/mujoco/mjspecmacro.h create mode 100644 test/mjspecmacro_test.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index b50248f8..0423d3f4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -117,6 +117,7 @@ set(MUJOCO_HEADERS include/mujoco/mjrender.h include/mujoco/mjsan.h include/mujoco/mjspec.h + include/mujoco/mjspecmacro.h include/mujoco/mjthread.h include/mujoco/mjtype.h include/mujoco/mjui.h diff --git a/doc/APIreference/APIglobals.rst b/doc/APIreference/APIglobals.rst index 80c758af..9890735b 100644 --- a/doc/APIreference/APIglobals.rst +++ b/doc/APIreference/APIglobals.rst @@ -706,7 +706,8 @@ X Macros ^^^^^^^^ The X Macros are not needed in most user projects. They are used internally to allocate the model, and are also -available for users who know how to use this programming technique. See the header file `mjxmacro.h -`_ for the actual definitions. They are +available for users who know how to use this programming technique. See the header files `mjxmacro.h +`_ and `mjspecmacro.h +`_ for the actual definitions. They are particularly useful in writing MuJoCo wrappers for scripting languages, where dynamic structures matching the MuJoCo data structures need to be constructed programmatically. diff --git a/doc/programming/index.rst b/doc/programming/index.rst index 99cc50dc..24c2402f 100644 --- a/doc/programming/index.rst +++ b/doc/programming/index.rst @@ -151,7 +151,7 @@ links below, to make this documentation self-contained. `mujoco.h `__ This is the main header file and must be included in all programs using MuJoCo. It defines all API functions and - global variables, and includes all other header files except mjxmacro.h. + global variables, and includes all other header files except mjxmacro.h and mjspecmacro.h. `mjmodel.h `__ Defines the C structure :ref:`mjModel` which is the runtime representation of the model being simulated. @@ -179,6 +179,10 @@ links below, to make this documentation self-contained. This file is optional and is not included by mujoco.h. It defines :ref:`X Macros ` that can automate the mapping of mjModel and mjData into scripting languages, as well as other operations that require accessing all fields of mjModel and mjData. +`mjspecmacro.h `__ + This file is optional and is not included by mujoco.h. It defines :ref:`X Macros ` that can + automate the mapping of mjSpec and its element structs into scripting languages, as well as other operations that require + accessing all fields of mjSpec during procedural model editing. `mjexport.h `__ Macros used for exporting public symbols from the MuJoCo library. This header should not be used directly by client code. diff --git a/include/mujoco/mjspecmacro.h b/include/mujoco/mjspecmacro.h new file mode 100644 index 00000000..81e861e1 --- /dev/null +++ b/include/mujoco/mjspecmacro.h @@ -0,0 +1,603 @@ +// Copyright 2026 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_MJSPECMACRO_H_ +#define MUJOCO_MJSPECMACRO_H_ + + +//-------------------------------- mjsElement ------------------------------------------------------ + +#define MJSELEMENT_FIELDS \ + X( mjtObj, elemtype, 1 ) \ + X( uint64_t, signature, 1 ) + + +//-------------------------------- mjsCompiler ----------------------------------------------------- + +#define MJSCOMPILER_FIELDS \ + X ( mjtByte, autolimits, 1 ) \ + X ( double, boundmass, 1 ) \ + X ( double, boundinertia, 1 ) \ + X ( double, settotalmass, 1 ) \ + X ( mjtByte, balanceinertia, 1 ) \ + X ( mjtByte, fitaabb, 1 ) \ + X ( mjtByte, degree, 1 ) \ + XVEC( char, eulerseq, 3 ) \ + X ( mjtByte, discardvisual, 1 ) \ + X ( mjtByte, usethread, 1 ) \ + X ( mjtByte, fusestatic, 1 ) \ + X ( int, inertiafromgeom, 1 ) \ + XVEC( int, inertiagrouprange, 2 ) \ + X ( mjtByte, saveinertial, 1 ) \ + X ( int, alignfree, 1 ) \ + X ( mjLROpt, LRopt, 1 ) \ + X ( mjString*, meshdir, 1 ) \ + X ( mjString*, texturedir, 1 ) + + +//-------------------------------- mjSpec ---------------------------------------------------------- + +#define MJSPEC_FIELDS \ + X( mjsElement*, element, 1 ) \ + X( mjString*, modelname, 1 ) \ + X( mjsCompiler, compiler, 1 ) \ + X( mjtByte, strippath, 1 ) \ + X( mjOption, option, 1 ) \ + X( mjVisual, visual, 1 ) \ + X( mjStatistic, stat, 1 ) \ + X( mjtSize, memory, 1 ) \ + X( int, nemax, 1 ) \ + X( int, nuserdata, 1 ) \ + X( int, nuser_body, 1 ) \ + X( int, nuser_jnt, 1 ) \ + X( int, nuser_geom, 1 ) \ + X( int, nuser_site, 1 ) \ + X( int, nuser_cam, 1 ) \ + X( int, nuser_tendon, 1 ) \ + X( int, nuser_actuator, 1 ) \ + X( int, nuser_sensor, 1 ) \ + X( int, nkey, 1 ) \ + X( int, njmax, 1 ) \ + X( int, nconmax, 1 ) \ + X( mjtSize, nstack, 1 ) \ + X( mjString*, comment, 1 ) \ + X( mjString*, modelfiledir, 1 ) \ + X( mjtByte, hasImplicitPluginElem, 1 ) + + +//-------------------------------- mjsOrientation -------------------------------------------------- + +#define MJSORIENTATION_FIELDS \ + X ( mjtOrientation, type, 1 ) \ + XVEC( double, axisangle, 4 ) \ + XVEC( double, xyaxes, 6 ) \ + XVEC( double, zaxis, 3 ) \ + XVEC( double, euler, 3 ) + + +//-------------------------------- mjsPlugin ------------------------------------------------------- + +#define MJSPLUGIN_FIELDS \ + X( mjsElement*, element, 1 ) \ + X( mjString*, name, 1 ) \ + X( mjString*, plugin_name, 1 ) \ + X( mjtByte, active, 1 ) \ + X( mjString*, info, 1 ) + + +//-------------------------------- mjsBody --------------------------------------------------------- + +#define MJSBODY_FIELDS \ + X ( mjsElement*, element, 1 ) \ + X ( mjString*, childclass, 1 ) \ + XVEC( double, pos, 3 ) \ + XVEC( double, quat, 4 ) \ + X ( mjsOrientation, alt, 1 ) \ + X ( double, mass, 1 ) \ + XVEC( double, ipos, 3 ) \ + XVEC( double, iquat, 4 ) \ + XVEC( double, inertia, 3 ) \ + X ( mjsOrientation, ialt, 1 ) \ + XVEC( double, fullinertia, 6 ) \ + X ( mjtByte, mocap, 1 ) \ + X ( double, gravcomp, 1 ) \ + X ( mjtSleepPolicy, sleep, 1 ) \ + X ( mjDoubleVec*, userdata, 1 ) \ + X ( mjtByte, explicitinertial, 1 ) \ + X ( mjsPlugin, plugin, 1 ) \ + X ( mjString*, info, 1 ) + + +//-------------------------------- mjsFrame -------------------------------------------------------- + +#define MJSFRAME_FIELDS \ + X ( mjsElement*, element, 1 ) \ + X ( mjString*, childclass, 1 ) \ + XVEC( double, pos, 3 ) \ + XVEC( double, quat, 4 ) \ + X ( mjsOrientation, alt, 1 ) \ + X ( mjString*, info, 1 ) + + +//-------------------------------- mjsJoint -------------------------------------------------------- + +#define MJSJOINT_FIELDS \ + X ( mjsElement*, element, 1 ) \ + X ( mjtJoint, type, 1 ) \ + XVEC( double, pos, 3 ) \ + XVEC( double, axis, 3 ) \ + X ( double, ref, 1 ) \ + X ( int, align, 1 ) \ + XVEC( double, stiffness, mjNPOLY+1 ) \ + X ( double, springref, 1 ) \ + XVEC( double, springdamper, 2 ) \ + X ( int, limited, 1 ) \ + XVEC( double, range, 2 ) \ + X ( double, margin, 1 ) \ + XVEC( mjtNum, solref_limit, mjNREF ) \ + XVEC( mjtNum, solimp_limit, mjNIMP ) \ + X ( int, actfrclimited, 1 ) \ + XVEC( double, actfrcrange, 2 ) \ + X ( double, armature, 1 ) \ + XVEC( double, damping, mjNPOLY+1 ) \ + X ( double, frictionloss, 1 ) \ + XVEC( mjtNum, solref_friction, mjNREF ) \ + XVEC( mjtNum, solimp_friction, mjNIMP ) \ + X ( int, group, 1 ) \ + X ( mjtByte, actgravcomp, 1 ) \ + X ( mjDoubleVec*, userdata, 1 ) \ + X ( mjString*, info, 1 ) + + +//-------------------------------- mjsGeom --------------------------------------------------------- + +#define MJSGEOM_FIELDS \ + X ( mjsElement*, element, 1 ) \ + X ( mjtGeom, type, 1 ) \ + XVEC( double, pos, 3 ) \ + XVEC( double, quat, 4 ) \ + X ( mjsOrientation, alt, 1 ) \ + XVEC( double, fromto, 6 ) \ + XVEC( double, size, 3 ) \ + X ( int, contype, 1 ) \ + X ( int, conaffinity, 1 ) \ + X ( int, condim, 1 ) \ + X ( int, priority, 1 ) \ + XVEC( double, friction, 3 ) \ + X ( double, solmix, 1 ) \ + XVEC( mjtNum, solref, mjNREF ) \ + XVEC( mjtNum, solimp, mjNIMP ) \ + X ( double, margin, 1 ) \ + X ( double, gap, 1 ) \ + X ( double, mass, 1 ) \ + X ( double, density, 1 ) \ + X ( mjtGeomInertia, typeinertia, 1 ) \ + X ( mjtNum, fluid_ellipsoid, 1 ) \ + XVEC( mjtNum, fluid_coefs, 5 ) \ + X ( mjString*, material, 1 ) \ + XVEC( float, rgba, 4 ) \ + X ( int, group, 1 ) \ + X ( mjString*, hfieldname, 1 ) \ + X ( mjString*, meshname, 1 ) \ + X ( double, fitscale, 1 ) \ + X ( mjDoubleVec*, userdata, 1 ) \ + X ( mjsPlugin, plugin, 1 ) \ + X ( mjString*, info, 1 ) + + +//-------------------------------- mjsSite --------------------------------------------------------- + +#define MJSSITE_FIELDS \ + X ( mjsElement*, element, 1 ) \ + XVEC( double, pos, 3 ) \ + XVEC( double, quat, 4 ) \ + X ( mjsOrientation, alt, 1 ) \ + XVEC( double, fromto, 6 ) \ + XVEC( double, size, 3 ) \ + X ( mjtGeom, type, 1 ) \ + X ( mjString*, material, 1 ) \ + X ( int, group, 1 ) \ + XVEC( float, rgba, 4 ) \ + X ( mjDoubleVec*, userdata, 1 ) \ + X ( mjString*, info, 1 ) + + +//-------------------------------- mjsCamera ------------------------------------------------------- + +#define MJSCAMERA_FIELDS \ + X ( mjsElement*, element, 1 ) \ + XVEC( double, pos, 3 ) \ + XVEC( double, quat, 4 ) \ + X ( mjsOrientation, alt, 1 ) \ + X ( mjtCamLight, mode, 1 ) \ + X ( mjString*, targetbody, 1 ) \ + X ( mjtProjection, proj, 1 ) \ + XVEC( int, resolution, 2 ) \ + X ( int, output, 1 ) \ + X ( double, fovy, 1 ) \ + X ( double, ipd, 1 ) \ + XVEC( float, intrinsic, 4 ) \ + XVEC( float, sensor_size, 2 ) \ + XVEC( float, focal_length, 2 ) \ + XVEC( float, focal_pixel, 2 ) \ + XVEC( float, principal_length, 2 ) \ + XVEC( float, principal_pixel, 2 ) \ + X ( mjDoubleVec*, userdata, 1 ) \ + X ( mjString*, info, 1 ) + + +//-------------------------------- mjsLight -------------------------------------------------------- + +#define MJSLIGHT_FIELDS \ + X ( mjsElement*, element, 1 ) \ + XVEC( double, pos, 3 ) \ + XVEC( double, dir, 3 ) \ + X ( mjtCamLight, mode, 1 ) \ + X ( mjString*, targetbody, 1 ) \ + X ( mjtByte, active, 1 ) \ + X ( mjtLightType, type, 1 ) \ + X ( mjString*, texture, 1 ) \ + X ( mjtByte, castshadow, 1 ) \ + X ( float, bulbradius, 1 ) \ + X ( float, intensity, 1 ) \ + X ( float, range, 1 ) \ + XVEC( float, attenuation, 3 ) \ + X ( float, cutoff, 1 ) \ + X ( float, exponent, 1 ) \ + XVEC( float, ambient, 3 ) \ + XVEC( float, diffuse, 3 ) \ + XVEC( float, specular, 3 ) \ + X ( mjString*, info, 1 ) + + +//-------------------------------- mjsFlex --------------------------------------------------------- + +#define MJSFLEX_FIELDS \ + X ( mjsElement*, element, 1 ) \ + X ( int, contype, 1 ) \ + X ( int, conaffinity, 1 ) \ + X ( int, condim, 1 ) \ + X ( int, priority, 1 ) \ + XVEC( double, friction, 3 ) \ + X ( double, solmix, 1 ) \ + XVEC( mjtNum, solref, mjNREF ) \ + XVEC( mjtNum, solimp, mjNIMP ) \ + X ( double, margin, 1 ) \ + X ( double, gap, 1 ) \ + X ( int, dim, 1 ) \ + X ( double, radius, 1 ) \ + XVEC( double, size, 3 ) \ + X ( mjtByte, internal, 1 ) \ + X ( mjtByte, flatskin, 1 ) \ + X ( int, selfcollide, 1 ) \ + X ( int, passive, 1 ) \ + X ( int, activelayers, 1 ) \ + X ( int, group, 1 ) \ + X ( double, edgestiffness, 1 ) \ + X ( double, edgedamping, 1 ) \ + XVEC( float, rgba, 4 ) \ + X ( mjString*, material, 1 ) \ + X ( double, young, 1 ) \ + X ( double, poisson, 1 ) \ + X ( double, damping, 1 ) \ + X ( double, thickness, 1 ) \ + X ( int, elastic2d, 1 ) \ + XVEC( int, cellcount, 3 ) \ + X ( int, order, 1 ) \ + X ( mjStringVec*, nodebody, 1 ) \ + X ( mjStringVec*, vertbody, 1 ) \ + X ( mjDoubleVec*, node, 1 ) \ + X ( mjDoubleVec*, vert, 1 ) \ + X ( mjIntVec*, elem, 1 ) \ + X ( mjFloatVec*, texcoord, 1 ) \ + X ( mjIntVec*, elemtexcoord, 1 ) \ + X ( mjString*, info, 1 ) + + +//-------------------------------- mjsMesh --------------------------------------------------------- + +#define MJSMESH_FIELDS \ + X ( mjsElement*, element, 1 ) \ + X ( mjString*, content_type, 1 ) \ + X ( mjString*, file, 1 ) \ + XVEC( double, refpos, 3 ) \ + XVEC( double, refquat, 4 ) \ + XVEC( double, scale, 3 ) \ + X ( mjtMeshInertia, inertia, 1 ) \ + X ( mjtByte, smoothnormal, 1 ) \ + X ( mjtByte, needsdf, 1 ) \ + X ( int, maxhullvert, 1 ) \ + X ( mjFloatVec*, uservert, 1 ) \ + X ( mjFloatVec*, usernormal, 1 ) \ + X ( mjFloatVec*, usertexcoord, 1 ) \ + X ( mjIntVec*, userface, 1 ) \ + X ( mjIntVec*, userfacenormal, 1 ) \ + X ( mjIntVec*, userfacetexcoord, 1 ) \ + X ( mjsPlugin, plugin, 1 ) \ + X ( mjString*, material, 1 ) \ + X ( int, octree_maxdepth, 1 ) \ + X ( mjString*, info, 1 ) + + +//-------------------------------- mjsHField ------------------------------------------------------- + +#define MJSHFIELD_FIELDS \ + X ( mjsElement*, element, 1 ) \ + X ( mjString*, content_type, 1 ) \ + X ( mjString*, file, 1 ) \ + XVEC( double, size, 4 ) \ + X ( int, nrow, 1 ) \ + X ( int, ncol, 1 ) \ + X ( mjFloatVec*, userdata, 1 ) \ + X ( mjString*, info, 1 ) + + +//-------------------------------- mjsSkin --------------------------------------------------------- + +#define MJSSKIN_FIELDS \ + X ( mjsElement*, element, 1 ) \ + X ( mjString*, file, 1 ) \ + X ( mjString*, material, 1 ) \ + XVEC( float, rgba, 4 ) \ + X ( float, inflate, 1 ) \ + X ( int, group, 1 ) \ + X ( mjFloatVec*, vert, 1 ) \ + X ( mjFloatVec*, texcoord, 1 ) \ + X ( mjIntVec*, face, 1 ) \ + X ( mjStringVec*, bodyname, 1 ) \ + X ( mjFloatVec*, bindpos, 1 ) \ + X ( mjFloatVec*, bindquat, 1 ) \ + X ( mjIntVecVec*, vertid, 1 ) \ + X ( mjFloatVecVec*, vertweight, 1 ) \ + X ( mjString*, info, 1 ) + + +//-------------------------------- mjsTexture ------------------------------------------------------ + +#define MJSTEXTURE_FIELDS \ + X ( mjsElement*, element, 1 ) \ + X ( mjtTexture, type, 1 ) \ + X ( mjtColorSpace, colorspace, 1 ) \ + X ( int, builtin, 1 ) \ + X ( int, mark, 1 ) \ + XVEC( double, rgb1, 3 ) \ + XVEC( double, rgb2, 3 ) \ + XVEC( double, markrgb, 3 ) \ + X ( double, random, 1 ) \ + X ( int, height, 1 ) \ + X ( int, width, 1 ) \ + X ( int, nchannel, 1 ) \ + X ( mjString*, content_type, 1 ) \ + X ( mjString*, file, 1 ) \ + XVEC( int, gridsize, 2 ) \ + XVEC( char, gridlayout, 12 ) \ + X ( mjStringVec*, cubefiles, 1 ) \ + X ( mjByteVec*, data, 1 ) \ + X ( mjtByte, hflip, 1 ) \ + X ( mjtByte, vflip, 1 ) \ + X ( mjString*, info, 1 ) + + +//-------------------------------- mjsMaterial ----------------------------------------------------- + +#define MJSMATERIAL_FIELDS \ + X ( mjsElement*, element, 1 ) \ + X ( mjStringVec*, textures, 1 ) \ + X ( mjtByte, texuniform, 1 ) \ + XVEC( float, texrepeat, 2 ) \ + X ( float, emission, 1 ) \ + X ( float, specular, 1 ) \ + X ( float, shininess, 1 ) \ + X ( float, reflectance, 1 ) \ + X ( float, metallic, 1 ) \ + X ( float, roughness, 1 ) \ + XVEC( float, rgba, 4 ) \ + X ( mjString*, info, 1 ) + + +//-------------------------------- mjsPair --------------------------------------------------------- + +#define MJSPAIR_FIELDS \ + X ( mjsElement*, element, 1 ) \ + X ( mjString*, geomname1, 1 ) \ + X ( mjString*, geomname2, 1 ) \ + X ( int, condim, 1 ) \ + XVEC( mjtNum, solref, mjNREF ) \ + XVEC( mjtNum, solreffriction, mjNREF ) \ + XVEC( mjtNum, solimp, mjNIMP ) \ + X ( double, margin, 1 ) \ + X ( double, gap, 1 ) \ + XVEC( double, friction, 5 ) \ + X ( mjString*, info, 1 ) + + +//-------------------------------- mjsExclude ------------------------------------------------------ + +#define MJSEXCLUDE_FIELDS \ + X( mjsElement*, element, 1 ) \ + X( mjString*, bodyname1, 1 ) \ + X( mjString*, bodyname2, 1 ) \ + X( mjString*, info, 1 ) + + +//-------------------------------- mjsEquality ----------------------------------------------------- + +#define MJSEQUALITY_FIELDS \ + X ( mjsElement*, element, 1 ) \ + X ( mjtEq, type, 1 ) \ + XVEC( double, data, mjNEQDATA ) \ + X ( mjtByte, active, 1 ) \ + X ( mjString*, name1, 1 ) \ + X ( mjString*, name2, 1 ) \ + X ( mjtObj, objtype, 1 ) \ + XVEC( mjtNum, solref, mjNREF ) \ + XVEC( mjtNum, solimp, mjNIMP ) \ + X ( mjString*, info, 1 ) + + +//-------------------------------- mjsTendon ------------------------------------------------------- + +#define MJSTENDON_FIELDS \ + X ( mjsElement*, element, 1 ) \ + XVEC( double, stiffness, mjNPOLY+1 ) \ + XVEC( double, springlength, 2 ) \ + XVEC( double, damping, mjNPOLY+1 ) \ + X ( double, frictionloss, 1 ) \ + XVEC( mjtNum, solref_friction, mjNREF ) \ + XVEC( mjtNum, solimp_friction, mjNIMP ) \ + X ( double, armature, 1 ) \ + X ( int, limited, 1 ) \ + X ( int, actfrclimited, 1 ) \ + XVEC( double, range, 2 ) \ + XVEC( double, actfrcrange, 2 ) \ + X ( double, margin, 1 ) \ + XVEC( mjtNum, solref_limit, mjNREF ) \ + XVEC( mjtNum, solimp_limit, mjNIMP ) \ + X ( mjString*, material, 1 ) \ + X ( double, width, 1 ) \ + XVEC( float, rgba, 4 ) \ + X ( int, group, 1 ) \ + X ( mjDoubleVec*, userdata, 1 ) \ + X ( mjString*, info, 1 ) + + +//-------------------------------- mjsWrap --------------------------------------------------------- + +#define MJSWRAP_FIELDS \ + X( mjsElement*, element, 1 ) \ + X( mjtWrap, type, 1 ) \ + X( mjString*, info, 1 ) + + +//-------------------------------- mjsActuator ----------------------------------------------------- + +#define MJSACTUATOR_FIELDS \ + X ( mjsElement*, element, 1 ) \ + X ( mjtGain, gaintype, 1 ) \ + XVEC( double, gainprm, mjNGAIN ) \ + X ( mjtBias, biastype, 1 ) \ + XVEC( double, biasprm, mjNGAIN ) \ + X ( mjtDyn, dyntype, 1 ) \ + XVEC( double, dynprm, mjNDYN ) \ + X ( int, actdim, 1 ) \ + X ( mjtByte, actearly, 1 ) \ + X ( mjtTrn, trntype, 1 ) \ + XVEC( double, gear, 6 ) \ + X ( mjString*, target, 1 ) \ + X ( mjString*, refsite, 1 ) \ + X ( mjString*, slidersite, 1 ) \ + X ( double, cranklength, 1 ) \ + XVEC( double, lengthrange, 2 ) \ + X ( double, inheritrange, 1 ) \ + XVEC( double, damping, mjNPOLY+1 ) \ + X ( double, armature, 1 ) \ + X ( int, ctrllimited, 1 ) \ + XVEC( double, ctrlrange, 2 ) \ + X ( int, forcelimited, 1 ) \ + XVEC( double, forcerange, 2 ) \ + X ( int, actlimited, 1 ) \ + XVEC( double, actrange, 2 ) \ + X ( int, group, 1 ) \ + X ( int, nsample, 1 ) \ + X ( int, interp, 1 ) \ + X ( double, delay, 1 ) \ + X ( mjDoubleVec*, userdata, 1 ) \ + X ( mjsPlugin, plugin, 1 ) \ + X ( mjString*, info, 1 ) + + +//-------------------------------- mjsSensor ------------------------------------------------------- + +#define MJSSENSOR_FIELDS \ + X ( mjsElement*, element, 1 ) \ + X ( mjtSensor, type, 1 ) \ + X ( mjtObj, objtype, 1 ) \ + X ( mjString*, objname, 1 ) \ + X ( mjtObj, reftype, 1 ) \ + X ( mjString*, refname, 1 ) \ + XVEC( int, intprm, mjNSENS ) \ + X ( mjtDataType, datatype, 1 ) \ + X ( mjtStage, needstage, 1 ) \ + X ( int, dim, 1 ) \ + X ( double, cutoff, 1 ) \ + X ( double, noise, 1 ) \ + X ( int, nsample, 1 ) \ + X ( int, interp, 1 ) \ + X ( double, delay, 1 ) \ + XVEC( double, interval, 2 ) \ + X ( mjDoubleVec*, userdata, 1 ) \ + X ( mjsPlugin, plugin, 1 ) \ + X ( mjString*, info, 1 ) + + +//-------------------------------- mjsNumeric ------------------------------------------------------ + +#define MJSNUMERIC_FIELDS \ + X( mjsElement*, element, 1 ) \ + X( mjDoubleVec*, data, 1 ) \ + X( int, size, 1 ) \ + X( mjString*, info, 1 ) + + +//-------------------------------- mjsText --------------------------------------------------------- + +#define MJSTEXT_FIELDS \ + X( mjsElement*, element, 1 ) \ + X( mjString*, data, 1 ) \ + X( mjString*, info, 1 ) + + +//-------------------------------- mjsTuple -------------------------------------------------------- + +#define MJSTUPLE_FIELDS \ + X( mjsElement*, element, 1 ) \ + X( mjIntVec*, objtype, 1 ) \ + X( mjStringVec*, objname, 1 ) \ + X( mjDoubleVec*, objprm, 1 ) \ + X( mjString*, info, 1 ) + + +//-------------------------------- mjsKey ---------------------------------------------------------- + +#define MJSKEY_FIELDS \ + X( mjsElement*, element, 1 ) \ + X( double, time, 1 ) \ + X( mjDoubleVec*, qpos, 1 ) \ + X( mjDoubleVec*, qvel, 1 ) \ + X( mjDoubleVec*, act, 1 ) \ + X( mjDoubleVec*, mpos, 1 ) \ + X( mjDoubleVec*, mquat, 1 ) \ + X( mjDoubleVec*, ctrl, 1 ) \ + X( mjString*, info, 1 ) + + +//-------------------------------- mjsDefault ------------------------------------------------------ + +#define MJSDEFAULT_FIELDS \ + X( mjsElement*, element, 1 ) \ + X( mjsJoint*, joint, 1 ) \ + X( mjsGeom*, geom, 1 ) \ + X( mjsSite*, site, 1 ) \ + X( mjsCamera*, camera, 1 ) \ + X( mjsLight*, light, 1 ) \ + X( mjsFlex*, flex, 1 ) \ + X( mjsMesh*, mesh, 1 ) \ + X( mjsMaterial*, material, 1 ) \ + X( mjsPair*, pair, 1 ) \ + X( mjsEquality*, equality, 1 ) \ + X( mjsTendon*, tendon, 1 ) \ + X( mjsActuator*, actuator, 1 ) + + +#endif // MUJOCO_MJSPECMACRO_H_ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a59b55e0..1fda2951 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -108,6 +108,8 @@ mujoco_test(fixture_test) mujoco_test(header_test) +mujoco_test(mjspecmacro_test) + mujoco_test(pipeline_test) add_subdirectory(benchmark) diff --git a/test/mjspecmacro_test.cc b/test/mjspecmacro_test.cc new file mode 100644 index 00000000..d94b42ba --- /dev/null +++ b/test/mjspecmacro_test.cc @@ -0,0 +1,145 @@ +// Copyright 2026 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. + +// Tests for X macros in mjspecmacro.h. + +#include +#include + +#include +#include +#include + +namespace mujoco { +namespace { + +namespace expected { +#define X(type, name, dim) type name; +#define XVEC(type, name, dim) type name[dim]; + +struct mjsElement { MJSELEMENT_FIELDS }; +struct mjsCompiler { MJSCOMPILER_FIELDS }; +struct mjSpec { MJSPEC_FIELDS }; +struct mjsOrientation { MJSORIENTATION_FIELDS }; +struct mjsPlugin { MJSPLUGIN_FIELDS }; +struct mjsBody { MJSBODY_FIELDS }; +struct mjsFrame { MJSFRAME_FIELDS }; +struct mjsJoint { MJSJOINT_FIELDS }; +struct mjsGeom { MJSGEOM_FIELDS }; +struct mjsSite { MJSSITE_FIELDS }; +struct mjsCamera { MJSCAMERA_FIELDS }; +struct mjsLight { MJSLIGHT_FIELDS }; +struct mjsFlex { MJSFLEX_FIELDS }; +struct mjsMesh { MJSMESH_FIELDS }; +struct mjsHField { MJSHFIELD_FIELDS }; +struct mjsSkin { MJSSKIN_FIELDS }; +struct mjsTexture { MJSTEXTURE_FIELDS }; +struct mjsMaterial { MJSMATERIAL_FIELDS }; +struct mjsPair { MJSPAIR_FIELDS }; +struct mjsExclude { MJSEXCLUDE_FIELDS }; +struct mjsEquality { MJSEQUALITY_FIELDS }; +struct mjsTendon { MJSTENDON_FIELDS }; +struct mjsWrap { MJSWRAP_FIELDS }; +struct mjsActuator { MJSACTUATOR_FIELDS }; +struct mjsSensor { MJSSENSOR_FIELDS }; +struct mjsNumeric { MJSNUMERIC_FIELDS }; +struct mjsText { MJSTEXT_FIELDS }; +struct mjsTuple { MJSTUPLE_FIELDS }; +struct mjsKey { MJSKEY_FIELDS }; +struct mjsDefault { MJSDEFAULT_FIELDS }; + +#undef XVEC +#undef X +} // namespace expected + +template +struct ArrayOrScalar { + using type = T[N]; +}; +template +struct ArrayOrScalar { + using type = T; +}; +template +using ArrayOrScalarT = typename ArrayOrScalar::type; + +#define XIMPL(type, name, dim) \ + static_assert( \ + std::is_same_v>, \ + "Type mismatch for " #name); \ + static_assert(offsetof(RealStruct, name) == offsetof(ExpectedStruct, name), \ + "Offset mismatch for " #name); + +#define X(type, name, dim) \ + static_assert(dim == 1, "use XVEC for non-scalar fields"); \ + XIMPL(type, name, dim) + +#define XVEC(type, name, dim) \ + static_assert(dim > 1, "use X for scalar fields"); \ + XIMPL(type, name, dim) + +#define CHECK_STRUCT(Name, Macro) \ + struct Check##Name { \ + using RealStruct = Name; \ + using ExpectedStruct = expected::Name; \ + static_assert(sizeof(RealStruct) == sizeof(ExpectedStruct), \ + "sizeof mismatch for " #Name); \ + static_assert(alignof(RealStruct) == alignof(ExpectedStruct), \ + "alignof mismatch for " #Name); \ + Macro \ + }; + +CHECK_STRUCT(mjsElement, MJSELEMENT_FIELDS) +CHECK_STRUCT(mjsCompiler, MJSCOMPILER_FIELDS) +CHECK_STRUCT(mjSpec, MJSPEC_FIELDS) +CHECK_STRUCT(mjsOrientation, MJSORIENTATION_FIELDS) +CHECK_STRUCT(mjsPlugin, MJSPLUGIN_FIELDS) +CHECK_STRUCT(mjsBody, MJSBODY_FIELDS) +CHECK_STRUCT(mjsFrame, MJSFRAME_FIELDS) +CHECK_STRUCT(mjsJoint, MJSJOINT_FIELDS) +CHECK_STRUCT(mjsGeom, MJSGEOM_FIELDS) +CHECK_STRUCT(mjsSite, MJSSITE_FIELDS) +CHECK_STRUCT(mjsCamera, MJSCAMERA_FIELDS) +CHECK_STRUCT(mjsLight, MJSLIGHT_FIELDS) +CHECK_STRUCT(mjsFlex, MJSFLEX_FIELDS) +CHECK_STRUCT(mjsMesh, MJSMESH_FIELDS) +CHECK_STRUCT(mjsHField, MJSHFIELD_FIELDS) +CHECK_STRUCT(mjsSkin, MJSSKIN_FIELDS) +CHECK_STRUCT(mjsTexture, MJSTEXTURE_FIELDS) +CHECK_STRUCT(mjsMaterial, MJSMATERIAL_FIELDS) +CHECK_STRUCT(mjsPair, MJSPAIR_FIELDS) +CHECK_STRUCT(mjsExclude, MJSEXCLUDE_FIELDS) +CHECK_STRUCT(mjsEquality, MJSEQUALITY_FIELDS) +CHECK_STRUCT(mjsTendon, MJSTENDON_FIELDS) +CHECK_STRUCT(mjsWrap, MJSWRAP_FIELDS) +CHECK_STRUCT(mjsActuator, MJSACTUATOR_FIELDS) +CHECK_STRUCT(mjsSensor, MJSSENSOR_FIELDS) +CHECK_STRUCT(mjsNumeric, MJSNUMERIC_FIELDS) +CHECK_STRUCT(mjsText, MJSTEXT_FIELDS) +CHECK_STRUCT(mjsTuple, MJSTUPLE_FIELDS) +CHECK_STRUCT(mjsKey, MJSKEY_FIELDS) +CHECK_STRUCT(mjsDefault, MJSDEFAULT_FIELDS) + +#undef CHECK_STRUCT +#undef XVEC +#undef X +#undef XIMPL + +TEST(MjspecmacroTest, CompileTimeChecks) { + // Verifies that the test binary compiled successfully and all static_asserts passed. + EXPECT_TRUE(true); +} + +} // namespace +} // namespace mujoco diff --git a/unity/Runtime/Bindings/MjBindings.cs b/unity/Runtime/Bindings/MjBindings.cs index 37aa73f1..0a6ba9b4 100644 --- a/unity/Runtime/Bindings/MjBindings.cs +++ b/unity/Runtime/Bindings/MjBindings.cs @@ -65,6 +65,7 @@ public const int mjMAXMATERIAL = 1000; public const bool THIRD_PARTY_MUJOCO_INCLUDE_MJSAN_H_ = true; public const bool ADDRESS_SANITIZER = true; public const bool THIRD_PARTY_MUJOCO_INCLUDE_MJSPEC_H_ = true; +public const bool THIRD_PARTY_MUJOCO_MJSPECMACRO_H_ = true; public const bool THIRD_PARTY_MUJOCO_INCLUDE_MJTHREAD_H_ = true; public const int mjMAXTHREAD = 128; public const bool THIRD_PARTY_MUJOCO_INCLUDE_MJTYPE_H_ = true;