From a9fa7ab1b0ab86d08f4dc1cc6a3430e5d71c8e3d Mon Sep 17 00:00:00 2001 From: MelchiorSchuh Date: Fri, 5 Jun 2026 12:44:37 +0200 Subject: [PATCH 1/6] feat(Validity): Added validity checks for all meshes --- bindings/python/src/validity/CMakeLists.txt | 5 +- .../src/validity/edgedcurve_validity.hpp | 37 +++++ .../python/src/validity/pointset_validity.hpp | 5 +- .../python/src/validity/solid_validity.hpp | 36 +++++ .../python/src/validity/surface_validity.hpp | 37 +++++ bindings/python/src/validity/validity.cpp | 6 + .../inspector/inspection/solid_inspector.hpp | 6 + .../validity/edgedcurve_validity.hpp | 39 ++++++ .../inspector/validity/object_validity.hpp | 43 ++++++ .../inspector/validity/pointset_validity.hpp | 4 +- .../inspector/validity/solid_validity.hpp | 39 ++++++ .../inspector/validity/surface_validity.hpp | 39 ++++++ .../manifold/surface_vertex_manifold.cpp | 1 + src/geode/inspector/validity/CMakeLists.txt | 8 ++ .../validity/edgedcurve_validity.cpp | 57 ++++++++ .../inspector/validity/object_validity.cpp | 42 ++++++ .../inspector/validity/pointset_validity.cpp | 20 +-- .../inspector/validity/solid_validity.cpp | 85 ++++++++++++ .../inspector/validity/surface_validity.cpp | 87 ++++++++++++ tests/validity/CMakeLists.txt | 27 ++++ tests/validity/test-edgedcurve-validity.cpp | 85 ++++++++++++ tests/validity/test-pointset-validity.cpp | 37 +++-- tests/validity/test-solid-validity.cpp | 117 ++++++++++++++++ tests/validity/test-surface-validity.cpp | 131 ++++++++++++++++++ 24 files changed, 965 insertions(+), 28 deletions(-) create mode 100644 bindings/python/src/validity/edgedcurve_validity.hpp create mode 100644 bindings/python/src/validity/solid_validity.hpp create mode 100644 bindings/python/src/validity/surface_validity.hpp create mode 100644 include/geode/inspector/validity/edgedcurve_validity.hpp create mode 100644 include/geode/inspector/validity/object_validity.hpp create mode 100644 include/geode/inspector/validity/solid_validity.hpp create mode 100644 include/geode/inspector/validity/surface_validity.hpp create mode 100644 src/geode/inspector/validity/edgedcurve_validity.cpp create mode 100644 src/geode/inspector/validity/object_validity.cpp create mode 100644 src/geode/inspector/validity/solid_validity.cpp create mode 100644 src/geode/inspector/validity/surface_validity.cpp create mode 100644 tests/validity/test-edgedcurve-validity.cpp create mode 100644 tests/validity/test-solid-validity.cpp create mode 100644 tests/validity/test-surface-validity.cpp diff --git a/bindings/python/src/validity/CMakeLists.txt b/bindings/python/src/validity/CMakeLists.txt index 4ac50e42..af8a60ab 100644 --- a/bindings/python/src/validity/CMakeLists.txt +++ b/bindings/python/src/validity/CMakeLists.txt @@ -21,8 +21,11 @@ add_geode_python_binding( NAME "py_validity" SOURCES - "validity.cpp" + "edgedcurve_validity.hpp" "pointset_validity.hpp" + "solid_validity.hpp" + "surface_validity.hpp" + "validity.cpp" DEPENDENCIES ${PROJECT_NAME}::validity ) \ No newline at end of file diff --git a/bindings/python/src/validity/edgedcurve_validity.hpp b/bindings/python/src/validity/edgedcurve_validity.hpp new file mode 100644 index 00000000..77fa747b --- /dev/null +++ b/bindings/python/src/validity/edgedcurve_validity.hpp @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2019 - 2026 Geode-solutions + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ +#include + +#include + +#include +#include + +namespace geode +{ + void define_edged_curve_validity( pybind11::module& module ) + { + module.def( "is_edged_curve_valid2D", &is_edged_curve_valid< 2 > ); + module.def( "is_edged_curve_valid3D", &is_edged_curve_valid< 3 > ); + } +} // namespace geode diff --git a/bindings/python/src/validity/pointset_validity.hpp b/bindings/python/src/validity/pointset_validity.hpp index ebb6a12c..88d4398f 100644 --- a/bindings/python/src/validity/pointset_validity.hpp +++ b/bindings/python/src/validity/pointset_validity.hpp @@ -24,13 +24,14 @@ #include +#include #include namespace geode { void define_point_set_validity( pybind11::module& module ) { - module.def( "pointset_invalidity2D", &pointset_invalidity< 2 > ); - module.def( "pointset_invalidity3D", &pointset_invalidity< 3 > ); + module.def( "is_pointset_valid2D", &is_pointset_valid< 2 > ); + module.def( "is_pointset_valid3D", &is_pointset_valid< 3 > ); } } // namespace geode diff --git a/bindings/python/src/validity/solid_validity.hpp b/bindings/python/src/validity/solid_validity.hpp new file mode 100644 index 00000000..d72447d4 --- /dev/null +++ b/bindings/python/src/validity/solid_validity.hpp @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2019 - 2026 Geode-solutions + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ +#include + +#include + +#include +#include + +namespace geode +{ + void define_solid_mesh_validity( pybind11::module& module ) + { + module.def( "is_solid_valid3D", &is_solid_valid< 3 > ); + } +} // namespace geode diff --git a/bindings/python/src/validity/surface_validity.hpp b/bindings/python/src/validity/surface_validity.hpp new file mode 100644 index 00000000..91e1962a --- /dev/null +++ b/bindings/python/src/validity/surface_validity.hpp @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2019 - 2026 Geode-solutions + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ +#include + +#include + +#include +#include + +namespace geode +{ + void define_surface_mesh_validity( pybind11::module& module ) + { + module.def( "is_surface_valid2D", &is_surface_valid< 2 > ); + module.def( "is_surface_valid3D", &is_surface_valid< 3 > ); + } +} // namespace geode diff --git a/bindings/python/src/validity/validity.cpp b/bindings/python/src/validity/validity.cpp index 9df5cd75..a4b1d9e7 100644 --- a/bindings/python/src/validity/validity.cpp +++ b/bindings/python/src/validity/validity.cpp @@ -25,7 +25,10 @@ #include "pybind11/pybind11.h" #include "pybind11/stl.h" +#include "edgedcurve_validity.hpp" #include "pointset_validity.hpp" +#include "solid_validity.hpp" +#include "surface_validity.hpp" PYBIND11_MODULE( opengeode_inspector_py_validity, module ) { @@ -34,5 +37,8 @@ PYBIND11_MODULE( opengeode_inspector_py_validity, module ) module, "OpenGeodeInspectorValidityLibrary" ) .def( "initialize", &geode::OpenGeodeInspectorValidityLibrary::initialize ); + geode::define_edged_curve_validity( module ); geode::define_point_set_validity( module ); + geode::define_solid_mesh_validity( module ); + geode::define_surface_mesh_validity( module ); } \ No newline at end of file diff --git a/include/geode/inspector/inspection/solid_inspector.hpp b/include/geode/inspector/inspection/solid_inspector.hpp index d67cda15..c6e60903 100644 --- a/include/geode/inspector/inspection/solid_inspector.hpp +++ b/include/geode/inspector/inspection/solid_inspector.hpp @@ -69,10 +69,16 @@ namespace geode [[nodiscard]] std::string inspection_type() const; }; + /*! * Class for inspecting a SolidMesh + * @extends SolidMeshAdjacency * @extends SolidMeshColocation * @extends SolidMeshDegeneration + * @extends SolidMeshVertexManifold + * @extends SolidMeshEdgeManifold + * @extends SolidMeshFacetManifold + * @extends SolidMeshNegativeElements */ template < index_t dimension > class opengeode_inspector_inspection_api SolidMeshInspector diff --git a/include/geode/inspector/validity/edgedcurve_validity.hpp b/include/geode/inspector/validity/edgedcurve_validity.hpp new file mode 100644 index 00000000..917b27cb --- /dev/null +++ b/include/geode/inspector/validity/edgedcurve_validity.hpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2019 - 2026 Geode-solutions + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#pragma once + +#include + +namespace geode +{ + FORWARD_DECLARATION_DIMENSION_CLASS( EdgedCurve ); + ALIAS_2D_AND_3D( EdgedCurve ); + struct ObjectValidity; +} // namespace geode + +namespace geode +{ + template < index_t dimension > + ObjectValidity is_edged_curve_valid( const EdgedCurve< dimension >& curve ); +} // namespace geode \ No newline at end of file diff --git a/include/geode/inspector/validity/object_validity.hpp b/include/geode/inspector/validity/object_validity.hpp new file mode 100644 index 00000000..89bf2497 --- /dev/null +++ b/include/geode/inspector/validity/object_validity.hpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019 - 2026 Geode-solutions + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#pragma once + +#include + +namespace geode +{ + struct opengeode_inspector_validity_api ObjectValidity + { + explicit operator bool() const + { + return invalidities.empty(); + } + + index_t nb_issues() const; + + std::string string() const; + + std::vector< std::string > invalidities{}; + }; +} // namespace geode \ No newline at end of file diff --git a/include/geode/inspector/validity/pointset_validity.hpp b/include/geode/inspector/validity/pointset_validity.hpp index 52233f63..0ad6cb5b 100644 --- a/include/geode/inspector/validity/pointset_validity.hpp +++ b/include/geode/inspector/validity/pointset_validity.hpp @@ -29,11 +29,11 @@ namespace geode { FORWARD_DECLARATION_DIMENSION_CLASS( PointSet ); ALIAS_2D_AND_3D( PointSet ); + struct ObjectValidity; } // namespace geode namespace geode { template < index_t dimension > - std::vector< std::string > pointset_invalidity( - const PointSet< dimension >& pointset ); + ObjectValidity is_pointset_valid( const PointSet< dimension >& pointset ); } // namespace geode \ No newline at end of file diff --git a/include/geode/inspector/validity/solid_validity.hpp b/include/geode/inspector/validity/solid_validity.hpp new file mode 100644 index 00000000..51abcec1 --- /dev/null +++ b/include/geode/inspector/validity/solid_validity.hpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2019 - 2026 Geode-solutions + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#pragma once + +#include + +namespace geode +{ + FORWARD_DECLARATION_DIMENSION_CLASS( SolidMesh ); + ALIAS_2D_AND_3D( SolidMesh ); + struct ObjectValidity; +} // namespace geode + +namespace geode +{ + template < index_t dimension > + ObjectValidity is_solid_valid( const SolidMesh< dimension >& solid ); +} // namespace geode \ No newline at end of file diff --git a/include/geode/inspector/validity/surface_validity.hpp b/include/geode/inspector/validity/surface_validity.hpp new file mode 100644 index 00000000..d067d312 --- /dev/null +++ b/include/geode/inspector/validity/surface_validity.hpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2019 - 2026 Geode-solutions + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#pragma once + +#include + +namespace geode +{ + FORWARD_DECLARATION_DIMENSION_CLASS( SurfaceMesh ); + ALIAS_2D_AND_3D( SurfaceMesh ); + struct ObjectValidity; +} // namespace geode + +namespace geode +{ + template < index_t dimension > + ObjectValidity is_surface_valid( const SurfaceMesh< dimension >& surface ); +} // namespace geode \ No newline at end of file diff --git a/src/geode/inspector/inspection/criterion/manifold/surface_vertex_manifold.cpp b/src/geode/inspector/inspection/criterion/manifold/surface_vertex_manifold.cpp index 8d179d77..e7a4479f 100644 --- a/src/geode/inspector/inspection/criterion/manifold/surface_vertex_manifold.cpp +++ b/src/geode/inspector/inspection/criterion/manifold/surface_vertex_manifold.cpp @@ -87,6 +87,7 @@ namespace geode { try { + DEBUG( vertex_id ); if( !polygons_around_vertex_are_the_same( polygons_around_vertices_list[vertex_id], mesh_.polygons_around_vertex( vertex_id ) ) ) diff --git a/src/geode/inspector/validity/CMakeLists.txt b/src/geode/inspector/validity/CMakeLists.txt index 895d6aaf..c1b80426 100644 --- a/src/geode/inspector/validity/CMakeLists.txt +++ b/src/geode/inspector/validity/CMakeLists.txt @@ -23,10 +23,18 @@ add_geode_library( FOLDER "geode/inspector/validity" SOURCES "common.cpp" + "edgedcurve_validity.cpp" + "object_validity.cpp" "pointset_validity.cpp" + "solid_validity.cpp" + "surface_validity.cpp" PUBLIC_HEADERS "common.hpp" + "edgedcurve_validity.hpp" + "object_validity.hpp" "pointset_validity.hpp" + "solid_validity.hpp" + "surface_validity.hpp" PUBLIC_DEPENDENCIES OpenGeode::basic PRIVATE_DEPENDENCIES diff --git a/src/geode/inspector/validity/edgedcurve_validity.cpp b/src/geode/inspector/validity/edgedcurve_validity.cpp new file mode 100644 index 00000000..42895baf --- /dev/null +++ b/src/geode/inspector/validity/edgedcurve_validity.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2019 - 2026 Geode-solutions + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#include + +#include + +#include +#include + +namespace geode +{ + template < index_t dimension > + ObjectValidity is_edged_curve_valid( const EdgedCurve< dimension >& curve ) + { + EdgedCurveInspector< dimension > edgedcurve_inspector{ curve }; + const auto inspection_result = + edgedcurve_inspector.inspect_edged_curve(); + ObjectValidity invalidities; + if( inspection_result.colocated_points_groups.nb_issues() != 0 ) + { + invalidities.invalidities.emplace_back( + inspection_result.colocated_points_groups.string() ); + } + if( inspection_result.degenerated_edges.nb_issues() != 0 ) + { + invalidities.invalidities.emplace_back( + inspection_result.degenerated_edges.string() ); + } + return invalidities; + } + + template ObjectValidity opengeode_inspector_validity_api + is_edged_curve_valid( const EdgedCurve< 2 >& ); + template ObjectValidity opengeode_inspector_validity_api + is_edged_curve_valid( const EdgedCurve< 3 >& ); +} // namespace geode diff --git a/src/geode/inspector/validity/object_validity.cpp b/src/geode/inspector/validity/object_validity.cpp new file mode 100644 index 00000000..8ab6b9f0 --- /dev/null +++ b/src/geode/inspector/validity/object_validity.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019 - 2026 Geode-solutions + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#include + +namespace geode +{ + index_t ObjectValidity::nb_issues() const + { + return invalidities.size(); + } + + std::string ObjectValidity::string() const + { + std::string result; + for( const auto& invalidity : invalidities ) + { + absl::StrAppend( &result, invalidity ); + } + return result; + } +} // namespace geode diff --git a/src/geode/inspector/validity/pointset_validity.cpp b/src/geode/inspector/validity/pointset_validity.cpp index 008f34cb..5fa983f9 100644 --- a/src/geode/inspector/validity/pointset_validity.cpp +++ b/src/geode/inspector/validity/pointset_validity.cpp @@ -26,24 +26,26 @@ #include #include +#include namespace geode { template < index_t dimension > - std::vector< std::string > pointset_invalidity( - const PointSet< dimension >& pointset ) + ObjectValidity is_pointset_valid( const PointSet< dimension >& pointset ) { PointSetInspector< dimension > pointset_inspector{ pointset }; const auto inspection_result = pointset_inspector.inspect_point_set(); - if( inspection_result.nb_issues() == 0 ) + ObjectValidity invalidities; + if( inspection_result.nb_issues() != 0 ) { - return {}; + invalidities.invalidities.emplace_back( + inspection_result.string() ); } - return { inspection_result.string() }; + return invalidities; } - template std::vector< std::string > opengeode_inspector_validity_api - pointset_invalidity( const PointSet< 2 >& ); - template std::vector< std::string > opengeode_inspector_validity_api - pointset_invalidity( const PointSet< 3 >& ); + template ObjectValidity opengeode_inspector_validity_api is_pointset_valid( + const PointSet< 2 >& ); + template ObjectValidity opengeode_inspector_validity_api is_pointset_valid( + const PointSet< 3 >& ); } // namespace geode diff --git a/src/geode/inspector/validity/solid_validity.cpp b/src/geode/inspector/validity/solid_validity.cpp new file mode 100644 index 00000000..c9d88e4d --- /dev/null +++ b/src/geode/inspector/validity/solid_validity.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2019 - 2026 Geode-solutions + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#include + +#include + +#include +#include + +namespace geode +{ + template < index_t dimension > + ObjectValidity is_solid_valid( const SolidMesh< dimension >& solid ) + { + SolidMeshInspector< dimension > solid_inspector{ solid }; + const auto inspection_result = solid_inspector.inspect_solid(); + ObjectValidity invalidities; + if( inspection_result.polyhedron_facets_with_wrong_adjacency.nb_issues() + != 0 ) + { + invalidities.invalidities.emplace_back( inspection_result + .polyhedron_facets_with_wrong_adjacency.string() ); + } + if( inspection_result.colocated_points_groups.nb_issues() != 0 ) + { + invalidities.invalidities.emplace_back( + inspection_result.colocated_points_groups.string() ); + } + if( inspection_result.degenerated_edges.nb_issues() != 0 ) + { + invalidities.invalidities.emplace_back( + inspection_result.degenerated_edges.string() ); + } + if( inspection_result.degenerated_polyhedra.nb_issues() != 0 ) + { + invalidities.invalidities.emplace_back( + inspection_result.degenerated_polyhedra.string() ); + } + if( inspection_result.non_manifold_vertices.nb_issues() != 0 ) + { + invalidities.invalidities.emplace_back( + inspection_result.non_manifold_vertices.string() ); + } + if( inspection_result.non_manifold_edges.nb_issues() != 0 ) + { + invalidities.invalidities.emplace_back( + inspection_result.non_manifold_edges.string() ); + } + if( inspection_result.non_manifold_facets.nb_issues() != 0 ) + { + invalidities.invalidities.emplace_back( + inspection_result.non_manifold_facets.string() ); + } + if( inspection_result.negative_polyhedra.nb_issues() != 0 ) + { + invalidities.invalidities.emplace_back( + inspection_result.negative_polyhedra.string() ); + } + return invalidities; + } + + template ObjectValidity opengeode_inspector_validity_api is_solid_valid( + const SolidMesh< 3 >& ); +} // namespace geode diff --git a/src/geode/inspector/validity/surface_validity.cpp b/src/geode/inspector/validity/surface_validity.cpp new file mode 100644 index 00000000..3e666df9 --- /dev/null +++ b/src/geode/inspector/validity/surface_validity.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2019 - 2026 Geode-solutions + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#include + +#include + +#include +#include + +namespace geode +{ + template < index_t dimension > + ObjectValidity is_surface_valid( const SurfaceMesh< dimension >& surface ) + { + SurfaceMeshInspector< dimension > surface_inspector{ surface }; + const auto inspection_result = surface_inspector.inspect_surface(); + ObjectValidity invalidities; + if( inspection_result.polygon_edges_with_wrong_adjacency.nb_issues() + != 0 ) + { + invalidities.invalidities.emplace_back( + inspection_result.polygon_edges_with_wrong_adjacency.string() ); + } + if( inspection_result.colocated_points_groups.nb_issues() != 0 ) + { + invalidities.invalidities.emplace_back( + inspection_result.colocated_points_groups.string() ); + } + if( inspection_result.degenerated_edges.nb_issues() != 0 ) + { + invalidities.invalidities.emplace_back( + inspection_result.degenerated_edges.string() ); + } + if( inspection_result.degenerated_polygons.nb_issues() != 0 ) + { + invalidities.invalidities.emplace_back( + inspection_result.degenerated_polygons.string() ); + } + if( inspection_result.non_manifold_edges.nb_issues() != 0 ) + { + invalidities.invalidities.emplace_back( + inspection_result.non_manifold_edges.string() ); + } + if( inspection_result.non_manifold_vertices.nb_issues() != 0 ) + { + invalidities.invalidities.emplace_back( + inspection_result.non_manifold_vertices.string() ); + } + if( inspection_result.intersecting_elements.nb_issues() != 0 ) + { + invalidities.invalidities.emplace_back( + inspection_result.intersecting_elements.string() ); + } + if( inspection_result.negative_polygons.nb_issues() != 0 ) + { + invalidities.invalidities.emplace_back( + inspection_result.negative_polygons.string() ); + } + return invalidities; + } + + template ObjectValidity opengeode_inspector_validity_api is_surface_valid( + const SurfaceMesh< 2 >& ); + template ObjectValidity opengeode_inspector_validity_api is_surface_valid( + const SurfaceMesh< 3 >& ); +} // namespace geode diff --git a/tests/validity/CMakeLists.txt b/tests/validity/CMakeLists.txt index f0e53876..005738be 100755 --- a/tests/validity/CMakeLists.txt +++ b/tests/validity/CMakeLists.txt @@ -18,6 +18,15 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +add_geode_test( + SOURCE "test-edgedcurve-validity.cpp" + DEPENDENCIES + OpenGeode::basic + OpenGeode::geometry + OpenGeode::mesh + ${PROJECT_NAME}::validity +) + add_geode_test( SOURCE "test-pointset-validity.cpp" DEPENDENCIES @@ -25,4 +34,22 @@ add_geode_test( OpenGeode::geometry OpenGeode::mesh ${PROJECT_NAME}::validity +) + +add_geode_test( + SOURCE "test-solid-validity.cpp" + DEPENDENCIES + OpenGeode::basic + OpenGeode::geometry + OpenGeode::mesh + ${PROJECT_NAME}::validity +) + +add_geode_test( + SOURCE "test-surface-validity.cpp" + DEPENDENCIES + OpenGeode::basic + OpenGeode::geometry + OpenGeode::mesh + ${PROJECT_NAME}::validity ) \ No newline at end of file diff --git a/tests/validity/test-edgedcurve-validity.cpp b/tests/validity/test-edgedcurve-validity.cpp new file mode 100644 index 00000000..8f4b044f --- /dev/null +++ b/tests/validity/test-edgedcurve-validity.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2019 - 2026 Geode-solutions + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#include +#include + +#include +#include +#include + +#include +#include + +void check_invalidity() +{ + auto curve = geode::EdgedCurve3D::create(); + auto builder = geode::EdgedCurveBuilder3D::create( *curve ); + builder->create_vertices( 7 ); + builder->set_point( 0, geode::Point3D{ { 0., 2., 1. } } ); + builder->set_point( 1, geode::Point3D{ { 0., 2., 1. } } ); + builder->set_point( 2, geode::Point3D{ { 0., 0., 0. } } ); + builder->set_point( 3, geode::Point3D{ { 2., 0., 0. } } ); + builder->set_point( 4, geode::Point3D{ { 1., 4., 3. } } ); + builder->set_point( 5, geode::Point3D{ { 2., geode::GLOBAL_EPSILON / 2, + geode::GLOBAL_EPSILON / 2 } } ); + builder->set_point( + 6, geode::Point3D{ { geode::GLOBAL_EPSILON / 1.1, 2., 1. } } ); + + const auto first_object_validity = geode::is_edged_curve_valid( *curve ); + geode::OpenGeodeInspectorValidityException::test( + first_object_validity.nb_issues() == 1, + "EdgedCurve should have 1 invalidity due to colocation, not ", + first_object_validity.nb_issues() ); + geode::Logger::info( "ObjectValidity: \n", first_object_validity.string() ); + + builder->create_edge( 0, 1 ); + builder->create_edge( 1, 2 ); + builder->create_edge( 2, 3 ); + builder->create_edge( 3, 4 ); + builder->create_edge( 4, 5 ); + builder->create_edge( 5, 6 ); + + const auto object_validity = geode::is_edged_curve_valid( *curve ); + geode::OpenGeodeInspectorValidityException::test( + object_validity.nb_issues() == 2, + "EdgedCurve should have 2 invalidity reasons, not ", + object_validity.nb_issues() ); + geode::Logger::info( "ObjectValidity: \n", object_validity.string() ); +} + +int main() +{ + try + { + geode::OpenGeodeInspectorValidityLibrary::initialize(); + check_invalidity(); + + geode::Logger::info( "TEST SUCCESS" ); + return 0; + } + catch( ... ) + { + return geode::geode_lippincott(); + } +} diff --git a/tests/validity/test-pointset-validity.cpp b/tests/validity/test-pointset-validity.cpp index 7b6ac338..58d61aed 100644 --- a/tests/validity/test-pointset-validity.cpp +++ b/tests/validity/test-pointset-validity.cpp @@ -28,6 +28,7 @@ #include #include +#include #include void check_non_validity2D() @@ -40,9 +41,11 @@ void check_non_validity2D() builder->set_point( 2, geode::Point2D{ { 1., 4. } } ); builder->set_point( 3, geode::Point2D{ { 3., 3. } } ); - const auto invalidities = geode::pointset_invalidity( *pointset ); - geode::OpenGeodeInspectorValidityException::test( invalidities.empty(), - "PointSet has invalidities when it should have none." ); + const auto object_validity = geode::is_pointset_valid( *pointset ); + geode::OpenGeodeInspectorValidityException::test( + object_validity.nb_issues() == 0, "PointSet has ", + object_validity.nb_issues(), + " object_validity when it should have none." ); } void check_validity2D() @@ -60,10 +63,12 @@ void check_validity2D() builder->set_point( 6, geode::Point2D{ { geode::GLOBAL_EPSILON / 1.1, 2. } } ); - const auto invalidities = geode::pointset_invalidity( *pointset ); - geode::OpenGeodeInspectorValidityException::test( invalidities.size() == 1, - "PointSet should have 1 invalidity reason, not ", invalidities.size() ); - geode::Logger::info( "2D invalidities: ", invalidities.front() ); + const auto object_validity = geode::is_pointset_valid( *pointset ); + geode::OpenGeodeInspectorValidityException::test( + object_validity.nb_issues() == 1, + "PointSet should have 1 invalidity reason, not ", + object_validity.nb_issues() ); + geode::Logger::info( "2D object_validity: \n", object_validity.string() ); } void check_non_validity3D() @@ -76,9 +81,11 @@ void check_non_validity3D() builder->set_point( 2, geode::Point3D{ { 1., 4., 1. } } ); builder->set_point( 3, geode::Point3D{ { 3., 3., 2. } } ); - const auto invalidities = geode::pointset_invalidity( *pointset ); - geode::OpenGeodeInspectorValidityException::test( invalidities.empty(), - "PointSet has invalidities when it should have none." ); + const auto object_validity = geode::is_pointset_valid( *pointset ); + geode::OpenGeodeInspectorValidityException::test( + object_validity.nb_issues() == 0, "PointSet has ", + object_validity.nb_issues(), + " object_validity when it should have none." ); } void check_validity3D() @@ -96,10 +103,12 @@ void check_validity3D() builder->set_point( 6, geode::Point3D{ { geode::GLOBAL_EPSILON / 1.1, 2., 1. } } ); - const auto invalidities = geode::pointset_invalidity( *pointset ); - geode::OpenGeodeInspectorValidityException::test( invalidities.size() == 1, - "PointSet should have 1 invalidity reason, not ", invalidities.size() ); - geode::Logger::info( "3D invalidities: ", invalidities.front() ); + const auto object_validity = geode::is_pointset_valid( *pointset ); + geode::OpenGeodeInspectorValidityException::test( + object_validity.nb_issues() == 1, + "PointSet should have 1 invalidity reason, not ", + object_validity.nb_issues() ); + geode::Logger::info( "3D object_validity: \n", object_validity.string() ); } int main() diff --git a/tests/validity/test-solid-validity.cpp b/tests/validity/test-solid-validity.cpp new file mode 100644 index 00000000..0bbe6cbd --- /dev/null +++ b/tests/validity/test-solid-validity.cpp @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2019 - 2026 Geode-solutions + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#include +#include + +#include +#include +#include +#include + +#include +#include + +void check_invalidity() +{ + auto solid = geode::TetrahedralSolid3D::create(); + auto builder = geode::TetrahedralSolidBuilder3D::create( *solid ); + builder->create_vertices( 7 ); + builder->set_point( 0, geode::Point3D{ { 0., 0., 2. } } ); + builder->set_point( 1, geode::Point3D{ { 3., .5, 0. } } ); + builder->set_point( 2, geode::Point3D{ { .5, 3., .5 } } ); + builder->set_point( 3, geode::Point3D{ { 2., 1.5, 3. } } ); + builder->set_point( 4, geode::Point3D{ { 3.5, 2.5, -.5 } } ); + builder->set_point( 5, geode::Point3D{ { 3., .5, 0. } } ); + builder->set_point( 6, geode::Point3D{ { .5, 3., .5 } } ); + + auto object_validity = geode::is_solid_valid( *solid ); + geode::Logger::info( "ObjectValidity: \n", object_validity.string() ); + geode::OpenGeodeInspectorValidityException::test( + object_validity.nb_issues() == 1, + "SolidMesh should have 1 invalidity due to colocation, not ", + object_validity.nb_issues() ); + + builder->create_tetrahedron( { 0, 1, 2, 3 } ); + builder->create_tetrahedron( { 5, 4, 6, 3 } ); + geode::save_tetrahedral_solid( *solid, "test_solid.og_tso3d" ); + + object_validity = geode::is_solid_valid( *solid ); + geode::Logger::info( "ObjectValidity: \n", object_validity.string() ); + geode::OpenGeodeInspectorValidityException::test( + object_validity.nb_issues() == 2, + "SolidMesh should have 2 object_validity due to colocation and non " + "manifold vertices, not ", + object_validity.nb_issues() ); + + builder->create_tetrahedron( { 2, 1, 6, 3 } ); + builder->create_tetrahedron( { 1, 5, 3, 6 } ); + + object_validity = geode::is_solid_valid( *solid ); + geode::Logger::info( "ObjectValidity: \n", object_validity.string() ); + geode::OpenGeodeInspectorValidityException::test( + object_validity.nb_issues() == 5, + "SolidMesh should have 5 object_validity due to colocation, " + "degenerated edges and polyhedra, and non manifold vertices and edges, " + "not ", + object_validity.nb_issues() ); + + builder->create_tetrahedron( { 1, 4, 3, 6 } ); + + object_validity = geode::is_solid_valid( *solid ); + geode::Logger::info( "ObjectValidity: \n", object_validity.string() ); + geode::OpenGeodeInspectorValidityException::test( + object_validity.nb_issues() == 7, + "SolidMesh should have 7 object_validity due to colocation, " + "degenerated edges and polyhedra, non manifold vertices, edges, and " + "facets, and negative polyhedra, not ", + object_validity.nb_issues() ); + + builder->set_polyhedron_adjacent( { 0, 0 }, 1 ); + + object_validity = geode::is_solid_valid( *solid ); + geode::Logger::info( "ObjectValidity: \n", object_validity.string() ); + geode::OpenGeodeInspectorValidityException::test( + object_validity.nb_issues() == 8, + "SolidMesh should have 8 object_validity due to adjacencies, " + "colocation, degenerated edges and polyhedra, non manifold vertices, " + "edges, and facets, and negative polyhedra, not ", + object_validity.nb_issues() ); +} + +int main() +{ + try + { + geode::OpenGeodeInspectorValidityLibrary::initialize(); + geode::Logger::set_level( geode::Logger::LEVEL::debug ); + check_invalidity(); + + geode::Logger::info( "TEST SUCCESS" ); + return 0; + } + catch( ... ) + { + return geode::geode_lippincott(); + } +} diff --git a/tests/validity/test-surface-validity.cpp b/tests/validity/test-surface-validity.cpp new file mode 100644 index 00000000..b6b6d6bf --- /dev/null +++ b/tests/validity/test-surface-validity.cpp @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2019 - 2026 Geode-solutions + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#include +#include + +#include +#include +#include + +#include +#include + +void check_invalidity() +{ + auto surface = geode::TriangulatedSurface3D::create(); + auto builder = geode::TriangulatedSurfaceBuilder3D::create( *surface ); + builder->create_vertices( 7 ); + builder->set_point( 0, geode::Point3D{ { 0., 2., 1. } } ); + builder->set_point( 1, geode::Point3D{ { 0., 2., 1. } } ); + builder->set_point( 2, geode::Point3D{ { 0., 0., 0. } } ); + builder->set_point( 3, geode::Point3D{ { 2., 0., 0. } } ); + builder->set_point( 4, geode::Point3D{ { 1., 4., 3. } } ); + builder->set_point( 5, geode::Point3D{ { 2., geode::GLOBAL_EPSILON / 2, + geode::GLOBAL_EPSILON / 2 } } ); + builder->set_point( + 6, geode::Point3D{ { geode::GLOBAL_EPSILON / 1.1, 2., 1. } } ); + + auto object_validity = geode::is_surface_valid( *surface ); + geode::Logger::info( "ObjectValidity: \n", object_validity.string() ); + geode::OpenGeodeInspectorValidityException::test( + object_validity.nb_issues() == 1, + "SurfaceMesh should have 1 invalidity due to colocation, not ", + object_validity.nb_issues() ); + + builder->create_triangle( { 0, 2, 3 } ); + builder->create_triangle( { 2, 4, 5 } ); + + object_validity = geode::is_surface_valid( *surface ); + geode::Logger::info( "ObjectValidity: \n", object_validity.string() ); + geode::OpenGeodeInspectorValidityException::test( + object_validity.nb_issues() == 2, + "SurfaceMesh should have 2 object_validity due to colocation and " + "non manifold vertices , not ", + object_validity.nb_issues(), "." ); + + builder->create_triangle( { 2, 5, 3 } ); + + object_validity = geode::is_surface_valid( *surface ); + geode::Logger::info( "ObjectValidity: \n", object_validity.string() ); + geode::OpenGeodeInspectorValidityException::test( + object_validity.nb_issues() == 5, + "SurfaceMesh should have 5 object_validity due to colocation, " + "degenerated " + "edges and polygons, and non manifold edges and vertices, not ", + object_validity.nb_issues(), "." ); + + builder->set_polygon_adjacent( { 0, 1 }, 2 ); + builder->set_polygon_adjacent( { 2, 2 }, 0 ); + builder->set_polygon_adjacent( { 2, 0 }, 1 ); + builder->set_polygon_adjacent( { 1, 2 }, 2 ); + + object_validity = geode::is_surface_valid( *surface ); + geode::Logger::info( "ObjectValidity: \n", object_validity.string() ); + geode::OpenGeodeInspectorValidityException::test( + object_validity.nb_issues() == 3, + "SurfaceMesh should have 3 object_validity due to colocation, " + "degenerated edges and polygons, not ", + object_validity.nb_issues(), "." ); + + builder->create_point( geode::Point3D{ { 1., -1., -1. } } ); + builder->create_triangle( { 2, 4, 7 } ); + + object_validity = geode::is_surface_valid( *surface ); + geode::Logger::info( "ObjectValidity: \n", object_validity.string() ); + geode::OpenGeodeInspectorValidityException::test( + object_validity.nb_issues() == 6, + "SurfaceMesh should have 6 object_validity due to colocation, " + "degenerated " + "edges and polygons, non manifold edges and vertices, and " + "intersections, not ", + object_validity.nb_issues(), "." ); + + builder->set_polygon_adjacent( { 1, 0 }, 3 ); + builder->set_polygon_adjacent( { 3, 0 }, 1 ); + + object_validity = geode::is_surface_valid( *surface ); + geode::Logger::info( "ObjectValidity: \n", object_validity.string() ); + geode::OpenGeodeInspectorValidityException::test( + object_validity.nb_issues() == 5, + "SurfaceMesh should have 5 object_validity due to adjacencies, " + "colocation, degenerated edges and polygons, and intersections, not ", + object_validity.nb_issues(), "." ); +} + +int main() +{ + try + { + geode::OpenGeodeInspectorValidityLibrary::initialize(); + geode::Logger::set_level( geode::Logger::LEVEL::debug ); + check_invalidity(); + + geode::Logger::info( "TEST SUCCESS" ); + return 0; + } + catch( ... ) + { + return geode::geode_lippincott(); + } +} From 5f058c87ba2f33ae5dbd5cbc1a0ee7be54302db6 Mon Sep 17 00:00:00 2001 From: MelchiorSchuh Date: Mon, 8 Jun 2026 15:24:37 +0200 Subject: [PATCH 2/6] arnaud comments and python tests --- bindings/python/src/validity/CMakeLists.txt | 1 + .../python/src/validity/object_validity.hpp | 39 ++++++++ bindings/python/src/validity/validity.cpp | 2 + bindings/python/tests/validity/CMakeLists.txt | 20 +++- ...dity.py => test-py-edgedcurve-validity.py} | 35 +++++-- .../validity/test-py-pointset-validity.py | 99 +++++++++++++++++++ .../tests/validity/test-py-solid-validity.py | 83 ++++++++++++++++ .../validity/test-py-surface-validity.py | 94 ++++++++++++++++++ .../validity/edgedcurve_validity.hpp | 3 +- .../inspector/validity/object_validity.hpp | 4 +- .../inspector/validity/pointset_validity.hpp | 3 +- .../inspector/validity/solid_validity.hpp | 3 +- .../inspector/validity/surface_validity.hpp | 3 +- .../manifold/surface_vertex_manifold.cpp | 1 - tests/validity/test-pointset-validity.cpp | 4 +- tests/validity/test-solid-validity.cpp | 1 - 16 files changed, 376 insertions(+), 19 deletions(-) create mode 100644 bindings/python/src/validity/object_validity.hpp rename bindings/python/tests/validity/{test-py-model-validity.py => test-py-edgedcurve-validity.py} (50%) create mode 100644 bindings/python/tests/validity/test-py-pointset-validity.py create mode 100644 bindings/python/tests/validity/test-py-solid-validity.py create mode 100644 bindings/python/tests/validity/test-py-surface-validity.py diff --git a/bindings/python/src/validity/CMakeLists.txt b/bindings/python/src/validity/CMakeLists.txt index af8a60ab..4110696b 100644 --- a/bindings/python/src/validity/CMakeLists.txt +++ b/bindings/python/src/validity/CMakeLists.txt @@ -22,6 +22,7 @@ add_geode_python_binding( NAME "py_validity" SOURCES "edgedcurve_validity.hpp" + "object_validity.hpp" "pointset_validity.hpp" "solid_validity.hpp" "surface_validity.hpp" diff --git a/bindings/python/src/validity/object_validity.hpp b/bindings/python/src/validity/object_validity.hpp new file mode 100644 index 00000000..06da4f10 --- /dev/null +++ b/bindings/python/src/validity/object_validity.hpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2019 - 2026 Geode-solutions + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#pragma once + +#include + +#include + +namespace geode +{ + inline void define_object_validity( pybind11::module& module ) + { + pybind11::class_< ObjectValidity >( module, "ObjectValidity" ) + .def( "nb_issues", &ObjectValidity::nb_issues ) + .def( "string", &ObjectValidity::string ) + .def_readwrite( "invalidities", &ObjectValidity::invalidities ); + } +} // namespace geode diff --git a/bindings/python/src/validity/validity.cpp b/bindings/python/src/validity/validity.cpp index a4b1d9e7..6dd0ddfd 100644 --- a/bindings/python/src/validity/validity.cpp +++ b/bindings/python/src/validity/validity.cpp @@ -26,6 +26,7 @@ #include "pybind11/stl.h" #include "edgedcurve_validity.hpp" +#include "object_validity.hpp" #include "pointset_validity.hpp" #include "solid_validity.hpp" #include "surface_validity.hpp" @@ -37,6 +38,7 @@ PYBIND11_MODULE( opengeode_inspector_py_validity, module ) module, "OpenGeodeInspectorValidityLibrary" ) .def( "initialize", &geode::OpenGeodeInspectorValidityLibrary::initialize ); + geode::define_object_validity( module ); geode::define_edged_curve_validity( module ); geode::define_point_set_validity( module ); geode::define_solid_mesh_validity( module ); diff --git a/bindings/python/tests/validity/CMakeLists.txt b/bindings/python/tests/validity/CMakeLists.txt index c60f7c87..386b169d 100644 --- a/bindings/python/tests/validity/CMakeLists.txt +++ b/bindings/python/tests/validity/CMakeLists.txt @@ -19,7 +19,25 @@ # SOFTWARE. add_geode_python_test( - SOURCE "test-py-model-validity.py" + SOURCE "test-py-edgedcurve-validity.py" + DEPENDENCIES + ${PROJECT_NAME}::py_validity + ) + + add_geode_python_test( + SOURCE "test-py-pointset-validity.py" + DEPENDENCIES + ${PROJECT_NAME}::py_validity + ) + + add_geode_python_test( + SOURCE "test-py-solid-validity.py" + DEPENDENCIES + ${PROJECT_NAME}::py_validity + ) + + add_geode_python_test( + SOURCE "test-py-surface-validity.py" DEPENDENCIES ${PROJECT_NAME}::py_validity ) \ No newline at end of file diff --git a/bindings/python/tests/validity/test-py-model-validity.py b/bindings/python/tests/validity/test-py-edgedcurve-validity.py similarity index 50% rename from bindings/python/tests/validity/test-py-model-validity.py rename to bindings/python/tests/validity/test-py-edgedcurve-validity.py index b04fc1f2..0c44e1d3 100644 --- a/bindings/python/tests/validity/test-py-model-validity.py +++ b/bindings/python/tests/validity/test-py-edgedcurve-validity.py @@ -27,19 +27,38 @@ for path in [x.strip() for x in os.environ["PATH"].split("") if x]: os.add_dll_directory(path) -import opengeode +import opengeode as geode import opengeode_inspector_py_validity as validity +def check_validity(): + curve = geode.EdgedCurve3D.create() + builder = geode.EdgedCurveBuilder3D.create( curve ) + builder.create_vertices( 7 ) + builder.set_point( 0, geode.Point3D([ 0., 2., 1. ]) ) + builder.set_point( 1, geode.Point3D([ 0., 2., 1. ]) ) + builder.set_point( 2, geode.Point3D([ 0., 0., 0. ]) ) + builder.set_point( 3, geode.Point3D([ 2., 0., 0. ]) ) + builder.set_point( 4, geode.Point3D([ 1., 4., 3. ]) ) + builder.set_point( 5, geode.Point3D([ 2., geode.GLOBAL_EPSILON / 2, geode.GLOBAL_EPSILON / 2 ]) ) + builder.set_point( 6, geode.Point3D([ geode.GLOBAL_EPSILON / 1.1, 2., 1. ]) ) -def test_validity(): - return True + first_object_validity = validity.is_edged_curve_valid3D( curve ) + print( "ObjectValidity: \n" + first_object_validity.string() ) + if not first_object_validity.nb_issues() == 1: + raise ValueError( "EdgedCurve should have 1 invalidity due to colocation, not "+ str(first_object_validity.nb_issues()) ) + builder.create_edge_with_vertices( 0, 1 ) + builder.create_edge_with_vertices( 1, 2 ) + builder.create_edge_with_vertices( 2, 3 ) + builder.create_edge_with_vertices( 3, 4 ) + builder.create_edge_with_vertices( 4, 5 ) + builder.create_edge_with_vertices( 5, 6 ) + + object_validity = validity.is_edged_curve_valid3D( curve ) + print( "ObjectValidity: \n" + object_validity.string() ); + if not object_validity.nb_issues() == 2: + raise ValueError( "EdgedCurve should have 2 invalidity reasons, not "+ str(object_validity.nb_issues()) ) -def check_validity(): - if not test_validity(): - raise ValueError( - "[Test] Should be true" - ) if __name__ == "__main__": validity.OpenGeodeInspectorValidityLibrary.initialize() diff --git a/bindings/python/tests/validity/test-py-pointset-validity.py b/bindings/python/tests/validity/test-py-pointset-validity.py new file mode 100644 index 00000000..da8d76e9 --- /dev/null +++ b/bindings/python/tests/validity/test-py-pointset-validity.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019 - 2026 Geode-solutions +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import os +import sys +import platform + +if sys.version_info >= (3, 8, 0) and platform.system() == "Windows": + for path in [x.strip() for x in os.environ["PATH"].split("") if x]: + os.add_dll_directory(path) + +import opengeode as geode +import opengeode_inspector_py_validity as validity + +def check_non_validity2D(): + pointset = geode.PointSet2D.create() + builder = geode.PointSetBuilder2D.create( pointset ) + builder.create_vertices( 4 ) + builder.set_point( 0, geode.Point2D( [0., 2.]) ) + builder.set_point( 1, geode.Point2D( [2., 0.]) ) + builder.set_point( 2, geode.Point2D( [1., 4.]) ) + builder.set_point( 3, geode.Point2D( [3., 3.]) ) + + object_validity = validity.is_pointset_valid2D( pointset ) + if not object_validity.nb_issues() == 0: + raise ValueError("PointSet has "+ str(object_validity.nb_issues())+ " object_validity when it should have none." ) + +def check_validity2D(): + pointset = geode.PointSet2D.create() + builder = geode.PointSetBuilder2D.create( pointset ) + builder.create_vertices( 7 ) + builder.set_point( 0, geode.Point2D([ 0., 2.])) + builder.set_point( 1, geode.Point2D([ 0., 2.])) + builder.set_point( 2, geode.Point2D([ 0., 0.])) + builder.set_point( 3, geode.Point2D([ 2., 0.])) + builder.set_point( 4, geode.Point2D([ 1., 4.])) + builder.set_point( 5, geode.Point2D([ 2., geode.GLOBAL_EPSILON / 2 ]) ) + builder.set_point( 6, geode.Point2D([ geode.GLOBAL_EPSILON / 1.1, 2. ]) ) + + object_validity = validity.is_pointset_valid2D( pointset ) + print( "2D object_validity: \n"+ object_validity.string() ) + if not object_validity.nb_issues() == 1: + raise ValueError("PointSet should have 1 invalidity reason, not "+str(object_validity.nb_issues()) ) + +def check_non_validity3D(): + pointset = geode.PointSet3D.create() + builder = geode.PointSetBuilder3D.create( pointset ) + builder.create_vertices( 4 ) + builder.set_point( 0, geode.Point3D([ 0., 2., 0. ]) ) + builder.set_point( 1, geode.Point3D([2., 0., 0.5 ]) ) + builder.set_point( 2, geode.Point3D([ 1., 4., 1. ]) ) + builder.set_point( 3, geode.Point3D([ 3., 3., 2. ]) ) + + object_validity = validity.is_pointset_valid3D( pointset ) + if not object_validity.nb_issues() == 0: + raise ValueError( "PointSet has "+ str(object_validity.nb_issues())+" object_validity when it should have none." ) + +def check_validity3D(): + pointset = geode.PointSet3D.create() + builder = geode.PointSetBuilder3D.create( pointset ) + builder.create_vertices( 7 ) + builder.set_point( 0, geode.Point3D([ 0., 2., 1. ]) ) + builder.set_point( 1, geode.Point3D([ 0., 2., 1. ]) ) + builder.set_point( 2, geode.Point3D([ 0., 0., 0. ]) ) + builder.set_point( 3, geode.Point3D([ 2., 0., 0. ]) ) + builder.set_point( 4, geode.Point3D([ 1., 4., 3. ]) ) + builder.set_point( 5, geode.Point3D([ 2., geode.GLOBAL_EPSILON / 2, geode.GLOBAL_EPSILON / 2 ]) ) + builder.set_point( 6, geode.Point3D([ geode.GLOBAL_EPSILON / 1.1, 2., 1. ]) ) + + object_validity = validity.is_pointset_valid3D( pointset ) + print( "3D object_validity: \n"+ object_validity.string() ) + if not object_validity.nb_issues() == 1: + raise ValueError( "PointSet should have 1 invalidity reason, not "+ str(object_validity.nb_issues()) ) + + +if __name__ == "__main__": + validity.OpenGeodeInspectorValidityLibrary.initialize() + check_non_validity2D() + check_validity2D() + check_non_validity3D() + check_validity3D() diff --git a/bindings/python/tests/validity/test-py-solid-validity.py b/bindings/python/tests/validity/test-py-solid-validity.py new file mode 100644 index 00000000..4744296e --- /dev/null +++ b/bindings/python/tests/validity/test-py-solid-validity.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019 - 2026 Geode-solutions +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import os +import sys +import platform + +if sys.version_info >= (3, 8, 0) and platform.system() == "Windows": + for path in [x.strip() for x in os.environ["PATH"].split("") if x]: + os.add_dll_directory(path) + +import opengeode as geode +import opengeode_inspector_py_validity as validity + +def check_validity(): + solid = geode.TetrahedralSolid3D.create() + builder = geode.TetrahedralSolidBuilder3D.create( solid ) + builder.create_vertices( 7 ) + builder.set_point( 0, geode.Point3D([ 0., 0., 2. ]) ) + builder.set_point( 1, geode.Point3D([ 3., .5, 0. ]) ) + builder.set_point( 2, geode.Point3D([ .5, 3., .5 ]) ) + builder.set_point( 3, geode.Point3D([ 2., 1.5, 3. ]) ) + builder.set_point( 4, geode.Point3D([ 3.5, 2.5, -.5 ]) ) + builder.set_point( 5, geode.Point3D([ 3., .5, 0. ]) ) + builder.set_point( 6, geode.Point3D([ .5, 3., .5 ]) ) + + object_validity = validity.is_solid_valid3D( solid ) + print( "ObjectValidity: \n" + object_validity.string() ) + if not object_validity.nb_issues() == 1: + raise ValueError( "SolidMesh should have 1 invalidity due to colocation, not " +str(object_validity.nb_issues()) ) + + builder.create_tetrahedron( [ 0, 1, 2, 3 ] ) + builder.create_tetrahedron( [ 5, 4, 6, 3 ] ) + + object_validity = validity.is_solid_valid3D( solid ) + print( "ObjectValidity: \n" + object_validity.string() ) + if not object_validity.nb_issues() == 2: + raise ValueError( "SolidMesh should have 2 object_validity due to colocation and non manifold vertices, not " + str(object_validity.nb_issues()) ) + + builder.create_tetrahedron( [ 2, 1, 6, 3 ] ) + builder.create_tetrahedron( [ 1, 5, 3, 6 ] ) + + object_validity = validity.is_solid_valid3D( solid ) + print( "ObjectValidity: \n" + object_validity.string() ) + if not object_validity.nb_issues() == 5: + raise ValueError( "SolidMesh should have 5 object_validity due to colocation, degenerated edges and polyhedra, and non manifold vertices and edges, not "+str(object_validity.nb_issues()) ) + + builder.create_tetrahedron( [ 1, 4, 3, 6 ] ) + + object_validity = validity.is_solid_valid3D( solid ) + print( "ObjectValidity: \n" + object_validity.string() ) + if not object_validity.nb_issues() == 7: + raise ValueError( "SolidMesh should have 7 object_validity due to colocation, degenerated edges and polyhedra, non manifold vertices, edges, and facets, and negative polyhedra, not "+str(object_validity.nb_issues())) + + builder.set_polyhedron_adjacent( geode.PolyhedronFacet( 0, 0 ), 1 ) + + object_validity = validity.is_solid_valid3D( solid ) + print( "ObjectValidity: \n" + object_validity.string() ) + if not object_validity.nb_issues() == 8: + raise ValueError( "SolidMesh should have 8 object_validity due to adjacencies, colocation, degenerated edges and polyhedra, non manifold vertices, edges, and facets, and negative polyhedra, not "+str(object_validity.nb_issues()) ) + + +if __name__ == "__main__": + validity.OpenGeodeInspectorValidityLibrary.initialize() + check_validity() diff --git a/bindings/python/tests/validity/test-py-surface-validity.py b/bindings/python/tests/validity/test-py-surface-validity.py new file mode 100644 index 00000000..b58b4f8d --- /dev/null +++ b/bindings/python/tests/validity/test-py-surface-validity.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019 - 2026 Geode-solutions +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import os +import sys +import platform + +if sys.version_info >= (3, 8, 0) and platform.system() == "Windows": + for path in [x.strip() for x in os.environ["PATH"].split("") if x]: + os.add_dll_directory(path) + +import opengeode as geode +import opengeode_inspector_py_validity as validity + +def check_validity(): + surface = geode.TriangulatedSurface3D.create() + builder = geode.TriangulatedSurfaceBuilder3D.create( surface ) + builder.create_vertices( 7 ) + builder.set_point( 0, geode.Point3D( [ 0., 2., 1. ] ) ) + builder.set_point( 1, geode.Point3D( [ 0., 2., 1. ] ) ) + builder.set_point( 2, geode.Point3D( [ 0., 0., 0. ] ) ) + builder.set_point( 3, geode.Point3D( [ 2., 0., 0. ] ) ) + builder.set_point( 4, geode.Point3D( [ 1., 4., 3. ] ) ) + builder.set_point( 5, geode.Point3D( [ 2., geode.GLOBAL_EPSILON / 2, geode.GLOBAL_EPSILON / 2 ] ) ) + builder.set_point( 6, geode.Point3D( [ geode.GLOBAL_EPSILON / 1.1, 2., 1. ] ) ) + + object_validity = validity.is_surface_valid3D( surface ) + print( "ObjectValidity: \n"+ object_validity.string() ) + if not object_validity.nb_issues() == 1: + raise ValueError( "SurfaceMesh should have 1 invalidity due to colocation, not "+str(object_validity.nb_issues()) ) + + builder.create_triangle( [ 0, 2, 3 ] ) + builder.create_triangle( [ 2, 4, 5 ] ) + + object_validity = validity.is_surface_valid3D( surface ) + print( "ObjectValidity: \n"+ object_validity.string() ) + if not object_validity.nb_issues() == 2: + raise ValueError( "SurfaceMesh should have 2 object_validity due to colocation and non manifold vertices , not "+str(object_validity.nb_issues()) ) + + builder.create_triangle( [ 2, 5, 3 ] ) + + object_validity = validity.is_surface_valid3D( surface ) + print( "ObjectValidity: \n"+ object_validity.string() ) + if not object_validity.nb_issues() == 5: + raise ValueError( "SurfaceMesh should have 5 object_validity due to colocation, degenerated edges and polygons, and non manifold edges and vertices, not "+str(object_validity.nb_issues()) ) + + builder.set_polygon_adjacent( geode.PolygonEdge( 0, 1 ), 2 ) + builder.set_polygon_adjacent( geode.PolygonEdge( 2, 2 ), 0 ) + builder.set_polygon_adjacent( geode.PolygonEdge( 2, 0 ), 1 ) + builder.set_polygon_adjacent( geode.PolygonEdge( 1, 2 ), 2 ) + + object_validity = validity.is_surface_valid3D( surface ) + print( "ObjectValidity: \n"+ object_validity.string() ) + if not object_validity.nb_issues() == 3: + raise ValueError( "SurfaceMesh should have 3 object_validity due to colocation, degenerated edges and polygons, not "+str(object_validity.nb_issues())) + + builder.create_point( geode.Point3D( [ 1., -1., -1. ] ) ) + builder.create_triangle( [ 2, 4, 7 ] ) + + object_validity = validity.is_surface_valid3D( surface ) + print( "ObjectValidity: \n"+ object_validity.string() ) + if not object_validity.nb_issues() == 6: + raise ValueError( "SurfaceMesh should have 6 object_validity due to colocation, degenerated edges and polygons, non manifold edges and vertices, and intersections, not "+str(object_validity.nb_issues()) ) + + builder.set_polygon_adjacent( geode.PolygonEdge( 1, 0 ), 3 ) + builder.set_polygon_adjacent( geode.PolygonEdge( 3, 0 ), 1 ) + + object_validity = validity.is_surface_valid3D( surface ) + print( "ObjectValidity: \n"+ object_validity.string() ) + if not object_validity.nb_issues() == 5: + raise ValueError( "SurfaceMesh should have 5 object_validity due to adjacencies, colocation, degenerated edges and polygons, and intersections, not "+str(object_validity.nb_issues()) ) + + +if __name__ == "__main__": + validity.OpenGeodeInspectorValidityLibrary.initialize() + check_validity() diff --git a/include/geode/inspector/validity/edgedcurve_validity.hpp b/include/geode/inspector/validity/edgedcurve_validity.hpp index 917b27cb..4eb91a51 100644 --- a/include/geode/inspector/validity/edgedcurve_validity.hpp +++ b/include/geode/inspector/validity/edgedcurve_validity.hpp @@ -35,5 +35,6 @@ namespace geode namespace geode { template < index_t dimension > - ObjectValidity is_edged_curve_valid( const EdgedCurve< dimension >& curve ); + [[nodiscard]] ObjectValidity is_edged_curve_valid( + const EdgedCurve< dimension >& curve ); } // namespace geode \ No newline at end of file diff --git a/include/geode/inspector/validity/object_validity.hpp b/include/geode/inspector/validity/object_validity.hpp index 89bf2497..0ff31af4 100644 --- a/include/geode/inspector/validity/object_validity.hpp +++ b/include/geode/inspector/validity/object_validity.hpp @@ -34,9 +34,9 @@ namespace geode return invalidities.empty(); } - index_t nb_issues() const; + [[nodiscard]] index_t nb_issues() const; - std::string string() const; + [[nodiscard]] std::string string() const; std::vector< std::string > invalidities{}; }; diff --git a/include/geode/inspector/validity/pointset_validity.hpp b/include/geode/inspector/validity/pointset_validity.hpp index 0ad6cb5b..37307e48 100644 --- a/include/geode/inspector/validity/pointset_validity.hpp +++ b/include/geode/inspector/validity/pointset_validity.hpp @@ -35,5 +35,6 @@ namespace geode namespace geode { template < index_t dimension > - ObjectValidity is_pointset_valid( const PointSet< dimension >& pointset ); + [[nodiscard]] ObjectValidity is_pointset_valid( + const PointSet< dimension >& pointset ); } // namespace geode \ No newline at end of file diff --git a/include/geode/inspector/validity/solid_validity.hpp b/include/geode/inspector/validity/solid_validity.hpp index 51abcec1..ca00203d 100644 --- a/include/geode/inspector/validity/solid_validity.hpp +++ b/include/geode/inspector/validity/solid_validity.hpp @@ -35,5 +35,6 @@ namespace geode namespace geode { template < index_t dimension > - ObjectValidity is_solid_valid( const SolidMesh< dimension >& solid ); + [[nodiscard]] ObjectValidity is_solid_valid( + const SolidMesh< dimension >& solid ); } // namespace geode \ No newline at end of file diff --git a/include/geode/inspector/validity/surface_validity.hpp b/include/geode/inspector/validity/surface_validity.hpp index d067d312..ebb4e26f 100644 --- a/include/geode/inspector/validity/surface_validity.hpp +++ b/include/geode/inspector/validity/surface_validity.hpp @@ -35,5 +35,6 @@ namespace geode namespace geode { template < index_t dimension > - ObjectValidity is_surface_valid( const SurfaceMesh< dimension >& surface ); + [[nodiscard]] ObjectValidity is_surface_valid( + const SurfaceMesh< dimension >& surface ); } // namespace geode \ No newline at end of file diff --git a/src/geode/inspector/inspection/criterion/manifold/surface_vertex_manifold.cpp b/src/geode/inspector/inspection/criterion/manifold/surface_vertex_manifold.cpp index e7a4479f..8d179d77 100644 --- a/src/geode/inspector/inspection/criterion/manifold/surface_vertex_manifold.cpp +++ b/src/geode/inspector/inspection/criterion/manifold/surface_vertex_manifold.cpp @@ -87,7 +87,6 @@ namespace geode { try { - DEBUG( vertex_id ); if( !polygons_around_vertex_are_the_same( polygons_around_vertices_list[vertex_id], mesh_.polygons_around_vertex( vertex_id ) ) ) diff --git a/tests/validity/test-pointset-validity.cpp b/tests/validity/test-pointset-validity.cpp index 58d61aed..ae8966c2 100644 --- a/tests/validity/test-pointset-validity.cpp +++ b/tests/validity/test-pointset-validity.cpp @@ -45,7 +45,7 @@ void check_non_validity2D() geode::OpenGeodeInspectorValidityException::test( object_validity.nb_issues() == 0, "PointSet has ", object_validity.nb_issues(), - " object_validity when it should have none." ); + " invalidities when it should have none." ); } void check_validity2D() @@ -85,7 +85,7 @@ void check_non_validity3D() geode::OpenGeodeInspectorValidityException::test( object_validity.nb_issues() == 0, "PointSet has ", object_validity.nb_issues(), - " object_validity when it should have none." ); + " invalidities when it should have none." ); } void check_validity3D() diff --git a/tests/validity/test-solid-validity.cpp b/tests/validity/test-solid-validity.cpp index 0bbe6cbd..1b6ba11d 100644 --- a/tests/validity/test-solid-validity.cpp +++ b/tests/validity/test-solid-validity.cpp @@ -54,7 +54,6 @@ void check_invalidity() builder->create_tetrahedron( { 0, 1, 2, 3 } ); builder->create_tetrahedron( { 5, 4, 6, 3 } ); - geode::save_tetrahedral_solid( *solid, "test_solid.og_tso3d" ); object_validity = geode::is_solid_valid( *solid ); geode::Logger::info( "ObjectValidity: \n", object_validity.string() ); From 6c09886a79d0e06ff02f9b263b174dd1b27edf52 Mon Sep 17 00:00:00 2001 From: MelchiorSchuh Date: Tue, 9 Jun 2026 12:10:31 +0200 Subject: [PATCH 3/6] Added model validity functions --- .../src/inspection/topology/brep_topology.hpp | 4 + .../topology/brep_corners_topology.hpp | 8 + .../inspector/validity/brep_validity.hpp | 38 ++ .../inspector/validity/section_validity.hpp | 38 ++ .../topology/brep_corners_topology.cpp | 36 ++ src/geode/inspector/validity/CMakeLists.txt | 4 + .../inspector/validity/brep_validity.cpp | 430 ++++++++++++++++++ .../inspector/validity/section_validity.cpp | 308 +++++++++++++ tests/inspection/test-brep.cpp | 3 +- tests/inspection/test-section.cpp | 1 - tests/validity/CMakeLists.txt | 20 + tests/validity/test-brep-validity.cpp | 119 +++++ tests/validity/test-section-validity.cpp | 61 +++ 13 files changed, 1068 insertions(+), 2 deletions(-) create mode 100644 include/geode/inspector/validity/brep_validity.hpp create mode 100644 include/geode/inspector/validity/section_validity.hpp create mode 100644 src/geode/inspector/validity/brep_validity.cpp create mode 100644 src/geode/inspector/validity/section_validity.cpp create mode 100644 tests/validity/test-brep-validity.cpp create mode 100644 tests/validity/test-section-validity.cpp diff --git a/bindings/python/src/inspection/topology/brep_topology.hpp b/bindings/python/src/inspection/topology/brep_topology.hpp index cb1bc22a..9efbb741 100644 --- a/bindings/python/src/inspection/topology/brep_topology.hpp +++ b/bindings/python/src/inspection/topology/brep_topology.hpp @@ -41,6 +41,10 @@ namespace geode .def_readwrite( "unique_vertices_linked_to_multiple_corners", &BRepCornersTopologyInspectionResult:: unique_vertices_linked_to_multiple_corners ) + .def_readwrite( + "unique_vertices_linked_to_multiply_embedded_corner", + &BRepCornersTopologyInspectionResult:: + unique_vertices_linked_to_multiply_embedded_corner ) .def_readwrite( "unique_vertices_linked_to_not_internal_nor_boundary_corner", &BRepCornersTopologyInspectionResult:: diff --git a/include/geode/inspector/inspection/topology/brep_corners_topology.hpp b/include/geode/inspector/inspection/topology/brep_corners_topology.hpp index 4b6492e4..21eec400 100644 --- a/include/geode/inspector/inspection/topology/brep_corners_topology.hpp +++ b/include/geode/inspector/inspection/topology/brep_corners_topology.hpp @@ -52,6 +52,11 @@ namespace geode InspectionIssues< index_t > unique_vertices_linked_to_multiple_corners{ "unique vertices shared by several Corners" }; + InspectionIssues< index_t > + unique_vertices_linked_to_multiply_embedded_corner{ + "unique vertices linked to a Corner embedded in more than two " + "components" + }; InspectionIssues< index_t > unique_vertices_linked_to_not_internal_nor_boundary_corner{ "unique vertices linked to a Corner with neither " @@ -95,6 +100,9 @@ namespace geode unique_vertex_has_multiple_corners( index_t unique_vertex_index ) const; + [[nodiscard]] std::optional< std::string > corner_is_multiply_embedded( + index_t unique_vertex_index ) const; + [[nodiscard]] std::optional< std::string > corner_is_not_internal_nor_boundary( index_t unique_vertex_index ) const; diff --git a/include/geode/inspector/validity/brep_validity.hpp b/include/geode/inspector/validity/brep_validity.hpp new file mode 100644 index 00000000..f87deadf --- /dev/null +++ b/include/geode/inspector/validity/brep_validity.hpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2019 - 2026 Geode-solutions + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#pragma once + +#include + +namespace geode +{ + class BRep; + struct ObjectValidity; +} // namespace geode + +namespace geode +{ + [[nodiscard]] ObjectValidity opengeode_inspector_validity_api is_brep_valid( + const BRep& brep ); +} // namespace geode \ No newline at end of file diff --git a/include/geode/inspector/validity/section_validity.hpp b/include/geode/inspector/validity/section_validity.hpp new file mode 100644 index 00000000..714583a3 --- /dev/null +++ b/include/geode/inspector/validity/section_validity.hpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2019 - 2026 Geode-solutions + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#pragma once + +#include + +namespace geode +{ + class Section; + struct ObjectValidity; +} // namespace geode + +namespace geode +{ + [[nodiscard]] ObjectValidity opengeode_inspector_validity_api + is_section_valid( const Section& section ); +} // namespace geode \ No newline at end of file diff --git a/src/geode/inspector/inspection/topology/brep_corners_topology.cpp b/src/geode/inspector/inspection/topology/brep_corners_topology.cpp index 13b11281..0536af70 100644 --- a/src/geode/inspector/inspection/topology/brep_corners_topology.cpp +++ b/src/geode/inspector/inspection/topology/brep_corners_topology.cpp @@ -39,6 +39,7 @@ namespace geode return corners_not_meshed.nb_issues() + corners_not_linked_to_a_unique_vertex.nb_issues() + unique_vertices_linked_to_multiple_corners.nb_issues() + + unique_vertices_linked_to_multiply_embedded_corner.nb_issues() + unique_vertices_linked_to_not_internal_nor_boundary_corner .nb_issues() + unique_vertices_liked_to_not_boundary_line_corner.nb_issues(); @@ -61,6 +62,12 @@ namespace geode absl::StrAppend( &message, unique_vertices_linked_to_multiple_corners.string() ); } + if( unique_vertices_linked_to_multiply_embedded_corner.nb_issues() + != 0 ) + { + absl::StrAppend( &message, + unique_vertices_linked_to_multiply_embedded_corner.string() ); + } if( unique_vertices_linked_to_not_internal_nor_boundary_corner .nb_issues() != 0 ) @@ -167,6 +174,29 @@ namespace geode return std::nullopt; } + std::optional< std::string > + BRepCornersTopology::corner_is_multiply_embedded( + index_t unique_vertex_index ) const + { + for( const auto& cmv : + brep_.component_mesh_vertices( unique_vertex_index ) ) + { + if( cmv.component_id.type() == Corner3D::component_type_static() + && brep_.corner( cmv.component_id.id() ).is_active() + && brep_.nb_embeddings( cmv.component_id.id() ) > 1 ) + { + return absl::StrCat( "unique vertex ", unique_vertex_index, + " is associated to Corner ", + brep_.corner( cmv.component_id.id() ) + .name() + .value_or( cmv.component_id.id().string() ), + " (", cmv.component_id.id().string(), + "), which is embedded in several components." ); + } + } + return std::nullopt; + } + std::optional< std::string > BRepCornersTopology::corner_is_not_internal_nor_boundary( index_t unique_vertex_index ) const @@ -300,6 +330,12 @@ namespace geode result.unique_vertices_linked_to_multiple_corners.add_issue( unique_vertex_id, problem_message.value() ); } + if( const auto problem_message = + corner_is_multiply_embedded( unique_vertex_id ) ) + { + result.unique_vertices_linked_to_multiply_embedded_corner + .add_issue( unique_vertex_id, problem_message.value() ); + } if( const auto problem_message = corner_is_not_internal_nor_boundary( unique_vertex_id ) ) { diff --git a/src/geode/inspector/validity/CMakeLists.txt b/src/geode/inspector/validity/CMakeLists.txt index c1b80426..7434d82b 100644 --- a/src/geode/inspector/validity/CMakeLists.txt +++ b/src/geode/inspector/validity/CMakeLists.txt @@ -22,17 +22,21 @@ add_geode_library( NAME validity FOLDER "geode/inspector/validity" SOURCES + "brep_validity.cpp" "common.cpp" "edgedcurve_validity.cpp" "object_validity.cpp" "pointset_validity.cpp" + "section_validity.cpp" "solid_validity.cpp" "surface_validity.cpp" PUBLIC_HEADERS + "brep_validity.hpp" "common.hpp" "edgedcurve_validity.hpp" "object_validity.hpp" "pointset_validity.hpp" + "section_validity.hpp" "solid_validity.hpp" "surface_validity.hpp" PUBLIC_DEPENDENCIES diff --git a/src/geode/inspector/validity/brep_validity.cpp b/src/geode/inspector/validity/brep_validity.cpp new file mode 100644 index 00000000..bbf721d5 --- /dev/null +++ b/src/geode/inspector/validity/brep_validity.cpp @@ -0,0 +1,430 @@ +/* + * Copyright (c) 2019 - 2026 Geode-solutions + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#include + +#include + +#include +#include + +namespace +{ + void add_brep_colocation_issues( + const geode::BRepMeshesInspectionResult& inspection, + geode::ObjectValidity& invalidities ) + { + const auto& uv_coloc = inspection.unique_vertices_colocation; + if( uv_coloc.colocated_unique_vertices_groups.nb_issues() > 0 ) + { + invalidities.invalidities.push_back( + uv_coloc.colocated_unique_vertices_groups.string() ); + } + if( uv_coloc.unique_vertices_linked_to_different_points.nb_issues() + > 0 ) + { + invalidities.invalidities.push_back( + uv_coloc.unique_vertices_linked_to_different_points.string() ); + } + const auto& meshes_coloc = + inspection.meshes_colocation.colocated_points_groups; + if( meshes_coloc.nb_issues() > 0 ) + { + invalidities.invalidities.push_back( meshes_coloc.string() ); + } + } + + void add_brep_adjacencies_issues( + const geode::BRepMeshesInspectionResult& inspection, + geode::ObjectValidity& invalidities ) + { + const auto& meshes_adj = inspection.meshes_adjacencies; + if( meshes_adj.surfaces_edges_with_wrong_adjacencies.nb_issues() > 0 ) + { + invalidities.invalidities.push_back( + meshes_adj.surfaces_edges_with_wrong_adjacencies.string() ); + } + if( meshes_adj.blocks_facets_with_wrong_adjacencies.nb_issues() > 0 ) + { + invalidities.invalidities.push_back( + meshes_adj.blocks_facets_with_wrong_adjacencies.string() ); + } + } + + void add_brep_degeneration_issues( + const geode::BRepMeshesInspectionResult& inspection, + geode::ObjectValidity& invalidities ) + { + const auto& meshes_degen = inspection.meshes_degenerations; + if( meshes_degen.degenerated_edges.nb_issues() > 0 ) + { + invalidities.invalidities.push_back( + meshes_degen.degenerated_edges.string() ); + } + if( meshes_degen.degenerated_polygons.nb_issues() > 0 ) + { + invalidities.invalidities.push_back( + meshes_degen.degenerated_polygons.string() ); + } + if( meshes_degen.degenerated_polyhedra.nb_issues() > 0 ) + { + invalidities.invalidities.push_back( + meshes_degen.degenerated_polyhedra.string() ); + } + } + + void add_brep_manifold_issues( + const geode::BRepMeshesInspectionResult& inspection, + geode::ObjectValidity& invalidities ) + { + const auto& manifolds = inspection.meshes_non_manifolds; + if( manifolds.meshes_non_manifold_vertices.nb_issues() > 0 ) + { + invalidities.invalidities.push_back( + manifolds.meshes_non_manifold_vertices.string() ); + } + if( manifolds.meshes_non_manifold_edges.nb_issues() > 0 ) + { + invalidities.invalidities.push_back( + manifolds.meshes_non_manifold_edges.string() ); + } + if( manifolds.meshes_non_manifold_facets.nb_issues() > 0 ) + { + invalidities.invalidities.push_back( + manifolds.meshes_non_manifold_facets.string() ); + } + if( manifolds.brep_non_manifold_edges.nb_issues() > 0 ) + { + invalidities.invalidities.push_back( + manifolds.brep_non_manifold_edges.string() ); + } + if( manifolds.brep_non_manifold_facets.nb_issues() > 0 ) + { + invalidities.invalidities.push_back( + manifolds.brep_non_manifold_facets.string() ); + } + } + + void add_brep_meshes_invalidities( + const geode::BRepMeshesInspectionResult& inspection, + geode::ObjectValidity& invalidities ) + { + add_brep_colocation_issues( inspection, invalidities ); + add_brep_adjacencies_issues( inspection, invalidities ); + add_brep_degeneration_issues( inspection, invalidities ); + const auto& meshes_inters = inspection.meshes_intersections; + if( meshes_inters.elements_intersections.nb_issues() > 0 ) + { + invalidities.invalidities.push_back( + meshes_inters.elements_intersections.string() ); + } + add_brep_manifold_issues( inspection, invalidities ); + const auto& negatives = + inspection.meshes_negative_elements.negative_polyhedra; + if( negatives.nb_issues() > 0 ) + { + invalidities.invalidities.push_back( negatives.string() ); + } + } + + void add_brep_corners_topology_issues( + const geode::BRepTopologyInspectionResult& inspection, + geode::ObjectValidity& invalidities ) + { + const auto& corners_topo = inspection.corners; + if( corners_topo.corners_not_meshed.nb_issues() > 0 ) + { + invalidities.invalidities.push_back( + corners_topo.corners_not_meshed.string() ); + } + if( corners_topo.corners_not_linked_to_a_unique_vertex.nb_issues() > 0 ) + { + invalidities.invalidities.push_back( + corners_topo.corners_not_linked_to_a_unique_vertex.string() ); + } + if( corners_topo.unique_vertices_linked_to_multiple_corners.nb_issues() + > 0 ) + { + invalidities.invalidities.push_back( corners_topo + .unique_vertices_linked_to_multiple_corners.string() ); + } + if( corners_topo.unique_vertices_linked_to_multiply_embedded_corner + .nb_issues() + > 0 ) + { + invalidities.invalidities.push_back( + corners_topo.unique_vertices_linked_to_multiply_embedded_corner + .string() ); + } + if( corners_topo + .unique_vertices_linked_to_not_internal_nor_boundary_corner + .nb_issues() + > 0 ) + { + invalidities.invalidities.push_back( corners_topo + .unique_vertices_linked_to_not_internal_nor_boundary_corner + .string() ); + } + if( corners_topo.unique_vertices_liked_to_not_boundary_line_corner + .nb_issues() + > 0 ) + { + invalidities.invalidities.push_back( + corners_topo.unique_vertices_liked_to_not_boundary_line_corner + .string() ); + } + } + + void add_brep_lines_topology_issues( + const geode::BRepTopologyInspectionResult& inspection, + geode::ObjectValidity& invalidities ) + { + const auto& lines_topo = inspection.lines; + if( lines_topo.lines_not_meshed.nb_issues() != 0 ) + { + invalidities.invalidities.push_back( + lines_topo.lines_not_meshed.string() ); + } + if( lines_topo + .unique_vertices_linked_to_line_with_wrong_relationship_to_surface + .nb_issues() + != 0 ) + { + invalidities.invalidities.push_back( lines_topo + .unique_vertices_linked_to_line_with_wrong_relationship_to_surface + .string() ); + } + if( lines_topo.lines_not_linked_to_a_unique_vertex.nb_issues() != 0 ) + { + invalidities.invalidities.push_back( + lines_topo.lines_not_linked_to_a_unique_vertex.string() ); + } + if( lines_topo.unique_vertices_linked_to_a_line_with_invalid_embeddings + .nb_issues() + != 0 ) + { + invalidities.invalidities.push_back( lines_topo + .unique_vertices_linked_to_a_line_with_invalid_embeddings + .string() ); + } + if( lines_topo.unique_vertices_linked_to_a_single_and_invalid_line + .nb_issues() + != 0 ) + { + invalidities.invalidities.push_back( + lines_topo.unique_vertices_linked_to_a_single_and_invalid_line + .string() ); + } + if( lines_topo + .unique_vertices_linked_to_several_lines_but_not_linked_to_a_corner + .nb_issues() + != 0 ) + { + invalidities.invalidities.push_back( lines_topo + .unique_vertices_linked_to_several_lines_but_not_linked_to_a_corner + .string() ); + } + if( lines_topo.line_edges_with_wrong_component_edges_around.nb_issues() + != 0 ) + { + invalidities.invalidities.push_back( lines_topo + .line_edges_with_wrong_component_edges_around.string() ); + } + } + + void add_brep_surfaces_topology_issues( + const geode::BRepTopologyInspectionResult& inspection, + geode::ObjectValidity& invalidities ) + { + const auto& surfaces_topo = inspection.surfaces; + if( surfaces_topo.surfaces_not_meshed.nb_issues() != 0 ) + { + invalidities.invalidities.push_back( + surfaces_topo.surfaces_not_meshed.string() ); + } + if( surfaces_topo.surfaces_not_linked_to_a_unique_vertex.nb_issues() + != 0 ) + { + invalidities.invalidities.push_back( + surfaces_topo.surfaces_not_linked_to_a_unique_vertex.string() ); + } + if( surfaces_topo + .unique_vertices_linked_to_a_surface_with_invalid_embbedings + .nb_issues() + != 0 ) + { + invalidities.invalidities.push_back( surfaces_topo + .unique_vertices_linked_to_a_surface_with_invalid_embbedings + .string() ); + } + if( surfaces_topo.unique_vertices_linked_to_several_and_invalid_surfaces + .nb_issues() + != 0 ) + { + invalidities.invalidities.push_back( surfaces_topo + .unique_vertices_linked_to_several_and_invalid_surfaces + .string() ); + } + if( surfaces_topo + .unique_vertices_linked_to_a_line_but_is_not_on_a_surface_border + .nb_issues() + != 0 ) + { + invalidities.invalidities.push_back( surfaces_topo + .unique_vertices_linked_to_a_line_but_is_not_on_a_surface_border + .string() ); + } + if( surfaces_topo.surface_polygons_with_wrong_component_facets_around + .nb_issues() + != 0 ) + { + invalidities.invalidities.push_back( surfaces_topo + .surface_polygons_with_wrong_component_facets_around + .string() ); + } + } + + void add_brep_blocks_topology_issues( + const geode::BRepTopologyInspectionResult& inspection, + geode::ObjectValidity& invalidities ) + { + const auto& blocks_topo = inspection.blocks; + if( blocks_topo.some_blocks_not_meshed.nb_issues() != 0 ) + { + invalidities.invalidities.push_back( + blocks_topo.some_blocks_not_meshed.string() ); + } + if( blocks_topo.wrong_block_boundary_surface.nb_issues() != 0 ) + { + invalidities.invalidities.push_back( + blocks_topo.wrong_block_boundary_surface.string() ); + } + if( blocks_topo.blocks_not_linked_to_a_unique_vertex.nb_issues() != 0 ) + { + invalidities.invalidities.push_back( + blocks_topo.blocks_not_linked_to_a_unique_vertex.string() ); + } + if( blocks_topo + .unique_vertices_part_of_two_blocks_and_no_boundary_surface + .nb_issues() + != 0 ) + { + invalidities.invalidities.push_back( blocks_topo + .unique_vertices_part_of_two_blocks_and_no_boundary_surface + .string() ); + } + if( blocks_topo.unique_vertices_with_incorrect_block_cmvs_count + .nb_issues() + != 0 ) + { + invalidities.invalidities.push_back( blocks_topo + .unique_vertices_with_incorrect_block_cmvs_count.string() ); + } + if( blocks_topo + .unique_vertices_linked_to_surface_with_wrong_relationship_to_blocks + .nb_issues() + != 0 ) + { + invalidities.invalidities.push_back( blocks_topo + .unique_vertices_linked_to_surface_with_wrong_relationship_to_blocks + .string() ); + } + if( blocks_topo.unique_vertices_linked_to_a_single_and_invalid_surface + .nb_issues() + != 0 ) + { + invalidities.invalidities.push_back( blocks_topo + .unique_vertices_linked_to_a_single_and_invalid_surface + .string() ); + } + if( blocks_topo.blocks_with_not_closed_boundary_surfaces.nb_issues() + != 0 ) + { + invalidities.invalidities.push_back( + blocks_topo.blocks_with_not_closed_boundary_surfaces.string() ); + } + if( blocks_topo.model_boundaries_dont_form_a_closed_surface.nb_issues() + != 0 ) + { + invalidities.invalidities.push_back( blocks_topo + .model_boundaries_dont_form_a_closed_surface.string() ); + } + if( blocks_topo.unique_vertex_linked_to_multiple_invalid_surfaces + .nb_issues() + != 0 ) + { + invalidities.invalidities.push_back( + blocks_topo.unique_vertex_linked_to_multiple_invalid_surfaces + .string() ); + } + } + + void add_brep_uv_topology_issues( + const geode::BRepTopologyInspectionResult& inspection, + geode::ObjectValidity& invalidities ) + { + if( inspection.unique_vertices_not_linked_to_any_component.nb_issues() + != 0 ) + { + invalidities.invalidities.push_back( inspection + .unique_vertices_not_linked_to_any_component.string() ); + } + if( inspection.unique_vertices_linked_to_inexistant_cmv.nb_issues() + != 0 ) + { + invalidities.invalidities.push_back( + inspection.unique_vertices_linked_to_inexistant_cmv.string() ); + } + if( inspection.unique_vertices_nonbijectively_linked_to_cmv.nb_issues() + != 0 ) + { + invalidities.invalidities.push_back( inspection + .unique_vertices_nonbijectively_linked_to_cmv.string() ); + } + } + + void add_brep_topology_invalidities( + const geode::BRepTopologyInspectionResult& inspection, + geode::ObjectValidity& invalidities ) + { + add_brep_corners_topology_issues( inspection, invalidities ); + add_brep_lines_topology_issues( inspection, invalidities ); + add_brep_surfaces_topology_issues( inspection, invalidities ); + add_brep_blocks_topology_issues( inspection, invalidities ); + add_brep_uv_topology_issues( inspection, invalidities ); + } +} // namespace + +namespace geode +{ + ObjectValidity is_brep_valid( const BRep& brep ) + { + BRepInspector brep_inspector{ brep }; + const auto inspection_result = brep_inspector.inspect_brep(); + ObjectValidity invalidities; + add_brep_meshes_invalidities( inspection_result.meshes, invalidities ); + add_brep_topology_invalidities( + inspection_result.topology, invalidities ); + return invalidities; + } +} // namespace geode diff --git a/src/geode/inspector/validity/section_validity.cpp b/src/geode/inspector/validity/section_validity.cpp new file mode 100644 index 00000000..74d53212 --- /dev/null +++ b/src/geode/inspector/validity/section_validity.cpp @@ -0,0 +1,308 @@ +/* + * Copyright (c) 2019 - 2026 Geode-solutions + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#include + +#include + +#include +#include + +namespace +{ + void add_section_colocation_issues( + const geode::SectionMeshesInspectionResult& inspection, + geode::ObjectValidity& invalidities ) + { + const auto& uv_coloc = inspection.unique_vertices_colocation; + if( uv_coloc.colocated_unique_vertices_groups.nb_issues() > 0 ) + { + invalidities.invalidities.push_back( + uv_coloc.colocated_unique_vertices_groups.string() ); + } + if( uv_coloc.unique_vertices_linked_to_different_points.nb_issues() + > 0 ) + { + invalidities.invalidities.push_back( + uv_coloc.unique_vertices_linked_to_different_points.string() ); + } + const auto& meshes_coloc = + inspection.meshes_colocation.colocated_points_groups; + if( meshes_coloc.nb_issues() > 0 ) + { + invalidities.invalidities.push_back( meshes_coloc.string() ); + } + } + + void add_section_adjacencies_issues( + const geode::SectionMeshesInspectionResult& inspection, + geode::ObjectValidity& invalidities ) + { + const auto& meshes_adj = inspection.meshes_adjacencies; + if( meshes_adj.surfaces_edges_with_wrong_adjacencies.nb_issues() > 0 ) + { + invalidities.invalidities.push_back( + meshes_adj.surfaces_edges_with_wrong_adjacencies.string() ); + } + } + + void add_section_degeneration_issues( + const geode::SectionMeshesInspectionResult& inspection, + geode::ObjectValidity& invalidities ) + { + const auto& meshes_degen = inspection.meshes_degenerations; + if( meshes_degen.degenerated_edges.nb_issues() > 0 ) + { + invalidities.invalidities.push_back( + meshes_degen.degenerated_edges.string() ); + } + if( meshes_degen.degenerated_polygons.nb_issues() > 0 ) + { + invalidities.invalidities.push_back( + meshes_degen.degenerated_polygons.string() ); + } + } + + void add_section_manifold_issues( + const geode::SectionMeshesInspectionResult& inspection, + geode::ObjectValidity& invalidities ) + { + const auto& manifolds = inspection.meshes_non_manifolds; + if( manifolds.meshes_non_manifold_vertices.nb_issues() > 0 ) + { + invalidities.invalidities.push_back( + manifolds.meshes_non_manifold_vertices.string() ); + } + if( manifolds.meshes_non_manifold_edges.nb_issues() > 0 ) + { + invalidities.invalidities.push_back( + manifolds.meshes_non_manifold_edges.string() ); + } + } + + void add_section_meshes_invalidities( + const geode::SectionMeshesInspectionResult& inspection, + geode::ObjectValidity& invalidities ) + { + add_section_colocation_issues( inspection, invalidities ); + add_section_adjacencies_issues( inspection, invalidities ); + add_section_degeneration_issues( inspection, invalidities ); + const auto& meshes_inters = inspection.meshes_intersections; + if( meshes_inters.elements_intersections.nb_issues() > 0 ) + { + invalidities.invalidities.push_back( + meshes_inters.elements_intersections.string() ); + } + add_section_manifold_issues( inspection, invalidities ); + const auto& negatives = + inspection.meshes_negative_elements.negative_polygons; + if( negatives.nb_issues() > 0 ) + { + invalidities.invalidities.push_back( negatives.string() ); + } + } + + void add_section_corners_topology_issues( + const geode::SectionTopologyInspectionResult& inspection, + geode::ObjectValidity& invalidities ) + { + const auto& corners_topo = inspection.corners; + if( corners_topo.corners_not_meshed.nb_issues() > 0 ) + { + invalidities.invalidities.push_back( + corners_topo.corners_not_meshed.string() ); + } + if( corners_topo.corners_not_linked_to_a_unique_vertex.nb_issues() > 0 ) + { + invalidities.invalidities.push_back( + corners_topo.corners_not_linked_to_a_unique_vertex.string() ); + } + if( corners_topo.unique_vertices_linked_to_multiple_corners.nb_issues() + > 0 ) + { + invalidities.invalidities.push_back( corners_topo + .unique_vertices_linked_to_multiple_corners.string() ); + } + if( corners_topo.unique_vertices_linked_to_multiple_internals_corner + .nb_issues() + > 0 ) + { + invalidities.invalidities.push_back( + corners_topo.unique_vertices_linked_to_multiple_internals_corner + .string() ); + } + if( corners_topo + .unique_vertices_linked_to_not_internal_nor_boundary_corner + .nb_issues() + > 0 ) + { + invalidities.invalidities.push_back( corners_topo + .unique_vertices_linked_to_not_internal_nor_boundary_corner + .string() ); + } + if( corners_topo.unique_vertices_linked_to_not_boundary_line_corner + .nb_issues() + > 0 ) + { + invalidities.invalidities.push_back( + corners_topo.unique_vertices_linked_to_not_boundary_line_corner + .string() ); + } + } + + void add_section_lines_topology_issues( + const geode::SectionTopologyInspectionResult& inspection, + geode::ObjectValidity& invalidities ) + { + const auto& lines_topo = inspection.lines; + if( lines_topo.lines_not_meshed.nb_issues() != 0 ) + { + invalidities.invalidities.push_back( + lines_topo.lines_not_meshed.string() ); + } + if( lines_topo.lines_not_linked_to_a_unique_vertex.nb_issues() != 0 ) + { + invalidities.invalidities.push_back( + lines_topo.lines_not_linked_to_a_unique_vertex.string() ); + } + if( lines_topo + .unique_vertices_linked_to_line_with_wrong_relationship_to_surface + .nb_issues() + != 0 ) + { + invalidities.invalidities.push_back( lines_topo + .unique_vertices_linked_to_line_with_wrong_relationship_to_surface + .string() ); + } + if( lines_topo.unique_vertices_linked_to_a_line_with_invalid_embeddings + .nb_issues() + != 0 ) + { + invalidities.invalidities.push_back( lines_topo + .unique_vertices_linked_to_a_line_with_invalid_embeddings + .string() ); + } + if( lines_topo.unique_vertices_linked_to_a_single_and_invalid_line + .nb_issues() + != 0 ) + { + invalidities.invalidities.push_back( + lines_topo.unique_vertices_linked_to_a_single_and_invalid_line + .string() ); + } + if( lines_topo + .unique_vertices_linked_to_several_lines_but_not_linked_to_a_corner + .nb_issues() + != 0 ) + { + invalidities.invalidities.push_back( lines_topo + .unique_vertices_linked_to_several_lines_but_not_linked_to_a_corner + .string() ); + } + } + + void add_section_surfaces_topology_issues( + const geode::SectionTopologyInspectionResult& inspection, + geode::ObjectValidity& invalidities ) + { + const auto& surfaces_topo = inspection.surfaces; + if( surfaces_topo.surfaces_not_meshed.nb_issues() != 0 ) + { + invalidities.invalidities.push_back( + surfaces_topo.surfaces_not_meshed.string() ); + } + if( surfaces_topo.surfaces_not_linked_to_a_unique_vertex.nb_issues() + != 0 ) + { + invalidities.invalidities.push_back( + surfaces_topo.surfaces_not_linked_to_a_unique_vertex.string() ); + } + if( surfaces_topo + .unique_vertices_linked_to_a_surface_with_invalid_embbedings + .nb_issues() + != 0 ) + { + invalidities.invalidities.push_back( surfaces_topo + .unique_vertices_linked_to_a_surface_with_invalid_embbedings + .string() ); + } + if( surfaces_topo + .unique_vertices_linked_to_a_line_but_is_not_on_a_surface_border + .nb_issues() + != 0 ) + { + invalidities.invalidities.push_back( surfaces_topo + .unique_vertices_linked_to_a_line_but_is_not_on_a_surface_border + .string() ); + } + } + + void add_section_uv_topology_issues( + const geode::SectionTopologyInspectionResult& inspection, + geode::ObjectValidity& invalidities ) + { + if( inspection.unique_vertices_not_linked_to_any_component.nb_issues() + != 0 ) + { + invalidities.invalidities.push_back( inspection + .unique_vertices_not_linked_to_any_component.string() ); + } + if( inspection.unique_vertices_linked_to_inexistant_cmv.nb_issues() + != 0 ) + { + invalidities.invalidities.push_back( + inspection.unique_vertices_linked_to_inexistant_cmv.string() ); + } + if( inspection.unique_vertices_nonbijectively_linked_to_cmv.nb_issues() + != 0 ) + { + invalidities.invalidities.push_back( inspection + .unique_vertices_nonbijectively_linked_to_cmv.string() ); + } + } + + void add_section_topology_invalidities( + const geode::SectionTopologyInspectionResult& inspection, + geode::ObjectValidity& invalidities ) + { + add_section_corners_topology_issues( inspection, invalidities ); + add_section_lines_topology_issues( inspection, invalidities ); + add_section_surfaces_topology_issues( inspection, invalidities ); + add_section_uv_topology_issues( inspection, invalidities ); + } +} // namespace + +namespace geode +{ + ObjectValidity is_section_valid( const Section& section ) + { + SectionInspector section_inspector{ section }; + const auto inspection_result = section_inspector.inspect_section(); + ObjectValidity invalidities; + add_section_meshes_invalidities( + inspection_result.meshes, invalidities ); + add_section_topology_invalidities( + inspection_result.topology, invalidities ); + return invalidities; + } +} // namespace geode diff --git a/tests/inspection/test-brep.cpp b/tests/inspection/test-brep.cpp index 8a2c752c..087c6858 100644 --- a/tests/inspection/test-brep.cpp +++ b/tests/inspection/test-brep.cpp @@ -26,7 +26,6 @@ #include #include -#include #include #include @@ -45,6 +44,8 @@ geode::index_t corners_topological_validity( nb_issues += result.unique_vertices_liked_to_not_boundary_line_corner.nb_issues(); nb_issues += result.unique_vertices_linked_to_multiple_corners.nb_issues(); + nb_issues += + result.unique_vertices_linked_to_multiply_embedded_corner.nb_issues(); nb_issues += result.unique_vertices_linked_to_not_internal_nor_boundary_corner .nb_issues(); diff --git a/tests/inspection/test-section.cpp b/tests/inspection/test-section.cpp index f5fe8c14..965c7d86 100644 --- a/tests/inspection/test-section.cpp +++ b/tests/inspection/test-section.cpp @@ -26,7 +26,6 @@ #include #include -#include #include #include diff --git a/tests/validity/CMakeLists.txt b/tests/validity/CMakeLists.txt index 005738be..ba5901aa 100755 --- a/tests/validity/CMakeLists.txt +++ b/tests/validity/CMakeLists.txt @@ -18,6 +18,16 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +add_geode_test( + SOURCE "test-brep-validity.cpp" + DEPENDENCIES + OpenGeode::basic + OpenGeode::geometry + OpenGeode::mesh + OpenGeode::model + ${PROJECT_NAME}::validity +) + add_geode_test( SOURCE "test-edgedcurve-validity.cpp" DEPENDENCIES @@ -36,6 +46,16 @@ add_geode_test( ${PROJECT_NAME}::validity ) +add_geode_test( + SOURCE "test-section-validity.cpp" + DEPENDENCIES + OpenGeode::basic + OpenGeode::geometry + OpenGeode::mesh + OpenGeode::model + ${PROJECT_NAME}::validity +) + add_geode_test( SOURCE "test-solid-validity.cpp" DEPENDENCIES diff --git a/tests/validity/test-brep-validity.cpp b/tests/validity/test-brep-validity.cpp new file mode 100644 index 00000000..ebabcf38 --- /dev/null +++ b/tests/validity/test-brep-validity.cpp @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2019 - 2026 Geode-solutions + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#include + +#include + +#include +#include + +#include +#include + +void check_model_a1() +{ + const auto model_brep = geode::load_brep( + absl::StrCat( geode::DATA_PATH, "model_A1.og_brep" ) ); + const auto invalidities = geode::is_brep_valid( model_brep ); + + geode::OpenGeodeInspectorValidityException::test( + invalidities.nb_issues() == 7, "model_A1 has ", + invalidities.nb_issues(), " issues instead of 7." ); +} + +void check_model_a1_valid() +{ + const auto model_brep = geode::load_brep( + absl::StrCat( geode::DATA_PATH, "model_A1_valid.og_brep" ) ); + const auto invalidities = geode::is_brep_valid( model_brep ); + + geode::OpenGeodeInspectorValidityException::test( + invalidities.nb_issues() == 7, "model_A1_valid has ", + invalidities.nb_issues(), " issues instead of 7." ); +} + +void check_model_mss() +{ + const auto model_brep = + geode::load_brep( absl::StrCat( geode::DATA_PATH, "mss.og_brep" ) ); + const auto invalidities = geode::is_brep_valid( model_brep ); + + geode::OpenGeodeInspectorValidityException::test( + invalidities.nb_issues() == 5, "model_mss has ", + invalidities.nb_issues(), " issues instead of 5." ); +} + +void check_model_D() +{ + const auto model_brep = + geode::load_brep( absl::StrCat( geode::DATA_PATH, "model_D.og_brep" ) ); + const auto invalidities = geode::is_brep_valid( model_brep ); + + geode::OpenGeodeInspectorValidityException::test( + invalidities.nb_issues() == 0, "model_D has ", invalidities.nb_issues(), + " issues instead of 0." ); +} + +void check_wrong_bsurfaces_model() +{ + const auto model_brep = geode::load_brep( absl::StrCat( + geode::DATA_PATH, "wrong_boundary_surface_model.og_brep" ) ); + const auto invalidities = geode::is_brep_valid( model_brep ); + + geode::OpenGeodeInspectorValidityException::test( + invalidities.nb_issues() == 2, "wrong_boundary_surface_model has ", + invalidities.nb_issues(), " issues instead of 2." ); +} + +void check_segmented_cube() +{ + const auto model_brep = geode::load_brep( + absl::StrCat( geode::DATA_PATH, "cube_segmented.og_brep" ) ); + const auto invalidities = geode::is_brep_valid( model_brep ); + + geode::OpenGeodeInspectorValidityException::test( + invalidities.nb_issues() == 1, "cube_segmented has ", + invalidities.nb_issues(), " issues instead of 1." ); +} + +int main() +{ + try + { + geode::OpenGeodeInspectorValidityLibrary::initialize(); + geode::Logger::set_level( geode::Logger::LEVEL::debug ); + check_model_a1(); + check_model_a1_valid(); + check_model_mss(); + check_model_D(); + check_wrong_bsurfaces_model(); + check_segmented_cube(); + geode::Logger::info( "TEST SUCCESS" ); + return 0; + } + catch( ... ) + { + return geode::geode_lippincott(); + } +} diff --git a/tests/validity/test-section-validity.cpp b/tests/validity/test-section-validity.cpp new file mode 100644 index 00000000..91323a43 --- /dev/null +++ b/tests/validity/test-section-validity.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2019 - 2026 Geode-solutions + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#include + +#include +#include + +#include +#include + +#include +#include + +void check_section() +{ + const auto model_section = geode::load_section( + absl::StrCat( geode::DATA_PATH, "vertical_lines.og_sctn" ) ); + const auto invalidities = geode::is_section_valid( model_section ); + + geode::OpenGeodeInspectorValidityException::test( + invalidities.nb_issues() == 0, "vertical_lines has ", + invalidities.nb_issues(), " issues instead of 0." ); +} + +int main() +{ + try + { + geode::OpenGeodeInspectorValidityLibrary::initialize(); + geode::Logger::set_level( geode::Logger::LEVEL::trace ); + check_section(); + + geode::Logger::info( "TEST SUCCESS" ); + return 0; + } + catch( ... ) + { + return geode::geode_lippincott(); + } +} \ No newline at end of file From addd853d3e533ee7cfbf06c79e80543ccda3f96f Mon Sep 17 00:00:00 2001 From: MelchiorSchuh Date: Tue, 9 Jun 2026 14:00:24 +0200 Subject: [PATCH 4/6] added model python bindings --- bindings/python/src/validity/CMakeLists.txt | 2 + .../python/src/validity/brep_validity.hpp | 36 ++++++++++ .../python/src/validity/section_validity.hpp | 36 ++++++++++ bindings/python/src/validity/validity.cpp | 4 ++ bindings/python/tests/validity/CMakeLists.txt | 12 ++++ .../tests/validity/test-py-brep-validity.py | 71 +++++++++++++++++++ .../validity/test-py-section-validity.py | 47 ++++++++++++ 7 files changed, 208 insertions(+) create mode 100644 bindings/python/src/validity/brep_validity.hpp create mode 100644 bindings/python/src/validity/section_validity.hpp create mode 100644 bindings/python/tests/validity/test-py-brep-validity.py create mode 100644 bindings/python/tests/validity/test-py-section-validity.py diff --git a/bindings/python/src/validity/CMakeLists.txt b/bindings/python/src/validity/CMakeLists.txt index 4110696b..52905bdf 100644 --- a/bindings/python/src/validity/CMakeLists.txt +++ b/bindings/python/src/validity/CMakeLists.txt @@ -21,9 +21,11 @@ add_geode_python_binding( NAME "py_validity" SOURCES + "brep_validity.hpp" "edgedcurve_validity.hpp" "object_validity.hpp" "pointset_validity.hpp" + "section_validity.hpp" "solid_validity.hpp" "surface_validity.hpp" "validity.cpp" diff --git a/bindings/python/src/validity/brep_validity.hpp b/bindings/python/src/validity/brep_validity.hpp new file mode 100644 index 00000000..ca239cde --- /dev/null +++ b/bindings/python/src/validity/brep_validity.hpp @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2019 - 2026 Geode-solutions + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ +#include + +#include + +#include +#include + +namespace geode +{ + void define_brep_validity( pybind11::module& module ) + { + module.def( "is_brep_valid", &is_brep_valid ); + } +} // namespace geode diff --git a/bindings/python/src/validity/section_validity.hpp b/bindings/python/src/validity/section_validity.hpp new file mode 100644 index 00000000..3f5ca72c --- /dev/null +++ b/bindings/python/src/validity/section_validity.hpp @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2019 - 2026 Geode-solutions + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ +#include + +#include + +#include +#include + +namespace geode +{ + void define_section_validity( pybind11::module& module ) + { + module.def( "is_section_valid", &is_section_valid ); + } +} // namespace geode diff --git a/bindings/python/src/validity/validity.cpp b/bindings/python/src/validity/validity.cpp index 6dd0ddfd..3983d525 100644 --- a/bindings/python/src/validity/validity.cpp +++ b/bindings/python/src/validity/validity.cpp @@ -25,9 +25,11 @@ #include "pybind11/pybind11.h" #include "pybind11/stl.h" +#include "brep_validity.hpp" #include "edgedcurve_validity.hpp" #include "object_validity.hpp" #include "pointset_validity.hpp" +#include "section_validity.hpp" #include "solid_validity.hpp" #include "surface_validity.hpp" @@ -39,8 +41,10 @@ PYBIND11_MODULE( opengeode_inspector_py_validity, module ) .def( "initialize", &geode::OpenGeodeInspectorValidityLibrary::initialize ); geode::define_object_validity( module ); + geode::define_brep_validity( module ); geode::define_edged_curve_validity( module ); geode::define_point_set_validity( module ); + geode::define_section_validity( module ); geode::define_solid_mesh_validity( module ); geode::define_surface_mesh_validity( module ); } \ No newline at end of file diff --git a/bindings/python/tests/validity/CMakeLists.txt b/bindings/python/tests/validity/CMakeLists.txt index 386b169d..f924d52a 100644 --- a/bindings/python/tests/validity/CMakeLists.txt +++ b/bindings/python/tests/validity/CMakeLists.txt @@ -18,6 +18,12 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. + add_geode_python_test( + SOURCE "test-py-brep-validity.py" + DEPENDENCIES + ${PROJECT_NAME}::py_validity + ) + add_geode_python_test( SOURCE "test-py-edgedcurve-validity.py" DEPENDENCIES @@ -30,6 +36,12 @@ ${PROJECT_NAME}::py_validity ) + add_geode_python_test( + SOURCE "test-py-section-validity.py" + DEPENDENCIES + ${PROJECT_NAME}::py_validity + ) + add_geode_python_test( SOURCE "test-py-solid-validity.py" DEPENDENCIES diff --git a/bindings/python/tests/validity/test-py-brep-validity.py b/bindings/python/tests/validity/test-py-brep-validity.py new file mode 100644 index 00000000..66284512 --- /dev/null +++ b/bindings/python/tests/validity/test-py-brep-validity.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019 - 2026 Geode-solutions +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import os +import sys +import platform + +if sys.version_info >= (3, 8, 0) and platform.system() == "Windows": + for path in [x.strip() for x in os.environ["PATH"].split("") if x]: + os.add_dll_directory(path) + +import opengeode as geode +import opengeode_inspector_py_validity as validity + +def data_dir(): + test_dir = os.path.dirname(__file__) + return os.path.abspath(os.path.join(test_dir, "../../../../tests/data")) + + +def check_a1(): + model_brep = geode.load_brep(data_dir() + "/model_A1.og_brep") + result = validity.is_brep_valid(model_brep) + if result.nb_issues()!=7: + raise ValueError( "[Test] model model_A1 should have 7 issues." ) + + +def check_a1_valid(): + model_brep = geode.load_brep(data_dir() + "/model_A1_valid.og_brep") + result = validity.is_brep_valid(model_brep) + if result.nb_issues()!=7: + raise ValueError( "[Test] model model_A1_valid should have 7 issues." ) + + +def check_model_mss(): + model_brep = geode.load_brep(data_dir() + "/mss.og_brep") + result = validity.is_brep_valid(model_brep) + if result.nb_issues()!=5: + raise ValueError( "[Test] model_mss should have 5 issues." ) + + +def check_model_D(): + model_brep = geode.load_brep(data_dir() + "/model_D.og_brep") + result = validity.is_brep_valid(model_brep) + if result.nb_issues()!=0: + raise ValueError( "[Test] model_D should have 0 issues." ) + + +if __name__ == "__main__": + validity.OpenGeodeInspectorValidityLibrary.initialize() + check_a1() + check_a1_valid() + check_model_mss() + check_model_D() diff --git a/bindings/python/tests/validity/test-py-section-validity.py b/bindings/python/tests/validity/test-py-section-validity.py new file mode 100644 index 00000000..19aa1542 --- /dev/null +++ b/bindings/python/tests/validity/test-py-section-validity.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019 - 2026 Geode-solutions +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import os +import sys +import platform + +if sys.version_info >= (3, 8, 0) and platform.system() == "Windows": + for path in [x.strip() for x in os.environ["PATH"].split("") if x]: + os.add_dll_directory(path) + +import opengeode as geode +import opengeode_inspector_py_validity as validity + +def data_dir(): + test_dir = os.path.dirname(__file__) + return os.path.abspath(os.path.join(test_dir, "../../../../tests/data")) + + +def check_section(): + model_section = geode.load_section(data_dir() + "/vertical_lines.og_sctn") + result = validity.is_section_valid(model_section) + if result.nb_issues()!=0: + raise ValueError( "[Test] Section vertical_lines should have 0 issues." ) + + +if __name__ == "__main__": + validity.OpenGeodeInspectorValidityLibrary.initialize() + check_section() \ No newline at end of file From cabceafb54bc73d5f33213e52c95f50f96907ea8 Mon Sep 17 00:00:00 2001 From: MelchiorSchuh Date: Tue, 9 Jun 2026 14:29:39 +0200 Subject: [PATCH 5/6] feat(Validity): Added validity checks for models --- .../python/src/inspection/topology/brep_topology.hpp | 5 +++-- bindings/python/tests/inspection/test-py-brep.py | 2 +- examples/model_inspection.py | 12 ++++++------ .../inspection/topology/brep_corners_topology.hpp | 2 +- .../inspection/topology/brep_corners_topology.cpp | 9 +++++---- src/geode/inspector/validity/brep_validity.cpp | 4 ++-- tests/inspection/test-brep.cpp | 2 +- 7 files changed, 19 insertions(+), 17 deletions(-) diff --git a/bindings/python/src/inspection/topology/brep_topology.hpp b/bindings/python/src/inspection/topology/brep_topology.hpp index 9efbb741..8a78b5ef 100644 --- a/bindings/python/src/inspection/topology/brep_topology.hpp +++ b/bindings/python/src/inspection/topology/brep_topology.hpp @@ -49,9 +49,10 @@ namespace geode "unique_vertices_linked_to_not_internal_nor_boundary_corner", &BRepCornersTopologyInspectionResult:: unique_vertices_linked_to_not_internal_nor_boundary_corner ) - .def_readwrite( "unique_vertices_liked_to_not_boundary_line_corner", + .def_readwrite( + "unique_vertices_linked_to_not_boundary_line_corner", &BRepCornersTopologyInspectionResult:: - unique_vertices_liked_to_not_boundary_line_corner ) + unique_vertices_linked_to_not_boundary_line_corner ) .def( "string", &BRepCornersTopologyInspectionResult::string ) .def( "inspection_type", &BRepCornersTopologyInspectionResult::inspection_type ); diff --git a/bindings/python/tests/inspection/test-py-brep.py b/bindings/python/tests/inspection/test-py-brep.py index 6937d2b9..6fbb36c8 100644 --- a/bindings/python/tests/inspection/test-py-brep.py +++ b/bindings/python/tests/inspection/test-py-brep.py @@ -36,7 +36,7 @@ def corners_topological_validity(result, verbose): for corner_issue in result.corners_not_linked_to_a_unique_vertex.issues_map(): nb_issues += corner_issue[1].nb_issues() nb_issues += result.corners_not_meshed.nb_issues() - nb_issues += result.unique_vertices_liked_to_not_boundary_line_corner.nb_issues() + nb_issues += result.unique_vertices_linked_to_not_boundary_line_corner.nb_issues() nb_issues += result.unique_vertices_linked_to_multiple_corners.nb_issues() nb_issues += ( result.unique_vertices_linked_to_not_internal_nor_boundary_corner.nb_issues() diff --git a/examples/model_inspection.py b/examples/model_inspection.py index 50d411ae..99094c12 100644 --- a/examples/model_inspection.py +++ b/examples/model_inspection.py @@ -85,16 +85,16 @@ def check_unique_vertices_linked_to_not_internal_nor_boundary_corner(brep_inspec ) -def check_unique_vertices_liked_to_not_boundary_line_corner(brep_inspector): - unique_vertices_liked_to_not_boundary_line_corner = ( - brep_inspector.unique_vertices_liked_to_not_boundary_line_corner() +def check_unique_vertices_linked_to_not_boundary_line_corner(brep_inspector): + unique_vertices_linked_to_not_boundary_line_corner = ( + brep_inspector.unique_vertices_linked_to_not_boundary_line_corner() ) print( "There are ", - len(unique_vertices_liked_to_not_boundary_line_corner), + len(unique_vertices_linked_to_not_boundary_line_corner), " corner vertices part of a line but not its boundary.", ) - for vertex_index in unique_vertices_liked_to_not_boundary_line_corner: + for vertex_index in unique_vertices_linked_to_not_boundary_line_corner: print( "Model unique vertex with index ", vertex_index, @@ -271,7 +271,7 @@ def launch_topological_validity_checks(brep_inspector): check_unique_vertices_linked_to_multiple_corners(brep_inspector) check_unique_vertices_linked_to_multiple_internals_corner(brep_inspector) check_unique_vertices_linked_to_not_internal_nor_boundary_corner(brep_inspector) - check_unique_vertices_liked_to_not_boundary_line_corner(brep_inspector) + check_unique_vertices_linked_to_not_boundary_line_corner(brep_inspector) check_unique_vertices_linked_to_line_with_wrong_relationship_to_surface( brep_inspector ) diff --git a/include/geode/inspector/inspection/topology/brep_corners_topology.hpp b/include/geode/inspector/inspection/topology/brep_corners_topology.hpp index 21eec400..dd247f29 100644 --- a/include/geode/inspector/inspection/topology/brep_corners_topology.hpp +++ b/include/geode/inspector/inspection/topology/brep_corners_topology.hpp @@ -63,7 +63,7 @@ namespace geode "boundary nor internal status" }; InspectionIssues< index_t > - unique_vertices_liked_to_not_boundary_line_corner{ + unique_vertices_linked_to_not_boundary_line_corner{ "unique vertices linked to a Line's Corner without boundary " "status" }; diff --git a/src/geode/inspector/inspection/topology/brep_corners_topology.cpp b/src/geode/inspector/inspection/topology/brep_corners_topology.cpp index 0536af70..1c2e8d94 100644 --- a/src/geode/inspector/inspection/topology/brep_corners_topology.cpp +++ b/src/geode/inspector/inspection/topology/brep_corners_topology.cpp @@ -42,7 +42,7 @@ namespace geode + unique_vertices_linked_to_multiply_embedded_corner.nb_issues() + unique_vertices_linked_to_not_internal_nor_boundary_corner .nb_issues() - + unique_vertices_liked_to_not_boundary_line_corner.nb_issues(); + + unique_vertices_linked_to_not_boundary_line_corner.nb_issues(); } std::string BRepCornersTopologyInspectionResult::string() const @@ -76,10 +76,11 @@ namespace geode unique_vertices_linked_to_not_internal_nor_boundary_corner .string() ); } - if( unique_vertices_liked_to_not_boundary_line_corner.nb_issues() != 0 ) + if( unique_vertices_linked_to_not_boundary_line_corner.nb_issues() + != 0 ) { absl::StrAppend( &message, - unique_vertices_liked_to_not_boundary_line_corner.string() ); + unique_vertices_linked_to_not_boundary_line_corner.string() ); } if( !message.empty() ) { @@ -347,7 +348,7 @@ namespace geode corner_is_part_of_line_but_not_boundary( unique_vertex_id ) ) { - result.unique_vertices_liked_to_not_boundary_line_corner + result.unique_vertices_linked_to_not_boundary_line_corner .add_issue( unique_vertex_id, problem_message.value() ); } } diff --git a/src/geode/inspector/validity/brep_validity.cpp b/src/geode/inspector/validity/brep_validity.cpp index bbf721d5..3ffec36c 100644 --- a/src/geode/inspector/validity/brep_validity.cpp +++ b/src/geode/inspector/validity/brep_validity.cpp @@ -185,12 +185,12 @@ namespace .unique_vertices_linked_to_not_internal_nor_boundary_corner .string() ); } - if( corners_topo.unique_vertices_liked_to_not_boundary_line_corner + if( corners_topo.unique_vertices_linked_to_not_boundary_line_corner .nb_issues() > 0 ) { invalidities.invalidities.push_back( - corners_topo.unique_vertices_liked_to_not_boundary_line_corner + corners_topo.unique_vertices_linked_to_not_boundary_line_corner .string() ); } } diff --git a/tests/inspection/test-brep.cpp b/tests/inspection/test-brep.cpp index 087c6858..82b6c0b6 100644 --- a/tests/inspection/test-brep.cpp +++ b/tests/inspection/test-brep.cpp @@ -42,7 +42,7 @@ geode::index_t corners_topological_validity( } nb_issues += result.corners_not_meshed.nb_issues(); nb_issues += - result.unique_vertices_liked_to_not_boundary_line_corner.nb_issues(); + result.unique_vertices_linked_to_not_boundary_line_corner.nb_issues(); nb_issues += result.unique_vertices_linked_to_multiple_corners.nb_issues(); nb_issues += result.unique_vertices_linked_to_multiply_embedded_corner.nb_issues(); From f6d60ae1de1faeb7c939b62cb3a6234839714ce6 Mon Sep 17 00:00:00 2001 From: MelchiorSchuh Date: Tue, 9 Jun 2026 15:01:16 +0200 Subject: [PATCH 6/6] fix test --- .../topology/brep_corners_topology.cpp | 44 ++++++++++++------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/src/geode/inspector/inspection/topology/brep_corners_topology.cpp b/src/geode/inspector/inspection/topology/brep_corners_topology.cpp index 1c2e8d94..6bad8800 100644 --- a/src/geode/inspector/inspection/topology/brep_corners_topology.cpp +++ b/src/geode/inspector/inspection/topology/brep_corners_topology.cpp @@ -28,6 +28,7 @@ #include +#include #include #include #include @@ -116,13 +117,9 @@ namespace geode } corner_found = true; const auto& corner_uuid = cmv.component_id.id(); - if( brep_.nb_embeddings( corner_uuid ) > 1 ) + if( brep_.nb_embeddings( corner_uuid ) == 0 ) { - return false; - } - if( brep_.nb_embeddings( corner_uuid ) != 1 ) - { - if( brep_.nb_incidences( corner_uuid ) < 1 ) + if( brep_.nb_incidences( corner_uuid ) == 0 ) { return false; } @@ -131,10 +128,14 @@ namespace geode { return false; } - if( corner_is_part_of_line_but_not_boundary( unique_vertex_index ) ) - { - return false; - } + } + if( corner_is_multiply_embedded( unique_vertex_index ) ) + { + return false; + } + if( corner_is_part_of_line_but_not_boundary( unique_vertex_index ) ) + { + return false; } return true; } @@ -186,13 +187,22 @@ namespace geode && brep_.corner( cmv.component_id.id() ).is_active() && brep_.nb_embeddings( cmv.component_id.id() ) > 1 ) { - return absl::StrCat( "unique vertex ", unique_vertex_index, - " is associated to Corner ", - brep_.corner( cmv.component_id.id() ) - .name() - .value_or( cmv.component_id.id().string() ), - " (", cmv.component_id.id().string(), - "), which is embedded in several components." ); + for( const auto& embedding : + brep_.embeddings( cmv.component_id.id() ) ) + { + if( embedding.type() == Block3D::component_type_static() + && brep_.block( embedding.id() ).is_active() ) + { + return absl::StrCat( "unique vertex ", + unique_vertex_index, " is associated to Corner ", + brep_.corner( cmv.component_id.id() ) + .name() + .value_or( cmv.component_id.id().string() ), + " (", cmv.component_id.id().string(), + "), which is embedded in several components " + "including a Block." ); + } + } } } return std::nullopt;