From bc478e65ef6e33ed75645991c66de54fe9ec5905 Mon Sep 17 00:00:00 2001 From: Sven Berger Date: Tue, 12 May 2026 14:02:19 +0200 Subject: [PATCH 1/4] Rebuild geometry data after face deletion --- src/preprocessing/geometries/polygon.jl | 52 +++++++++++++++++-- src/preprocessing/geometries/triangle_mesh.jl | 25 +++++++-- test/preprocessing/geometries/geometries.jl | 28 ++++++++++ 3 files changed, 98 insertions(+), 7 deletions(-) diff --git a/src/preprocessing/geometries/polygon.jl b/src/preprocessing/geometries/polygon.jl index c56315b729..2a3780274e 100644 --- a/src/preprocessing/geometries/polygon.jl +++ b/src/preprocessing/geometries/polygon.jl @@ -1,5 +1,5 @@ # This is the data format returned by `load(file)` when used with `.asc` files -struct Polygon{NDIMS, ELTYPE} +mutable struct Polygon{NDIMS, ELTYPE} vertices :: Vector{SVector{NDIMS, ELTYPE}} edge_vertices :: Vector{NTuple{2, SVector{NDIMS, ELTYPE}}} vertex_normals :: Vector{NTuple{2, SVector{NDIMS, ELTYPE}}} @@ -97,6 +97,33 @@ struct Polygon{NDIMS, ELTYPE} end end +function rebuild_polygon_from_edges(edge_vertices, vertex_normals, edge_normals) + NDIMS = length(first(edge_normals)) + ELTYPE = eltype(first(edge_normals)) + vertices = SVector{NDIMS, ELTYPE}[] + vertex_ids = Dict{SVector{NDIMS, ELTYPE}, Int}() + + edge_vertices_ids = map(edge_vertices) do edge + v1, v2 = edge + id1 = get!(vertex_ids, v1) do + push!(vertices, v1) + return length(vertices) + end + id2 = get!(vertex_ids, v2) do + push!(vertices, v2) + return length(vertices) + end + + return (id1, id2) + end + + min_corner = SVector([minimum(v[i] for v in vertices) for i in 1:NDIMS]...) + max_corner = SVector([maximum(v[i] for v in vertices) for i in 1:NDIMS]...) + + return (; vertices, edge_vertices, vertex_normals, edge_normals, edge_vertices_ids, + min_corner, max_corner) +end + function Base.show(io::IO, geometry::Polygon) @nospecialize geometry # reduce precompilation time @@ -120,12 +147,31 @@ end @inline Base.eltype(::Polygon{NDIMS, ELTYPE}) where {NDIMS, ELTYPE} = ELTYPE @inline function Base.deleteat!(polygon::Polygon, indices) - (; edge_vertices, edge_normals, edge_vertices_ids) = polygon + edge_vertices = copy(polygon.edge_vertices) + vertex_normals = copy(polygon.vertex_normals) + edge_normals = copy(polygon.edge_normals) deleteat!(edge_vertices, indices) - deleteat!(edge_vertices_ids, indices) + deleteat!(vertex_normals, indices) deleteat!(edge_normals, indices) + if isempty(edge_vertices) + throw(ArgumentError("cannot delete all polygon edges")) + end + + (; vertices, edge_vertices_ids, min_corner, + max_corner) = rebuild_polygon_from_edges(edge_vertices, + vertex_normals, + edge_normals) + + polygon.vertices = vertices + polygon.edge_vertices = edge_vertices + polygon.vertex_normals = vertex_normals + polygon.edge_normals = edge_normals + polygon.edge_vertices_ids = edge_vertices_ids + polygon.min_corner = min_corner + polygon.max_corner = max_corner + return polygon end diff --git a/src/preprocessing/geometries/triangle_mesh.jl b/src/preprocessing/geometries/triangle_mesh.jl index ede02d4682..31cd18502e 100644 --- a/src/preprocessing/geometries/triangle_mesh.jl +++ b/src/preprocessing/geometries/triangle_mesh.jl @@ -1,5 +1,5 @@ # This is the data format returned by `load(file)` when used with `.stl` files -struct TriangleMesh{NDIMS, ELTYPE} +mutable struct TriangleMesh{NDIMS, ELTYPE} vertices :: Vector{SVector{NDIMS, ELTYPE}} face_vertices :: Vector{NTuple{3, SVector{NDIMS, ELTYPE}}} face_vertices_ids :: Vector{NTuple{3, Int}} @@ -172,13 +172,30 @@ end @inline face_normal(triangle, geometry::TriangleMesh) = geometry.face_normals[triangle] @inline function Base.deleteat!(mesh::TriangleMesh, indices) - (; face_vertices, face_vertices_ids, face_edges_ids, face_normals) = mesh + face_vertices = copy(mesh.face_vertices) + face_normals = copy(mesh.face_normals) deleteat!(face_vertices, indices) - deleteat!(face_vertices_ids, indices) - deleteat!(face_edges_ids, indices) deleteat!(face_normals, indices) + if isempty(face_vertices) + throw(ArgumentError("cannot delete all triangle mesh faces")) + end + + vertices = collect(Iterators.flatten(face_vertices)) + rebuilt = TriangleMesh(face_vertices, face_normals, vertices) + + mesh.vertices = rebuilt.vertices + mesh.face_vertices = rebuilt.face_vertices + mesh.face_vertices_ids = rebuilt.face_vertices_ids + mesh.face_edges_ids = rebuilt.face_edges_ids + mesh.edge_vertices_ids = rebuilt.edge_vertices_ids + mesh.vertex_normals = rebuilt.vertex_normals + mesh.edge_normals = rebuilt.edge_normals + mesh.face_normals = rebuilt.face_normals + mesh.min_corner = rebuilt.min_corner + mesh.max_corner = rebuilt.max_corner + return mesh end diff --git a/test/preprocessing/geometries/geometries.jl b/test/preprocessing/geometries/geometries.jl index 4f428d266c..3906af0aff 100644 --- a/test/preprocessing/geometries/geometries.jl +++ b/test/preprocessing/geometries/geometries.jl @@ -54,6 +54,34 @@ end end + @testset verbose=true "`deleteat!` Rebuilds Derived Data" begin + triangle = [0.0 1.0 0.5 0.0; + 0.0 0.0 0.7 0.0] + + edge_only = deleteat!(TrixiParticles.Polygon(triangle), [1, 2]) + + @test TrixiParticles.nfaces(edge_only) == 1 + @test length(edge_only.vertices) == 2 + @test length(edge_only.vertex_normals) == 1 + @test edge_only.min_corner == min.(edge_only.edge_vertices[1]...) + @test edge_only.max_corner == max.(edge_only.edge_vertices[1]...) + + A = SVector(0.0, 0.0, 0.0) + B = SVector(1.0, 0.0, 0.0) + C = SVector(0.0, 1.0, 0.0) + D = SVector(1.0, 1.0, 0.0) + face_vertices = [(A, B, C), (B, D, C)] + face_normals = [SVector(0.0, 0.0, 1.0), SVector(0.0, 0.0, 1.0)] + mesh = TrixiParticles.TriangleMesh(face_vertices, face_normals, [A, B, C, D]) + + deleteat!(mesh, 1) + + @test TrixiParticles.nfaces(mesh) == 1 + @test length(mesh.vertices) == 3 + @test length(mesh.edge_normals) == 3 + @test mesh.face_vertices == [face_vertices[2]] + end + @testset verbose=true "Real World Data" begin data_dir = pkgdir(TrixiParticles, "examples", "preprocessing", "data") validation_dir = pkgdir(TrixiParticles, "test", "preprocessing", "data") From 495543ab643ba1196a0fbece8c9951a172b6f0e4 Mon Sep 17 00:00:00 2001 From: Sven Berger Date: Sun, 17 May 2026 23:11:05 +0200 Subject: [PATCH 2/4] fix --- src/preprocessing/geometries/polygon.jl | 28 ++++++++++--------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/src/preprocessing/geometries/polygon.jl b/src/preprocessing/geometries/polygon.jl index 2a3780274e..3192633618 100644 --- a/src/preprocessing/geometries/polygon.jl +++ b/src/preprocessing/geometries/polygon.jl @@ -1,5 +1,5 @@ # This is the data format returned by `load(file)` when used with `.asc` files -mutable struct Polygon{NDIMS, ELTYPE} +struct Polygon{NDIMS, ELTYPE} vertices :: Vector{SVector{NDIMS, ELTYPE}} edge_vertices :: Vector{NTuple{2, SVector{NDIMS, ELTYPE}}} vertex_normals :: Vector{NTuple{2, SVector{NDIMS, ELTYPE}}} @@ -95,6 +95,13 @@ mutable struct Polygon{NDIMS, ELTYPE} return new{NDIMS, ELTYPE}(vertices, edge_vertices, vertex_normals, edge_normals, edge_vertices_ids, min_corner, max_corner) end + + function Polygon{NDIMS, ELTYPE}(vertices, edge_vertices, vertex_normals, + edge_normals, edge_vertices_ids, + min_corner, max_corner) where {NDIMS, ELTYPE} + return new{NDIMS, ELTYPE}(vertices, edge_vertices, vertex_normals, edge_normals, + edge_vertices_ids, min_corner, max_corner) + end end function rebuild_polygon_from_edges(edge_vertices, vertex_normals, edge_normals) @@ -120,8 +127,8 @@ function rebuild_polygon_from_edges(edge_vertices, vertex_normals, edge_normals) min_corner = SVector([minimum(v[i] for v in vertices) for i in 1:NDIMS]...) max_corner = SVector([maximum(v[i] for v in vertices) for i in 1:NDIMS]...) - return (; vertices, edge_vertices, vertex_normals, edge_normals, edge_vertices_ids, - min_corner, max_corner) + return Polygon{NDIMS, ELTYPE}(vertices, edge_vertices, vertex_normals, edge_normals, + edge_vertices_ids, min_corner, max_corner) end function Base.show(io::IO, geometry::Polygon) @@ -159,20 +166,7 @@ end throw(ArgumentError("cannot delete all polygon edges")) end - (; vertices, edge_vertices_ids, min_corner, - max_corner) = rebuild_polygon_from_edges(edge_vertices, - vertex_normals, - edge_normals) - - polygon.vertices = vertices - polygon.edge_vertices = edge_vertices - polygon.vertex_normals = vertex_normals - polygon.edge_normals = edge_normals - polygon.edge_vertices_ids = edge_vertices_ids - polygon.min_corner = min_corner - polygon.max_corner = max_corner - - return polygon + return rebuild_polygon_from_edges(edge_vertices, vertex_normals, edge_normals) end @inline nfaces(mesh::Polygon) = length(mesh.edge_normals) From f47d55fbf70a37f31f525a6d6dbe91398ab2ae15 Mon Sep 17 00:00:00 2001 From: Sven Berger Date: Sun, 17 May 2026 23:18:54 +0200 Subject: [PATCH 3/4] fix --- src/preprocessing/geometries/triangle_mesh.jl | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/src/preprocessing/geometries/triangle_mesh.jl b/src/preprocessing/geometries/triangle_mesh.jl index 31cd18502e..f35b95dcd1 100644 --- a/src/preprocessing/geometries/triangle_mesh.jl +++ b/src/preprocessing/geometries/triangle_mesh.jl @@ -1,5 +1,5 @@ # This is the data format returned by `load(file)` when used with `.stl` files -mutable struct TriangleMesh{NDIMS, ELTYPE} +struct TriangleMesh{NDIMS, ELTYPE} vertices :: Vector{SVector{NDIMS, ELTYPE}} face_vertices :: Vector{NTuple{3, SVector{NDIMS, ELTYPE}}} face_vertices_ids :: Vector{NTuple{3, Int}} @@ -183,20 +183,7 @@ end end vertices = collect(Iterators.flatten(face_vertices)) - rebuilt = TriangleMesh(face_vertices, face_normals, vertices) - - mesh.vertices = rebuilt.vertices - mesh.face_vertices = rebuilt.face_vertices - mesh.face_vertices_ids = rebuilt.face_vertices_ids - mesh.face_edges_ids = rebuilt.face_edges_ids - mesh.edge_vertices_ids = rebuilt.edge_vertices_ids - mesh.vertex_normals = rebuilt.vertex_normals - mesh.edge_normals = rebuilt.edge_normals - mesh.face_normals = rebuilt.face_normals - mesh.min_corner = rebuilt.min_corner - mesh.max_corner = rebuilt.max_corner - - return mesh + return TriangleMesh(face_vertices, face_normals, vertices) end @inline nfaces(mesh::TriangleMesh) = length(mesh.face_normals) From a16e0a4328359c1b7816ae7886dd9152a70a92ad Mon Sep 17 00:00:00 2001 From: Sven Berger Date: Wed, 27 May 2026 14:05:07 +0200 Subject: [PATCH 4/4] fix tests --- docs/src/preprocessing/preprocessing.md | 6 +++--- src/TrixiParticles.jl | 2 +- src/preprocessing/geometries/polygon.jl | 7 ++++++- src/preprocessing/geometries/triangle_mesh.jl | 2 +- test/preprocessing/geometries/geometries.jl | 6 +++--- test/preprocessing/packing/nhs_faces.jl | 5 +++-- 6 files changed, 17 insertions(+), 11 deletions(-) diff --git a/docs/src/preprocessing/preprocessing.md b/docs/src/preprocessing/preprocessing.md index 0518574b73..570c400279 100644 --- a/docs/src/preprocessing/preprocessing.md +++ b/docs/src/preprocessing/preprocessing.md @@ -31,9 +31,9 @@ triangle = [125.0 375.0 250.0 125.0; 175.0 175.0 350.0 175.0] # Delete all edges but one -edge1 = deleteat!(TrixiParticles.Polygon(triangle), [2, 3]) -edge2 = deleteat!(TrixiParticles.Polygon(triangle), [1, 3]) -edge3 = deleteat!(TrixiParticles.Polygon(triangle), [1, 2]) +edge1 = delete_faces(TrixiParticles.Polygon(triangle), [2, 3]) +edge2 = delete_faces(TrixiParticles.Polygon(triangle), [1, 3]) +edge3 = delete_faces(TrixiParticles.Polygon(triangle), [1, 2]) algorithm = WindingNumberJacobson() diff --git a/src/TrixiParticles.jl b/src/TrixiParticles.jl index 215a2b3bf1..c606bfd08e 100644 --- a/src/TrixiParticles.jl +++ b/src/TrixiParticles.jl @@ -98,7 +98,7 @@ export trixi2vtk, vtk2trixi export RectangularTank, RectangularShape, SphereShape, ComplexShape export ParticlePackingSystem, SignedDistanceField export WindingNumberHormann, WindingNumberJacobson -export VoxelSphere, RoundSphere, reset_wall!, extrude_geometry, load_geometry, +export VoxelSphere, RoundSphere, reset_wall!, extrude_geometry, load_geometry, delete_faces, sample_boundary, planar_geometry_to_face export SourceTermDamping export ShepardKernelCorrection, KernelCorrection, AkinciFreeSurfaceCorrection, diff --git a/src/preprocessing/geometries/polygon.jl b/src/preprocessing/geometries/polygon.jl index 3192633618..6c0407a739 100644 --- a/src/preprocessing/geometries/polygon.jl +++ b/src/preprocessing/geometries/polygon.jl @@ -153,7 +153,12 @@ end @inline Base.eltype(::Polygon{NDIMS, ELTYPE}) where {NDIMS, ELTYPE} = ELTYPE -@inline function Base.deleteat!(polygon::Polygon, indices) +""" + delete_faces(geometry, indices) + +Return a geometry with the faces at `indices` removed and derived geometry data rebuilt. +""" +@inline function delete_faces(polygon::Polygon, indices) edge_vertices = copy(polygon.edge_vertices) vertex_normals = copy(polygon.vertex_normals) edge_normals = copy(polygon.edge_normals) diff --git a/src/preprocessing/geometries/triangle_mesh.jl b/src/preprocessing/geometries/triangle_mesh.jl index f35b95dcd1..13512786b6 100644 --- a/src/preprocessing/geometries/triangle_mesh.jl +++ b/src/preprocessing/geometries/triangle_mesh.jl @@ -171,7 +171,7 @@ end @inline face_normal(triangle, geometry::TriangleMesh) = geometry.face_normals[triangle] -@inline function Base.deleteat!(mesh::TriangleMesh, indices) +@inline function delete_faces(mesh::TriangleMesh, indices) face_vertices = copy(mesh.face_vertices) face_normals = copy(mesh.face_normals) diff --git a/test/preprocessing/geometries/geometries.jl b/test/preprocessing/geometries/geometries.jl index 3906af0aff..77cc5b4003 100644 --- a/test/preprocessing/geometries/geometries.jl +++ b/test/preprocessing/geometries/geometries.jl @@ -54,11 +54,11 @@ end end - @testset verbose=true "`deleteat!` Rebuilds Derived Data" begin + @testset verbose=true "`delete_faces` Rebuilds Derived Data" begin triangle = [0.0 1.0 0.5 0.0; 0.0 0.0 0.7 0.0] - edge_only = deleteat!(TrixiParticles.Polygon(triangle), [1, 2]) + edge_only = TrixiParticles.delete_faces(TrixiParticles.Polygon(triangle), [1, 2]) @test TrixiParticles.nfaces(edge_only) == 1 @test length(edge_only.vertices) == 2 @@ -74,7 +74,7 @@ face_normals = [SVector(0.0, 0.0, 1.0), SVector(0.0, 0.0, 1.0)] mesh = TrixiParticles.TriangleMesh(face_vertices, face_normals, [A, B, C, D]) - deleteat!(mesh, 1) + mesh = TrixiParticles.delete_faces(mesh, 1) @test TrixiParticles.nfaces(mesh) == 1 @test length(mesh.vertices) == 3 diff --git a/test/preprocessing/packing/nhs_faces.jl b/test/preprocessing/packing/nhs_faces.jl index 292522a91b..a9b038f2ed 100644 --- a/test/preprocessing/packing/nhs_faces.jl +++ b/test/preprocessing/packing/nhs_faces.jl @@ -4,7 +4,7 @@ 0.0 0.0 0.7 0.0] # Only use the third edge of the triangle, i.e. the edge from [0.1, 0.0] to [0.0, 0.0] - edge_aligned = deleteat!(TrixiParticles.Polygon(triangle), [1, 2]) + edge_aligned = TrixiParticles.delete_faces(TrixiParticles.Polygon(triangle), [1, 2]) edge_id = 1 # Only one edge in `Polygon` cell_sizes = [1.0 + sqrt(eps()), 0.1] @@ -27,7 +27,8 @@ end # Only use the first edge of the triangle, i.e. the edge from [0.0, 0.0] to [0.5, 0.7] - edge_arbitrary = deleteat!(TrixiParticles.Polygon(triangle), [2, 3]) + edge_arbitrary = TrixiParticles.delete_faces(TrixiParticles.Polygon(triangle), + [2, 3]) edge_id = 1 # Only one edge in `Polygon` expected_ncells_bbox = [(1, 1), (6, 7)]