@@ -266,16 +266,34 @@ TriangulatorInterface::MeshedHole::MeshedHole(const MeshBase & mesh,
266266 MeshSerializer serial (const_cast < MeshBase & > (mesh ),
267267 /* serial */ true, /* only proc 0 */ true);
268268
269+ // Try to keep in sync even if we throw an error on proc 0, so we
270+ // can examine errors in our unit tests in parallel too.
271+ std ::string error_reported ;
272+
273+ auto report_error = [& mesh , & error_reported ](std ::string er ) {
274+ error_reported = std ::move (er );
275+ mesh .comm ().broadcast (error_reported );
276+ libmesh_error_msg (error_reported );
277+ };
278+
269279 if (mesh .processor_id () != 0 )
270280 {
271- // Receive what proc 0 will send later
281+ // Make sure proc 0 didn't just fail
282+ mesh .comm ().broadcast (error_reported );
283+ libmesh_error_msg_if (!error_reported .empty (), error_reported );
284+
285+ // Receive the points proc 0 will send later
272286 mesh .comm ().broadcast (_points );
273287 return ;
274288 }
275289
276290 // We'll find all the line segments first, then stitch them together
277- // afterward
278- std ::multimap < const Node * , const Node * > hole_edge_map ;
291+ // afterward. If the line segments come from 2D element sides then
292+ // we'll label their edge_type as "1" for clockwise orientation
293+ // around the element or "2" for CCW, to make it easier to detect
294+ // and scream about cases where we have a disconnected outer
295+ // boundary.
296+ std ::multimap < const Node * , std ::pair < const Node * , int >> hole_edge_map ;
279297
280298 std ::vector < boundary_id_type > bcids ;
281299
@@ -288,9 +306,11 @@ TriangulatorInterface::MeshedHole::MeshedHole(const MeshBase & mesh,
288306 if (ids .empty () || ids .count (elem - > subdomain_id ()))
289307 {
290308 hole_edge_map .emplace (elem -> node_ptr (0 ),
291- elem -> node_ptr (1 ));
309+ std ::make_pair (elem -> node_ptr (1 ),
310+ /*edge*/ 0 ));
292311 hole_edge_map .emplace (elem -> node_ptr (1 ),
293- elem -> node_ptr (0 ));
312+ std ::make_pair (elem -> node_ptr (0 ),
313+ /*edge*/ 0 ));
294314 }
295315 continue ;
296316 }
@@ -314,30 +334,35 @@ TriangulatorInterface::MeshedHole::MeshedHole(const MeshBase & mesh,
314334 if (add_edge )
315335 {
316336 hole_edge_map .emplace (elem -> node_ptr (s ),
317- elem -> node_ptr ((s + 1 )%ns ));
337+ std ::make_pair (elem -> node_ptr ((s + 1 )%ns ),
338+ /*counter-CW*/ 2 ));
318339 // Do we really need to support flipped 2D elements?
319340 hole_edge_map .emplace (elem -> node_ptr ((s + 1 )%ns ),
320- elem -> node_ptr (s ));
341+ std ::make_pair (elem -> node_ptr (s ),
342+ /*clockwise*/ 1 ));
321343 continue ;
322344 }
323345 }
324346 }
325347 }
326348
327- libmesh_error_msg_if
328- (hole_edge_map .empty (),
329- "No valid hole edges found in mesh!" );
349+ if (hole_edge_map .empty ())
350+ report_error ("No valid hole edges found in mesh!" );
330351
331352 // Function to pull a vector of points out of the map; a loop of
332353 // edges connecting these points defines a hole boundary. If the
333354 // mesh has multiple boundaries (e.g. because it had holes itself),
334355 // then a random vector will be extracted; this function will be
335356 // called multiple times so that the various options can be
336- // compared.
337- auto extract_edge_vector = [& hole_edge_map ]() {
357+ // compared. We choose the largest option.
358+ auto extract_edge_vector = [& report_error , & hole_edge_map ]() {
338359 // Start with any edge
339- std ::vector < const Node * > hole_points
340- {hole_edge_map .begin ()-> first , hole_edge_map .begin ()-> second };
360+ std ::pair < std ::vector < const Node * > , int > hole_points_and_edge_type
361+ {{hole_edge_map .begin ()-> first , hole_edge_map .begin ()-> second .first },
362+ hole_edge_map .begin ()-> second .second };
363+
364+ int & edge_type = hole_points_and_edge_type .second ;
365+ auto & hole_points = hole_points_and_edge_type .first ;
341366
342367 // We won't be needing to search for this edge
343368 hole_edge_map .erase (hole_points .front ());
@@ -351,19 +376,31 @@ TriangulatorInterface::MeshedHole::MeshedHole(const MeshBase & mesh,
351376 {
352377 auto [next_it_begin , next_it_end ] = hole_edge_map .equal_range (n );
353378
354- libmesh_error_msg_if
355- (std ::distance (next_it_begin , next_it_end ) != 2 ,
356- "Bad edge topology in MeshedHole" );
379+ if (std ::distance (next_it_begin , next_it_end ) != 2 )
380+ report_error ("Bad edge topology found by MeshedHole" );
357381
358382 const Node * next = nullptr ;
359- for (const auto [key , val ] : as_range (next_it_begin , next_it_end ))
383+ for (const auto & [key , val ] : as_range (next_it_begin , next_it_end ))
360384 {
361385 libmesh_assert_equal_to (key , n );
362386 libmesh_ignore (key );
363- libmesh_assert_not_equal_to (val , n );
364- if (val == last )
387+ libmesh_assert_not_equal_to (val .first , n );
388+
389+ // Don't go backwards on the edge we just traversed
390+ if (val .first == last )
365391 continue ;
366- next = val ;
392+
393+ // We can support mixes of Edge and Tri-side edges, but we
394+ // can't do proper error detection on flipped triangles.
395+ if (val .second != edge_type &&
396+ val .second != 0 )
397+ {
398+ if (!edge_type )
399+ edge_type = val .second ;
400+ else
401+ report_error ("MeshedHole sees inconsistent triangle orientations on boundary" );
402+ }
403+ next = val .first ;
367404 }
368405
369406 // We should never hit the same n twice!
@@ -374,17 +411,38 @@ TriangulatorInterface::MeshedHole::MeshedHole(const MeshBase & mesh,
374411
375412 hole_points .pop_back ();
376413
377- return hole_points ;
414+ return hole_points_and_edge_type ;
378415 };
379416
417+ /*
418+ * If it's not obvious which loop we find is really the loop we
419+ * want, then we should die with a nice error message.
420+ */
421+ int n_negative_areas = 0 ,
422+ n_positive_areas = 0 ,
423+ n_edgeelem_loops = 0 ;
424+
380425 std ::vector < const Node * > outer_hole_points ;
381- Real twice_outer_area = 0 ;
426+ int outer_edge_type = -1 ;
427+ Real twice_outer_area = 0 ,
428+ abs_twice_outer_area = 0 ;
429+
382430 while (!hole_edge_map .empty ()) {
383- std ::vector < const Node * > hole_points = extract_edge_vector ();
431+ auto [hole_points , edge_type ] = extract_edge_vector ();
432+
433+ if (edge_type == 0 )
434+ {
435+ ++ n_edgeelem_loops ;
436+ if (n_edgeelem_loops > 1 )
437+ report_error ("MeshedHole is confused by multiple loops of Edge elements" );
438+ if (n_positive_areas || n_negative_areas )
439+ report_error ("MeshedHole is confused by meshes with both Edge and 2D-side boundaries" );
440+ }
441+
384442 const std ::size_t n_hole_points = hole_points .size ();
385- libmesh_error_msg_if
386- ( n_hole_points < 3 , "Loop with only " << n_hole_points <<
387- " hole edges found in mesh!" );
443+ if ( n_hole_points < 3 )
444+ report_error ( "Loop with only " + std :: to_string ( n_hole_points ) +
445+ " hole edges found in mesh!" );
388446
389447 Real twice_this_area = 0 ;
390448 const Point p0 = * hole_points [0 ];
@@ -396,10 +454,20 @@ TriangulatorInterface::MeshedHole::MeshedHole(const MeshBase & mesh,
396454 twice_this_area += e_0im .cross (e_0i )(2 );
397455 }
398456
399- if (std ::abs (twice_this_area ) > std ::abs (twice_outer_area ))
457+ auto abs_twice_this_area = std ::abs (twice_this_area );
458+
459+ if (((abs_twice_this_area == twice_this_area ) && edge_type == 2 ) ||
460+ (edge_type == 1 ))
461+ ++ n_positive_areas ;
462+ else
463+ ++ n_negative_areas ;
464+
465+ if (abs_twice_this_area > abs_twice_outer_area )
400466 {
401467 twice_outer_area = twice_this_area ;
468+ abs_twice_outer_area = abs_twice_this_area ;
402469 outer_hole_points = std ::move (hole_points );
470+ outer_edge_type = edge_type ;
403471 }
404472 }
405473
@@ -409,15 +477,34 @@ TriangulatorInterface::MeshedHole::MeshedHole(const MeshBase & mesh,
409477 _points .begin (),
410478 [](const Node * n ){ return Point (* n ); });
411479
412- libmesh_error_msg_if
413- (!twice_outer_area ,
414- "Zero-area MeshedHoles are not currently supported" );
480+ if (!twice_outer_area )
481+ report_error ("Zero-area MeshedHoles are not currently supported" );
415482
416483 // We ordered ourselves counter-clockwise? But a hole is expected
417484 // to be clockwise, so use the reverse order.
418485 if (twice_outer_area > 0 )
419486 std ::reverse (_points .begin (), _points .end ());
420487
488+ if (((twice_outer_area > 0 ) && outer_edge_type == 2 ) ||
489+ outer_edge_type == 1 )
490+ {
491+ if (n_positive_areas > 1 )
492+ report_error ("MeshedHole found " +
493+ std ::to_string (n_positive_areas ) +
494+ " counter-clockwise boundaries and cannot choose one!" );
495+
496+ }
497+ else if (outer_edge_type != 0 )
498+ {
499+ if (n_negative_areas > 1 )
500+ report_error ("MeshedHole found " +
501+ std ::to_string (n_positive_areas ) +
502+ " clockwise boundaries and cannot choose one!" );
503+
504+ }
505+
506+ // Hey, no errors! Broadcast that empty string.
507+ mesh .comm ().broadcast (error_reported );
421508 mesh .comm ().broadcast (_points );
422509}
423510
0 commit comments