diff --git a/doc/includes/references.h b/doc/includes/references.h index cfc186f6..c9549e69 100644 --- a/doc/includes/references.h +++ b/doc/includes/references.h @@ -861,6 +861,7 @@ struct mjModel_ { int nuser_sensor; // number of mjtNums in sensor_user int nnames; // number of chars in all names int nnames_map; // number of slots in the names hash map + int npaths; // number of chars in all paths // sizes set after mjModel construction (only affect mjData) int nM; // number of non-zeros in sparse inertia matrix @@ -1045,6 +1046,7 @@ struct mjModel_ { int* mesh_graphadr; // graph data address; -1: no graph (nmesh x 1) mjtNum* mesh_pos; // translation applied to asset vertices (nmesh x 3) mjtNum* mesh_quat; // rotation applied to asset vertices (nmesh x 4) + int* mesh_assetpathadr; // address of asset path for mesh; -1: none (nmesh x 1) float* mesh_vert; // vertex positions for all meshes (nmeshvert x 3) float* mesh_normal; // normals for all meshes (nmeshnormal x 3) float* mesh_texcoord; // vertex texcoords for all meshes (nmeshtexcoord x 2) @@ -1251,6 +1253,9 @@ struct mjModel_ { int* name_pluginadr; // plugin instance name pointers (nplugin x 1) char* names; // names of all objects, 0-terminated (nnames x 1) int* names_map; // internal hash map of names (nnames_map x 1) + + // paths + char* paths; // paths to assets, 0-terminated (npaths x 1) }; typedef struct mjModel_ mjModel; struct mjResource_ { @@ -1995,6 +2000,7 @@ struct mjvSceneState_ { int nwrap; int nsensor; int nnames; + int npaths; int nsensordata; mjOption opt; @@ -2062,6 +2068,7 @@ struct mjvSceneState_ { int* mesh_bvhnum; int* mesh_texcoordadr; int* mesh_graphadr; + int* mesh_assetpathadr; int* skin_matid; int* skin_group; @@ -2137,6 +2144,7 @@ struct mjvSceneState_ { int* name_tendonadr; int* name_actuatoradr; char* names; + char* paths; } model; // fields in mjData that are necessary to re-render a scene diff --git a/include/mujoco/mjmodel.h b/include/mujoco/mjmodel.h index 0afab214..1e9cdea3 100644 --- a/include/mujoco/mjmodel.h +++ b/include/mujoco/mjmodel.h @@ -593,6 +593,7 @@ struct mjModel_ { int nuser_sensor; // number of mjtNums in sensor_user int nnames; // number of chars in all names int nnames_map; // number of slots in the names hash map + int npaths; // number of chars in all paths // sizes set after mjModel construction (only affect mjData) int nM; // number of non-zeros in sparse inertia matrix @@ -777,6 +778,7 @@ struct mjModel_ { int* mesh_graphadr; // graph data address; -1: no graph (nmesh x 1) mjtNum* mesh_pos; // translation applied to asset vertices (nmesh x 3) mjtNum* mesh_quat; // rotation applied to asset vertices (nmesh x 4) + int* mesh_assetpathadr; // address of asset path for mesh; -1: none (nmesh x 1) float* mesh_vert; // vertex positions for all meshes (nmeshvert x 3) float* mesh_normal; // normals for all meshes (nmeshnormal x 3) float* mesh_texcoord; // vertex texcoords for all meshes (nmeshtexcoord x 2) @@ -983,6 +985,9 @@ struct mjModel_ { int* name_pluginadr; // plugin instance name pointers (nplugin x 1) char* names; // names of all objects, 0-terminated (nnames x 1) int* names_map; // internal hash map of names (nnames_map x 1) + + // paths + char* paths; // paths to assets, 0-terminated (npaths x 1) }; typedef struct mjModel_ mjModel; diff --git a/include/mujoco/mjvisualize.h b/include/mujoco/mjvisualize.h index ff1a745c..527a1f2b 100644 --- a/include/mujoco/mjvisualize.h +++ b/include/mujoco/mjvisualize.h @@ -401,6 +401,7 @@ struct mjvSceneState_ { int nwrap; int nsensor; int nnames; + int npaths; int nsensordata; mjOption opt; @@ -468,6 +469,7 @@ struct mjvSceneState_ { int* mesh_bvhnum; int* mesh_texcoordadr; int* mesh_graphadr; + int* mesh_assetpathadr; int* skin_matid; int* skin_group; @@ -543,6 +545,7 @@ struct mjvSceneState_ { int* name_tendonadr; int* name_actuatoradr; char* names; + char* paths; } model; // fields in mjData that are necessary to re-render a scene diff --git a/include/mujoco/mjxmacro.h b/include/mujoco/mjxmacro.h index 67909b36..aba80032 100644 --- a/include/mujoco/mjxmacro.h +++ b/include/mujoco/mjxmacro.h @@ -118,6 +118,7 @@ X ( nuser_actuator ) \ X ( nuser_sensor ) \ XMJV( nnames ) \ + XMJV( npaths ) \ X ( nnames_map ) \ X ( nM ) \ X ( nD ) \ @@ -132,6 +133,8 @@ X ( narena ) \ X ( nbuffer ) + /* nbuffer needs to be the final field */ + // define symbols needed in MJMODEL_POINTERS (corresponding to number of columns) #define MJMODEL_POINTERS_PREAMBLE( m ) \ @@ -296,6 +299,7 @@ XMJV( int, mesh_graphadr, nmesh, 1 ) \ X ( mjtNum, mesh_pos, nmesh, 3 ) \ X ( mjtNum, mesh_quat, nmesh, 4 ) \ + XMJV( int, mesh_assetpathadr, nmesh, 1 ) \ X ( float, mesh_vert, nmeshvert, 3 ) \ X ( float, mesh_normal, nmeshnormal, 3 ) \ X ( float, mesh_texcoord, nmeshtexcoord, 2 ) \ @@ -468,6 +472,7 @@ X ( int, name_pluginadr, nplugin, 1 ) \ XMJV( char, names, nnames, 1 ) \ X ( int, names_map, nnames_map, 1 ) \ + XMJV( char, paths, npaths, 1 ) \ //-------------------------------- mjData ---------------------------------------------------------- diff --git a/introspect/structs.py b/introspect/structs.py index 2ddbfebb..063e6cc3 100644 --- a/introspect/structs.py +++ b/introspect/structs.py @@ -1070,6 +1070,11 @@ STRUCTS: Mapping[str, StructDecl] = dict([ type=ValueType(name='int'), doc='number of slots in the names hash map', ), + StructFieldDecl( + name='npaths', + type=ValueType(name='int'), + doc='number of chars in all paths', + ), StructFieldDecl( name='nM', type=ValueType(name='int'), @@ -2118,6 +2123,13 @@ STRUCTS: Mapping[str, StructDecl] = dict([ ), doc='rotation applied to asset vertices (nmesh x 4)', ), + StructFieldDecl( + name='mesh_assetpathadr', + type=PointerType( + inner_type=ValueType(name='int'), + ), + doc='address of asset path for mesh; -1: none (nmesh x 1)', + ), StructFieldDecl( name='mesh_vert', type=PointerType( @@ -3322,6 +3334,13 @@ STRUCTS: Mapping[str, StructDecl] = dict([ ), doc='internal hash map of names (nnames_map x 1)', # pylint: disable=line-too-long ), + StructFieldDecl( + name='paths', + type=PointerType( + inner_type=ValueType(name='char'), + ), + doc='paths to assets, 0-terminated (npaths x 1)', + ), ), )), ('mjThreadPool', @@ -5554,6 +5573,11 @@ STRUCTS: Mapping[str, StructDecl] = dict([ type=ValueType(name='int'), doc='', ), + StructFieldDecl( + name='npaths', + type=ValueType(name='int'), + doc='', + ), StructFieldDecl( name='nsensordata', type=ValueType(name='int'), @@ -5952,6 +5976,13 @@ STRUCTS: Mapping[str, StructDecl] = dict([ ), doc='', ), + StructFieldDecl( + name='mesh_assetpathadr', + type=PointerType( + inner_type=ValueType(name='int'), + ), + doc='', + ), StructFieldDecl( name='skin_matid', type=PointerType( @@ -6428,6 +6459,13 @@ STRUCTS: Mapping[str, StructDecl] = dict([ ), doc='', ), + StructFieldDecl( + name='paths', + type=PointerType( + inner_type=ValueType(name='char'), + ), + doc='', + ), ), ), doc='', diff --git a/python/mujoco/structs.cc b/python/mujoco/structs.cc index 6c54f3b5..90862175 100644 --- a/python/mujoco/structs.cc +++ b/python/mujoco/structs.cc @@ -234,6 +234,7 @@ MjModelWrapper::MjWrapper(raw::MjModel* ptr) MJMODEL_POINTERS, text_data_bytes(ptr->text_data, ptr->ntextdata), names_bytes(ptr->names, ptr->nnames), + paths_bytes(ptr->paths, ptr->npaths), indexer_(ptr, owner_) { bool is_newly_inserted = false; { @@ -255,6 +256,7 @@ MjModelWrapper::MjWrapper(MjModelWrapper&& other) MJMODEL_POINTERS, text_data_bytes(ptr_->text_data, ptr_->ntextdata), names_bytes(ptr_->names, ptr_->nnames), + paths_bytes(ptr_->paths, ptr_->npaths), indexer_(ptr_, owner_) { bool is_newly_inserted = false; { @@ -1509,10 +1511,11 @@ This is useful for example when the MJB is not available as a file on disk.)")); MJMODEL_INTS #undef X -#define X(dtype, var, dim0, dim1) \ - if constexpr (std::string_view(#var) != "text_data" && \ - std::string_view(#var) != "names") { \ - DefinePyArray(mjModel, #var, &MjModelWrapper::var); \ +#define X(dtype, var, dim0, dim1) \ + if constexpr (std::string_view(#var) != "text_data" && \ + std::string_view(#var) != "names" && \ + std::string_view(#var) != "paths") { \ + DefinePyArray(mjModel, #var, &MjModelWrapper::var); \ } MJMODEL_POINTERS #undef X @@ -1527,6 +1530,11 @@ This is useful for example when the MJB is not available as a file on disk.)")); // Return the full bytes array of concatenated names return m.names_bytes; }); + mjModel.def_property_readonly( + "paths", [](const MjModelWrapper& m) -> const auto& { + // Return the full bytes array of concatenated paths + return m.paths_bytes; + }); #define XGROUP(MjModelGroupedViews, field, nfield, FIELD_XMACROS) \ mjModel.def( \ diff --git a/python/mujoco/structs.h b/python/mujoco/structs.h index 2c84d814..c49681ab 100644 --- a/python/mujoco/structs.h +++ b/python/mujoco/structs.h @@ -477,6 +477,7 @@ class MjWrapper : public WrapperBase { // TODO(nimrod): Exclude text_data and names from the MJMODEL_POINTERS macro. pybind11::bytes text_data_bytes; pybind11::bytes names_bytes; + pybind11::bytes paths_bytes; protected: explicit MjWrapper(raw::MjModel* ptr); diff --git a/src/engine/engine_io.c b/src/engine/engine_io.c index f8b0c0c1..71efa04a 100644 --- a/src/engine/engine_io.c +++ b/src/engine/engine_io.c @@ -450,7 +450,7 @@ mjModel* mj_makeModel(int nq, int nv, int nu, int na, int nbody, int nbvh, int n int ntuple, int ntupledata, int nkey, int nmocap, int nplugin, int npluginattr, int nuser_body, int nuser_jnt, int nuser_geom, int nuser_site, int nuser_cam, int nuser_tendon, int nuser_actuator, - int nuser_sensor, int nnames) { + int nuser_sensor, int nnames, int npaths) { intptr_t offset = 0; // allocate mjModel @@ -519,6 +519,7 @@ mjModel* mj_makeModel(int nq, int nv, int nu, int na, int nbody, int nbvh, int n + nskin + nhfield + ntex + nmat + npair + nexclude + neq + ntendon + nu + nsensor + nnumeric + ntext + ntuple + nkey + nplugin); + m->npaths = npaths; #define X(name) \ if ((m->name) < 0) { \ @@ -597,7 +598,7 @@ mjModel* mj_copyModel(mjModel* dest, const mjModel* src) { src->ntuple, src->ntupledata, src->nkey, src->nmocap, src->nplugin, src->npluginattr, src->nuser_body, src->nuser_jnt, src->nuser_geom, src->nuser_site, src->nuser_cam, src->nuser_tendon, src->nuser_actuator, - src->nuser_sensor, src->nnames); + src->nuser_sensor, src->nnames, src->npaths); } if (!dest) { mjERROR("failed to make mjModel. Invalid sizes."); @@ -762,7 +763,7 @@ mjModel* mj_loadModel(const char* filename, const mjVFS* vfs) { ints[28], ints[29], ints[30], ints[31], ints[32], ints[33], ints[34], ints[35], ints[36], ints[37], ints[38], ints[39], ints[40], ints[41], ints[42], ints[43], ints[44], ints[45], ints[46], ints[47], ints[48], - ints[49], ints[50], ints[51], ints[52]); + ints[49], ints[50], ints[51], ints[52], ints[53]); if (!m || m->nbuffer != sizes[getnsize()-1]) { mju_closeResource(r); mju_warning("Corrupted model, wrong size parameters"); @@ -1791,7 +1792,8 @@ const char* mj_validateReferences(const mjModel* m) { X(name_numericadr, nnumeric, nnames , 0 ) \ X(name_textadr, ntext, nnames , 0 ) \ X(name_tupleadr, ntuple, nnames , 0 ) \ - X(name_keyadr, nkey, nnames , 0 ) + X(name_keyadr, nkey, nnames , 0 ) \ + X(mesh_assetpathadr, nmesh, npaths , 0 ) #define X(adrarray, nadrs, ntarget, numarray) { \ int *nums = (numarray); \ diff --git a/src/engine/engine_io.h b/src/engine/engine_io.h index 3fcf695f..d833281a 100644 --- a/src/engine/engine_io.h +++ b/src/engine/engine_io.h @@ -62,7 +62,7 @@ mjModel* mj_makeModel(int nq, int nv, int nu, int na, int nbody, int nbvh, int n int ntuple, int ntupledata, int nkey, int nmocap, int nplugin, int npluginattr, int nuser_body, int nuser_jnt, int nuser_geom, int nuser_site, int nuser_cam, int nuser_tendon, int nuser_actuator, - int nuser_sensor, int nnames); + int nuser_sensor, int nnames, int npaths); // copy mjModel; allocate new if dest is NULL MJAPI mjModel* mj_copyModel(mjModel* dest, const mjModel* src); diff --git a/src/user/user_model.cc b/src/user/user_model.cc index 4cb2a541..a6904257 100644 --- a/src/user/user_model.cc +++ b/src/user/user_model.cc @@ -275,6 +275,7 @@ void mjCModel::Clear(void) { ntupledata = 0; npluginattr = 0; nnames = 0; + npaths = 0; memory = -1; nstack = -1; nemax = 0; @@ -1002,6 +1003,18 @@ void mjCModel::SetSizes(void) { for (int i=0; iname.length() + 1; for (int i=0; iname.length() + 1; + // npaths + npaths = 0; + for (int i=0; ifile().empty()) { + continue; + } + npaths += (int)meshes[i]->file().length() + 1; + } + if (npaths == 0) { + npaths = 1; + } + // nemax for (int i=0; itype==mjEQ_CONNECT) { @@ -1197,7 +1210,6 @@ void mjCModel::LengthRange(mjModel* m, mjData* data) { } - // process names from one list: concatenate, compute addresses template static int namelist(vector& list, int adr, int* name_adr, char* names, int* map) { @@ -1313,6 +1325,30 @@ void mjCModel::CopyNames(mjModel* m) { } +void mjCModel::CopyPaths(mjModel* m) { + // start with 0 address. + size_t adr = 0; + m->paths[0] = 0; + + for (unsigned int i=0; ifile().empty()) { + m->mesh_assetpathadr[i] = -1; + continue; + } + m->mesh_assetpathadr[i] = adr; + + // copy path + size_t path_size = meshes[i]->file().size(); + memcpy(m->paths+adr, meshes[i]->file().c_str(), path_size); + adr += path_size; + + // append 0 + m->paths[adr] = 0; + adr++; + } +} + + // copy objects inside kinematic tree void mjCModel::CopyTree(mjModel* m) { @@ -2703,7 +2739,7 @@ void mjCModel::TryCompile(mjModel*& m, mjData*& d, const mjVFS* vfs) { nnumeric, nnumericdata, ntext, ntextdata, ntuple, ntupledata, nkey, nmocap, nplugin, npluginattr, nuser_body, nuser_jnt, nuser_geom, nuser_site, nuser_cam, - nuser_tendon, nuser_actuator, nuser_sensor, nnames); + nuser_tendon, nuser_actuator, nuser_sensor, nnames, npaths); if (!m) { throw mjCError(0, "could not create mjModel"); } @@ -2712,6 +2748,7 @@ void mjCModel::TryCompile(mjModel*& m, mjData*& d, const mjVFS* vfs) { m->opt = option; m->vis = visual; CopyNames(m); + CopyPaths(m); CopyTree(m); // assign plugin slots and copy plugin config attributes @@ -2918,7 +2955,8 @@ bool mjCModel::CopyBack(const mjModel* m) { neq!=m->neq || ntendon!=m->ntendon || nwrap!=m->nwrap || nsensor!=m->nsensor || nnumeric!=m->nnumeric || nnumericdata!=m->nnumericdata || ntext!=m->ntext || ntextdata!=m->ntextdata || nnames!=m->nnames || nM!=m->nM || nD!=m->nD || - nB!=m->nB || nemax!=m->nemax || nconmax!=m->nconmax || njmax!=m->njmax) { + nB!=m->nB || nemax!=m->nemax || nconmax!=m->nconmax || njmax!=m->njmax || + npaths!=m->npaths) { errInfo = mjCError(0, "incompatible models in CopyBack"); return false; } diff --git a/src/user/user_model.h b/src/user/user_model.h index f4a3ed68..28d24d96 100644 --- a/src/user/user_model.h +++ b/src/user/user_model.h @@ -182,6 +182,7 @@ class mjCModel { void AutoSpringDamper(mjModel*);// automatic stiffness and damping computation void LengthRange(mjModel*, mjData*); // compute actuator lengthrange void CopyNames(mjModel*); // copy names, compute name addresses + void CopyPaths(mjModel*); // copy paths, compute path addresses void CopyObjects(mjModel*); // copy objects outside kinematic tree void CopyTree(mjModel*); // copy objects inside kinematic tree @@ -235,6 +236,7 @@ class mjCModel { int ntupledata; // number of objects in all tuple fields int npluginattr; // number of chars in all plugin config attributes int nnames; // number of chars in all names + int npaths; // number of chars in all paths int nM; // number of non-zeros in sparse inertia matrix int nD; // number of non-zeros in sparse dof-dof matrix int nB; // number of non-zeros in sparse body-dof matrix diff --git a/src/xml/xml.cc b/src/xml/xml.cc index a9758572..36710b44 100644 --- a/src/xml/xml.cc +++ b/src/xml/xml.cc @@ -321,6 +321,7 @@ mjCModel* mjParseXML(const char* filename, const mjVFS* vfs, char* error, int er // this is separate from the Parser to allow multiple URDFs to be loaded. model->strippath = true; model->fusestatic = true; + model->discardvisual = true; parser.SetModel(model); parser.Parse(root); diff --git a/src/xml/xml_urdf.cc b/src/xml/xml_urdf.cc index 01b4d6a7..13c12ce2 100644 --- a/src/xml/xml_urdf.cc +++ b/src/xml/xml_urdf.cc @@ -87,9 +87,6 @@ void mjXURDF::Parse( int id_parent, id_child; urPrefix = prefix; - // set compiler defaults suitable for URDF - model->discardvisual = true; - // parse MuJoCo sections (not part of URDF) XMLElement* mjc = FindSubElem(root, "mujoco"); if (mjc) { diff --git a/test/user/user_mesh_test.cc b/test/user/user_mesh_test.cc index 4013ed74..418f71c0 100644 --- a/test/user/user_mesh_test.cc +++ b/test/user/user_mesh_test.cc @@ -375,6 +375,9 @@ TEST_F(MjCMeshTest, SaveMeshOnce) { const std::string xml_path = GetTestDataFilePath(kCubePath); std::array error; mjModel* model = mj_loadXML(xml_path.c_str(), 0, error.data(), error.size()); + // Confirm the mesh file is loaded and stored in paths + EXPECT_EQ( + std::string(&model->paths[model->mesh_assetpathadr[0]]), "cube.obj"); std::string saved_xml = SaveAndReadXml(model); EXPECT_THAT(saved_xml, Not(testing::HasSubstr("vertex"))); mj_deleteModel(model); diff --git a/unity/Runtime/Bindings/MjBindings.cs b/unity/Runtime/Bindings/MjBindings.cs index 245de196..0f299097 100644 --- a/unity/Runtime/Bindings/MjBindings.cs +++ b/unity/Runtime/Bindings/MjBindings.cs @@ -5095,6 +5095,7 @@ public unsafe struct mjModel_ { public int nuser_sensor; public int nnames; public int nnames_map; + public int npaths; public int nM; public int nD; public int nB; @@ -5249,6 +5250,7 @@ public unsafe struct mjModel_ { public int* mesh_graphadr; public double* mesh_pos; public double* mesh_quat; + public int* mesh_assetpathadr; public float* mesh_vert; public float* mesh_normal; public float* mesh_texcoord; @@ -5421,6 +5423,7 @@ public unsafe struct mjModel_ { public int* name_pluginadr; public char* names; public int* names_map; + public char* paths; } [StructLayout(LayoutKind.Sequential)] @@ -5944,6 +5947,7 @@ public unsafe struct model { public int nwrap; public int nsensor; public int nnames; + public int npaths; public int nsensordata; public mjOption_ opt; public mjVisual_ vis; @@ -6002,6 +6006,7 @@ public unsafe struct model { public int* mesh_bvhnum; public int* mesh_texcoordadr; public int* mesh_graphadr; + public int* mesh_assetpathadr; public int* skin_matid; public int* skin_group; public float* skin_rgba; @@ -6070,6 +6075,7 @@ public unsafe struct model { public int* name_tendonadr; public int* name_actuatoradr; public char* names; + public char* paths; } [StructLayout(LayoutKind.Sequential)]