Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
2e08735
fix(Manifold): edges shared by more than 255 polygons are not manifold
BotellaA Oct 14, 2026
684af55
fix(Adjacency): out of range adjacencies are reported instead of cras…
BotellaA Oct 14, 2026
488cf68
fix(Manifold): model edges without unique vertices are not compared
BotellaA Oct 14, 2026
df1365a
fix(Manifold): single element components without unique vertices no l…
BotellaA Oct 14, 2026
e4d4427
fix(Manifold): broken adjacencies make solid edges non manifold inste…
BotellaA Oct 14, 2026
5b984af
perf(Manifold): solid vertex manifold without polyhedra around vertex…
BotellaA Oct 14, 2026
2120327
perf(Manifold): faster solid edges manifold
BotellaA Oct 14, 2026
ae22521
perf(Manifold): count polyhedra around facets with a reserved flat ha…
BotellaA Oct 14, 2026
ecfd3a3
fix(Manifold): surface vertices with broken adjacencies are not manifold
BotellaA Oct 14, 2026
129c360
perf(Manifold): surface vertex manifold without polygons around verte…
BotellaA Oct 14, 2026
fc32557
perf(Manifold): move block issues into the issues maps instead of cop…
BotellaA Oct 14, 2026
5dc9b56
perf(Manifold): run the model manifold inspections in parallel
BotellaA Oct 14, 2026
958093c
perf(Manifold): lighter BRep model edges manifold
BotellaA Oct 14, 2026
a78cd0f
perf(Adjacency): direct check of surface border edges on lines
BotellaA Oct 14, 2026
7d51835
fix(Adjacency): block border facets with unlinked vertices are reported
BotellaA Oct 14, 2026
4b91d44
perf(Adjacency): look up block border facets in a surface polygons set
BotellaA Oct 14, 2026
ff55a56
perf(Adjacency): check adjacency back-link before comparing facet cycles
BotellaA Oct 14, 2026
b22acab
perf(Adjacency): check solid facets adjacencies in parallel
BotellaA Oct 14, 2026
20df9c5
perf(NegativeElements): no polygon allocation for triangles
BotellaA Oct 14, 2026
bc55f48
Apply prepare changes
BotellaA Oct 14, 2026
da82a61
Merge branch 'next' of https://github.com/Geode-solutions/OpenGeode-I…
BotellaA Oct 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,20 @@

#include <geode/inspector/inspection/criterion/adjacency/brep_meshes_adjacency.hpp>

#include <absl/algorithm/container.h>
#include <absl/container/flat_hash_set.h>

#include <async++.h>

#include <geode/basic/logger.hpp>
#include <geode/basic/pimpl_impl.hpp>
#include <geode/basic/range.hpp>

#include <geode/mesh/core/solid_mesh.hpp>
#include <geode/mesh/core/surface_mesh.hpp>

#include <geode/model/helpers/component_mesh_polygons.hpp>
#include <geode/model/mixin/core/block.hpp>
#include <geode/model/mixin/core/surface.hpp>
#include <geode/model/representation/core/brep.hpp>

#include <geode/inspector/inspection/criterion/adjacency/solid_adjacency.hpp>
Expand Down Expand Up @@ -75,7 +79,7 @@
: public internal::ComponentMeshesAdjacency< BRep >
{
public:
Impl( const BRep& brep )

Check warning on line 82 in src/geode/inspector/inspection/criterion/adjacency/brep_meshes_adjacency.cpp

View workflow job for this annotation

GitHub Actions / test / tidy

src/geode/inspector/inspection/criterion/adjacency/brep_meshes_adjacency.cpp:82:9 [google-explicit-constructor]

single-argument constructors must be marked explicit to avoid unintentional implicit conversions
: internal::ComponentMeshesAdjacency< BRep >( brep )
{
}
Expand All @@ -84,61 +88,135 @@
InspectionIssuesMap< PolyhedronFacet >&
components_wrong_adjacencies ) const
{
if( model().nb_active_blocks() == 0 )
{
return;
}
const auto surface_polygons = surfaces_polygons_unique_vertices();
std::vector< async::task<
std::pair< uuid, InspectionIssues< PolyhedronFacet > > > >
tasks;
for( const auto& block : model().active_blocks() )
{
const geode::SolidMeshAdjacency3D inspector{ block.mesh() };
auto wrong_adjacencies =
inspector.polyhedron_facets_with_wrong_adjacency();
wrong_adjacencies.set_description( absl::StrCat( "Block ",
block.name().value_or( block.id().string() ), " (",
block.id().string(),
") polyhedron facets adjacencies issues" ) );
const auto& mesh = block.mesh();
for( const auto polyhedron_id : Range{ mesh.nb_polyhedra() } )
{
for( const auto facet_id :
LRange{ mesh.nb_polyhedron_facets( polyhedron_id ) } )
tasks.emplace_back( async::spawn( [this, &block,
&surface_polygons] {
return std::pair{ block.id(),
block_wrong_adjacencies( block, surface_polygons ) };
} ) );
}
for( auto& task : async::when_all( tasks ).get() )
{
auto [block_id, issues] = task.get();
components_wrong_adjacencies.add_issues_to_map(
block_id, std::move( issues ) );
}
}

private:
using SortedUniqueVertices = PolygonVertices;

std::vector< index_t > mesh_unique_vertices(

Check warning on line 118 in src/geode/inspector/inspection/criterion/adjacency/brep_meshes_adjacency.cpp

View workflow job for this annotation

GitHub Actions / test / tidy

src/geode/inspector/inspection/criterion/adjacency/brep_meshes_adjacency.cpp:118:9 [modernize-use-nodiscard]

function 'mesh_unique_vertices' should be marked [[nodiscard]]
const ComponentID& component_id, index_t nb_vertices ) const
{
std::vector< index_t > unique_vertices( nb_vertices );
for( const auto v : Indices{ unique_vertices } )
{
unique_vertices[v] =
model().unique_vertex( { component_id, v } );
}
return unique_vertices;
}

// Sorted unique vertices of every surface polygon
absl::flat_hash_set< SortedUniqueVertices >

Check warning on line 131 in src/geode/inspector/inspection/criterion/adjacency/brep_meshes_adjacency.cpp

View workflow job for this annotation

GitHub Actions / test / tidy

src/geode/inspector/inspection/criterion/adjacency/brep_meshes_adjacency.cpp:131:9 [modernize-use-nodiscard]

function 'surfaces_polygons_unique_vertices' should be marked [[nodiscard]]
surfaces_polygons_unique_vertices() const

Check warning on line 132 in src/geode/inspector/inspection/criterion/adjacency/brep_meshes_adjacency.cpp

View workflow job for this annotation

GitHub Actions / test / tidy

src/geode/inspector/inspection/criterion/adjacency/brep_meshes_adjacency.cpp:132:13 [readability-function-cognitive-complexity]

function 'surfaces_polygons_unique_vertices' has cognitive complexity of 15 (threshold 10)
{
std::vector< async::task< std::vector< SortedUniqueVertices > > >
tasks;
for( const auto& surface : model().surfaces() )
{
tasks.emplace_back( async::spawn( [this, &surface] {
const auto& mesh = surface.mesh();
const auto unique_vertices = mesh_unique_vertices(
surface.component_id(), mesh.nb_vertices() );
std::vector< SortedUniqueVertices > surface_polygons;
surface_polygons.reserve( mesh.nb_polygons() );
for( const auto polygon_id : Range{ mesh.nb_polygons() } )
{
const PolyhedronFacet polyhedron_facet{ polyhedron_id,
facet_id };
if( !mesh.is_polyhedron_facet_on_border(
polyhedron_facet ) )
auto vertices = mesh.polygon_vertices( polygon_id );
for( auto& vertex : vertices )
{
continue;
vertex = unique_vertices[vertex];
}
const auto is_on_surface =
polyhedron_facet_is_on_a_surface(
block, polyhedron_facet );
if( is_on_surface.has_value()
&& !is_on_surface.value() )
if( absl::c_contains( vertices, NO_ID ) )
{
wrong_adjacencies.add_issue( polyhedron_facet,
absl::StrCat( "facet ", facet_id,
" of polyhedron ", polyhedron_id,
" has no adjacencies but is not part of a "
"model Surface." ) );
continue;
}
absl::c_sort( vertices );
surface_polygons.emplace_back( std::move( vertices ) );
}
return surface_polygons;
} ) );
}
absl::flat_hash_set< SortedUniqueVertices > polygons;
for( auto& task : async::when_all( tasks ).get() )
{
for( auto& polygon : task.get() )
{
polygons.emplace( std::move( polygon ) );
}
components_wrong_adjacencies.add_issues_to_map(
block.id(), std::move( wrong_adjacencies ) );
}
return polygons;
}

private:
absl::optional< bool > polyhedron_facet_is_on_a_surface(
InspectionIssues< PolyhedronFacet > block_wrong_adjacencies(

Check warning on line 172 in src/geode/inspector/inspection/criterion/adjacency/brep_meshes_adjacency.cpp

View workflow job for this annotation

GitHub Actions / test / tidy

src/geode/inspector/inspection/criterion/adjacency/brep_meshes_adjacency.cpp:172:45 [readability-function-cognitive-complexity]

function 'block_wrong_adjacencies' has cognitive complexity of 13 (threshold 10)

Check warning on line 172 in src/geode/inspector/inspection/criterion/adjacency/brep_meshes_adjacency.cpp

View workflow job for this annotation

GitHub Actions / test / tidy

src/geode/inspector/inspection/criterion/adjacency/brep_meshes_adjacency.cpp:172:9 [modernize-use-nodiscard]

function 'block_wrong_adjacencies' should be marked [[nodiscard]]
const Block3D& block,
const PolyhedronFacet& polyhedron_facet ) const
const absl::flat_hash_set< SortedUniqueVertices >&
surface_polygons ) const
{
const auto facet_unique_vertices =
polygon_unique_vertices( model(), block, polyhedron_facet );
if( absl::c_contains( facet_unique_vertices, geode::NO_ID ) )
const auto& mesh = block.mesh();
const geode::SolidMeshAdjacency3D inspector{ mesh };
auto wrong_adjacencies =
inspector.polyhedron_facets_with_wrong_adjacency();
wrong_adjacencies.set_description( absl::StrCat( "Block ",
block.name().value_or( block.id().string() ), " (",
block.id().string(),
") polyhedron facets adjacencies issues" ) );
const auto unique_vertices = mesh_unique_vertices(
block.component_id(), mesh.nb_vertices() );
for( const auto polyhedron_id : Range{ mesh.nb_polyhedra() } )
{
return std::nullopt;
for( const auto facet_id :
LRange{ mesh.nb_polyhedron_facets( polyhedron_id ) } )
{
const PolyhedronFacet polyhedron_facet{ polyhedron_id,
facet_id };
if( !mesh.is_polyhedron_facet_on_border(
polyhedron_facet ) )
{
continue;
}
SortedUniqueVertices facet_unique_vertices;
for( const auto vertex :
mesh.polyhedron_facet_vertices( polyhedron_facet ) )
{
facet_unique_vertices.push_back(
unique_vertices[vertex] );
}
absl::c_sort( facet_unique_vertices );
// Facets with unlinked vertices cannot be on a Surface
if( absl::c_contains( facet_unique_vertices, NO_ID )
|| !surface_polygons.contains( facet_unique_vertices ) )
{
wrong_adjacencies.add_issue( polyhedron_facet,
absl::StrCat( "facet ", facet_id, " of polyhedron ",
polyhedron_id,
" has no adjacencies but is not part of a "
"model Surface." ) );
}
}
}
return !detail::surface_component_mesh_polygons(
model(), facet_unique_vertices )
.empty();
return wrong_adjacencies;
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@

#include <geode/inspector/inspection/criterion/adjacency/solid_adjacency.hpp>

#include <absl/container/inlined_vector.h>

#include <async++.h>

#include <geode/basic/pimpl_impl.hpp>
#include <geode/basic/uuid.hpp>

Expand All @@ -35,9 +39,9 @@
class SolidMeshAdjacency< dimension >::Impl
{
public:
Impl( const SolidMesh< dimension >& mesh ) : mesh_( mesh ) {}

Check warning on line 42 in src/geode/inspector/inspection/criterion/adjacency/solid_adjacency.cpp

View workflow job for this annotation

GitHub Actions / test / tidy

src/geode/inspector/inspection/criterion/adjacency/solid_adjacency.cpp:42:9 [google-explicit-constructor]

single-argument constructors must be marked explicit to avoid unintentional implicit conversions

bool mesh_has_wrong_adjacencies() const

Check warning on line 44 in src/geode/inspector/inspection/criterion/adjacency/solid_adjacency.cpp

View workflow job for this annotation

GitHub Actions / test / tidy

src/geode/inspector/inspection/criterion/adjacency/solid_adjacency.cpp:44:9 [modernize-use-nodiscard]

function 'mesh_has_wrong_adjacencies' should be marked [[nodiscard]]
{
for( const auto polyhedron_id : Range{ mesh_.nb_polyhedra() } )
{
Expand All @@ -57,7 +61,7 @@
return false;
}

InspectionIssues< PolyhedronFacet >

Check warning on line 64 in src/geode/inspector/inspection/criterion/adjacency/solid_adjacency.cpp

View workflow job for this annotation

GitHub Actions / test / tidy

src/geode/inspector/inspection/criterion/adjacency/solid_adjacency.cpp:64:9 [modernize-use-nodiscard]

function 'polyhedron_facets_with_wrong_adjacency' should be marked [[nodiscard]]
polyhedron_facets_with_wrong_adjacency() const
{
InspectionIssues< PolyhedronFacet > wrong_adjacency_facets{
Expand All @@ -65,46 +69,83 @@
mesh_.name().value_or( mesh_.id().string() ),
" polyhedron facets adjacencies issues" )
};
const auto wrong_facets = facets_with_wrong_adjacency();
for( const auto polyhedron_id : Range{ mesh_.nb_polyhedra() } )
{
for( const auto facet_id :
LRange{ mesh_.nb_polyhedron_facets( polyhedron_id ) } )
for( const auto facet_id : wrong_facets[polyhedron_id] )
{
const PolyhedronFacet polyhedron_facet{ polyhedron_id,
facet_id };
if( !mesh_.is_polyhedron_facet_on_border( polyhedron_facet )
&& !mesh_polyhedron_facet_has_right_adjacency(
polyhedron_facet ) )
{
wrong_adjacency_facets.add_issue( polyhedron_facet,
absl::StrCat( "facet ", facet_id, " of polyhedron ",
polyhedron_id, " has wrong adjacencies" ) );
}
wrong_adjacency_facets.add_issue(
PolyhedronFacet{ polyhedron_id, facet_id },
absl::StrCat( "facet ", facet_id, " of polyhedron ",
polyhedron_id, " has wrong adjacencies" ) );
}
}
return wrong_adjacency_facets;
}

private:
// Facets with wrong adjacency of each polyhedron
std::vector< absl::InlinedVector< local_index_t, 1 > >

Check warning on line 88 in src/geode/inspector/inspection/criterion/adjacency/solid_adjacency.cpp

View workflow job for this annotation

GitHub Actions / test / tidy

src/geode/inspector/inspection/criterion/adjacency/solid_adjacency.cpp:88:9 [modernize-use-nodiscard]

function 'facets_with_wrong_adjacency' should be marked [[nodiscard]]
facets_with_wrong_adjacency() const
{
std::vector< absl::InlinedVector< local_index_t, 1 > > wrong_facets(
mesh_.nb_polyhedra() );
async::parallel_for(
async::irange( index_t{ 0 }, mesh_.nb_polyhedra() ),
[this, &wrong_facets]( index_t polyhedron_id ) {
for( const auto facet_id :
LRange{ mesh_.nb_polyhedron_facets( polyhedron_id ) } )
{
const PolyhedronFacet polyhedron_facet{ polyhedron_id,
facet_id };
if( !mesh_.is_polyhedron_facet_on_border(
polyhedron_facet )
&& !mesh_polyhedron_facet_has_right_adjacency(
polyhedron_facet ) )
{
wrong_facets[polyhedron_id].push_back( facet_id );
}
}
} );
return wrong_facets;
}

bool mesh_polyhedron_facet_has_right_adjacency(
const PolyhedronFacet& polyhedron_facet ) const
{
const auto adjacent_polyhedron =
mesh_.polyhedron_adjacent( polyhedron_facet );
const auto polyhedron_adj_id = adjacent_polyhedron.value();
const auto facet_vertices =
detail::OrientedVertexCycle< PolyhedronFacetVertices >(
mesh_.polyhedron_facet_vertices( polyhedron_facet ) );
if( polyhedron_adj_id >= mesh_.nb_polyhedra() )
{
return false;
}
const auto nb_facet_vertices =
mesh_.nb_polyhedron_facet_vertices( polyhedron_facet );
std::optional<
detail::OrientedVertexCycle< PolyhedronFacetVertices > >
facet_vertices;
for( const auto facet_id :
LRange{ mesh_.nb_polyhedron_facets( polyhedron_adj_id ) } )
{
const PolyhedronFacet adj_facet{ polyhedron_adj_id, facet_id };
const auto adj_facet_vertices =
detail::OrientedVertexCycle< PolyhedronFacetVertices >(
mesh_.polyhedron_facet_vertices( adj_facet ) );
if( facet_vertices.is_opposite( adj_facet_vertices )
&& mesh_.polyhedron_adjacent( adj_facet )
== polyhedron_facet.polyhedron_id )
// Cheap checks before comparing the vertex cycles
if( mesh_.polyhedron_adjacent( adj_facet )
!= polyhedron_facet.polyhedron_id
|| mesh_.nb_polyhedron_facet_vertices( adj_facet )
!= nb_facet_vertices )
{
continue;
}
if( !facet_vertices )
{
facet_vertices.emplace(
mesh_.polyhedron_facet_vertices( polyhedron_facet ) );
}
const detail::OrientedVertexCycle< PolyhedronFacetVertices >
adj_facet_vertices{ mesh_.polyhedron_facet_vertices(
adj_facet ) };
if( facet_vertices->is_opposite( adj_facet_vertices ) )
{
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ namespace geode
const auto adjacent_polygon =
mesh_.polygon_adjacent( polygon_edge );
const auto polygon_adj_id = adjacent_polygon.value();
if( polygon_adj_id >= mesh_.nb_polygons() )
{
return false;
}
const auto v0 = mesh_.polygon_edge_vertex( polygon_edge, 0 );
const auto v1 = mesh_.polygon_edge_vertex( polygon_edge, 1 );
for( const auto edge_id :
Expand Down
Loading
Loading