Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion src/meshes/CutCellMeshes/layered.jl
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ function layered_mesh(
mesh::UnstructuredMesh{3},
surfaces::Vector{<:PolygonalSurface};
min_cut_fraction::Real = 0.01,
merge_cells::Bool = true
merge_cells::Bool = false
)
T = Float64
n_surfaces = length(surfaces)
Expand Down
21 changes: 17 additions & 4 deletions src/meshes/CutCellMeshes/merge_faces.jl
Original file line number Diff line number Diff line change
Expand Up @@ -203,11 +203,24 @@ function _merge_face_group!(
end
end

# Extract the boundary polygon by chaining boundary edges
# Check that the boundary forms a simple polygon: every boundary
# node must have exactly two boundary edges. If any node has a
# different degree the merged outline is non-simple and cannot be
# properly chained into a single polygon.
bnd_deg = Dict{Int, Int}()
for (a, b) in boundary_edges
bnd_deg[a] = get(bnd_deg, a, 0) + 1
bnd_deg[b] = get(bnd_deg, b, 0) + 1
end
if any(d != 2 for d in values(bnd_deg))
continue # non-simple boundary; leave faces un-merged
end

# Extract the boundary polygon by chaining boundary edges.
merged_nodes = _chain_boundary_edges(boundary_edges)
if isempty(merged_nodes)
# Fallback: use all nodes sorted by angle
merged_nodes = collect(all_nodes)
if isempty(merged_nodes) || length(merged_nodes) != length(bnd_deg)
# Chain did not cover all boundary nodes
continue # leave faces un-merged
end

# Order the polygon nodes correctly
Expand Down
37 changes: 37 additions & 0 deletions test/cut_cell_meshes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,43 @@ import Jutul.CutCellMeshes: PlaneCut, PolygonalSurface, cut_mesh, layered_mesh,
end
end

@testset "merge_coplanar_faces after layered_mesh preserves volume" begin
import Jutul.CutCellMeshes: merge_coplanar_faces

# Reproduce a bug where merge_coplanar_faces created holes
# when applied after layered_mesh with a non-planar surface. The root
# cause was non-simple polygon boundaries (nodes with degree != 2) that
# the greedy chain algorithm could not trace correctly.
L = 1000.0
g = CartesianMesh((3, 3, 3), (L, L, 100.0))
mesh = UnstructuredMesh(g)

N = 4
xs = range(0.0, L, length = N)
ys = range(0.0, L, length = N)

# Use a deterministic non-planar surface that exercises the fix
zvals = [50.0 + 5.0 * sin(2π * (i - 1) / (N - 1)) * cos(2π * (j - 1) / (N - 1))
for i in 1:N, j in 1:N]
surf = depth_grid_to_surface(xs, ys, zvals)
result, _ = layered_mesh(mesh, [surf])

geo_before = tpfv_geometry(result)
vol_before = sum(geo_before.volumes)

merged = merge_coplanar_faces(result)
geo_after = tpfv_geometry(merged)
vol_after = sum(geo_after.volumes)

# Volume must be preserved (tight tolerance)
@test vol_after ≈ vol_before rtol = 1e-6
# All cell volumes must remain positive
@test all(geo_after.volumes .> 0)
# Face count must not increase
@test number_of_faces(merged) <= number_of_faces(result)
@test number_of_boundary_faces(merged) <= number_of_boundary_faces(result)
end

@testset "merge_faces with extra_out" begin
g = CartesianMesh((3, 3, 3))
mesh = UnstructuredMesh(g)
Expand Down
Loading