diff --git a/doc/APIreference/functions.rst b/doc/APIreference/functions.rst index 4406e5c5..4bbf42e2 100644 --- a/doc/APIreference/functions.rst +++ b/doc/APIreference/functions.rst @@ -2163,6 +2163,15 @@ Update entire scene given model state. Update entire scene from a scene state, return the number of new mjWARN_VGEOMFULL warnings. +.. _mjv_copyModel: + +`mjv_copyModel <#mjv_copyModel>`__ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. mujoco-include:: mjv_copyModel + +Copy mjModel, skip large arrays not required for abstract visualization. + .. _mjv_defaultSceneState: `mjv_defaultSceneState <#mjv_defaultSceneState>`__ diff --git a/doc/includes/references.h b/doc/includes/references.h index fe917ab1..adac0d47 100644 --- a/doc/includes/references.h +++ b/doc/includes/references.h @@ -3347,6 +3347,7 @@ void mjv_updateScene(const mjModel* m, mjData* d, const mjvOption* opt, int mjv_updateSceneFromState(const mjvSceneState* scnstate, const mjvOption* opt, const mjvPerturb* pert, mjvCamera* cam, int catmask, mjvScene* scn); +void mjv_copyModel(mjModel* dest, const mjModel* src); void mjv_defaultSceneState(mjvSceneState* scnstate); void mjv_makeSceneState(const mjModel* m, const mjData* d, mjvSceneState* scnstate, int maxgeom); diff --git a/include/mujoco/mjxmacro.h b/include/mujoco/mjxmacro.h index 05d51606..95a06b2c 100644 --- a/include/mujoco/mjxmacro.h +++ b/include/mujoco/mjxmacro.h @@ -378,13 +378,13 @@ X ( mjtNum, mesh_scale, nmesh, 3 ) \ X ( mjtNum, mesh_pos, nmesh, 3 ) \ X ( mjtNum, mesh_quat, nmesh, 4 ) \ - X ( float, mesh_vert, nmeshvert, 3 ) \ - X ( float, mesh_normal, nmeshnormal, 3 ) \ - X ( float, mesh_texcoord, nmeshtexcoord, 2 ) \ - X ( int, mesh_face, nmeshface, 3 ) \ - X ( int, mesh_facenormal, nmeshface, 3 ) \ - X ( int, mesh_facetexcoord, nmeshface, 3 ) \ - X ( int, mesh_graph, nmeshgraph, 1 ) \ + XNV ( float, mesh_vert, nmeshvert, 3 ) \ + XNV ( float, mesh_normal, nmeshnormal, 3 ) \ + XNV ( float, mesh_texcoord, nmeshtexcoord, 2 ) \ + XNV ( int, mesh_face, nmeshface, 3 ) \ + XNV ( int, mesh_facenormal, nmeshface, 3 ) \ + XNV ( int, mesh_facetexcoord, nmeshface, 3 ) \ + XNV ( int, mesh_graph, nmeshgraph, 1 ) \ XMJV( int, mesh_pathadr, nmesh, 1 ) \ XMJV( int, skin_matid, nskin, 1 ) \ XMJV( int, skin_group, nskin, 1 ) \ @@ -412,14 +412,14 @@ X ( int, hfield_nrow, nhfield, 1 ) \ X ( int, hfield_ncol, nhfield, 1 ) \ X ( int, hfield_adr, nhfield, 1 ) \ - X ( float, hfield_data, nhfielddata, 1 ) \ + XNV ( float, hfield_data, nhfielddata, 1 ) \ XMJV( int, hfield_pathadr, nhfield, 1 ) \ X ( int, tex_type, ntex, 1 ) \ X ( int, tex_height, ntex, 1 ) \ X ( int, tex_width, ntex, 1 ) \ X ( int, tex_nchannel, ntex, 1 ) \ X ( int, tex_adr, ntex, 1 ) \ - X ( mjtByte, tex_data, ntexdata, 1 ) \ + XNV ( mjtByte, tex_data, ntexdata, 1 ) \ XMJV( int, tex_pathadr, ntex, 1 ) \ XMJV( int, mat_texid, nmat, mjNTEXROLE ) \ XMJV( mjtByte, mat_texuniform, nmat, 1 ) \ @@ -779,4 +779,9 @@ // redefine X to expand to nothing, and XMJV to do what's required #define XMJV X +// alias XNV to be the same as X +// to obtain only X macros for fields that are relevant for mjvScene creation, +// redefine XNV to expand to nothing +#define XNV X + #endif // MUJOCO_MJXMACRO_H_ diff --git a/include/mujoco/mujoco.h b/include/mujoco/mujoco.h index 7a38575d..10b2f548 100644 --- a/include/mujoco/mujoco.h +++ b/include/mujoco/mujoco.h @@ -692,6 +692,9 @@ MJAPI int mjv_updateSceneFromState(const mjvSceneState* scnstate, const mjvOptio const mjvPerturb* pert, mjvCamera* cam, int catmask, mjvScene* scn); +// Copy mjModel, skip large arrays not required for abstract visualization. +MJAPI void mjv_copyModel(mjModel* dest, const mjModel* src); + // Set default scene state. MJAPI void mjv_defaultSceneState(mjvSceneState* scnstate); diff --git a/introspect/functions.py b/introspect/functions.py index 89b28e4a..ed936aef 100644 --- a/introspect/functions.py +++ b/introspect/functions.py @@ -4550,6 +4550,26 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([ ), doc='Update entire scene from a scene state, return the number of new mjWARN_VGEOMFULL warnings.', # pylint: disable=line-too-long )), + ('mjv_copyModel', + FunctionDecl( + name='mjv_copyModel', + return_type=ValueType(name='void'), + parameters=( + FunctionParameterDecl( + name='dest', + type=PointerType( + inner_type=ValueType(name='mjModel'), + ), + ), + FunctionParameterDecl( + name='src', + type=PointerType( + inner_type=ValueType(name='mjModel', is_const=True), + ), + ), + ), + doc='Copy mjModel, skip large arrays not required for abstract visualization.', # pylint: disable=line-too-long + )), ('mjv_defaultSceneState', FunctionDecl( name='mjv_defaultSceneState', diff --git a/src/engine/engine_io.c b/src/engine/engine_io.c index 57684692..ec24d4cf 100644 --- a/src/engine/engine_io.c +++ b/src/engine/engine_io.c @@ -625,10 +625,10 @@ void mj_makeModel(mjModel** dest, } } + + // copy mjModel, if dest==NULL create new model mjModel* mj_copyModel(mjModel* dest, const mjModel* src) { - void* save_bufptr; - // allocate new model if needed if (!dest) { mj_makeModel(&dest, @@ -658,13 +658,13 @@ mjModel* mj_copyModel(mjModel* dest, const mjModel* src) { mjERROR("dest and src models have different buffer size"); } - // save buffer ptr, copy everything, restore buffer and other pointers - save_bufptr = dest->buffer; + // save buffer ptr, copy struct, restore buffer and other pointers + void* save_bufptr = dest->buffer; *dest = *src; dest->buffer = save_bufptr; mj_setPtrModel(dest); - // copy buffer + // copy buffer contents { MJMODEL_POINTERS_PREAMBLE(src) #define X(type, name, nr, nc) \ @@ -678,6 +678,38 @@ mjModel* mj_copyModel(mjModel* dest, const mjModel* src) { +// copy mjModel, skip large arrays not required for abstract visualization +void mjv_copyModel(mjModel* dest, const mjModel* src) { + // check sizes + if (dest->nbuffer != src->nbuffer) { + mjERROR("dest and src models have different buffer size"); + } + + // save buffer ptr, copy struct, restore buffer and other pointers + void* save_bufptr = dest->buffer; + *dest = *src; + dest->buffer = save_bufptr; + mj_setPtrModel(dest); + + // redefine XNV to do nothing + #undef XNV + #define XNV(type, name, nr, nc) + + // copy buffer contents, skipping arrays marked XNV + { + MJMODEL_POINTERS_PREAMBLE(src) + #define X(type, name, nr, nc) \ + memcpy((char*)dest->name, (const char*)src->name, sizeof(type)*(src->nr)*nc); + MJMODEL_POINTERS + #undef X + } + // redefine XNV to be the same as X + #undef XNV + #define XNV X +} + + + // save model to binary file, or memory buffer of szbuf>0 void mj_saveModel(const mjModel* m, const char* filename, void* buffer, int buffer_sz) { FILE* fp = 0; @@ -1401,26 +1433,28 @@ mjData* mj_copyData(mjData* dest, const mjModel* m, const mjData* src) { #undef X } - // copy arena memory -#undef MJ_D -#define MJ_D(n) (src->n) -#undef MJ_M -#define MJ_M(n) (m->n) -#define X(type, name, nr, nc) \ - if (src->name) { \ - dest->name = (type*)((char*)dest->arena + PTRDIFF(src->name, src->arena)); \ - ASAN_UNPOISON_MEMORY_REGION(dest->name, sizeof(type)*nr*nc); \ - memcpy((char*)dest->name, (const char*)src->name, sizeof(type)*nr*nc); \ - } else { \ - dest->name = NULL; \ - } + // copy arena memory + #undef MJ_D + #define MJ_D(n) (src->n) + #undef MJ_M + #define MJ_M(n) (m->n) + + #define X(type, name, nr, nc) \ + if (src->name) { \ + dest->name = (type*)((char*)dest->arena + PTRDIFF(src->name, src->arena)); \ + ASAN_UNPOISON_MEMORY_REGION(dest->name, sizeof(type) * nr * nc); \ + memcpy((char*)dest->name, (const char*)src->name, sizeof(type) * nr * nc); \ + } else { \ + dest->name = NULL; \ + } MJDATA_ARENA_POINTERS -#undef X -#undef MJ_M -#define MJ_M(n) n -#undef MJ_D -#define MJ_D(n) n + #undef X + + #undef MJ_M + #define MJ_M(n) n + #undef MJ_D + #define MJ_D(n) n // restore contact pointer dest->contact = dest->arena; diff --git a/src/engine/engine_io.h b/src/engine/engine_io.h index 4b34784f..a6fac94e 100644 --- a/src/engine/engine_io.h +++ b/src/engine/engine_io.h @@ -69,6 +69,9 @@ void mj_makeModel(mjModel** dest, // copy mjModel; allocate new if dest is NULL MJAPI mjModel* mj_copyModel(mjModel* dest, const mjModel* src); +// copy mjModel, skip large arrays not required for abstract visualization +MJAPI void mjv_copyModel(mjModel* dest, const mjModel* src); + // save model to binary file MJAPI void mj_saveModel(const mjModel* m, const char* filename, void* buffer, int buffer_sz); diff --git a/test/engine/engine_io_test.cc b/test/engine/engine_io_test.cc index 7b65de4a..3e6eb9af 100644 --- a/test/engine/engine_io_test.cc +++ b/test/engine/engine_io_test.cc @@ -193,6 +193,43 @@ TEST_F(EngineIoTest, MakeDataResetsAllArenaPointerSizes) { mj_deleteModel(model); } +TEST_F(EngineIoTest, MjvCopyModel) { + static constexpr char xml[] = R"( + + + + + + + + + )"; + char error[1024]; + mjModel* model1 = LoadModelFromString(xml, error, sizeof(error)); + ASSERT_THAT(model1, NotNull()) << error; + + mjModel* model2 = mj_copyModel(nullptr, model1); + ASSERT_THAT(model2, NotNull()) << error; + + model1->mesh_vert[0] = 0.1; + model1->geom_rgba[0] = 0.2; + mj_copyModel(model2, model1); + + EXPECT_FLOAT_EQ(model2->mesh_vert[0], 0.1); + EXPECT_FLOAT_EQ(model2->geom_rgba[0], 0.2); + + model1->mesh_vert[0] = 0.3; + model1->geom_rgba[0] = 0.4; + mjv_copyModel(model2, model1); + + EXPECT_FLOAT_EQ(model2->mesh_vert[0], 0.1); // unchanged + EXPECT_FLOAT_EQ(model2->geom_rgba[0], 0.4); + + // mj_deleteData(data); + mj_deleteModel(model2); + mj_deleteModel(model1); +} + using ValidateReferencesTest = MujocoTest; TEST_F(ValidateReferencesTest, BodyReferences) { diff --git a/unity/Runtime/Bindings/MjBindings.cs b/unity/Runtime/Bindings/MjBindings.cs index 8858f99c..2a553678 100644 --- a/unity/Runtime/Bindings/MjBindings.cs +++ b/unity/Runtime/Bindings/MjBindings.cs @@ -6940,6 +6940,9 @@ public static unsafe extern void mjv_updateScene(mjModel_* m, mjData_* d, mjvOpt [DllImport("mujoco", CallingConvention = CallingConvention.Cdecl)] public static unsafe extern int mjv_updateSceneFromState(mjvSceneState_* scnstate, mjvOption_* opt, mjvPerturb_* pert, mjvCamera_* cam, int catmask, mjvScene_* scn); +[DllImport("mujoco", CallingConvention = CallingConvention.Cdecl)] +public static unsafe extern void mjv_copyModel(mjModel_* dest, mjModel_* src); + [DllImport("mujoco", CallingConvention = CallingConvention.Cdecl)] public static unsafe extern void mjv_defaultSceneState(mjvSceneState_* scnstate);