From aa14ba8d11ba5b67b4ec0bf53ae48dce4e7811ad Mon Sep 17 00:00:00 2001 From: Ian Chen Date: Wed, 20 Mar 2024 17:41:37 -0700 Subject: [PATCH 1/7] backport - Add mesh optimization attribute to #1382 Signed-off-by: Ian Chen --- include/sdf/Mesh.hh | 72 ++++++++++++++ sdf/1.11/mesh_shape.sdf | 14 +++ src/Mesh.cc | 163 ++++++++++++++++++++++++++++++- src/Mesh_TEST.cc | 74 ++++++++++++++ test/integration/geometry_dom.cc | 6 ++ test/sdf/shapes.sdf | 5 +- 6 files changed, 332 insertions(+), 2 deletions(-) diff --git a/include/sdf/Mesh.hh b/include/sdf/Mesh.hh index f19d98818..7cc48c955 100644 --- a/include/sdf/Mesh.hh +++ b/include/sdf/Mesh.hh @@ -37,6 +37,47 @@ namespace sdf // Forward declarations. class ParserConfig; + /// \brief Mesh optimization method + enum class MeshOptimization + { + /// \brief No mesh optimization + NONE, + /// \brief Convex hull + CONVEX_HULL, + /// \brief Convex decomposition + CONVEX_DECOMPOSITION + }; + + /// \brief Convex decomposition + class SDFORMAT_VISIBLE ConvexDecomposition + { + /// \brief Default constructor + public: ConvexDecomposition(); + + /// \brief Load the contact based on a element pointer. This is *not* the + /// usual entry point. Typical usage of the SDF DOM is through the Root + /// object. + /// \param[in] _sdf The SDF Element pointer + /// \return Errors, which is a vector of Error objects. Each Error includes + /// an error code and message. An empty vector indicates no error. + public: Errors Load(ElementPtr _sdf); + + /// \brief Get a pointer to the SDF element that was used during + /// load. + /// \return SDF element pointer. The value will be nullptr if Load has + /// not been called. + public: sdf::ElementPtr Element() const; + + /// \brief Get the maximum number of convex hulls that can be generated. + public: unsigned int MaxConvexHulls() const; + + /// \brief Set the maximum number of convex hulls that can be generated. + public: void SetMaxConvexHulls(unsigned int _maxConvexHulls); + + /// \brief Private data pointer. + GZ_UTILS_IMPL_PTR(dataPtr) + }; + /// \brief Mesh represents a mesh shape, and is usually accessed through a /// Geometry. class SDFORMAT_VISIBLE Mesh @@ -61,6 +102,37 @@ namespace sdf /// an error code and message. An empty vector indicates no error. public: Errors Load(sdf::ElementPtr _sdf, const ParserConfig &_config); + /// \brief Get the mesh's optimization method + /// \return The mesh optimization method. + /// MeshOptimization::NONE if no mesh simplificaton is done. + public: MeshOptimization Optimization() const; + + /// \brief Get the mesh's optimization method + /// \return The mesh optimization method. + /// Empty string if no mesh simplificaton is done. + public: std::string OptimizationStr() const; + + /// \brief Set the mesh optimization method. + /// \param[in] _optimization The mesh optimization method. + public: void SetOptimization(MeshOptimization _optimization); + + /// \brief Set the mesh optimization method. + /// \param[in] _optimization The mesh optimization method. + /// \return True if the _optimizationStr parameter matched a known + /// mesh optimization method. False if the mesh optimization method + /// could not be set. + public: bool SetOptimization(const std::string &_optimizationStr); + + /// \brief Get the associated ConvexDecomposition object + /// \returns Pointer to the associated ConvexDecomposition object, + /// nullptr if the Mesh doesn't contain a ConvexDecomposition element. + public: const sdf::ConvexDecomposition *ConvexDecomposition() const; + + /// \brief Set the associated ConvexDecomposition object. + /// \param[in] _convexDecomposition The ConvexDecomposition object. + public: void SetConvexDecomposition( + const sdf::ConvexDecomposition &_convexDecomposition); + /// \brief Get the mesh's URI. /// \return The URI of the mesh data. public: std::string Uri() const; diff --git a/sdf/1.11/mesh_shape.sdf b/sdf/1.11/mesh_shape.sdf index 61bce76dd..a59300d46 100644 --- a/sdf/1.11/mesh_shape.sdf +++ b/sdf/1.11/mesh_shape.sdf @@ -1,5 +1,19 @@ Mesh shape + + + + Set whether to optimize the mesh using one of the specified methods. Values include: "convex_hull" - a single convex hull that encapsulates the mesh, "convex_decomposition" - decompose the mesh into multiple convex hull meshes. Default value is an empty string which means no mesh optimization. + + + + + Convex decomposition parameters. Applicable if the mesh optimization attribute is set to convex_decomposition + + Maximum number of convex hulls to decompose into. If the input mesh has multiple submeshes, this limit is applied when decomposing each submesh + + + Mesh uri diff --git a/src/Mesh.cc b/src/Mesh.cc index 9dfddcf2b..f026e2d63 100644 --- a/src/Mesh.cc +++ b/src/Mesh.cc @@ -14,8 +14,10 @@ * limitations under the License. * */ +#include #include #include +#include #include #include "sdf/CustomInertiaCalcProperties.hh" @@ -27,9 +29,35 @@ using namespace sdf; -// Private data class +/// Mesh Optimization method strings. These should match the data in +/// `enum class MeshOptimization` located in Mesh.hh, and the size +/// template parameter should match the number of elements as well. +constexpr std::array kMeshOptimizationStrs = +{ + "", + "convex_hull", + "convex_decomposition" +}; + +// Private data class for ConvexDecomposition +class sdf::ConvexDecomposition::Implementation +{ + /// \brief Maximum number of convex hulls to generate. + public: unsigned int maxConvexHulls{16u}; + + /// \brief The SDF element pointer used during load. + public: sdf::ElementPtr sdf = nullptr; +}; + +// Private data class for Mesh class sdf::Mesh::Implementation { + /// \brief Mesh optimization method + public: MeshOptimization optimization = MeshOptimization::NONE; + + /// \brief Optional convex decomposition. + public: std::optional convexDecomposition; + /// \brief The mesh's URI. public: std::string uri = ""; @@ -49,6 +77,62 @@ class sdf::Mesh::Implementation public: sdf::ElementPtr sdf = nullptr; }; +///////////////////////////////////////////////// +ConvexDecomposition::ConvexDecomposition() + : dataPtr(gz::utils::MakeImpl()) +{ +} + +///////////////////////////////////////////////// +Errors ConvexDecomposition::Load(ElementPtr _sdf) +{ + Errors errors; + + this->dataPtr->sdf = _sdf; + + // Check that sdf is a valid pointer + if (!_sdf) + { + errors.push_back({ErrorCode::ELEMENT_MISSING, + "Attempting to load convex decomposition, " + "but the provided SDF element is null."}); + return errors; + } + + // We need a convex_decomposition element + if (_sdf->GetName() != "convex_decomposition") + { + errors.push_back({ErrorCode::ELEMENT_INCORRECT_TYPE, + "Attempting to load convex decomposition, but the provided SDF " + "element is not ."}); + return errors; + } + + this->dataPtr->maxConvexHulls = _sdf->Get( + errors, "max_convex_hulls", + this->dataPtr->maxConvexHulls).first; + + return errors; +} + +///////////////////////////////////////////////// +sdf::ElementPtr ConvexDecomposition::Element() const +{ + return this->dataPtr->sdf; +} + +///////////////////////////////////////////////// +unsigned int ConvexDecomposition::MaxConvexHulls() const +{ + return this->dataPtr->maxConvexHulls; +} + +///////////////////////////////////////////////// +void ConvexDecomposition::SetMaxConvexHulls(unsigned int _maxConvexHulls) +{ + this->dataPtr->maxConvexHulls = _maxConvexHulls; +} + ///////////////////////////////////////////////// Mesh::Mesh() : dataPtr(gz::utils::MakeImpl()) @@ -61,6 +145,7 @@ Errors Mesh::Load(ElementPtr _sdf) return this->Load(_sdf, ParserConfig::GlobalConfig()); } + ///////////////////////////////////////////////// Errors Mesh::Load(ElementPtr _sdf, const ParserConfig &_config) { @@ -87,6 +172,20 @@ Errors Mesh::Load(ElementPtr _sdf, const ParserConfig &_config) return errors; } + // Optimization + if (_sdf->HasAttribute("optimization")) + { + this->SetOptimization(_sdf->Get("optimization", "").first); + } + + if (_sdf->HasElement("convex_decomposition")) + { + this->dataPtr->convexDecomposition.emplace(); + Errors err = this->dataPtr->convexDecomposition->Load( + _sdf->GetElement("convex_decomposition", errors)); + errors.insert(errors.end(), err.begin(), err.end()); + } + if (_sdf->HasElement("uri")) { std::unordered_set paths; @@ -140,6 +239,56 @@ sdf::ElementPtr Mesh::Element() const return this->dataPtr->sdf; } +////////////////////////////////////////////////// +MeshOptimization Mesh::Optimization() const +{ + return this->dataPtr->optimization; +} + +////////////////////////////////////////////////// +std::string Mesh::OptimizationStr() const +{ + size_t index = static_cast(this->dataPtr->optimization); + if (index < kMeshOptimizationStrs.size()) + return std::string(kMeshOptimizationStrs[index]); + return ""; +} + +////////////////////////////////////////////////// +bool Mesh::SetOptimization(const std::string &_optimizationStr) +{ + for (size_t i = 0; i < kMeshOptimizationStrs.size(); ++i) + { + if (_optimizationStr == kMeshOptimizationStrs[i]) + { + this->dataPtr->optimization = static_cast(i); + return true; + } + } + return false; +} + +////////////////////////////////////////////////// +void Mesh::SetOptimization(MeshOptimization _optimization) +{ + this->dataPtr->optimization = _optimization; +} + +////////////////////////////////////////////////// +const sdf::ConvexDecomposition *Mesh::ConvexDecomposition() const +{ + if (this->dataPtr->convexDecomposition.has_value()) + return &this->dataPtr->convexDecomposition.value(); + return nullptr; +} + +////////////////////////////////////////////////// + void Mesh::SetConvexDecomposition( + const sdf::ConvexDecomposition &_convexDecomposition) +{ + this->dataPtr->convexDecomposition = _convexDecomposition; +} + ////////////////////////////////////////////////// std::string Mesh::Uri() const { @@ -244,6 +393,18 @@ sdf::ElementPtr Mesh::ToElement(sdf::Errors &_errors) const sdf::ElementPtr elem(new sdf::Element); sdf::initFile("mesh_shape.sdf", elem); + // Optimization + elem->GetAttribute("optimization")->Set( + this->OptimizationStr()); + + if (this->dataPtr->convexDecomposition.has_value()) + { + sdf::ElementPtr convexDecomp = elem->GetElement("convex_decomposition", + _errors); + convexDecomp->GetElement("max_convex_hulls")->Set( + this->dataPtr->convexDecomposition->MaxConvexHulls()); + } + // Uri sdf::ElementPtr uriElem = elem->GetElement("uri", _errors); uriElem->Set(_errors, this->Uri()); diff --git a/src/Mesh_TEST.cc b/src/Mesh_TEST.cc index 51c1751f4..29ef70d85 100644 --- a/src/Mesh_TEST.cc +++ b/src/Mesh_TEST.cc @@ -34,6 +34,9 @@ TEST(DOMMesh, Construction) sdf::Mesh mesh; EXPECT_EQ(nullptr, mesh.Element()); + EXPECT_EQ(std::string(), mesh.OptimizationStr()); + EXPECT_EQ(sdf::MeshOptimization::NONE, mesh.Optimization()); + EXPECT_EQ(nullptr, mesh.ConvexDecomposition()); EXPECT_EQ(std::string(), mesh.FilePath()); EXPECT_EQ(std::string(), mesh.Uri()); EXPECT_EQ(std::string(), mesh.Submesh()); @@ -45,24 +48,37 @@ TEST(DOMMesh, Construction) TEST(DOMMesh, MoveConstructor) { sdf::Mesh mesh; + EXPECT_TRUE(mesh.SetOptimization("convex_decomposition")); mesh.SetUri("banana"); mesh.SetSubmesh("watermelon"); mesh.SetCenterSubmesh(true); mesh.SetScale({0.5, 0.6, 0.7}); mesh.SetFilePath("/pear"); + sdf::ConvexDecomposition convexDecomp; + EXPECT_EQ(nullptr, convexDecomp.Element()); + convexDecomp.SetMaxConvexHulls(10u); + mesh.SetConvexDecomposition(convexDecomp); + sdf::Mesh mesh2(std::move(mesh)); + EXPECT_EQ("convex_decomposition", mesh2.OptimizationStr()); + EXPECT_EQ(sdf::MeshOptimization::CONVEX_DECOMPOSITION, mesh2.Optimization()); EXPECT_EQ("banana", mesh2.Uri()); EXPECT_EQ("watermelon", mesh2.Submesh()); EXPECT_EQ(gz::math::Vector3d(0.5, 0.6, 0.7), mesh2.Scale()); EXPECT_TRUE(mesh2.CenterSubmesh()); EXPECT_EQ("/pear", mesh2.FilePath()); + + auto convexDecomp2 = mesh2.ConvexDecomposition(); + ASSERT_NE(nullptr, convexDecomp2); + EXPECT_EQ(10u, convexDecomp2->MaxConvexHulls()); } ///////////////////////////////////////////////// TEST(DOMMesh, CopyConstructor) { sdf::Mesh mesh; + EXPECT_TRUE(mesh.SetOptimization("convex_hull")); mesh.SetUri("banana"); mesh.SetSubmesh("watermelon"); mesh.SetCenterSubmesh(true); @@ -70,6 +86,9 @@ TEST(DOMMesh, CopyConstructor) mesh.SetFilePath("/pear"); sdf::Mesh mesh2(mesh); + EXPECT_EQ("convex_hull", mesh2.OptimizationStr()); + EXPECT_EQ(sdf::MeshOptimization::CONVEX_HULL, mesh2.Optimization()); + EXPECT_EQ(nullptr, mesh2.ConvexDecomposition()); EXPECT_EQ("banana", mesh2.Uri()); EXPECT_EQ("watermelon", mesh2.Submesh()); EXPECT_EQ(gz::math::Vector3d(0.5, 0.6, 0.7), mesh2.Scale()); @@ -81,6 +100,7 @@ TEST(DOMMesh, CopyConstructor) TEST(DOMMesh, CopyAssignmentOperator) { sdf::Mesh mesh; + EXPECT_TRUE(mesh.SetOptimization("convex_hull")); mesh.SetUri("banana"); mesh.SetSubmesh("watermelon"); mesh.SetCenterSubmesh(true); @@ -89,6 +109,9 @@ TEST(DOMMesh, CopyAssignmentOperator) sdf::Mesh mesh2; mesh2 = mesh; + EXPECT_EQ("convex_hull", mesh2.OptimizationStr()); + EXPECT_EQ(sdf::MeshOptimization::CONVEX_HULL, mesh2.Optimization()); + EXPECT_EQ(nullptr, mesh2.ConvexDecomposition()); EXPECT_EQ("banana", mesh2.Uri()); EXPECT_EQ("watermelon", mesh2.Submesh()); EXPECT_EQ(gz::math::Vector3d(0.5, 0.6, 0.7), mesh2.Scale()); @@ -100,6 +123,7 @@ TEST(DOMMesh, CopyAssignmentOperator) TEST(DOMMesh, MoveAssignmentOperator) { sdf::Mesh mesh; + EXPECT_TRUE(mesh.SetOptimization("convex_hull")); mesh.SetUri("banana"); mesh.SetSubmesh("watermelon"); mesh.SetCenterSubmesh(true); @@ -108,6 +132,9 @@ TEST(DOMMesh, MoveAssignmentOperator) sdf::Mesh mesh2; mesh2 = std::move(mesh); + EXPECT_EQ("convex_hull", mesh2.OptimizationStr()); + EXPECT_EQ(sdf::MeshOptimization::CONVEX_HULL, mesh2.Optimization()); + EXPECT_EQ(nullptr, mesh2.ConvexDecomposition()); EXPECT_EQ("banana", mesh2.Uri()); EXPECT_EQ("watermelon", mesh2.Submesh()); EXPECT_EQ(gz::math::Vector3d(0.5, 0.6, 0.7), mesh2.Scale()); @@ -140,6 +167,29 @@ TEST(DOMMesh, Set) sdf::Mesh mesh; EXPECT_EQ(nullptr, mesh.Element()); + EXPECT_EQ(std::string(), mesh.OptimizationStr()); + EXPECT_TRUE(mesh.SetOptimization("convex_hull")); + EXPECT_EQ("convex_hull", mesh.OptimizationStr()); + EXPECT_EQ(sdf::MeshOptimization::CONVEX_HULL, mesh.Optimization()); + mesh.SetOptimization(sdf::MeshOptimization::CONVEX_DECOMPOSITION); + EXPECT_EQ("convex_decomposition", mesh.OptimizationStr()); + EXPECT_EQ(sdf::MeshOptimization::CONVEX_DECOMPOSITION, + mesh.Optimization()); + // check invalid inputs + EXPECT_FALSE(mesh.SetOptimization("invalid")); + { + auto invalidMeshOpt = static_cast(99); + mesh.SetOptimization(invalidMeshOpt); + EXPECT_EQ(invalidMeshOpt, mesh.Optimization()); + EXPECT_EQ("", mesh.OptimizationStr()); + } + + sdf::ConvexDecomposition convexDecomp; + convexDecomp.SetMaxConvexHulls(10u); + mesh.SetConvexDecomposition(convexDecomp); + ASSERT_NE(nullptr, mesh.ConvexDecomposition()); + EXPECT_EQ(10u, mesh.ConvexDecomposition()->MaxConvexHulls()); + EXPECT_EQ(std::string(), mesh.Uri()); mesh.SetUri("http://myuri.com"); EXPECT_EQ("http://myuri.com", mesh.Uri()); @@ -165,6 +215,7 @@ TEST(DOMMesh, Set) TEST(DOMMesh, Load) { sdf::Mesh mesh; + sdf::ConvexDecomposition convexDecomp; sdf::Errors errors; // Null element name @@ -173,6 +224,11 @@ TEST(DOMMesh, Load) EXPECT_EQ(sdf::ErrorCode::ELEMENT_MISSING, errors[0].Code()); EXPECT_EQ(nullptr, mesh.Element()); + errors = convexDecomp.Load(nullptr); + ASSERT_EQ(1u, errors.size()); + EXPECT_EQ(sdf::ErrorCode::ELEMENT_MISSING, errors[0].Code()); + EXPECT_EQ(nullptr, convexDecomp.Element()); + // Bad element name sdf::ElementPtr sdf(new sdf::Element()); sdf->SetName("bad"); @@ -181,6 +237,11 @@ TEST(DOMMesh, Load) EXPECT_EQ(sdf::ErrorCode::ELEMENT_INCORRECT_TYPE, errors[0].Code()); EXPECT_NE(nullptr, mesh.Element()); + errors = convexDecomp.Load(sdf); + ASSERT_EQ(1u, errors.size()); + EXPECT_EQ(sdf::ErrorCode::ELEMENT_INCORRECT_TYPE, errors[0].Code()); + EXPECT_NE(nullptr, convexDecomp.Element()); + // Missing element sdf->SetName("mesh"); errors = mesh.Load(sdf); @@ -296,21 +357,30 @@ TEST(DOMMesh, ToElement) { sdf::Mesh mesh; + EXPECT_TRUE(mesh.SetOptimization("convex_decomposition")); mesh.SetUri("mesh-uri"); mesh.SetScale(gz::math::Vector3d(1, 2, 3)); mesh.SetSubmesh("submesh"); mesh.SetCenterSubmesh(false); + sdf::ConvexDecomposition convexDecomp; + convexDecomp.SetMaxConvexHulls(10u); + mesh.SetConvexDecomposition(convexDecomp); + sdf::ElementPtr elem = mesh.ToElement(); ASSERT_NE(nullptr, elem); sdf::Mesh mesh2; mesh2.Load(elem); + EXPECT_EQ(mesh.OptimizationStr(), mesh2.OptimizationStr()); + EXPECT_EQ(mesh.Optimization(), mesh2.Optimization()); EXPECT_EQ(mesh.Uri(), mesh2.Uri()); EXPECT_EQ(mesh.Scale(), mesh2.Scale()); EXPECT_EQ(mesh.Submesh(), mesh2.Submesh()); EXPECT_EQ(mesh.CenterSubmesh(), mesh2.CenterSubmesh()); + ASSERT_NE(nullptr, mesh2.ConvexDecomposition()); + EXPECT_EQ(10u, mesh2.ConvexDecomposition()->MaxConvexHulls()); } ///////////////////////////////////////////////// @@ -332,6 +402,7 @@ TEST(DOMMesh, ToElementErrorOutput) sdf::Mesh mesh; sdf::Errors errors; + EXPECT_TRUE(mesh.SetOptimization("convex_hull")); mesh.SetUri("mesh-uri"); mesh.SetScale(gz::math::Vector3d(1, 2, 3)); mesh.SetSubmesh("submesh"); @@ -345,6 +416,9 @@ TEST(DOMMesh, ToElementErrorOutput) errors = mesh2.Load(elem); EXPECT_TRUE(errors.empty()); + EXPECT_EQ(mesh.OptimizationStr(), mesh2.OptimizationStr()); + EXPECT_EQ(mesh.Optimization(), mesh2.Optimization()); + EXPECT_EQ(nullptr, mesh2.ConvexDecomposition()); EXPECT_EQ(mesh.Uri(), mesh2.Uri()); EXPECT_EQ(mesh.Scale(), mesh2.Scale()); EXPECT_EQ(mesh.Submesh(), mesh2.Submesh()); diff --git a/test/integration/geometry_dom.cc b/test/integration/geometry_dom.cc index 07d50d570..297d1f0af 100644 --- a/test/integration/geometry_dom.cc +++ b/test/integration/geometry_dom.cc @@ -179,6 +179,12 @@ TEST(DOMGeometry, Shapes) EXPECT_EQ(sdf::GeometryType::MESH, meshCol->Geom()->Type()); const sdf::Mesh *meshColGeom = meshCol->Geom()->MeshShape(); ASSERT_NE(nullptr, meshColGeom); + EXPECT_EQ("convex_decomposition", meshColGeom->OptimizationStr()); + EXPECT_EQ(sdf::MeshOptimization::CONVEX_DECOMPOSITION, + meshColGeom->Optimization()); + ASSERT_NE(nullptr, meshColGeom->ConvexDecomposition()); + EXPECT_EQ(4u, meshColGeom->ConvexDecomposition()->MaxConvexHulls()); + EXPECT_EQ("https://fuel.gazebosim.org/1.0/an_org/models/a_model/mesh/" "mesh.dae", meshColGeom->Uri()); EXPECT_TRUE(gz::math::Vector3d(0.1, 0.2, 0.3) == diff --git a/test/sdf/shapes.sdf b/test/sdf/shapes.sdf index 1f5f4fa27..f411afa37 100644 --- a/test/sdf/shapes.sdf +++ b/test/sdf/shapes.sdf @@ -120,7 +120,10 @@ - + + + 4 + https://fuel.gazebosim.org/1.0/an_org/models/a_model/mesh/mesh.dae my_submesh From d50b3ff90e3b709ef64bfca9b400d0e071f97009 Mon Sep 17 00:00:00 2001 From: Ian Chen Date: Tue, 26 Mar 2024 14:00:35 -0700 Subject: [PATCH 2/7] backport - Update max_convex_hulls description #1386 Signed-off-by: Ian Chen --- sdf/1.11/mesh_shape.sdf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdf/1.11/mesh_shape.sdf b/sdf/1.11/mesh_shape.sdf index a59300d46..1ed64ca44 100644 --- a/sdf/1.11/mesh_shape.sdf +++ b/sdf/1.11/mesh_shape.sdf @@ -10,7 +10,7 @@ Convex decomposition parameters. Applicable if the mesh optimization attribute is set to convex_decomposition - Maximum number of convex hulls to decompose into. If the input mesh has multiple submeshes, this limit is applied when decomposing each submesh + Maximum number of convex hulls to decompose into. This sets the maximum number of submeshes that the final decomposed mesh will contain. From 1c0bc304d43285223e69e52abc8c3d9269d67713 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Hern=C3=A1ndez=20Cordero?= Date: Tue, 9 Apr 2024 22:45:58 +0200 Subject: [PATCH 3/7] Added Python wrapper to ConvexDecomposition (#1398) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Alejandro Hernández Cordero Co-authored-by: Ian Chen --- python/CMakeLists.txt | 1 + python/src/sdf/_gz_sdformat_pybind11.cc | 2 + python/src/sdf/pyConvexDecomposition.cc | 50 +++++++++++++++++++++++++ python/src/sdf/pyConvexDecomposition.hh | 41 ++++++++++++++++++++ python/src/sdf/pyMesh.cc | 22 +++++++++++ python/test/pyMesh_TEST.py | 44 +++++++++++++++++++++- 6 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 python/src/sdf/pyConvexDecomposition.cc create mode 100644 python/src/sdf/pyConvexDecomposition.hh diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 62078d8db..f9cc0917d 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -52,6 +52,7 @@ pybind11_add_module(${BINDINGS_MODULE_NAME} MODULE src/sdf/pyCamera.cc src/sdf/pyCapsule.cc src/sdf/pyCollision.cc + src/sdf/pyConvexDecomposition.cc src/sdf/pyCylinder.cc src/sdf/pyElement.cc src/sdf/pyEllipsoid.cc diff --git a/python/src/sdf/_gz_sdformat_pybind11.cc b/python/src/sdf/_gz_sdformat_pybind11.cc index d982e13f4..298cd1362 100644 --- a/python/src/sdf/_gz_sdformat_pybind11.cc +++ b/python/src/sdf/_gz_sdformat_pybind11.cc @@ -26,6 +26,7 @@ #include "pyCamera.hh" #include "pyCapsule.hh" #include "pyCollision.hh" +#include "pyConvexDecomposition.hh" #include "pyCylinder.hh" #include "pyElement.hh" #include "pyEllipsoid.hh" @@ -85,6 +86,7 @@ PYBIND11_MODULE(BINDINGS_MODULE_NAME, m) { sdf::python::defineCamera(m); sdf::python::defineCapsule(m); sdf::python::defineCollision(m); + sdf::python::defineConvexDecomposition(m); sdf::python::defineContact(m); sdf::python::defineCylinder(m); // PrintConfig has to be defined before Param and Element because it's used as diff --git a/python/src/sdf/pyConvexDecomposition.cc b/python/src/sdf/pyConvexDecomposition.cc new file mode 100644 index 000000000..aacd7ee42 --- /dev/null +++ b/python/src/sdf/pyConvexDecomposition.cc @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2024 Open Source Robotics Foundation + * + * 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. + */ + +#include "pyMesh.hh" + +#include + +#include "sdf/ParserConfig.hh" +#include "sdf/Mesh.hh" + +using namespace pybind11::literals; + +namespace sdf +{ +// Inline bracket to help doxygen filtering. +inline namespace SDF_VERSION_NAMESPACE { +namespace python +{ +///////////////////////////////////////////////// +void defineConvexDecomposition(pybind11::object module) +{ + pybind11::class_(module, "ConvexDecomposition") + .def(pybind11::init<>()) + .def("max_convex_hulls", &sdf::ConvexDecomposition::MaxConvexHulls, + "Get the maximum number of convex hulls that can be generated.") + .def("set_max_convex_hulls", &sdf::ConvexDecomposition::SetMaxConvexHulls, + "Set the maximum number of convex hulls that can be generated.") + .def("__copy__", [](const sdf::ConvexDecomposition &self) { + return sdf::ConvexDecomposition(self); + }) + .def("__deepcopy__", [](const sdf::ConvexDecomposition &self, pybind11::dict) { + return sdf::ConvexDecomposition(self); + }, "memo"_a); +} +} // namespace python +} // namespace SDF_VERSION_NAMESPACE +} // namespace sdf diff --git a/python/src/sdf/pyConvexDecomposition.hh b/python/src/sdf/pyConvexDecomposition.hh new file mode 100644 index 000000000..78a58840f --- /dev/null +++ b/python/src/sdf/pyConvexDecomposition.hh @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2024 Open Source Robotics Foundation + * + * 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 SDFORMAT_PYTHON_CONVEX_DECOMPOSITION_HH_ +#define SDFORMAT_PYTHON_CONVEX_DECOMPOSITION_HH_ + +#include + +#include "sdf/Mesh.hh" + +#include "sdf/config.hh" + +namespace sdf +{ +// Inline bracket to help doxygen filtering. +inline namespace SDF_VERSION_NAMESPACE { +namespace python +{ +/// Define a pybind11 wrapper for an sdf::ConvexDecomposition +/** + * \param[in] module a pybind11 module to add the definition to + */ +void defineConvexDecomposition(pybind11::object module); +} // namespace python +} // namespace SDF_VERSION_NAMESPACE +} // namespace sdf + +#endif // SDFORMAT_PYTHON_CONVEX_DECOMPOSITION_HH_ diff --git a/python/src/sdf/pyMesh.cc b/python/src/sdf/pyMesh.cc index 85705f874..b9fdafd17 100644 --- a/python/src/sdf/pyMesh.cc +++ b/python/src/sdf/pyMesh.cc @@ -35,6 +35,23 @@ void defineMesh(pybind11::object module) pybind11::class_(module, "Mesh") .def(pybind11::init<>()) .def(pybind11::init()) + .def("optimization", &sdf::Mesh::Optimization, + "Get the mesh's optimization method.") + .def("optimization_str", &sdf::Mesh::OptimizationStr, + "Get the mesh's optimization method") + .def("set_optimization", + pybind11::overload_cast( + &sdf::Mesh::SetOptimization), + "Set the mesh optimization method.") + .def("set_optimization", + pybind11::overload_cast( + &sdf::Mesh::SetOptimization), + "Set the mesh optimization method.") + .def("convex_decomposition", &sdf::Mesh::ConvexDecomposition, + pybind11::return_value_policy::reference_internal, + "Get the associated ConvexDecomposition object") + .def("set_convex_decomposition", &sdf::Mesh::SetConvexDecomposition, + "Set the associated ConvexDecomposition object.") .def("uri", &sdf::Mesh::Uri, "Get the mesh's URI.") .def("set_uri", &sdf::Mesh::SetUri, @@ -67,6 +84,11 @@ void defineMesh(pybind11::object module) .def("__deepcopy__", [](const sdf::Mesh &self, pybind11::dict) { return sdf::Mesh(self); }, "memo"_a); + + pybind11::enum_(module, "MeshOptimization") + .value("NONE", sdf::MeshOptimization::NONE) + .value("CONVEX_HULL", sdf::MeshOptimization::CONVEX_HULL) + .value("CONVEX_DECOMPOSITION", sdf::MeshOptimization::CONVEX_DECOMPOSITION); } } // namespace python } // namespace SDF_VERSION_NAMESPACE diff --git a/python/test/pyMesh_TEST.py b/python/test/pyMesh_TEST.py index 64d89a57a..ac9840ea6 100644 --- a/python/test/pyMesh_TEST.py +++ b/python/test/pyMesh_TEST.py @@ -13,8 +13,9 @@ # limitations under the License. import copy -from gz_test_deps.sdformat import Mesh +from gz_test_deps.sdformat import Mesh, ConvexDecomposition from gz_test_deps.math import Vector3d +import gz_test_deps.sdformat as sdf import unittest @@ -23,6 +24,9 @@ class MeshTEST(unittest.TestCase): def test_default_construction(self): mesh = Mesh() + self.assertEqual("", mesh.optimization_str()) + self.assertEqual(sdf.MeshOptimization.NONE, mesh.optimization()) + self.assertEqual(None, mesh.convex_decomposition()) self.assertEqual("", mesh.file_path()) self.assertEqual("", mesh.uri()) self.assertEqual("", mesh.submesh()) @@ -32,19 +36,29 @@ def test_default_construction(self): def test_assigment(self): mesh = Mesh() + self.assertTrue(mesh.set_optimization("convex_decomposition")) mesh.set_uri("banana") mesh.set_submesh("watermelon") mesh.set_center_submesh(True) mesh.set_scale(Vector3d(0.5, 0.6, 0.7)) mesh.set_file_path("/pear") + convexDecomp = ConvexDecomposition() + convexDecomp.set_max_convex_hulls(10) + mesh.set_convex_decomposition(convexDecomp) + mesh2 = mesh + self.assertEqual("convex_decomposition", mesh2.optimization_str()) + self.assertEqual(sdf.MeshOptimization.CONVEX_DECOMPOSITION, mesh2.optimization()) self.assertEqual("banana", mesh2.uri()) self.assertEqual("watermelon", mesh2.submesh()) self.assertEqual(Vector3d(0.5, 0.6, 0.7), mesh2.scale()) self.assertTrue(mesh2.center_submesh()) self.assertEqual("/pear", mesh2.file_path()) + convexDecomp2 = mesh2.convex_decomposition() + self.assertEqual(10, convexDecomp2.max_convex_hulls()) + mesh.set_file_path("/apple") self.assertEqual("/apple", mesh2.file_path()) @@ -63,19 +77,29 @@ def test_assigment(self): def test_deepcopy_construction(self): mesh = Mesh() + self.assertTrue(mesh.set_optimization("convex_decomposition")) mesh.set_uri("banana") mesh.set_submesh("watermelon") mesh.set_center_submesh(True) mesh.set_scale(Vector3d(0.5, 0.6, 0.7)) mesh.set_file_path("/pear") + convexDecomp = ConvexDecomposition() + convexDecomp.set_max_convex_hulls(10) + mesh.set_convex_decomposition(convexDecomp) + mesh2 = copy.deepcopy(mesh) + self.assertEqual("convex_decomposition", mesh2.optimization_str()) + self.assertEqual(sdf.MeshOptimization.CONVEX_DECOMPOSITION, mesh2.optimization()) self.assertEqual("banana", mesh2.uri()) self.assertEqual("watermelon", mesh2.submesh()) self.assertEqual(Vector3d(0.5, 0.6, 0.7), mesh2.scale()) self.assertTrue(mesh2.center_submesh()) self.assertEqual("/pear", mesh2.file_path()) + convexDecomp2 = mesh2.convex_decomposition() + self.assertEqual(10, convexDecomp2.max_convex_hulls()) + mesh.set_file_path("/apple") mesh.set_scale(Vector3d(0.3, 0.2, 0.4)) mesh.set_center_submesh(False) @@ -92,6 +116,24 @@ def test_deepcopy_construction(self): def test_set(self): mesh = Mesh() + self.assertEqual("", mesh.optimization_str()) + self.assertTrue(mesh.set_optimization("convex_hull")) + self.assertEqual("convex_hull", mesh.optimization_str()) + self.assertEqual(sdf.MeshOptimization.CONVEX_HULL, mesh.optimization()) + mesh.set_optimization(sdf.MeshOptimization.CONVEX_DECOMPOSITION) + self.assertEqual("convex_decomposition", mesh.optimization_str()) + self.assertEqual(sdf.MeshOptimization.CONVEX_DECOMPOSITION, mesh.optimization()) + + self.assertFalse(mesh.set_optimization("invalid")) + mesh.set_optimization(sdf.MeshOptimization(99)) + self.assertEqual(sdf.MeshOptimization(99), mesh.optimization()) + self.assertEqual("", mesh.optimization_str()) + + convexDecomp = ConvexDecomposition() + convexDecomp.set_max_convex_hulls(10) + mesh.set_convex_decomposition(convexDecomp) + self.assertEqual(10, mesh.convex_decomposition().max_convex_hulls()) + self.assertEqual("", mesh.uri()) mesh.set_uri("http://myuri.com") self.assertEqual("http://myuri.com", mesh.uri()) From 3e93e685486ea4a1eed710284179aebce2505f88 Mon Sep 17 00:00:00 2001 From: "Addisu Z. Taddese" Date: Fri, 19 Apr 2024 15:13:50 -0500 Subject: [PATCH 4/7] Add package.xml, fix `gz sdf` tests on Windows (#1374) The `gz sdf` tests are fixed by * fixing dll path issue with Ruby on windows * Setting home path --------- Signed-off-by: Addisu Z. Taddese --- .github/workflows/package_xml.yml | 11 ++++++ package.xml | 32 +++++++++++++++++ src/Console.cc | 6 ---- src/cmd/CMakeLists.txt | 8 ++++- src/cmd/cmdsdformat.rb.in | 16 ++++++--- src/gz_TEST.cc | 58 +++++++++++++++++-------------- 6 files changed, 94 insertions(+), 37 deletions(-) create mode 100644 .github/workflows/package_xml.yml create mode 100644 package.xml diff --git a/.github/workflows/package_xml.yml b/.github/workflows/package_xml.yml new file mode 100644 index 000000000..4bd4a9aa0 --- /dev/null +++ b/.github/workflows/package_xml.yml @@ -0,0 +1,11 @@ +name: Validate package.xml + +on: + pull_request: + +jobs: + package-xml: + runs-on: ubuntu-latest + name: Validate package.xml + steps: + - uses: gazebo-tooling/action-gz-ci/validate_package_xml@jammy diff --git a/package.xml b/package.xml new file mode 100644 index 000000000..33d829ad8 --- /dev/null +++ b/package.xml @@ -0,0 +1,32 @@ + + + sdformat14 + 14.1.1 + SDFormat is an XML file format that describes environments, objects, and robots +in a manner suitable for robotic applications + + Addisu Z. Taddese + Steve Peters + + Apache License 2.0 + + https://github.com/gazebosim/sdformat + + cmake + gz-cmake3 + gz-math7 + gz-utils2 + tinyxml2 + liburdfdom-dev + pybind11-dev + + gz-tools2 + + libxml2-utils + python3-psutil + python3-pytest + + + cmake + + diff --git a/src/Console.cc b/src/Console.cc index d9ec8d044..558f10d87 100644 --- a/src/Console.cc +++ b/src/Console.cc @@ -34,13 +34,7 @@ using namespace sdf; static std::shared_ptr myself; static std::mutex g_instance_mutex; -/// \todo Output disabled for windows, to allow tests to pass. We should -/// disable output just for tests on windows. -#ifndef _WIN32 static bool g_quiet = false; -#else -static bool g_quiet = true; -#endif static Console::ConsoleStream g_NullStream(nullptr); diff --git a/src/cmd/CMakeLists.txt b/src/cmd/CMakeLists.txt index a1cf707c1..c1c91078b 100644 --- a/src/cmd/CMakeLists.txt +++ b/src/cmd/CMakeLists.txt @@ -31,7 +31,13 @@ set(cmd_script_configured "${CMAKE_CURRENT_BINARY_DIR}/cmd${PROJECT_NAME}.rb.con # Set the library_location variable to the relative path to the library file # within the install directory structure. -set(library_location "../../../${CMAKE_INSTALL_LIBDIR}/$") +if (MSVC) + set(library_location_prefix "${CMAKE_INSTALL_BINDIR}") +else() + set(library_location_prefix "${CMAKE_INSTALL_LIBDIR}") +endif() + +set(library_location "../../../${library_location_prefix}/$") configure_file( "cmd${PROJECT_NAME_NO_VERSION_LOWER}.rb.in" diff --git a/src/cmd/cmdsdformat.rb.in b/src/cmd/cmdsdformat.rb.in index 7c93d7680..c4bd35f4c 100644 --- a/src/cmd/cmdsdformat.rb.in +++ b/src/cmd/cmdsdformat.rb.in @@ -26,6 +26,8 @@ else end require 'optparse' +require 'pathname' + # Constants. LIBRARY_NAME = '@library_location@' @@ -174,9 +176,7 @@ class Cmd # puts options # Read the plugin that handles the command. - if LIBRARY_NAME[0] == '/' - # If the first character is a slash, we'll assume that we've been given an - # absolute path to the library. This is only used during test mode. + if Pathname.new(LIBRARY_NAME).absolute? plugin = LIBRARY_NAME else # We're assuming that the library path is relative to the current @@ -185,10 +185,18 @@ class Cmd end conf_version = LIBRARY_VERSION + if defined? RubyInstaller + # RubyInstaller does not search for dlls in PATH or the directory that tests are running from, + # so we'll add the parent directory of the plugin to the search path. + # https://github.com/oneclick/rubyinstaller2/wiki/For-gem-developers#-dll-loading + RubyInstaller::Runtime.add_dll_directory(File.dirname(plugin)) + end + begin Importer.dlload plugin - rescue DLError + rescue DLError => error puts "Library error: [#{plugin}] not found." + puts "DLError: #{error.message}" exit(-1) end diff --git a/src/gz_TEST.cc b/src/gz_TEST.cc index 816882f56..2e16d19db 100644 --- a/src/gz_TEST.cc +++ b/src/gz_TEST.cc @@ -73,7 +73,7 @@ std::string custom_exec_str(std::string _cmd) } ///////////////////////////////////////////////// -TEST(checkUnrecognizedElements, GZ_UTILS_TEST_DISABLED_ON_WIN32(SDF)) +TEST(checkUnrecognizedElements, SDF) { // Check an SDFormat file with unrecognized elements { @@ -120,7 +120,7 @@ TEST(checkUnrecognizedElements, GZ_UTILS_TEST_DISABLED_ON_WIN32(SDF)) } ///////////////////////////////////////////////// -TEST(check, GZ_UTILS_TEST_DISABLED_ON_WIN32(SDF)) +TEST(check, SDF) { // Check a good SDF file { @@ -1011,7 +1011,7 @@ TEST(check, GZ_UTILS_TEST_DISABLED_ON_WIN32(SDF)) } ///////////////////////////////////////////////// -TEST(check_shapes_sdf, GZ_UTILS_TEST_DISABLED_ON_WIN32(SDF)) +TEST(check_shapes_sdf, SDF) { { const auto path = @@ -1035,7 +1035,7 @@ TEST(check_shapes_sdf, GZ_UTILS_TEST_DISABLED_ON_WIN32(SDF)) } ///////////////////////////////////////////////// -TEST(check_model_sdf, GZ_UTILS_TEST_DISABLED_ON_WIN32(SDF)) +TEST(check_model_sdf, SDF) { // Check a good SDF file by passing the absolute path { @@ -1062,7 +1062,7 @@ TEST(check_model_sdf, GZ_UTILS_TEST_DISABLED_ON_WIN32(SDF)) } ///////////////////////////////////////////////// -TEST(describe, GZ_UTILS_TEST_DISABLED_ON_WIN32(SDF)) +TEST(describe, SDF) { // Get the description std::string output = @@ -1074,7 +1074,7 @@ TEST(describe, GZ_UTILS_TEST_DISABLED_ON_WIN32(SDF)) } ///////////////////////////////////////////////// -TEST(print, GZ_UTILS_TEST_DISABLED_ON_WIN32(SDF)) +TEST(print, SDF) { // Check a good SDF file { @@ -1103,7 +1103,7 @@ TEST(print, GZ_UTILS_TEST_DISABLED_ON_WIN32(SDF)) } ///////////////////////////////////////////////// -TEST(print_rotations_in_degrees, GZ_UTILS_TEST_DISABLED_ON_WIN32(SDF)) +TEST(print_rotations_in_degrees, SDF) { const std::string path = sdf::testing::TestFile("sdf", "rotations_in_degrees.sdf"); @@ -1171,7 +1171,7 @@ TEST(print_rotations_in_degrees, GZ_UTILS_TEST_DISABLED_ON_WIN32(SDF)) } ///////////////////////////////////////////////// -TEST(print_rotations_in_radians, GZ_UTILS_TEST_DISABLED_ON_WIN32(SDF)) +TEST(print_rotations_in_radians, SDF) { const std::string path = sdf::testing::TestFile("sdf", "rotations_in_radians.sdf"); @@ -1239,7 +1239,7 @@ TEST(print_rotations_in_radians, GZ_UTILS_TEST_DISABLED_ON_WIN32(SDF)) } ///////////////////////////////////////////////// -TEST(print_rotations_in_quaternions, GZ_UTILS_TEST_DISABLED_ON_WIN32(SDF)) +TEST(print_rotations_in_quaternions, SDF) { const auto path = sdf::testing::TestFile( "sdf", "rotations_in_quaternions.sdf"); @@ -1308,7 +1308,7 @@ TEST(print_rotations_in_quaternions, GZ_UTILS_TEST_DISABLED_ON_WIN32(SDF)) } ///////////////////////////////////////////////// -TEST(print_includes_rotations_in_degrees, GZ_UTILS_TEST_DISABLED_ON_WIN32(SDF)) +TEST(print_includes_rotations_in_degrees, SDF) { // Set SDF_PATH so that included models can be found gz::utils::setenv( @@ -1379,7 +1379,7 @@ TEST(print_includes_rotations_in_degrees, GZ_UTILS_TEST_DISABLED_ON_WIN32(SDF)) } ///////////////////////////////////////////////// -TEST(print_includes_rotations_in_radians, GZ_UTILS_TEST_DISABLED_ON_WIN32(SDF)) +TEST(print_includes_rotations_in_radians, SDF) { // Set SDF_PATH so that included models can be found gz::utils::setenv( @@ -1450,8 +1450,7 @@ TEST(print_includes_rotations_in_radians, GZ_UTILS_TEST_DISABLED_ON_WIN32(SDF)) } ///////////////////////////////////////////////// -TEST(print_includes_rotations_in_quaternions, - GZ_UTILS_TEST_DISABLED_ON_WIN32(SDF)) +TEST(print_includes_rotations_in_quaternions, SDF) { // Set SDF_PATH so that included models can be found gz::utils::setenv( @@ -1523,8 +1522,7 @@ TEST(print_includes_rotations_in_quaternions, } ///////////////////////////////////////////////// -TEST(print_rotations_in_unnormalized_degrees, - GZ_UTILS_TEST_DISABLED_ON_WIN32(SDF)) +TEST(print_rotations_in_unnormalized_degrees, SDF) { const std::string path = sdf::testing::TestFile("sdf", "rotations_in_unnormalized_degrees.sdf"); @@ -1595,8 +1593,7 @@ TEST(print_rotations_in_unnormalized_degrees, } ///////////////////////////////////////////////// -TEST(print_rotations_in_unnormalized_radians, - GZ_UTILS_TEST_DISABLED_ON_WIN32(SDF)) +TEST(print_rotations_in_unnormalized_radians, SDF) { const std::string path = sdf::testing::TestFile("sdf", "rotations_in_unnormalized_radians.sdf"); @@ -1664,7 +1661,7 @@ TEST(print_rotations_in_unnormalized_radians, } ///////////////////////////////////////////////// -TEST(shuffled_cmd_flags, GZ_UTILS_TEST_DISABLED_ON_WIN32(SDF)) +TEST(shuffled_cmd_flags, SDF) { const std::string path = sdf::testing::TestFile("sdf", "rotations_in_unnormalized_radians.sdf"); @@ -1713,8 +1710,7 @@ TEST(shuffled_cmd_flags, GZ_UTILS_TEST_DISABLED_ON_WIN32(SDF)) } ///////////////////////////////////////////////// -TEST(print_snap_to_degrees_tolerance_too_high, - GZ_UTILS_TEST_DISABLED_ON_WIN32(SDF)) +TEST(print_snap_to_degrees_tolerance_too_high, SDF) { const std::string path = sdf::testing::TestFile( "sdf", @@ -1731,7 +1727,7 @@ TEST(print_snap_to_degrees_tolerance_too_high, } ///////////////////////////////////////////////// -TEST(GraphCmd, GZ_UTILS_TEST_DISABLED_ON_WIN32(WorldPoseRelativeTo)) +TEST(GraphCmd, WorldPoseRelativeTo) { // world pose relative_to graph const std::string path = @@ -1780,7 +1776,7 @@ TEST(GraphCmd, GZ_UTILS_TEST_DISABLED_ON_WIN32(WorldPoseRelativeTo)) } ///////////////////////////////////////////////// -TEST(GraphCmd, GZ_UTILS_TEST_DISABLED_ON_WIN32(ModelPoseRelativeTo)) +TEST(GraphCmd, ModelPoseRelativeTo) { const auto path = sdf::testing::TestFile("sdf", "model_relative_to_nested_reference.sdf"); @@ -1857,7 +1853,7 @@ TEST(GraphCmd, GZ_UTILS_TEST_DISABLED_ON_WIN32(ModelPoseRelativeTo)) } ///////////////////////////////////////////////// -TEST(GraphCmd, GZ_UTILS_TEST_DISABLED_ON_WIN32(WorldFrameAttachedTo)) +TEST(GraphCmd, WorldFrameAttachedTo) { const auto path = sdf::testing::TestFile("sdf", "world_nested_frame_attached_to.sdf"); @@ -1903,7 +1899,7 @@ TEST(GraphCmd, GZ_UTILS_TEST_DISABLED_ON_WIN32(WorldFrameAttachedTo)) } ///////////////////////////////////////////////// -TEST(GraphCmd, GZ_UTILS_TEST_DISABLED_ON_WIN32(ModelFrameAttachedTo)) +TEST(GraphCmd, ModelFrameAttachedTo) { const auto path = sdf::testing::TestFile("sdf", "model_nested_frame_attached_to.sdf"); @@ -1955,7 +1951,7 @@ TEST(GraphCmd, GZ_UTILS_TEST_DISABLED_ON_WIN32(ModelFrameAttachedTo)) // Disable on arm #if !defined __ARM_ARCH ///////////////////////////////////////////////// -TEST(inertial_stats, GZ_UTILS_TEST_DISABLED_ON_WIN32(SDF)) +TEST(inertial_stats, SDF) { std::string expectedOutput = "Inertial statistics for model: test_model\n" @@ -2043,7 +2039,7 @@ TEST(inertial_stats, GZ_UTILS_TEST_DISABLED_ON_WIN32(SDF)) ////////////////////////////////////////////////// /// \brief Check help message and bash completion script for consistent flags -TEST(HelpVsCompletionFlags, SDF) +TEST(HelpVsCompletionFlags, GZ_UTILS_TEST_DISABLED_ON_WIN32(SDF)) { // Flags in help message std::string helpOutput = custom_exec_str(GzCommand() + " sdf --help"); @@ -2098,6 +2094,16 @@ int main(int argc, char **argv) gz::utils::setenv("LD_LIBRARY_PATH", testLibraryPath); #endif + // temporarily set HOME + std::string homeDir; + sdf::testing::TestTmpPath(homeDir); + +#ifdef _WIN32 + gz::utils::setenv("HOMEPATH", homeDir); +#else + gz::utils::setenv("HOME", homeDir); +#endif + ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } From 2f2d8e13a23a7473b5903f1a0f8e74ad2c1e9344 Mon Sep 17 00:00:00 2001 From: Jose Luis Rivero Date: Mon, 22 Apr 2024 19:32:34 +0200 Subject: [PATCH 5/7] Fix trivial warning on 24.04 for JointAxis_TEST.cc (#1402) * Fix trivial warning on 24.04 for JointAxis_TEST.cc --------- Signed-off-by: Jose Luis Rivero Co-authored-by: Steve Peters --- src/JointAxis_TEST.cc | 50 +++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src/JointAxis_TEST.cc b/src/JointAxis_TEST.cc index 0e04be6fd..bc612c689 100644 --- a/src/JointAxis_TEST.cc +++ b/src/JointAxis_TEST.cc @@ -226,58 +226,58 @@ TEST(DOMJointAxis, ToElement) sdf::ElementPtr dynElem = elem->GetElement("dynamics", errors); ASSERT_TRUE(errors.empty()); - double damping = 0; - damping = dynElem->Get(errors, "damping", damping).first; + double damping; + damping = dynElem->Get(errors, "damping", 0.0).first; ASSERT_TRUE(errors.empty()); EXPECT_DOUBLE_EQ(0.2, damping); - double friction = 0; - friction = dynElem->Get(errors, "friction", friction).first; + double friction; + friction = dynElem->Get(errors, "friction", 0.0).first; ASSERT_TRUE(errors.empty()); EXPECT_DOUBLE_EQ(1.3, friction); - double springReference = 0; + double springReference; springReference = dynElem->Get( - errors, "spring_reference", springReference).first; + errors, "spring_reference", 0.0).first; ASSERT_TRUE(errors.empty()); EXPECT_DOUBLE_EQ(2.4, springReference); - double springStiffness = 0; + double springStiffness; springStiffness = dynElem->Get( - errors, "spring_stiffness", springStiffness).first; + errors, "spring_stiffness", 0.0).first; ASSERT_TRUE(errors.empty()); EXPECT_DOUBLE_EQ(-1.2, springStiffness); // Check //axis/limit sdf::ElementPtr limitElem = elem->GetElement("limit", errors); - double lower = 0; - lower = limitElem->Get(errors, "lower", lower).first; + double lower; + lower = limitElem->Get(errors, "lower", 0.0).first; ASSERT_TRUE(errors.empty()); EXPECT_DOUBLE_EQ(-10.8, lower); - double upper = 0; - upper = limitElem->Get(errors, "upper", upper).first; + double upper; + upper = limitElem->Get(errors, "upper", 0.0).first; ASSERT_TRUE(errors.empty()); EXPECT_DOUBLE_EQ(123.4, upper); - double effort = 0; - effort = limitElem->Get(errors, "effort", effort).first; + double effort; + effort = limitElem->Get(errors, "effort", 0.0).first; ASSERT_TRUE(errors.empty()); EXPECT_DOUBLE_EQ(3.2, effort); - double maxVel = 0; - maxVel = limitElem->Get(errors, "velocity", maxVel).first; + double maxVel; + maxVel = limitElem->Get(errors, "velocity", 0.0).first; ASSERT_TRUE(errors.empty()); EXPECT_DOUBLE_EQ(54.2, maxVel); - double stiffness = 0; - stiffness = limitElem->Get(errors, "stiffness", stiffness).first; + double stiffness; + stiffness = limitElem->Get(errors, "stiffness", 0.0).first; ASSERT_TRUE(errors.empty()); EXPECT_DOUBLE_EQ(1e2, stiffness); - double dissipation = 0; + double dissipation; dissipation = limitElem->Get( - errors, "dissipation", dissipation).first; + errors, "dissipation", 0.0).first; ASSERT_TRUE(errors.empty()); EXPECT_DOUBLE_EQ(1.5, dissipation); @@ -286,31 +286,31 @@ TEST(DOMJointAxis, ToElement) ASSERT_NE(nullptr, mimicElem); std::string mimicJointName; mimicJointName = mimicElem->Get( - errors, "joint", mimicJointName).first; + errors, "joint", "").first; ASSERT_TRUE(errors.empty()); EXPECT_EQ("test_joint", mimicJointName); std::string mimicAxisName; mimicAxisName = mimicElem->Get( - errors, "axis", mimicAxisName).first; + errors, "axis", "").first; ASSERT_TRUE(errors.empty()); EXPECT_EQ("axis2", mimicAxisName); double multiplier; multiplier = mimicElem->Get( - errors, "multiplier", multiplier).first; + errors, "multiplier", 0.0).first; ASSERT_TRUE(errors.empty()); EXPECT_DOUBLE_EQ(5.0, multiplier); double offset; offset = mimicElem->Get( - errors, "offset", offset).first; + errors, "offset", 0.0).first; ASSERT_TRUE(errors.empty()); EXPECT_DOUBLE_EQ(1.0, offset); double reference; reference = mimicElem->Get( - errors, "reference", reference).first; + errors, "reference", 0.0).first; ASSERT_TRUE(errors.empty()); EXPECT_DOUBLE_EQ(2.0, reference); From 6f1c36502f1085836ab9876e26afac3238f26820 Mon Sep 17 00:00:00 2001 From: Ian Chen Date: Tue, 23 Apr 2024 10:54:04 -0700 Subject: [PATCH 6/7] Prepare for 14.2.0 release (#1405) Signed-off-by: Ian Chen --- CMakeLists.txt | 2 +- Changelog.md | 16 ++++++++++++++++ package.xml | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f18d73f91..f771a9c65 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ if(COMMAND CMAKE_POLICY) CMAKE_POLICY(SET CMP0004 NEW) endif(COMMAND CMAKE_POLICY) -project (sdformat14 VERSION 14.1.1) +project (sdformat14 VERSION 14.2.0) # The protocol version has nothing to do with the package version. # It represents the current version of SDFormat implemented by the software diff --git a/Changelog.md b/Changelog.md index c41c7af4e..ecb491ea1 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,21 @@ ## libsdformat 14.X +### libsdformat 14.2.0 (2024-04-23) + +1. Fix trivial warning on 24.04 for JointAxis_TEST.cc + * [Pull request #1402](https://github.com/gazebosim/sdformat/pull/1402) + +1. Add package.xml, fix `gz sdf` tests on Windows + * [Pull request #1374](https://github.com/gazebosim/sdformat/pull/1374) + +1. Backport mesh optimization feature + * [Pull request #1398](https://github.com/gazebosim/sdformat/pull/1398) + * [Pull request #1386](https://github.com/gazebosim/sdformat/pull/1386) + * [Pull request #1382](https://github.com/gazebosim/sdformat/pull/1382) + +1. Param_TEST: Check return values of Param::Get/Set + * [Pull request #1394](https://github.com/gazebosim/sdformat/pull/1394) + ### libsdformat 14.1.1 (2024-03-28) 1. Fix warning with pybind11 2.12 diff --git a/package.xml b/package.xml index 33d829ad8..3132f5f8f 100644 --- a/package.xml +++ b/package.xml @@ -1,7 +1,7 @@ sdformat14 - 14.1.1 + 14.2.0 SDFormat is an XML file format that describes environments, objects, and robots in a manner suitable for robotic applications From 321d85ec87ad838e42e43748acb1cdf48f32ea41 Mon Sep 17 00:00:00 2001 From: Jorge Perez Date: Tue, 14 May 2024 05:10:51 -0300 Subject: [PATCH 7/7] (Backport) Enable 24.04 CI, remove distutils dependency (#1413) * Enable 24.04 CI, remove distutils dependency (#1408) distutils is no longer required since this branch requires a new enough version of cmake. Signed-off-by: Steve Peters Signed-off-by: Jorge Perez Co-authored-by: Steve Peters --- .github/ci/packages.apt | 1 - .github/workflows/ci.yml | 9 +++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/ci/packages.apt b/.github/ci/packages.apt index acee87feb..1c65edcc2 100644 --- a/.github/ci/packages.apt +++ b/.github/ci/packages.apt @@ -7,7 +7,6 @@ libtinyxml2-dev liburdfdom-dev libxml2-utils python3-dev -python3-distutils python3-gz-math7 python3-psutil python3-pybind11 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fe275a3cb..8a0a9947a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,3 +21,12 @@ jobs: codecov-enabled: true cppcheck-enabled: true cpplint-enabled: true + noble-ci: + runs-on: ubuntu-latest + name: Ubuntu Noble CI + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Compile and test + id: ci + uses: gazebo-tooling/action-gz-ci@noble