diff --git a/src/algorithms/triangulation/check_args.jl b/src/algorithms/triangulation/check_args.jl index beb1413de..93ddef49a 100644 --- a/src/algorithms/triangulation/check_args.jl +++ b/src/algorithms/triangulation/check_args.jl @@ -1,5 +1,5 @@ """ - check_args(points, boundary_nodes, hierarchy::PolygonHierarchy) -> Bool + check_args(points, boundary_nodes, hierarchy::PolygonHierarchy, boundary_curves = ()) -> Bool Check that the arguments `points` and `boundary_nodes` to [`triangulate`](@ref), and a constructed [`PolygonHierarchy`](@ref) given by `hierarchy`, are valid. In particular, the function checks: @@ -20,13 +20,13 @@ If `boundary_nodes` are provided, meaning [`has_boundary_nodes`](@ref), then the Another requirement for [`triangulate`](@ref) is that none of the boundaries intersect in their interior, which also prohibits interior self-intersections. This is NOT checked. Similarly, segments should not intersect in their interior, which is not checked. """ -function check_args(points, boundary_nodes, hierarchy) +function check_args(points, boundary_nodes, hierarchy, boundary_curves = ()) has_unique_points(points) has_enough_points(points) has_bnd = has_boundary_nodes(boundary_nodes) if has_bnd has_consistent_connections(boundary_nodes) - has_consistent_orientations(hierarchy) + has_consistent_orientations(hierarchy, boundary_nodes, is_curve_bounded(boundary_curves)) end return true end @@ -47,6 +47,8 @@ end struct InconsistentOrientationError{I} <: Exception index::I should_be_positive::Bool + is_sectioned::Bool + is_curve_bounded::Bool end function Base.showerror(io::IO, err::DuplicatePointsError) points = err.points @@ -86,11 +88,20 @@ function Base.showerror(io::IO, err::InconsistentConnectionError) end function Base.showerror(io::IO, err::InconsistentOrientationError) print(io, "InconsistentOrientationError: ") - if err.should_be_positive - print(io, "The orientation of the boundary curve with index ", err.index, " should be positive, but it is negative.") - else - print(io, "The orientation of the boundary curve with index ", err.index, " should be negative, but it is positive.") + suggestion = err.is_sectioned ? "reverse(reverse.(curve))" : "reverse(curve)" + str = " You may be able to fix this by passing the curve as $suggestion." + if err.is_curve_bounded + # Only show this longer message if this part of the boundary could be defined by an AbstractParametricCurve. + # It's hard to detect if the curve is indeed defined by an AbstractParametricCurve since the curve could be defined + # by a combination of multiple AbstractParametricCurves and possibly a PiecewiseLinear part. Thus, the above advice + # might nto be wrong. + str2 = "\nIf this curve is defined by an AbstractParametricCurve, you may instead need to reverse the order of the control points defining" * + " the sections of the curve; the `positive` keyword may also be of interest for CircularArcs and EllipticalArcs." + str *= str2 end + sign = err.should_be_positive ? "positive" : "negative" + sign2 = err.should_be_positive ? "negative" : "positive" + print(io, "The orientation of the boundary curve with index ", err.index, " should be $sign, but it is $sign2.", str) return io end @@ -106,24 +117,25 @@ function has_enough_points(points) return true end -function has_consistent_orientations(hierarchy::PolygonHierarchy) +function has_consistent_orientations(hierarchy::PolygonHierarchy, boundary_nodes, is_curve_bounded) # Since trees start at height zero, the heights 0, 2, 4, ... must be positive, and 1, 3, 5, ... must be negative. for (_, tree) in get_trees(hierarchy) - has_consistent_orientations(tree, hierarchy) + has_consistent_orientations(tree, hierarchy, boundary_nodes, is_curve_bounded) end return true end -function has_consistent_orientations(tree::PolygonTree, hierarchy::PolygonHierarchy) +function has_consistent_orientations(tree::PolygonTree, hierarchy::PolygonHierarchy, boundary_nodes, is_curve_bounded) height = get_height(tree) index = get_index(tree) pos_orientation = get_polygon_orientation(hierarchy, index) + is_sectioned = has_multiple_curves(boundary_nodes) || has_multiple_sections(boundary_nodes) if iseven(height) - !pos_orientation && throw(InconsistentOrientationError(index, true)) + !pos_orientation && throw(InconsistentOrientationError(index, true, is_sectioned, is_curve_bounded)) else - pos_orientation && throw(InconsistentOrientationError(index, false)) + pos_orientation && throw(InconsistentOrientationError(index, false, is_sectioned, is_curve_bounded)) end for child in get_children(tree) - has_consistent_orientations(child, hierarchy) + has_consistent_orientations(child, hierarchy, boundary_nodes, is_curve_bounded) end return true end @@ -169,4 +181,4 @@ function has_consistent_connections_contiguous(boundary_nodes) vₙ = get_boundary_nodes(boundary_nodes, nn) v₁ ≠ vₙ && throw(InconsistentConnectionError(0, 0, 0, v₁, vₙ)) return true -end \ No newline at end of file +end diff --git a/src/algorithms/triangulation/main.jl b/src/algorithms/triangulation/main.jl index a4c961849..d2d2e4768 100644 --- a/src/algorithms/triangulation/main.jl +++ b/src/algorithms/triangulation/main.jl @@ -140,7 +140,7 @@ function triangulate(points::P; if isnothing(full_polygon_hierarchy) full_polygon_hierarchy = construct_polygon_hierarchy(points, boundary_nodes; IntegerType) end - check_arguments && check_args(points, boundary_nodes, full_polygon_hierarchy) + check_arguments && check_args(points, boundary_nodes, full_polygon_hierarchy, boundary_curves) tri = Triangulation(points; IntegerType, EdgeType, TriangleType, EdgesType, TrianglesType, weights, boundary_curves, boundary_enricher, build_cache = Val(true)) return _triangulate!(tri, segments, boundary_nodes, randomise, try_last_inserted_point, skip_points, num_sample_rule, rng, insertion_order, recompute_representative_points, delete_holes, full_polygon_hierarchy, delete_ghosts, delete_empty_features) diff --git a/src/data_structures/mesh_refinement/boundary_enricher.jl b/src/data_structures/mesh_refinement/boundary_enricher.jl index a27a2d679..b866ed6f8 100644 --- a/src/data_structures/mesh_refinement/boundary_enricher.jl +++ b/src/data_structures/mesh_refinement/boundary_enricher.jl @@ -419,7 +419,8 @@ function check_args(enricher::BoundaryEnricher) points = get_points(enricher) boundary_nodes = get_boundary_nodes(enricher) hierarchy = get_polygon_hierarchy(enricher) - return check_args(points, boundary_nodes, hierarchy) + boundary_curves = get_boundary_curves(enricher) + return check_args(points, boundary_nodes, hierarchy, boundary_curves) end """ diff --git a/test/triangulation/check_args.jl b/test/triangulation/check_args.jl index cd5c5da99..a0b7744e6 100644 --- a/test/triangulation/check_args.jl +++ b/test/triangulation/check_args.jl @@ -1,6 +1,5 @@ using ..DelaunayTriangulation const DT = DelaunayTriangulation -using CairoMakie _test_throws(e1, e2=e1) = @static VERSION ≥ v"1.9" ? e1 : e2 @@ -46,7 +45,7 @@ end boundary_nodes = [4, 3, 2, 1, 4] hierarchy = DT.construct_polygon_hierarchy(points, boundary_nodes) @test_throws _test_throws(DT.InconsistentOrientationError) DT.check_args(points, boundary_nodes, hierarchy) - @test_throws _test_throws("InconsistentOrientationError: The orientation of the boundary curve with index 1 should be positive, but it is negative.", DT.InconsistentOrientationError) DT.check_args(points, boundary_nodes, hierarchy) + @test_throws _test_throws("InconsistentOrientationError: The orientation of the boundary curve with index 1 should be positive, but it is negative. You may be able to fix this by passing the curve as reverse(curve).", DT.InconsistentOrientationError) DT.check_args(points, boundary_nodes, hierarchy) @test_throws _test_throws(DT.InconsistentOrientationError) triangulate(points; boundary_nodes) boundary_nodes = [[1, 2, 3, 4, 1]] @@ -60,7 +59,7 @@ end boundary_nodes = [[4, 3, 2, 1, 4]] hierarchy = DT.construct_polygon_hierarchy(points, boundary_nodes) @test_throws _test_throws(DT.InconsistentOrientationError) DT.check_args(points, boundary_nodes, hierarchy) - @test_throws _test_throws("InconsistentOrientationError: The orientation of the boundary curve with index 1 should be positive, but it is negative.", DT.InconsistentOrientationError) DT.check_args(points, boundary_nodes, hierarchy) + @test_throws _test_throws("InconsistentOrientationError: The orientation of the boundary curve with index 1 should be positive, but it is negative. You may be able to fix this by passing the curve as reverse(reverse.(curve)).", DT.InconsistentOrientationError) DT.check_args(points, boundary_nodes, hierarchy) @test_throws _test_throws(DT.InconsistentOrientationError) triangulate(points; boundary_nodes) boundary_nodes = [[[1, 2, 3, 4, 1]]] @@ -74,7 +73,7 @@ end boundary_nodes = [[[4, 3, 2, 1, 4]]] hierarchy = DT.construct_polygon_hierarchy(points, boundary_nodes) @test_throws _test_throws(DT.InconsistentOrientationError) DT.check_args(points, boundary_nodes, hierarchy) - @test_throws _test_throws("InconsistentOrientationError: The orientation of the boundary curve with index 1 should be positive, but it is negative.", DT.InconsistentOrientationError) DT.check_args(points, boundary_nodes, hierarchy) + @test_throws _test_throws("InconsistentOrientationError: The orientation of the boundary curve with index 1 should be positive, but it is negative. You may be able to fix this by passing the curve as reverse(reverse.(curve)).", DT.InconsistentOrientationError) DT.check_args(points, boundary_nodes, hierarchy) @test_throws _test_throws(DT.InconsistentOrientationError) triangulate(points; boundary_nodes) end @@ -103,7 +102,7 @@ end boundary_nodes = [[1, 8, 7, 6], [6, 5, 4], [4, 3], [3, 2, 1]] hierarchy = DT.construct_polygon_hierarchy(points, boundary_nodes) @test_throws _test_throws(DT.InconsistentOrientationError) DT.check_args(points, boundary_nodes, hierarchy) - @test_throws _test_throws("InconsistentOrientationError: The orientation of the boundary curve with index 1 should be positive, but it is negative.", DT.InconsistentOrientationError) DT.check_args(points, boundary_nodes, hierarchy) + @test_throws _test_throws("InconsistentOrientationError: The orientation of the boundary curve with index 1 should be positive, but it is negative. You may be able to fix this by passing the curve as reverse(reverse.(curve)).", DT.InconsistentOrientationError) DT.check_args(points, boundary_nodes, hierarchy) @test_throws _test_throws(DT.InconsistentOrientationError) triangulate(points; boundary_nodes) points = [(0.0, 0.0), (1.0, 0.0), (0.0, 1.0)] @@ -149,12 +148,12 @@ end boundary_nodes = [[[1, 7, 6, 5, 4, 3, 2, 1]], [[11, 10, 9, 8, 11]]] hierarchy = DT.construct_polygon_hierarchy(points, boundary_nodes) @test_throws _test_throws(DT.InconsistentOrientationError) DT.check_args(points, boundary_nodes, hierarchy) - @test_throws _test_throws("InconsistentOrientationError: The orientation of the boundary curve with index 1 should be positive, but it is negative.", DT.InconsistentOrientationError) DT.check_args(points, boundary_nodes, hierarchy) + @test_throws _test_throws("InconsistentOrientationError: The orientation of the boundary curve with index 1 should be positive, but it is negative. You may be able to fix this by passing the curve as reverse(reverse.(curve)).", DT.InconsistentOrientationError) DT.check_args(points, boundary_nodes, hierarchy) @test_throws _test_throws(DT.InconsistentOrientationError) triangulate(points; boundary_nodes) boundary_nodes = [[[1, 2, 3, 4, 5, 6, 7, 1]], [[11, 8, 9, 10, 11]]] hierarchy = DT.construct_polygon_hierarchy(points, boundary_nodes) @test_throws _test_throws(DT.InconsistentOrientationError) DT.check_args(points, boundary_nodes, hierarchy) - @test_throws _test_throws("InconsistentOrientationError: The orientation of the boundary curve with index 2 should be negative, but it is positive.", DT.InconsistentOrientationError) DT.check_args(points, boundary_nodes, hierarchy) + @test_throws _test_throws("InconsistentOrientationError: The orientation of the boundary curve with index 2 should be negative, but it is positive. You may be able to fix this by passing the curve as reverse(reverse.(curve)).", DT.InconsistentOrientationError) DT.check_args(points, boundary_nodes, hierarchy) @test_throws _test_throws(DT.InconsistentOrientationError) triangulate(points; boundary_nodes) end @@ -322,7 +321,7 @@ end boundary_nodes, points = convert_boundary_points_to_indices(curves) hierarchy = DT.construct_polygon_hierarchy(points, boundary_nodes) @test_throws _test_throws(DT.InconsistentOrientationError) DT.check_args(points, boundary_nodes, hierarchy) - @test_throws _test_throws("InconsistentOrientationError: The orientation of the boundary curve with index 7 should be positive, but it is negative.", DT.InconsistentOrientationError) DT.check_args(points, boundary_nodes, hierarchy) + @test_throws _test_throws("InconsistentOrientationError: The orientation of the boundary curve with index 7 should be positive, but it is negative. You may be able to fix this by passing the curve as reverse(reverse.(curve)).", DT.InconsistentOrientationError) DT.check_args(points, boundary_nodes, hierarchy) @test_throws _test_throws(DT.InconsistentOrientationError) triangulate(points; boundary_nodes) end @@ -332,7 +331,7 @@ end enricher_I = DT.BoundaryEnricher(points_I, curve_I) points, boundary_nodes = get_points(enricher_I), get_boundary_nodes(enricher_I) hierarchy = DT.get_polygon_hierarchy(enricher_I) - @test DT.check_args(points, boundary_nodes, hierarchy) + @test DT.check_args(points, boundary_nodes, hierarchy, DT.get_boundary_curves(enricher_I)) @test DT.check_args(enricher_I) curve_II = [[1, 2, 3, 4, 5], [5, 6, 7, 8, 9], [9, 10, 11, 1]] @@ -344,7 +343,7 @@ end enricher_II = DT.BoundaryEnricher(points_II, curve_II) points, boundary_nodes = get_points(enricher_II), get_boundary_nodes(enricher_II) hierarchy = DT.get_polygon_hierarchy(enricher_II) - @test DT.check_args(points, boundary_nodes, hierarchy) + @test DT.check_args(points, boundary_nodes, hierarchy, DT.get_boundary_curves(enricher_II)) @test DT.check_args(enricher_II) curve_III = [[[1, 2, 3, 4, 5], [5, 6, 7, 8, 9], [9, 10, 11, 1]], [[15, 14, 13, 12], [12, 15]]] @@ -357,7 +356,7 @@ end enricher_III = DT.BoundaryEnricher(points_III, curve_III) points, boundary_nodes = get_points(enricher_III), get_boundary_nodes(enricher_III) hierarchy = DT.get_polygon_hierarchy(enricher_III) - @test DT.check_args(points, boundary_nodes, hierarchy) + @test DT.check_args(points, boundary_nodes, hierarchy, DT.get_boundary_curves(enricher_III)) @test DT.check_args(enricher_III) curve_IV = [CircularArc((1.0, 0.0), (1.0, 0.0), (0.0, 0.0))] @@ -372,9 +371,11 @@ end enricher_IV = DT.BoundaryEnricher(points_IV, curve_IV) points, boundary_nodes = get_points(enricher_IV), get_boundary_nodes(enricher_IV) hierarchy = DT.get_polygon_hierarchy(enricher_IV) - @test_throws _test_throws(DT.InconsistentOrientationError) DT.check_args(points, boundary_nodes, hierarchy) - @test_throws _test_throws("InconsistentOrientationError: The orientation of the boundary curve with index 1 should be positive, but it is negative.", DT.InconsistentOrientationError) DT.check_args(points, boundary_nodes, hierarchy) + @test_throws _test_throws(DT.InconsistentOrientationError) DT.check_args(points, boundary_nodes, hierarchy, DT.get_boundary_curves(enricher_IV)) + str = "If this curve is defined by an AbstractParametricCurve, you may instead need to reverse the order of the control points defining the sections of the curve; the `positive` keyword may also be of interest for CircularArcs and EllipticalArcs." + @test_throws _test_throws("InconsistentOrientationError: The orientation of the boundary curve with index 1 should be positive, but it is negative. You may be able to fix this by passing the curve as reverse(curve).\n$str", DT.InconsistentOrientationError) DT.check_args(points, boundary_nodes, hierarchy, DT.get_boundary_curves(enricher_IV)) @test_throws _test_throws(DT.InconsistentOrientationError) DT.check_args(enricher_IV) + @test_throws _test_throws("InconsistentOrientationError: The orientation of the boundary curve with index 1 should be positive, but it is negative. You may be able to fix this by passing the curve as reverse(curve).\n$str", DT.InconsistentOrientationError) DT.triangulate(NTuple{2,Float64}[]; boundary_nodes=[CircularArc((1.0, 0.0), (1.0, 0.0), (0.0, 0.0), positive=false)]) curve_V = [BezierCurve([(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0), (0.0, 0.0)])] points_V = [(0.0, 0.0), (0.2, 0.25)] @@ -526,13 +527,16 @@ end [CircularArc((1.1, -3.0), (1.1, -3.0), (0.0, -3.0), positive=false)] ] ] + _curve_XI = deepcopy(curve_XI) points_XI = [(-2.0, 0.0), (0.0, 0.0), (2.0, 0.0), (-2.0, -5.0), (2.0, -5.0), (2.0, -1 / 10), (-2.0, -1 / 10), (-1.0, -3.0), (0.0, -4.0), (0.0, -2.3), (-0.5, -3.5), (0.9, -3.0)] + _points_XI = deepcopy(points_XI) enricher_XI = DT.BoundaryEnricher(points_XI, curve_XI) points, boundary_nodes = get_points(enricher_XI), get_boundary_nodes(enricher_XI) hierarchy = DT.get_polygon_hierarchy(enricher_XI) - @test_throws _test_throws(DT.InconsistentOrientationError) DT.check_args(points, boundary_nodes, hierarchy) - @test_throws _test_throws("InconsistentOrientationError: The orientation of the boundary curve with index 4 should be positive, but it is negative.", DT.InconsistentOrientationError) DT.check_args(points, boundary_nodes, hierarchy) + @test_throws _test_throws(DT.InconsistentOrientationError) DT.check_args(points, boundary_nodes, hierarchy, DT.get_boundary_curves(enricher_XI)) + @test_throws _test_throws("InconsistentOrientationError: The orientation of the boundary curve with index 4 should be positive, but it is negative. You may be able to fix this by passing the curve as reverse(reverse.(curve)).\n$str", DT.InconsistentOrientationError) DT.check_args(points, boundary_nodes, hierarchy, DT.get_boundary_curves(enricher_XI)) @test_throws _test_throws(DT.InconsistentOrientationError) DT.check_args(enricher_XI) + @test_throws _test_throws("InconsistentOrientationError: The orientation of the boundary curve with index 4 should be positive, but it is negative. You may be able to fix this by passing the curve as reverse(reverse.(curve)).\n$str", DT.InconsistentOrientationError) triangulate(_points_XI; boundary_nodes=_curve_XI) ctrl = [ (0.0, 0.0), (2.0, 0.0), (1.6, -0.1),