Clean up X macros and usages for mjOption, mjStatistic, and mjVisual.

* Replace various MJOPTION_ macros with a single MJOPTION_FIELDS.
* Use XVEC macro to denote vector members where there is a mixture of
  vector and scalar members in a given struct type.
* Split up MJVISUAL_FIELDS into separate macros for each substruct.
* Modify engine_print.c and Python bindings struct.h/cc to use the
  new X macros.

PiperOrigin-RevId: 869760513
Change-Id: Idac4fe9aa99f0258e7c79802ca9023a0d4486e2d
This commit is contained in:
Saran Tunyasuvunakool
2026-02-13 09:09:45 -08:00
committed by Copybara-Service
parent 6d8b6028e2
commit 5274e5f720
7 changed files with 521 additions and 359 deletions
+126 -127
View File
@@ -18,143 +18,142 @@
//-------------------------------- mjOption --------------------------------------------------------
// scalar fields of mjOption
#define MJOPTION_FLOATS \
X( mjtNum, timestep ) \
X( mjtNum, impratio ) \
X( mjtNum, tolerance ) \
X( mjtNum, ls_tolerance ) \
X( mjtNum, noslip_tolerance ) \
X( mjtNum, ccd_tolerance ) \
X( mjtNum, sleep_tolerance ) \
X( mjtNum, density ) \
X( mjtNum, viscosity ) \
X( mjtNum, o_margin )
#define MJOPTION_INTS \
X( int, integrator ) \
X( int, cone ) \
X( int, jacobian ) \
X( int, solver ) \
X( int, iterations ) \
X( int, ls_iterations ) \
X( int, noslip_iterations ) \
X( int, ccd_iterations ) \
X( int, disableflags ) \
X( int, enableflags ) \
X( int, disableactuator ) \
X( int, sdf_initpoints ) \
X( int, sdf_iterations )
#define MJOPTION_SCALARS \
MJOPTION_FLOATS \
MJOPTION_INTS
// vector fields of mjOption
#define MJOPTION_VECTORS \
X( gravity, 3 ) \
X( wind, 3 ) \
X( magnetic, 3 ) \
X( o_solref, mjNREF ) \
X( o_solimp, mjNIMP ) \
X( o_friction, 5 )
// fields of mjOption
// XVEC means that a field is a vector (i.e. size > 1)
#define MJOPTION_FIELDS \
X ( mjtNum, timestep, 1 ) \
X ( mjtNum, impratio, 1 ) \
X ( mjtNum, tolerance, 1 ) \
X ( mjtNum, ls_tolerance, 1 ) \
X ( mjtNum, noslip_tolerance, 1 ) \
X ( mjtNum, ccd_tolerance, 1 ) \
X ( mjtNum, sleep_tolerance, 1 ) \
XVEC( mjtNum, gravity, 3 ) \
XVEC( mjtNum, wind, 3 ) \
XVEC( mjtNum, magnetic, 3 ) \
X ( mjtNum, density, 1 ) \
X ( mjtNum, viscosity, 1 ) \
X ( mjtNum, o_margin, 1 ) \
XVEC( mjtNum, o_solref, mjNREF ) \
XVEC( mjtNum, o_solimp, mjNIMP ) \
XVEC( mjtNum, o_friction, 5 ) \
X ( int, integrator, 1 ) \
X ( int, cone, 1 ) \
X ( int, jacobian, 1 ) \
X ( int, solver, 1 ) \
X ( int, iterations, 1 ) \
X ( int, ls_iterations, 1 ) \
X ( int, noslip_iterations, 1 ) \
X ( int, ccd_iterations, 1 ) \
X ( int, disableflags, 1 ) \
X ( int, enableflags, 1 ) \
X ( int, disableactuator, 1 ) \
X ( int, sdf_initpoints, 1 ) \
X ( int, sdf_iterations, 1 )
//-------------------------------- mjStatistic -----------------------------------------------------
// fields of mjStatistic
#define MJSTATISTIC_FIELDS \
X( mjtNum, meaninertia, 1 ) \
X( mjtNum, meanmass, 1 ) \
X( mjtNum, meansize, 1 ) \
X( mjtNum, extent, 1 ) \
X( mjtNum, center, 3 )
#define MJSTATISTIC_FIELDS \
X ( meaninertia, 1 ) \
X ( meanmass, 1 ) \
X ( meansize, 1 ) \
X ( extent, 1 ) \
XVEC( center, 3 )
//-------------------------------- mjVisual --------------------------------------------------------
// fields of mjVisual
#define MJVISUAL_FIELDS \
X( global, int, cameraid, 1 ) \
X( global, int, orthographic, 1 ) \
X( global, float, fovy, 1 ) \
X( global, float, ipd, 1 ) \
X( global, float, azimuth, 1 ) \
X( global, float, elevation, 1 ) \
X( global, float, linewidth, 1 ) \
X( global, float, glow, 1 ) \
X( global, float, realtime, 1 ) \
X( global, int, offwidth, 1 ) \
X( global, int, offheight, 1 ) \
X( global, int, ellipsoidinertia, 1 ) \
X( global, int, bvactive, 1 ) \
X( quality, int, shadowsize, 1 ) \
X( quality, int, offsamples, 1 ) \
X( quality, int, numslices, 1 ) \
X( quality, int, numstacks, 1 ) \
X( quality, int, numquads, 1 ) \
X( headlight, float, ambient, 3 ) \
X( headlight, float, diffuse, 3 ) \
X( headlight, float, specular, 3 ) \
X( headlight, int, active, 1 ) \
X( map, float, stiffness, 1 ) \
X( map, float, stiffnessrot, 1 ) \
X( map, float, force, 1 ) \
X( map, float, torque, 1 ) \
X( map, float, alpha, 1 ) \
X( map, float, fogstart, 1 ) \
X( map, float, fogend, 1 ) \
X( map, float, znear, 1 ) \
X( map, float, zfar, 1 ) \
X( map, float, haze, 1 ) \
X( map, float, shadowclip, 1 ) \
X( map, float, shadowscale, 1 ) \
X( map, float, actuatortendon, 1 ) \
X( scale, float, forcewidth, 1 ) \
X( scale, float, contactwidth, 1 ) \
X( scale, float, contactheight, 1 ) \
X( scale, float, connect, 1 ) \
X( scale, float, com, 1 ) \
X( scale, float, camera, 1 ) \
X( scale, float, light, 1 ) \
X( scale, float, selectpoint, 1 ) \
X( scale, float, jointlength, 1 ) \
X( scale, float, jointwidth, 1 ) \
X( scale, float, actuatorlength, 1 ) \
X( scale, float, actuatorwidth, 1 ) \
X( scale, float, framelength, 1 ) \
X( scale, float, framewidth, 1 ) \
X( scale, float, constraint, 1 ) \
X( scale, float, slidercrank, 1 ) \
X( scale, float, frustum, 1 ) \
X( rgba, float, fog, 4 ) \
X( rgba, float, haze, 4 ) \
X( rgba, float, force, 4 ) \
X( rgba, float, inertia, 4 ) \
X( rgba, float, joint, 4 ) \
X( rgba, float, actuator, 4 ) \
X( rgba, float, actuatornegative, 4 ) \
X( rgba, float, actuatorpositive, 4 ) \
X( rgba, float, com, 4 ) \
X( rgba, float, camera, 4 ) \
X( rgba, float, light, 4 ) \
X( rgba, float, selectpoint, 4 ) \
X( rgba, float, connect, 4 ) \
X( rgba, float, contactpoint, 4 ) \
X( rgba, float, contactforce, 4 ) \
X( rgba, float, contactfriction, 4 ) \
X( rgba, float, contacttorque, 4 ) \
X( rgba, float, contactgap, 4 ) \
X( rgba, float, rangefinder, 4 ) \
X( rgba, float, constraint, 4 ) \
X( rgba, float, slidercrank, 4 ) \
X( rgba, float, crankbroken, 4 ) \
X( rgba, float, frustum, 4 ) \
X( rgba, float, bv, 4 ) \
X( rgba, float, bvactive, 4 )
#define MJVISUAL_GLOBAL_FIELDS \
X( int, cameraid ) \
X( int, orthographic ) \
X( float, fovy ) \
X( float, ipd ) \
X( float, azimuth ) \
X( float, elevation ) \
X( float, linewidth ) \
X( float, glow ) \
X( float, realtime ) \
X( int, offwidth ) \
X( int, offheight ) \
X( int, ellipsoidinertia ) \
X( int, bvactive )
#define MJVISUAL_QUALITY_FIELDS \
X( shadowsize ) \
X( offsamples ) \
X( numslices ) \
X( numstacks ) \
X( numquads )
#define MJVISUAL_HEADLIGHT_FIELDS \
XVEC( float, ambient, 3 ) \
XVEC( float, diffuse, 3 ) \
XVEC( float, specular, 3 ) \
X ( int, active, 1 )
#define MJVISUAL_MAP_FIELDS \
X( stiffness ) \
X( stiffnessrot ) \
X( force ) \
X( torque ) \
X( alpha ) \
X( fogstart ) \
X( fogend ) \
X( znear ) \
X( zfar ) \
X( haze ) \
X( shadowclip ) \
X( shadowscale ) \
X( actuatortendon )
#define MJVISUAL_SCALE_FIELDS \
X( forcewidth ) \
X( contactwidth ) \
X( contactheight ) \
X( connect ) \
X( com ) \
X( camera ) \
X( light ) \
X( selectpoint ) \
X( jointlength ) \
X( jointwidth ) \
X( actuatorlength ) \
X( actuatorwidth ) \
X( framelength ) \
X( framewidth ) \
X( constraint ) \
X( slidercrank ) \
X( frustum )
#define MJVISUAL_RGBA_FIELDS \
X( fog ) \
X( haze ) \
X( force ) \
X( inertia ) \
X( joint ) \
X( actuator ) \
X( actuatornegative ) \
X( actuatorpositive ) \
X( com ) \
X( camera ) \
X( light ) \
X( selectpoint ) \
X( connect ) \
X( contactpoint ) \
X( contactforce ) \
X( contactfriction ) \
X( contacttorque ) \
X( contactgap ) \
X( rangefinder ) \
X( constraint ) \
X( slidercrank ) \
X( crankbroken ) \
X( frustum ) \
X( bv ) \
X( bvactive )
//-------------------------------- mjModel ---------------------------------------------------------
+51 -118
View File
@@ -103,50 +103,53 @@ PYBIND11_MODULE(_structs, m) {
});
mjOption.def_property_readonly_static("_all_fields", [](py::object) {
std::vector<std::string> fields;
#define X(dtype, name) fields.push_back(#name);
MJOPTION_FLOATS
#undef X
#define X(name, dim0) fields.push_back(#name);
MJOPTION_VECTORS
#undef X
#define X(dtype, name) fields.push_back(#name);
MJOPTION_INTS
#define X(type, var, dim) fields.push_back(#var);
#define XVEC X
MJOPTION_FIELDS
#undef XVEC
#undef X
return py::tuple(py::cast(fields));
});
DefineStructFunctions(mjOption);
#define X(type, var) \
#define X(type, var, dim) \
mjOption.def_property( \
#var, [](const MjOptionWrapper& c) { return c.get()->var; }, \
[](MjOptionWrapper& c, type rhs) { c.get()->var = rhs; });
MJOPTION_SCALARS
#undef X
#define X(var, dim) DefinePyArray(mjOption, #var, &MjOptionWrapper::var);
MJOPTION_VECTORS
#define XVEC(type, var, dim) \
DefinePyArray(mjOption, #var, &MjOptionWrapper::var);
MJOPTION_FIELDS
#undef XVEC
#undef X
mjOption.def_property_readonly_static("_float_fields", [](py::object) {
std::vector<std::string> field_names;
#define X(type, var) field_names.push_back(#var);
MJOPTION_FLOATS
#define X(type, var, dim) \
if constexpr (std::is_floating_point_v<type>) field_names.push_back(#var);
#define XVEC(type, var, dim)
MJOPTION_FIELDS
#undef XVEC
#undef X
return py::tuple(py::cast(field_names));
});
mjOption.def_property_readonly_static("_int_fields", [](py::object) {
std::vector<std::string> field_names;
#define X(type, var) field_names.push_back(#var);
MJOPTION_INTS
#define X(type, var, dim) \
if constexpr (std::is_integral_v<type>) field_names.push_back(#var);
#define XVEC(type, var, dim)
MJOPTION_FIELDS
#undef XVEC
#undef X
return py::tuple(py::cast(field_names));
});
mjOption.def_property_readonly_static("_floatarray_fields", [](py::object) {
std::vector<std::string> field_names;
#define X(var, sz) field_names.push_back(#var);
MJOPTION_VECTORS
#define X(type, var, dim)
#define XVEC(type, var, dim) field_names.push_back(#var);
MJOPTION_FIELDS
#undef XVEC
#undef X
return py::tuple(py::cast(field_names));
});
@@ -192,20 +195,9 @@ PYBIND11_MODULE(_structs, m) {
return raw::MjVisualGlobal(other);
});
DefineStructFunctions(mjVisualGlobal);
#define X(var) mjVisualGlobal.def_readwrite(#var, &raw::MjVisualGlobal::var)
X(cameraid);
X(orthographic);
X(fovy);
X(ipd);
X(azimuth);
X(elevation);
X(linewidth);
X(glow);
X(realtime);
X(offwidth);
X(offheight);
X(ellipsoidinertia);
X(bvactive);
#define X(type, var) \
mjVisualGlobal.def_readwrite(#var, &raw::MjVisualGlobal::var);
MJVISUAL_GLOBAL_FIELDS
#undef X
py::class_<raw::MjVisualQuality> mjVisualQuality(mjVisual, "Quality");
@@ -217,12 +209,8 @@ PYBIND11_MODULE(_structs, m) {
return raw::MjVisualQuality(other);
});
DefineStructFunctions(mjVisualQuality);
#define X(var) mjVisualQuality.def_readwrite(#var, &raw::MjVisualQuality::var)
X(shadowsize);
X(offsamples);
X(numslices);
X(numstacks);
X(numquads);
#define X(var) mjVisualQuality.def_readwrite(#var, &raw::MjVisualQuality::var);
MJVISUAL_QUALITY_FIELDS
#undef X
py::class_<MjVisualHeadlightWrapper> mjVisualHeadlight(mjVisual, "Headlight");
@@ -234,18 +222,17 @@ PYBIND11_MODULE(_structs, m) {
return MjVisualHeadlightWrapper(other);
});
DefineStructFunctions(mjVisualHeadlight);
#define X(var) \
DefinePyArray(mjVisualHeadlight, #var, &MjVisualHeadlightWrapper::var)
X(ambient);
X(diffuse);
X(specular);
#undef X
mjVisualHeadlight.def_property(
"active",
[](const MjVisualHeadlightWrapper& c) { return c.get()->active; },
[](MjVisualHeadlightWrapper& c, int rhs) {
return c.get()->active = rhs;
#define X(type, var, dim) \
mjVisualHeadlight.def_property( \
#var, [](const MjVisualHeadlightWrapper& c) { return c.get()->var; }, \
[](MjVisualHeadlightWrapper& c, type rhs) { \
return c.get()->var = rhs; \
});
#define XVEC(type, var, dim) \
DefinePyArray(mjVisualHeadlight, #var, &MjVisualHeadlightWrapper::var);
MJVISUAL_HEADLIGHT_FIELDS
#undef XVEC
#undef X
py::class_<raw::MjVisualMap> mjVisualMap(mjVisual, "Map");
mjVisualMap.def("__copy__", [](const raw::MjVisualMap& other) {
@@ -255,20 +242,8 @@ PYBIND11_MODULE(_structs, m) {
return raw::MjVisualMap(other);
});
DefineStructFunctions(mjVisualMap);
#define X(var) mjVisualMap.def_readwrite(#var, &raw::MjVisualMap::var)
X(stiffness);
X(stiffnessrot);
X(force);
X(torque);
X(alpha);
X(fogstart);
X(fogend);
X(znear);
X(zfar);
X(haze);
X(shadowclip);
X(shadowscale);
X(actuatortendon);
#define X(var) mjVisualMap.def_readwrite(#var, &raw::MjVisualMap::var);
MJVISUAL_MAP_FIELDS
#undef X
py::class_<raw::MjVisualScale> mjVisualScale(mjVisual, "Scale");
@@ -280,24 +255,8 @@ PYBIND11_MODULE(_structs, m) {
return raw::MjVisualScale(other);
});
DefineStructFunctions(mjVisualScale);
#define X(var) mjVisualScale.def_readwrite(#var, &raw::MjVisualScale::var)
X(forcewidth);
X(contactwidth);
X(contactheight);
X(connect);
X(com);
X(camera);
X(light);
X(selectpoint);
X(jointlength);
X(jointwidth);
X(actuatorlength);
X(actuatorwidth);
X(framelength);
X(framewidth);
X(constraint);
X(slidercrank);
X(frustum);
#define X(var) mjVisualScale.def_readwrite(#var, &raw::MjVisualScale::var);
MJVISUAL_SCALE_FIELDS
#undef X
py::class_<MjVisualRgbaWrapper> mjVisualRgba(mjVisual, "Rgba");
@@ -309,32 +268,8 @@ PYBIND11_MODULE(_structs, m) {
return MjVisualRgbaWrapper(other);
});
DefineStructFunctions(mjVisualRgba);
#define X(var) DefinePyArray(mjVisualRgba, #var, &MjVisualRgbaWrapper::var)
X(fog);
X(haze);
X(force);
X(inertia);
X(joint);
X(actuator);
X(actuatornegative);
X(actuatorpositive);
X(com);
X(camera);
X(light);
X(selectpoint);
X(connect);
X(contactpoint);
X(contactforce);
X(contactfriction);
X(contacttorque);
X(contactgap);
X(rangefinder);
X(constraint);
X(slidercrank);
X(crankbroken);
X(frustum);
X(bv);
X(bvactive);
#define X(var) DefinePyArray(mjVisualRgba, #var, &MjVisualRgbaWrapper::var);
MJVISUAL_RGBA_FIELDS
#undef X
#define X(var) \
@@ -927,20 +862,18 @@ This is useful for example when the MJB is not available as a file on disk.)"));
});
DefineStructFunctions(mjStatistic);
#define X(var) \
#define X(var, dim) \
mjStatistic.def_property( \
#var, [](const MjStatisticWrapper& c) { return c.get()->var; }, \
[](MjStatisticWrapper& c, decltype(raw::MjStatistic::var) rhs) { \
c.get()->var = rhs; \
})
X(meaninertia);
X(meanmass);
X(meansize);
X(extent);
#undef X
});
#define XVEC(var, dim) \
DefinePyArray(mjStatistic, #var, &MjStatisticWrapper::var);
#define X(var) DefinePyArray(mjStatistic, #var, &MjStatisticWrapper::var)
X(center);
MJSTATISTIC_FIELDS
#undef XVEC
#undef X
// ==================== MJLROPT ==============================================
+16 -36
View File
@@ -217,11 +217,13 @@ class MjWrapper<raw::MjOption> : public WrapperBase<raw::MjOption> {
MjWrapper(raw::MjOption* ptr, pybind11::handle owner);
~MjWrapper() = default;
#define X(var, dim) \
#define X(type, var, dim)
#define XVEC(type, var, dim) \
py_array_or_tuple_t< \
std::remove_all_extents_t<decltype(raw::MjOption::var)>> \
var;
MJOPTION_VECTORS
MJOPTION_FIELDS
#undef XVEC
#undef X
};
@@ -241,13 +243,13 @@ class MjWrapper<raw::MjVisualHeadlight>
MjWrapper(raw::MjVisualHeadlight* ptr, pybind11::handle owner);
~MjWrapper() = default;
#define X(var) \
#define X(type, var, dim)
#define XVEC(type, var, dim) \
py_array_or_tuple_t< \
std::remove_all_extents_t<decltype(raw::MjVisualHeadlight::var)>> \
var
X(ambient);
X(diffuse);
X(specular);
var;
MJVISUAL_HEADLIGHT_FIELDS
#undef XVEC
#undef X
};
@@ -268,32 +270,8 @@ class MjWrapper<raw::MjVisualRgba> : public WrapperBase<raw::MjVisualRgba> {
#define X(var) \
py_array_or_tuple_t< \
std::remove_all_extents_t<decltype(raw::MjVisualRgba::var)>> \
var
X(fog);
X(haze);
X(force);
X(inertia);
X(joint);
X(actuator);
X(actuatornegative);
X(actuatorpositive);
X(com);
X(camera);
X(light);
X(selectpoint);
X(connect);
X(contactpoint);
X(contactforce);
X(contactfriction);
X(contacttorque);
X(contactgap);
X(rangefinder);
X(constraint);
X(slidercrank);
X(crankbroken);
X(frustum);
X(bv);
X(bvactive);
var;
MJVISUAL_RGBA_FIELDS
#undef X
};
@@ -330,11 +308,13 @@ class MjWrapper<raw::MjStatistic> : public WrapperBase<raw::MjStatistic> {
MjWrapper(raw::MjStatistic* ptr, pybind11::handle owner);
~MjWrapper() = default;
#define X(var) \
#define X(var, dim)
#define XVEC(var, dim) \
py_array_or_tuple_t< \
std::remove_all_extents_t<decltype(raw::MjStatistic::var)>> \
var
X(center);
var;
MJSTATISTIC_FIELDS
#undef XVEC
#undef X
};
+7 -3
View File
@@ -83,16 +83,20 @@ inline std::size_t NConMax(const mjData* d) {
} // namespace
// ==================== MJOPTION ===============================================
#define X(var, dim) , var(InitPyArray(std::array{dim}, ptr_->var, owner_))
#define X(type, var, dim)
#define XVEC(type, var, dim) \
, var(InitPyArray(std::array{dim}, ptr_->var, owner_))
MjOptionWrapper::MjWrapper()
: WrapperBase([]() {
raw::MjOption* const opt = new raw::MjOption;
mj_defaultOption(opt);
return opt;
}()) MJOPTION_VECTORS {}
}()) MJOPTION_FIELDS {}
MjOptionWrapper::MjWrapper(raw::MjOption* ptr, py::handle owner)
: WrapperBase(ptr, owner) MJOPTION_VECTORS {}
: WrapperBase(ptr, owner) MJOPTION_FIELDS {}
#undef XVEC
#undef X
MjOptionWrapper::MjWrapper(const MjOptionWrapper& other) : MjOptionWrapper() {
+112 -36
View File
@@ -24,6 +24,7 @@
#include <mujoco/mjmacro.h>
#include <mujoco/mjmodel.h>
#include <mujoco/mjsan.h> // IWYU pragma: keep
#include <mujoco/mjtnum.h>
#include <mujoco/mjxmacro.h>
#include "engine/engine_core_constraint.h"
#include "engine/engine_core_util.h"
@@ -600,33 +601,111 @@ void mj_printFormattedModel(const mjModel* m, const char* filename, const char*
#undef X
fprintf(fp, "\n");
// scalar options
// options
fprintf(fp, "OPTION\n");
#define X( type, name ) \
fprintf(fp, NAME_FORMAT, " " #name); \
fprintf(fp, float_format, m->opt.name); \
#define X(type, name, sz) \
fprintf(fp, NAME_FORMAT, " " #name); \
{ \
const char* format = \
_Generic(m->opt.name, mjtNum: float_format, int: INT_FORMAT); \
fprintf(fp, format, m->opt.name); \
} \
fprintf(fp, "\n");
#define XVEC(type, name, sz) \
fprintf(fp, NAME_FORMAT, " " #name); \
{ \
const char* format = \
_Generic(m->opt.name[0], mjtNum: float_format, int: INT_FORMAT); \
for (int i = 0; i < sz; i++) { \
fprintf(fp, format, m->opt.name[i]); \
} \
} \
fprintf(fp, "\n");
MJOPTION_FLOATS
MJOPTION_FIELDS
#undef XVEC
#undef X
#define X( type, name ) \
fprintf(fp, NAME_FORMAT, " " #name); \
fprintf(fp, INT_FORMAT "\n", m->opt.name);
MJOPTION_INTS
#undef X
// vector options
#define X( name, sz ) \
fprintf(fp, NAME_FORMAT, " " #name); \
for (int i=0; i < sz; i++) { \
fprintf(fp, float_format, m->opt.name[i]); \
fprintf(fp, " "); \
} \
fprintf(fp, "\n");
MJOPTION_VECTORS
// visual
fprintf(fp, "VISUAL\n");
fprintf(fp, " GLOBAL\n");
#define X(type, name) \
fprintf(fp, NAME_FORMAT, " " #name); \
{ \
const char* format = \
_Generic(m->vis.global.name, float: float_format, int: INT_FORMAT); \
fprintf(fp, format, m->vis.global.name); \
} \
fprintf(fp, "\n");
MJVISUAL_GLOBAL_FIELDS
#undef X
fprintf(fp, " QUALITY\n");
#define X(name) \
fprintf(fp, NAME_FORMAT, " " #name); \
fprintf(fp, INT_FORMAT, m->vis.quality.name); \
fprintf(fp, "\n");
MJVISUAL_QUALITY_FIELDS
#undef X
fprintf(fp, " HEADLIGHT\n");
#define X(type, name, sz) \
fprintf(fp, NAME_FORMAT, " " #name); \
{ \
const char* format = \
_Generic(m->vis.headlight.name, float: float_format, int: INT_FORMAT); \
fprintf(fp, format, m->vis.headlight.name); \
} \
fprintf(fp, "\n");
#define XVEC(type, name, sz) \
fprintf(fp, NAME_FORMAT, " " #name); \
{ \
const char* format = _Generic( \
m->vis.headlight.name[0], \
float: float_format, \
int: INT_FORMAT); \
for (int i = 0; i < sz; i++) { \
fprintf(fp, format, m->vis.headlight.name[i]); \
} \
} \
fprintf(fp, "\n");
MJVISUAL_HEADLIGHT_FIELDS
#undef XVEC
#undef X
fprintf(fp, " MAP\n");
#define X(name) \
fprintf(fp, NAME_FORMAT, " " #name); \
fprintf(fp, float_format, m->vis.map.name); \
fprintf(fp, "\n");
MJVISUAL_MAP_FIELDS
#undef X
fprintf(fp, " SCALE\n");
#define X(name) \
fprintf(fp, NAME_FORMAT, " " #name); \
fprintf(fp, float_format, m->vis.scale.name); \
fprintf(fp, "\n");
MJVISUAL_SCALE_FIELDS
#undef X
fprintf(fp, " RGBA\n");
#define X(name) \
fprintf(fp, NAME_FORMAT, " " #name); \
{ \
for (int i = 0; i < 4; i++) { \
fprintf(fp, float_format, m->vis.rgba.name[i]); \
} \
} \
fprintf(fp, "\n");
MJVISUAL_RGBA_FIELDS
#undef X
fprintf(fp, "\n");
@@ -637,23 +716,20 @@ void mj_printFormattedModel(const mjModel* m, const char* filename, const char*
// statistics
fprintf(fp, "STATISTIC\n");
fprintf(fp, NAME_FORMAT, " meaninertia");
fprintf(fp, float_format, m->stat.meaninertia);
#define X(name, sz) \
fprintf(fp, NAME_FORMAT, " " #name); \
{ \
for (int i = 0; i < sz; i++) { \
fprintf(fp, float_format, ((mjtNum*)(&m->stat.name))[i]); \
} \
} \
fprintf(fp, "\n");
fprintf(fp, NAME_FORMAT, " meanmass");
fprintf(fp, float_format, m->stat.meanmass);
#define XVEC X
MJSTATISTIC_FIELDS
#undef XVEC
#undef X
fprintf(fp, "\n");
fprintf(fp, NAME_FORMAT, " meansize");
fprintf(fp, float_format, m->stat.meansize);
fprintf(fp, "\n");
fprintf(fp, NAME_FORMAT, " extent");
fprintf(fp, float_format, m->stat.extent);
fprintf(fp, "\n");
fprintf(fp, NAME_FORMAT, " center");
fprintf(fp, float_format, m->stat.center[0]);
fprintf(fp, float_format, m->stat.center[1]);
fprintf(fp, float_format, m->stat.center[2]);
fprintf(fp, "\n\n");
// qpos0
fprintf(fp, NAME_FORMAT, "qpos0");
+5 -8
View File
@@ -274,19 +274,16 @@ mjtNum CompareModel(const mjModel* m1, const mjModel* m2,
MJMODEL_POINTERS
#undef X
// compare scalars in mjOption
#define X(type, name) \
// compare fields in mjOption
#define X(type, name, n) \
dif = Compare(m1->opt.name, m2->opt.name); \
if (dif > maxdif) {maxdif = dif; field = #name;}
MJOPTION_SCALARS
#undef X
// compare arrays in mjOption
#define X(name, n) \
#define XVEC(type, name, n) \
for (int c=0; c < n; c++) { \
dif = Compare(m1->opt.name[c], m2->opt.name[c]); \
if (dif > maxdif) {maxdif = dif; field = #name;} }
MJOPTION_VECTORS
MJOPTION_FIELDS
#undef XVEC
#undef X
// Return largest difference and field name
+204 -31
View File
@@ -24,6 +24,7 @@
#include <mujoco/mjdata.h>
#include <mujoco/mjmodel.h>
#include <mujoco/mjrender.h>
#include <mujoco/mjtnum.h>
#include <mujoco/mjui.h>
#include <mujoco/mjvisualize.h>
#include <mujoco/mjxmacro.h>
@@ -110,86 +111,258 @@ TEST_F(HeaderTest, EnumsAreInts) {
EXPECT_EQ(sizeof(mjtStereo), sizeof(int));
}
TEST_F(HeaderTest, MjOptionFloatsOrdered) {
TEST_F(HeaderTest, MjOptionFields) {
mjOption o;
std::vector<std::pair<const void*, const char*>> floats;
std::vector<std::pair<const void*, const char*>> fields;
#define X(type, name) \
floats.push_back({static_cast<const void*>(&o.name), #name});
MJOPTION_FLOATS
// check that all X macros have the correct type and dim
#define XIMPL(type, name, dim) \
static_assert( \
std::is_same_v<decltype(mjOption::name), ArrayOrScalarT<type, dim>>, \
"incorrect type for mjOption::" #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)
MJOPTION_FIELDS
#undef XVEC
#undef X
#undef XIMPL
// check that the ordering of X macros agrees with the struct fields
#define X(type, name, dim) \
fields.push_back({static_cast<const void*>(&o.name), #name});
#define XVEC X
MJOPTION_FIELDS
#undef XVEC
#undef X
CheckAddressOrdering(floats, "MJOPTION_FLOATS");
}
CheckAddressOrdering(fields, "MJOPTION_FIELDS");
TEST_F(HeaderTest, MjOptionVectorsOrdered) {
mjOption o;
std::vector<std::pair<const void*, const char*>> vectors;
#define X(name, dim) \
vectors.push_back({static_cast<const void*>(&o.name), #name});
MJOPTION_VECTORS
// check that MJOPTION_FIELDS is a complete list of struct fields
struct ExpectedMjOption {
#define XVEC(type, name, dim) type name[dim];
#define X XVEC
MJOPTION_FIELDS
#undef X
CheckAddressOrdering(vectors, "MJOPTION_VECTORS");
#undef XVEC
};
static_assert(sizeof(mjOption) == sizeof(ExpectedMjOption));
static_assert(alignof(mjOption) == alignof(ExpectedMjOption));
}
TEST_F(HeaderTest, MjStatisticFields) {
mjStatistic s;
std::vector<std::pair<const void*, const char*>> fields;
// All fields in mjStatistic are expected to be of type mjtNum.
using ScalarType = mjtNum;
// check that all X macros have the correct type and dim
#define X(type, name, dim) \
static_assert( \
std::is_same_v<decltype(mjStatistic::name), ArrayOrScalarT<type, dim>>, \
"incorrect type for mjStatistic::" #name); \
#define XIMPL(name, dim) \
static_assert(std::is_same_v<decltype(mjStatistic::name), \
ArrayOrScalarT<ScalarType, dim>>, \
"incorrect type for mjStatistic::" #name);
#define X(name, dim) \
static_assert(dim == 1, "use XVEC for non-scalar fields"); \
XIMPL(name, dim)
#define XVEC(name, dim) \
static_assert(dim > 1, "use X for scalar fields"); \
XIMPL(name, dim)
MJSTATISTIC_FIELDS
#undef XVEC
#undef X
#undef XIMPL
// check that the ordering of X macros agrees with the struct fields
#define X(type, name, dim) \
#define X(name, dim) \
fields.push_back({static_cast<const void*>(&s.name), #name});
#define XVEC X
MJSTATISTIC_FIELDS
#undef XVEC
#undef X
CheckAddressOrdering(fields, "MJSTATISTIC_FIELDS");
// check that MJSTATISTIC_FIELDS is a complete list of struct fields
struct ExpectedMjStatistic {
#define X(type, name, dim) type name[dim];
#define XVEC(name, dim) ScalarType name[dim];
#define X XVEC
MJSTATISTIC_FIELDS;
#undef XVEC
#undef X
};
static_assert(sizeof(mjStatistic) == sizeof(ExpectedMjStatistic));
static_assert(alignof(mjStatistic) == alignof(ExpectedMjStatistic));
}
TEST_F(HeaderTest, MjVisualFields) {
mjVisual v;
std::vector<std::pair<const void*, const char*>> fields;
// All member fields in quality, map, scale, and rgba have the same type.
using QualityMemberType = int;
using MapMemberType = float;
using ScaleMemberType = float;
using RgbaMemberType = float[4];
// check that all X macros have the correct type and dim
#define X(substruct, type, name, dim) \
#define X(type, name) \
static_assert( \
std::is_same_v<decltype(v.global.name), type>, \
"incorrect type for mjVisual::global::" #name);
MJVISUAL_GLOBAL_FIELDS
#undef X
#define X(name) \
static_assert( \
std::is_same_v<decltype(v.quality.name), QualityMemberType>, \
"incorrect type for mjVisual::quality::" #name);
MJVISUAL_QUALITY_FIELDS
#undef X
#define XIMPL(type, name, dim) \
static_assert( \
std::is_same_v<decltype(v.substruct.name), ArrayOrScalarT<type, dim>>, \
"incorrect type for mjVisual::" #substruct "::" #name); \
MJVISUAL_FIELDS
std::is_same_v<decltype(v.headlight.name), ArrayOrScalarT<type, dim>>, \
"incorrect type for mjVisual::headlight::" #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)
MJVISUAL_HEADLIGHT_FIELDS
#undef X
#undef XVEC
#define X(name) \
static_assert( \
std::is_same_v<decltype(v.map.name), MapMemberType>, \
"incorrect type for mjVisual::map::" #name);
MJVISUAL_MAP_FIELDS
#undef X
#define X(name) \
static_assert( \
std::is_same_v<decltype(v.scale.name), ScaleMemberType>, \
"incorrect type for mjVisual::scale::" #name);
MJVISUAL_SCALE_FIELDS
#undef X
#define X(name) \
static_assert( \
std::is_same_v<decltype(v.rgba.name), RgbaMemberType>, \
"incorrect type for mjVisual::rgba::" #name);
MJVISUAL_RGBA_FIELDS
#undef X
// check that the ordering of X macros agrees with the struct fields
#define X(substruct, type, name, dim) \
fields.push_back({static_cast<const void*>(&v.substruct.name), #name});
MJVISUAL_FIELDS
#define X(type, name) \
fields.push_back({static_cast<const void*>(&v.global.name), #name});
MJVISUAL_GLOBAL_FIELDS
#undef X
#define X(name) \
fields.push_back({static_cast<const void*>(&v.quality.name), #name});
MJVISUAL_QUALITY_FIELDS
#undef X
#define X(type, name, dim) \
fields.push_back({static_cast<const void*>(&v.headlight.name), #name});
#define XVEC X
MJVISUAL_HEADLIGHT_FIELDS
#undef XVEC
#undef X
#define X(name) \
fields.push_back({static_cast<const void*>(&v.map.name), #name});
MJVISUAL_MAP_FIELDS
#undef X
#define X(name) \
fields.push_back({static_cast<const void*>(&v.scale.name), #name});
MJVISUAL_SCALE_FIELDS
#undef X
#define X(name) \
fields.push_back({static_cast<const void*>(&v.rgba.name), #name});
MJVISUAL_RGBA_FIELDS
#undef X
CheckAddressOrdering(fields, "MJVISUAL_FIELDS");
// check that MJVISUAL_FIELDS is a complete list of fields
struct ExpectedMjVisual {
#define X(substruct, type, name, dim) type substruct##_##name[dim];
MJVISUAL_FIELDS;
struct {
#define X(type, name) type name;
MJVISUAL_GLOBAL_FIELDS;
#undef X
} global;
struct {
#define X(name) QualityMemberType name;
MJVISUAL_QUALITY_FIELDS;
#undef X
} quality;
struct {
#define X(type, name, dim) type name[dim];
#define XVEC X
MJVISUAL_HEADLIGHT_FIELDS;
#undef XVEC
#undef X
} headlight;
struct {
#define X(name) MapMemberType name;
MJVISUAL_MAP_FIELDS;
#undef X
} map;
struct {
#define X(name) ScaleMemberType name;
MJVISUAL_SCALE_FIELDS;
#undef X
} scale;
struct {
#define X(name) RgbaMemberType name;
MJVISUAL_RGBA_FIELDS;
#undef X
} rgba;
};
static_assert(sizeof(mjVisual) == sizeof(ExpectedMjVisual));
static_assert(alignof(mjVisual) == alignof(ExpectedMjVisual));
static_assert(sizeof(mjVisual::global) == sizeof(ExpectedMjVisual::global));
static_assert(alignof(mjVisual::global) == alignof(ExpectedMjVisual::global));
static_assert(sizeof(mjVisual::quality) == sizeof(ExpectedMjVisual::quality));
static_assert(alignof(mjVisual::quality) ==
alignof(ExpectedMjVisual::quality));
static_assert(sizeof(mjVisual::headlight) ==
sizeof(ExpectedMjVisual::headlight));
static_assert(alignof(mjVisual::headlight) ==
alignof(ExpectedMjVisual::headlight));
static_assert(sizeof(mjVisual::map) == sizeof(ExpectedMjVisual::map));
static_assert(alignof(mjVisual::map) == alignof(ExpectedMjVisual::map));
static_assert(sizeof(mjVisual::scale) == sizeof(ExpectedMjVisual::scale));
static_assert(alignof(mjVisual::scale) == alignof(ExpectedMjVisual::scale));
static_assert(sizeof(mjVisual::rgba) == sizeof(ExpectedMjVisual::rgba));
static_assert(alignof(mjVisual::rgba) == alignof(ExpectedMjVisual::rgba));
}
TEST_F(HeaderTest, MjModelIntsOrdered) {