From 131b1745db8007516450838435522778eb6e0696 Mon Sep 17 00:00:00 2001 From: Mohammad Hamid Date: Wed, 1 May 2024 08:42:53 -0700 Subject: [PATCH] Added support for GMSH22 surface loading PiperOrigin-RevId: 629734063 Change-Id: I6a250b81de4e7c8f7520795bfe74a81d719dfb50 --- doc/XMLreference.rst | 7 +- doc/changelog.rst | 35 +++--- src/user/user_flexcomp.cc | 71 ++++++++--- .../testdata/cube_22_ascii_surf_gmshApp.msh | 47 ++++++++ .../testdata/cube_22_ascii_surf_gmshApp.xml | 26 ++++ .../testdata/cube_22_binary_surf_gmshApp.msh | Bin 0 -> 1344 bytes .../testdata/cube_22_binary_surf_gmshApp.xml | 26 ++++ .../testdata/cube_41_ascii_surf_gmshApp.msh | 67 +++++++++++ .../testdata/cube_41_ascii_surf_gmshApp.xml | 26 ++++ .../testdata/cube_41_binary_surf_gmshApp.msh | Bin 0 -> 1526 bytes .../testdata/cube_41_binary_surf_gmshApp.xml | 26 ++++ ...rmed_shark_41_ascii_missing_node_index.xml | 2 +- .../shark_41_ascii_missing_node_index.msh | 1 - test/user/user_flex_test.cc | 111 +++++++++++++++++- 14 files changed, 403 insertions(+), 42 deletions(-) create mode 100644 test/user/testdata/cube_22_ascii_surf_gmshApp.msh create mode 100644 test/user/testdata/cube_22_ascii_surf_gmshApp.xml create mode 100644 test/user/testdata/cube_22_binary_surf_gmshApp.msh create mode 100644 test/user/testdata/cube_22_binary_surf_gmshApp.xml create mode 100644 test/user/testdata/cube_41_ascii_surf_gmshApp.msh create mode 100644 test/user/testdata/cube_41_ascii_surf_gmshApp.xml create mode 100644 test/user/testdata/cube_41_binary_surf_gmshApp.msh create mode 100644 test/user/testdata/cube_41_binary_surf_gmshApp.xml diff --git a/doc/XMLreference.rst b/doc/XMLreference.rst index 746d4440..f166401b 100644 --- a/doc/XMLreference.rst +++ b/doc/XMLreference.rst @@ -3452,9 +3452,10 @@ saving the XML: .. _body-flexcomp-file: :at:`file`: :at-val:`string, optional` - The name of the file from which a **mesh** or a **gmsh** is loaded. For mesh, the file extentsion is used to - determine the file format. Supported formats are the same as in :ref:`mesh assets`. For gmsh, the file is - expected to be in GMSH format 4.1 or 2.2, ascii or binary, see :ref:`here`. + The name of the file from which a **surface** (triangular) or **volumetric** (tetrahedral) mesh is loaded. For + surface meshes, the file extension is used to determine the file format. Supported formats are the same as in + :ref:`mesh assets` and also including GMSH. Volumetric meshes are supported only in GMSH format. + See :ref:`here` for more information on GMSH files. .. _body-flexcomp-rigid: diff --git a/doc/changelog.rst b/doc/changelog.rst index 7b82e34a..37fa8565 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -24,39 +24,40 @@ General field. 4. The type of the ``size`` argument of :ref:`mj_stackAllocNum` and :ref:`mj_stackAllocInt` was changed from ``int`` to ``size_t``. +5. Added support for gmsh format version 2.2 surface meshes in :ref:`flexcomp`. MJX ^^^ .. admonition:: Breaking API changes :class: attention - 5. Removed deprecated ``mjx.device_get_into`` and ``mjx.device_put`` functions as they lack critical new + 6. Removed deprecated ``mjx.device_get_into`` and ``mjx.device_put`` functions as they lack critical new functionality. **Migration:** Use ``mjx.get_data_into`` instead of ``mjx.device_get_into``, and ``mjx.put_data`` instead of ``mjx.device_put``. -6. Added cylinder plane collisions. -7. Added ``efc_type`` to ``mjx.Data`` and ``dim``, ``efc_address`` to ``mjx.Contact``. -8. Added ``geom`` to ``mjx.Contact`` and marked ``geom1``, ``geom2`` deprecated. -9. Added ``ne``, ``nf``, ``nl``, ``nefc``, and ``ncon`` to ``mjx.Data`` to match ``mujoco.MjData``. -10. Given the above added fields, removed ``mjx.get_params``, ``mjx.ncon``, and ``mjx.count_constraints``. -11. Changed the way meshes are organized on device to speed up collision detection when a mesh is replicated for many +7. Added cylinder plane collisions. +8. Added ``efc_type`` to ``mjx.Data`` and ``dim``, ``efc_address`` to ``mjx.Contact``. +9. Added ``geom`` to ``mjx.Contact`` and marked ``geom1``, ``geom2`` deprecated. +10. Added ``ne``, ``nf``, ``nl``, ``nefc``, and ``ncon`` to ``mjx.Data`` to match ``mujoco.MjData``. +11. Given the above added fields, removed ``mjx.get_params``, ``mjx.ncon``, and ``mjx.count_constraints``. +12. Changed the way meshes are organized on device to speed up collision detection when a mesh is replicated for many geoms. -12. Fixed a bug where capsules might be ignored in broadphase colliision checking. -13. Added cylinder collisions using SDFs. -14. Added support for all :ref:`condim `: 1, 3, 4, 6. -15. Add support functions for ``id2name`` and ``name2id``, MJX versions of :ref:`mj_id2name` and :ref:`mj_name2id`. -16. Added support for :ref:`gravcomp` and :ref:`actuatorgravcomp`. -17. Fixed a bug in ``mjx.ray`` for sometimes allowed negative distances for ray-mesh tests. +13. Fixed a bug where capsules might be ignored in broadphase colliision checking. +14. Added cylinder collisions using SDFs. +15. Added support for all :ref:`condim `: 1, 3, 4, 6. +16. Add support functions for ``id2name`` and ``name2id``, MJX versions of :ref:`mj_id2name` and :ref:`mj_name2id`. +17. Added support for :ref:`gravcomp` and :ref:`actuatorgravcomp`. +18. Fixed a bug in ``mjx.ray`` for sometimes allowed negative distances for ray-mesh tests. Bug fixes ^^^^^^^^^ -18. Defaults of lights were not being saved, now fixed. -19. Prevent overwriting of frame names by body names when saving an XML. Bug introduced in 3.1.4. -20. Fixed bug in Python binding of :ref:`mj_saveModel`: ``buffer`` argument was documented as optional but was actually +19. Defaults of lights were not being saved, now fixed. +20. Prevent overwriting of frame names by body names when saving an XML. Bug introduced in 3.1.4. +21. Fixed bug in Python binding of :ref:`mj_saveModel`: ``buffer`` argument was documented as optional but was actually not optional. -21. Fixed bug that prevented memory allocations larger than 2.15 GB. +22. Fixed bug that prevented memory allocations larger than 2.15 GB. Version 3.1.4 (April 10th, 2024) diff --git a/src/user/user_flexcomp.cc b/src/user/user_flexcomp.cc index c319e1ab..ce49b644 100644 --- a/src/user/user_flexcomp.cc +++ b/src/user/user_flexcomp.cc @@ -62,7 +62,7 @@ static void ReadStrFromBuffer(char* dest, const char* src, int maxlen) { std::strncpy(dest, src, maxlen); } -bool IsValidElementHeader22(const std::string& line) { +bool IsValidElementOrNodeHeader22(const std::string& line) { // making sure characters are numbers for (char c : line) { if (!std::isdigit(c)) { @@ -1296,10 +1296,21 @@ void mjCFlexcomp::LoadGMSH41(char* buffer, int binary, int nodeend, // load GMSH format 2.2 void mjCFlexcomp::LoadGMSH22(char* buffer, int binary, int nodeend, int nodebegin, int elemend, int elembegin) { + // number of nodes + size_t numNodes = 0; + // ascii nodes if (binary == 0) { // convert node char buffer to stringstream stringstream ss(std::string(buffer + nodebegin, nodeend - nodebegin)); + std::string line; + + // checking header template + std::getline(ss, line); + if (!IsValidElementOrNodeHeader22(line)) { + throw mjCError(NULL, "Invalid node header"); + } + ss.seekg(-(line.size()+1), std::ios::cur); // read header size_t maxNodeTag = 0; @@ -1307,7 +1318,7 @@ void mjCFlexcomp::LoadGMSH22(char* buffer, int binary, int nodeend, if (!ss.good()) { throw mjCError(NULL, "Error reading Nodes header"); } - size_t numNodes = maxNodeTag; + numNodes = maxNodeTag; if (numNodes < 0 || numNodes >= INT_MAX / 3) { throw mjCError(NULL, "Invalid number of nodes."); @@ -1349,7 +1360,7 @@ void mjCFlexcomp::LoadGMSH22(char* buffer, int binary, int nodeend, ReadStrFromBuffer(maxNodeTagChar, buffer + nodebegin, std::min(10, nodeend - nodebegin)); size_t measuredHeaderSize = strnlen(maxNodeTagChar, 10) - 1; size_t maxNodeTag = std::stoi(maxNodeTagChar); - size_t numNodes = maxNodeTag; + numNodes = maxNodeTag; // check number of nodes is a positive number if (numNodes < 0) { @@ -1396,7 +1407,7 @@ void mjCFlexcomp::LoadGMSH22(char* buffer, int binary, int nodeend, // checking header template std::getline(ss, line); - if (!IsValidElementHeader22(line)) { + if (!IsValidElementOrNodeHeader22(line)) { throw mjCError(NULL, "Invalid elements header"); } ss.seekg(-(line.size()+1), std::ios::cur); @@ -1404,7 +1415,7 @@ void mjCFlexcomp::LoadGMSH22(char* buffer, int binary, int nodeend, size_t maxElementTag = 0; ss >> maxElementTag; if (!ss.good()) { - throw mjCError(NULL, "GetMaxElementTag::Error reading Elements header"); + throw mjCError(NULL, "Error reading Elements header"); } size_t numElements = maxElementTag; @@ -1419,18 +1430,30 @@ void mjCFlexcomp::LoadGMSH22(char* buffer, int binary, int nodeend, } // reading first element's type - int tag = 0, type = 0, numTags = 0; - ss >> tag >> type >> numTags; + int tag = 0, elementType = 0, numTags = 0; + ss >> tag >> elementType >> numTags; if (!ss.good()) { throw mjCError(NULL, "Error reading Elements"); } - int numNodeTags = type; + + size_t entityDim = 0; + int numNodeTags = 0; + // surface + if (elementType == 2) { + entityDim = 2; + numNodeTags = 3; + } + // tetrahedral + else if (elementType == 4) { + entityDim = 3; + numNodeTags = 4; + } + if (numNodeTags < 1 || numNodeTags >4) { throw mjCError(NULL, "Invalid number of node tags"); } // setting entityDim - size_t entityDim = numNodeTags - 1; def.spec.flex->dim = entityDim; // read elements, discard all tags @@ -1438,7 +1461,7 @@ void mjCFlexcomp::LoadGMSH22(char* buffer, int binary, int nodeend, for (size_t i=0; i> tag >> type >> numTags; + ss >> tag >> elementType >> numTags; if (!ss.good()) { throw mjCError(NULL, "Error reading Elements"); } @@ -1454,7 +1477,7 @@ void mjCFlexcomp::LoadGMSH22(char* buffer, int binary, int nodeend, if (!ss.good()) { throw mjCError(NULL, "Error reading Elements"); } - if (nodeTag > numElements || nodeTag < 1) { + if (nodeTag > numNodes || nodeTag < 1) { throw mjCError(NULL, "Invalid node tag"); } element.push_back((int)(nodeTag-1)); @@ -1464,7 +1487,7 @@ void mjCFlexcomp::LoadGMSH22(char* buffer, int binary, int nodeend, // binary elements else { // header size for gmshApp - constexpr int elementHeaderSizeGmshApp = 6; + constexpr int elementHeaderSizeGmshApp = 4; // header size for Ftetwild constexpr int elementHeaderSizeFtetwild = 17; // check header size @@ -1480,7 +1503,7 @@ void mjCFlexcomp::LoadGMSH22(char* buffer, int binary, int nodeend, int numElements = maxElementTag; int tag, numTags; int nodeTag; - int numNodeTags; + int elementType; // check number of elements is a positive number if (numElements < 0) { @@ -1491,14 +1514,28 @@ void mjCFlexcomp::LoadGMSH22(char* buffer, int binary, int nodeend, int componentSize = sizeof(int); // element buffer const char* elementsBuffer = buffer + elembegin + measuredHeaderSize; - ReadFromBuffer(&numNodeTags, elementsBuffer); + ReadFromBuffer(&elementType, elementsBuffer); ReadFromBuffer(&numTags, elementsBuffer + componentSize*2); ReadFromBuffer(&tag, elementsBuffer + componentSize*3); + // tetrahedral has 4 node tags and surface has 3 + int numNodeTags = 0; + size_t entityDim = 0; + // surface + if (elementType == 2) { + entityDim = 2; + numNodeTags = 3; + } + // tetrahedral + else if (elementType == 4) { + entityDim = 3; + numNodeTags = 4; + } + if (numNodeTags < 1 || numNodeTags >4) { throw mjCError(NULL, "Invalid number of node tags"); } - size_t entityDim = numNodeTags - 1; + def.spec.flex->dim = entityDim; // element data(Ftetwild): tag and 4 nodeTag @@ -1534,7 +1571,7 @@ void mjCFlexcomp::LoadGMSH22(char* buffer, int binary, int nodeend, // read first element for (int k =0; k numElements || nodeTag < 1) { + if (nodeTag > numNodes || nodeTag < 1) { throw mjCError(NULL, "Invalid node tag"); } element.push_back(nodeTag-1); @@ -1564,7 +1601,7 @@ void mjCFlexcomp::LoadGMSH22(char* buffer, int binary, int nodeend, for (int k = 0; k < numNodeTags; k++) { const char* nodeTagBuffer = elementsBuffer + componentSize*(4+k); ReadFromBuffer(&nodeTag, nodeTagBuffer); - if (nodeTag > numElements || nodeTag < 1) { + if (nodeTag > numNodes || nodeTag < 1) { throw mjCError(NULL, "Invalid node tag"); } element.push_back(nodeTag-1); diff --git a/test/user/testdata/cube_22_ascii_surf_gmshApp.msh b/test/user/testdata/cube_22_ascii_surf_gmshApp.msh new file mode 100644 index 00000000..ae5c7d82 --- /dev/null +++ b/test/user/testdata/cube_22_ascii_surf_gmshApp.msh @@ -0,0 +1,47 @@ +$MeshFormat +2.2 0 8 +$EndMeshFormat +$Nodes +14 +1 -0.5 -0.5 0 +2 0.5 -0.5 0 +3 0.5 0.5 0 +4 -0.5 0.5 0 +5 -0.5 -0.5 1 +6 0.5 -0.5 1 +7 0.5 0.5 1 +8 -0.5 0.5 1 +9 0 0 0 +10 0 -0.5 0.5 +11 0.5 0 0.5 +12 0 0.5 0.5 +13 -0.5 0 0.5 +14 0 0 1 +$EndNodes +$Elements +24 +1 2 2 0 1 9 4 3 +2 2 2 0 1 9 3 2 +3 2 2 0 1 9 1 4 +4 2 2 0 1 9 2 1 +5 2 2 0 1 2 10 1 +6 2 2 0 1 1 10 5 +7 2 2 0 1 6 10 2 +8 2 2 0 1 5 10 6 +9 2 2 0 1 3 11 2 +10 2 2 0 1 2 11 6 +11 2 2 0 1 7 11 3 +12 2 2 0 1 6 11 7 +13 2 2 0 1 4 12 3 +14 2 2 0 1 3 12 7 +15 2 2 0 1 8 12 4 +16 2 2 0 1 7 12 8 +17 2 2 0 1 1 13 4 +18 2 2 0 1 5 13 1 +19 2 2 0 1 4 13 8 +20 2 2 0 1 8 13 5 +21 2 2 0 1 5 6 14 +22 2 2 0 1 8 5 14 +23 2 2 0 1 6 7 14 +24 2 2 0 1 7 8 14 +$EndElements diff --git a/test/user/testdata/cube_22_ascii_surf_gmshApp.xml b/test/user/testdata/cube_22_ascii_surf_gmshApp.xml new file mode 100644 index 00000000..425608ed --- /dev/null +++ b/test/user/testdata/cube_22_ascii_surf_gmshApp.xml @@ -0,0 +1,26 @@ + + diff --git a/test/user/testdata/cube_22_binary_surf_gmshApp.msh b/test/user/testdata/cube_22_binary_surf_gmshApp.msh new file mode 100644 index 0000000000000000000000000000000000000000..b0f13174232b48a71756f85f7d1870abe3acd91d GIT binary patch literal 1344 zcmZXTJ8r`;42IQrtG?eIGk7kLAVW4y>DJz%Kn!$f5}?lgdKDkRN2oqqzs<@742k?d zQnY9n?`8G%w*6Uu?)>a^HknQqe$P4Qi{)lzwBlpCDl0#ow`v5U)xZB&=k~h*TFAA7 zv6dL;4hKy&d+lhfC8i0-gO+&p)P$3+sW&U;O@6sW_6 zw*4xm4wpkL^`bx>uCyJkO=u_7O{G|Vm+Nw~tNd*4nE?~jN-93@#XU0zX{*$++L4(QbT*nv&!#%*{^SX&K42FCd zSliES^f4G7`pfIw#TbUpJ;SiKb?&2&5houeKkFgJFc>tLeD04ihUqNsk^8V`Jpn{h c9ctvvb4zp#HF*D=c`*z+J;Aj9UH`-V0o`#z*#H0l literal 0 HcmV?d00001 diff --git a/test/user/testdata/cube_22_binary_surf_gmshApp.xml b/test/user/testdata/cube_22_binary_surf_gmshApp.xml new file mode 100644 index 00000000..f8f08836 --- /dev/null +++ b/test/user/testdata/cube_22_binary_surf_gmshApp.xml @@ -0,0 +1,26 @@ + + diff --git a/test/user/testdata/cube_41_ascii_surf_gmshApp.msh b/test/user/testdata/cube_41_ascii_surf_gmshApp.msh new file mode 100644 index 00000000..4f97c8cb --- /dev/null +++ b/test/user/testdata/cube_41_ascii_surf_gmshApp.msh @@ -0,0 +1,67 @@ +$MeshFormat +4.1 0 8 +$EndMeshFormat +$Entities +0 0 1 0 +1 -0.5 -0.5 0 0.5 0.5 1 0 0 +$EndEntities +$Nodes +1 14 1 14 +2 1 0 14 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +-0.5 -0.5 0 +0.5 -0.5 0 +0.5 0.5 0 +-0.5 0.5 0 +-0.5 -0.5 1 +0.5 -0.5 1 +0.5 0.5 1 +-0.5 0.5 1 +0 0 0 +0 -0.5 0.5 +0.5 0 0.5 +0 0.5 0.5 +-0.5 0 0.5 +0 0 1 +$EndNodes +$Elements +1 24 1 24 +2 1 2 24 +1 9 4 3 +2 9 3 2 +3 9 1 4 +4 9 2 1 +5 2 10 1 +6 1 10 5 +7 6 10 2 +8 5 10 6 +9 3 11 2 +10 2 11 6 +11 7 11 3 +12 6 11 7 +13 4 12 3 +14 3 12 7 +15 8 12 4 +16 7 12 8 +17 1 13 4 +18 5 13 1 +19 4 13 8 +20 8 13 5 +21 5 6 14 +22 8 5 14 +23 6 7 14 +24 7 8 14 +$EndElements diff --git a/test/user/testdata/cube_41_ascii_surf_gmshApp.xml b/test/user/testdata/cube_41_ascii_surf_gmshApp.xml new file mode 100644 index 00000000..7aa65250 --- /dev/null +++ b/test/user/testdata/cube_41_ascii_surf_gmshApp.xml @@ -0,0 +1,26 @@ + + diff --git a/test/user/testdata/cube_41_binary_surf_gmshApp.msh b/test/user/testdata/cube_41_binary_surf_gmshApp.msh new file mode 100644 index 0000000000000000000000000000000000000000..51c0a79812f843b02a487615cd147864de274966 GIT binary patch literal 1526 zcmZ|ONlwE+5Cu>(gPCWRS$K^Ui3K}WVF!0WiX|eEL?XT)SIH4_1njt;6B?yAU%&pZ zsvfF~w{rIRI{hAhRQc0mzt`_Q=UZ8p<;7q!T+?b=jjB;O%WLt^YTWB<#pge59CFQH zA|AL1q(}FzWAQ#6){PZD3t!5&>v$T&8?9M8Eo8fadx86b2Z4uyM}ba#9Q-8kG|-+} z>cB8eS$FWbn1Au)BIL{K{E<(0fqWa|QNQ!6lf?Degz9(wBA;J8<>K;F9fymTR(G4I@muE2f=;|3* zemcK>Ifp@(&yUmP-U%n57)Ac)#w{}jtdKaPd)A=uxSxNVE6}tPQ rdynhT^`onQ6FN>;?>4FLo}BwG$(KjJ4_%&f(#^TJebAR5srx~{8ca}~ literal 0 HcmV?d00001 diff --git a/test/user/testdata/cube_41_binary_surf_gmshApp.xml b/test/user/testdata/cube_41_binary_surf_gmshApp.xml new file mode 100644 index 00000000..c61dab17 --- /dev/null +++ b/test/user/testdata/cube_41_binary_surf_gmshApp.xml @@ -0,0 +1,26 @@ + + diff --git a/test/user/testdata/malformed_shark_41_ascii_missing_node_index.xml b/test/user/testdata/malformed_shark_41_ascii_missing_node_index.xml index 5e810d10..b3ac3664 100644 --- a/test/user/testdata/malformed_shark_41_ascii_missing_node_index.xml +++ b/test/user/testdata/malformed_shark_41_ascii_missing_node_index.xml @@ -19,7 +19,7 @@ - diff --git a/test/user/testdata/shark_41_ascii_missing_node_index.msh b/test/user/testdata/shark_41_ascii_missing_node_index.msh index ab7e2de6..8789156b 100644 --- a/test/user/testdata/shark_41_ascii_missing_node_index.msh +++ b/test/user/testdata/shark_41_ascii_missing_node_index.msh @@ -8,7 +8,6 @@ $EndEntities $Nodes 1 652 1 652 3 0 0 652 -1 2 3 4 diff --git a/test/user/user_flex_test.cc b/test/user/user_flex_test.cc index bf2198c0..7b382dfe 100644 --- a/test/user/user_flex_test.cc +++ b/test/user/user_flex_test.cc @@ -23,6 +23,8 @@ #include #include "test/fixture.h" + + namespace mujoco { namespace { @@ -32,6 +34,8 @@ using ::testing::HasSubstr; using UserFlexTest = MujocoTest; + + TEST_F(UserFlexTest, ParentMustHaveName) { static constexpr char xml[] = R"( @@ -255,6 +259,56 @@ TEST_F(UserFlexTest, LoadMSHBinaryGMSH_22_Success) { mj_deleteData(d); } +TEST_F(UserFlexTest, LoadMSHSurfaceBinaryGMSH_41_Success) { + const std::string xml_path = + GetTestDataFilePath("user/testdata/cube_41_binary_surf_gmshApp.xml"); + std::array error; + mjModel* m = mj_loadXML(xml_path.c_str(), 0, error.data(), error.size()); + ASSERT_THAT(m, NotNull()) << error.data(); + mjData* d = mj_makeData(m); + EXPECT_EQ(m->nflexvert, 14); + EXPECT_EQ(m->nflexelem, 24); + + // first node x y z + EXPECT_EQ(m->flex_xvert0[0], -0.5 ); + EXPECT_EQ(m->flex_xvert0[1], -0.5 ); + EXPECT_EQ(m->flex_xvert0[2], 0 ); + + // first element + EXPECT_EQ(m->flex_elem[0], 9-1 ); + EXPECT_EQ(m->flex_elem[1], 4-1 ); + EXPECT_EQ(m->flex_elem[2], 3-1 ); + + mj_step(m, d); + mj_deleteModel(m); + mj_deleteData(d); +} + +TEST_F(UserFlexTest, LoadMSHSurfaceBinaryGMSH_22_Success) { + const std::string xml_path = + GetTestDataFilePath("user/testdata/cube_22_binary_surf_gmshApp.xml"); + std::array error; + mjModel* m = mj_loadXML(xml_path.c_str(), 0, error.data(), error.size()); + ASSERT_THAT(m, NotNull()) << error.data(); + mjData* d = mj_makeData(m); + EXPECT_EQ(m->nflexvert, 14); + EXPECT_EQ(m->nflexelem, 24); + + // first node x y z + EXPECT_EQ(m->flex_xvert0[0], -0.5 ); + EXPECT_EQ(m->flex_xvert0[1], -0.5 ); + EXPECT_EQ(m->flex_xvert0[2], 0 ); + + // first element + EXPECT_EQ(m->flex_elem[0], 9-1 ); + EXPECT_EQ(m->flex_elem[1], 4-1 ); + EXPECT_EQ(m->flex_elem[2], 3-1 ); + + mj_step(m, d); + mj_deleteModel(m); + mj_deleteData(d); +} + TEST_F(UserFlexTest, LoadMSHBinaryFTETWILD_22_Success) { const std::string xml_path = GetTestDataFilePath("user/testdata/shark_22_binary_fTetWild.xml"); @@ -297,6 +351,56 @@ TEST_F(UserFlexTest, LoadMSHASCIIGMSH_22_Success) { mj_deleteData(d); } +TEST_F(UserFlexTest, LoadMSHSurfaceASCIIGMSH_41_Success) { + const std::string xml_path = + GetTestDataFilePath("user/testdata/cube_41_ascii_surf_gmshApp.xml"); + std::array error; + mjModel* m = mj_loadXML(xml_path.c_str(), 0, error.data(), error.size()); + ASSERT_THAT(m, NotNull()) << error.data(); + mjData* d = mj_makeData(m); + EXPECT_EQ(m->nflexvert, 14); + EXPECT_EQ(m->nflexelem, 24); + + // first node x y z + EXPECT_EQ(m->flex_xvert0[0], -0.5 ); + EXPECT_EQ(m->flex_xvert0[1], -0.5 ); + EXPECT_EQ(m->flex_xvert0[2], 0 ); + + // first element + EXPECT_EQ(m->flex_elem[0], 9-1 ); + EXPECT_EQ(m->flex_elem[1], 4-1 ); + EXPECT_EQ(m->flex_elem[2], 3-1 ); + + mj_step(m, d); + mj_deleteModel(m); + mj_deleteData(d); +} + +TEST_F(UserFlexTest, LoadMSHSurfaceASCIIGMSH_22_Success) { + const std::string xml_path = + GetTestDataFilePath("user/testdata/cube_22_ascii_surf_gmshApp.xml"); + std::array error; + mjModel* m = mj_loadXML(xml_path.c_str(), 0, error.data(), error.size()); + ASSERT_THAT(m, NotNull()) << error.data(); + mjData* d = mj_makeData(m); + EXPECT_EQ(m->nflexvert, 14); + EXPECT_EQ(m->nflexelem, 24); + + // first node x y z + EXPECT_EQ(m->flex_xvert0[0], -0.5 ); + EXPECT_EQ(m->flex_xvert0[1], -0.5 ); + EXPECT_EQ(m->flex_xvert0[2], 0 ); + + // first element + EXPECT_EQ(m->flex_elem[0], 9-1 ); + EXPECT_EQ(m->flex_elem[1], 4-1 ); + EXPECT_EQ(m->flex_elem[2], 3-1 ); + + mj_step(m, d); + mj_deleteModel(m); + mj_deleteData(d); +} + TEST_F(UserFlexTest, LoadMSHASCIIFTETWILD_22_Success) { const std::string xml_path = GetTestDataFilePath("user/testdata/shark_22_ascii_fTetWild.xml"); @@ -329,7 +433,7 @@ TEST_F(UserFlexTest, LoadMSHASCII_41_MissingNodeIndex_Fail) { std::array error; mjModel* m = mj_loadXML(xml_path.c_str(), 0, error.data(), error.size()); EXPECT_THAT(error.data(), HasSubstr( - "XML Error: Element size must be a multiple of dim+1")); + "XML Error: Error: Node tags must be sequential")); mj_deleteModel(m); } @@ -361,8 +465,9 @@ TEST_F(UserFlexTest, LoadMSHASCII_22_MissingNumNodes_Fail) { "user/testdata/malformed_shark_22_ascii_missing_num_nodes.xml"); std::array error; mjModel* m = mj_loadXML(xml_path.c_str(), 0, error.data(), error.size()); - EXPECT_THAT(error.data(), HasSubstr( - "XML Error: Element size must be a multiple of dim+1")); + // TODO(mohammadhamid): Replace with an assertion about the error message. For + // some reason, on Windows the error message is different on GH Actions + EXPECT_THAT(m, IsNull()); mj_deleteModel(m); }