https://mooseframework.inl.gov
Boundary2DDelaunayGenerator.C
Go to the documentation of this file.
1 //* This file is part of the MOOSE framework
2 //* https://www.mooseframework.org
3 //*
4 //* All rights reserved, see COPYRIGHT for full restrictions
5 //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6 //*
7 //* Licensed under LGPL 2.1, please see LICENSE for details
8 //* https://www.gnu.org/licenses/lgpl-2.1.html
9 
11 
12 #include "MooseMeshUtils.h"
13 #include "CastUniquePointer.h"
14 
15 #include "libmesh/boundary_info.h"
16 #include "libmesh/poly2tri_triangulator.h"
17 #include "libmesh/mesh_triangle_holes.h"
18 #include "libmesh/mesh_modification.h"
19 #include "libmesh/parallel_algebra.h"
20 
22 
25 {
28 
29  params.addClassDescription(
30  "Mesh generator that convert a 2D surface given as one or a few boundaries of a 3D mesh into "
31  "a 2D mesh using Delaunay triangulation.");
32  params.addRequiredParam<MeshGeneratorName>("input", "The mesh we want to modify");
33  params.addRequiredParam<std::vector<BoundaryName>>("boundary_names", "The boundaries to be used");
34  params.addParam<std::vector<std::vector<BoundaryName>>>(
35  "hole_boundary_names",
36  std::vector<std::vector<BoundaryName>>(),
37  "The optional boundaries to be used as the holes in the mesh during triangulation. Note that "
38  "this is a vector of vectors, which allows each hole to be defined as a combination of "
39  "multiple boundaries.");
40 
41  params.addParam<BoundaryName>(
42  "output_external_boundary_name",
43  "",
44  "The optional name of the external boundary of the mesh to generate. If not provided, the "
45  "external boundary will be have a trivial id of 0.");
46 
47  return params;
48 }
49 
51  : SurfaceDelaunayGeneratorBase(parameters),
52  LevelSetMeshingHelper(parameters),
53  _input(getMesh("input")),
54  _boundary_names(getParam<std::vector<BoundaryName>>("boundary_names")),
55  _hole_boundary_names(getParam<std::vector<std::vector<BoundaryName>>>("hole_boundary_names")),
56  _output_external_boundary_name(getParam<BoundaryName>("output_external_boundary_name"))
57 {
58 }
59 
60 std::unique_ptr<MeshBase>
62 {
63  std::unique_ptr<MeshBase> mesh_3d = std::move(_input);
64 
65  // Generate a new 2D block based on the sidesets
66  const auto new_block_id = MooseMeshUtils::getNextFreeSubdomainID(*mesh_3d);
67  try
68  {
70  *mesh_3d, _boundary_names, new_block_id, SubdomainName(), type());
71  }
72  catch (MooseException & e)
73  {
74  if (((std::string)e.what()).compare(0, 12, "The sideset ") == 0)
75  paramError("boundary_names", e.what());
76  else
77  mooseError(e.what());
78  }
79 
80  // If holes are provided, we need to create new blocks for them too
81  std::vector<subdomain_id_type> hole_block_ids;
82  for (const auto & hole : _hole_boundary_names)
83  {
84  hole_block_ids.push_back(MooseMeshUtils::getNextFreeSubdomainID(*mesh_3d));
85  try
86  {
88  *mesh_3d, hole, hole_block_ids.back(), SubdomainName(), type());
89  }
90  catch (MooseException & e)
91  {
92  if (((std::string)e.what()).compare(0, 12, "The sideset ") == 0)
93  paramError("hole_boundary_names", e.what());
94  else
95  mooseError(e.what());
96  }
97  }
98 
99  // Create a 2D mesh form the 2D block
100  auto mesh_2d = buildMeshBaseObject();
101  MooseMeshUtils::convertBlockToMesh(*mesh_3d, *mesh_2d, {std::to_string(new_block_id)});
102  // If holes are provided, we need to create a 2D mesh for each hole
103  std::vector<std::unique_ptr<MeshBase>> hole_meshes_2d;
104  for (const auto & hole_block_id : hole_block_ids)
105  {
106  hole_meshes_2d.push_back(buildMeshBaseObject());
108  *mesh_3d, *hole_meshes_2d.back(), {std::to_string(hole_block_id)});
109  }
110 
111  // We do not need the 3D mesh anymore
112  mesh_3d->clear();
113 
114  return General2DDelaunay(mesh_2d, hole_meshes_2d);
115 }
116 
117 std::unique_ptr<MeshBase>
119  std::unique_ptr<MeshBase> & mesh_2d, std::vector<std::unique_ptr<MeshBase>> & hole_meshes_2d)
120 {
121  // If a level set is provided, we need to check if the nodes in the original 2D mesh match the
122  // level set
123  if (_func_level_set)
124  {
125  for (const auto & node : mesh_2d->node_ptr_range())
126  {
128  {
129  paramError("level_set",
130  "The level set function does not match the nodes in the given boundary of the "
131  "input mesh.");
132  }
133  }
134  }
135 
136  // Easier to work with a TRI3 mesh
137  // all_tri() also prepares the mesh for use
138  mesh_2d->prepare_for_use();
139  MeshTools::Modification::all_tri(*mesh_2d);
140  const auto mesh_2d_ext_bdry = MooseMeshUtils::getNextFreeBoundaryID(*mesh_2d);
141  for (const auto & elem : mesh_2d->active_element_ptr_range())
142  for (const auto & i_side : elem->side_index_range())
143  if (elem->neighbor_ptr(i_side) == nullptr)
144  mesh_2d->get_boundary_info().add_side(elem, i_side, mesh_2d_ext_bdry);
145 
146  // Create a clone of the 2D mesh to be used for the 1D mesh generation
147  auto mesh_2d_dummy = dynamic_pointer_cast<MeshBase>(mesh_2d->clone());
148  // Generate a new 1D block based on the external boundary
149  const auto new_block_id_1d = MooseMeshUtils::getNextFreeSubdomainID(*mesh_2d_dummy);
150 
152  *mesh_2d_dummy, {std::to_string(mesh_2d_ext_bdry)}, new_block_id_1d, SubdomainName(), type());
153 
154  // Create a 1D mesh form the 1D block
155  auto mesh_1d = buildMeshBaseObject();
156  MooseMeshUtils::convertBlockToMesh(*mesh_2d_dummy, *mesh_1d, {std::to_string(new_block_id_1d)});
157  mesh_2d_dummy->clear();
158 
159  // If we have holes, we need to create a 1D mesh for each hole
160  std::vector<std::unique_ptr<MeshBase>> hole_meshes_1d;
161  for (auto & hole_mesh_2d : hole_meshes_2d)
162  {
163  // As we do not need these holes for reverse projection, we do not need to convert them to TRI3
164  // meshes, but we still need to create a 1D mesh for each hole
165  hole_mesh_2d->find_neighbors();
166  const auto hole_mesh_2d_ext_bdry = MooseMeshUtils::getNextFreeBoundaryID(*hole_mesh_2d);
167  for (const auto & elem : hole_mesh_2d->active_element_ptr_range())
168  for (const auto & i_side : elem->side_index_range())
169  if (elem->neighbor_ptr(i_side) == nullptr)
170  hole_mesh_2d->get_boundary_info().add_side(elem, i_side, hole_mesh_2d_ext_bdry);
171  const auto new_hole_block_id_1d = MooseMeshUtils::getNextFreeSubdomainID(*hole_mesh_2d);
173  {std::to_string(hole_mesh_2d_ext_bdry)},
174  new_hole_block_id_1d,
175  SubdomainName(),
176  type());
177  // Create a 1D mesh form the 1D block
178  hole_meshes_1d.push_back(buildMeshBaseObject());
180  *hole_mesh_2d, *hole_meshes_1d.back(), {std::to_string(new_hole_block_id_1d)});
181  hole_mesh_2d->clear();
182  }
183 
184  // Find centroid of the 2D mesh
185  const Point centroid = MooseMeshUtils::meshCentroidCalculator(*mesh_2d);
186  // calculate an average normal vector of the 2D mesh
187  const Point mesh_norm = meshNormal2D(*mesh_2d);
188  // Check the deviation of the mesh normal vector from the global average normal vector
189  if (meshNormalDeviation2D(*mesh_2d, mesh_norm) > _max_angle_deviation)
190  paramError("boundary_names",
191  "The normal vector of some elements in the 2D mesh deviates too much from the "
192  "global average normal vector. The maximum deviation is " +
193  std::to_string(_max_angle_deviation) +
194  ". Consider dividing the boundary into several parts to "
195  "reduce the angle deviation.");
196 
197  // Move both 2d and 1d meshes to the centroid of the 2D mesh
198  MeshTools::Modification::translate(*mesh_1d, -centroid(0), -centroid(1), -centroid(2));
199  MeshTools::Modification::translate(*mesh_2d, -centroid(0), -centroid(1), -centroid(2));
200  // Alsop need to translate the 1D hole meshes if applicable
201  for (auto & hole_mesh_1d : hole_meshes_1d)
202  MeshTools::Modification::translate(*hole_mesh_1d, -centroid(0), -centroid(1), -centroid(2));
203 
204  // Calculate the Euler angles to rotate the meshes so that the 2D mesh is close to the XY plane
205  // (i.e., the normal vector of the 2D mesh is aligned with the Z axis)
206  const Real theta = std::acos(mesh_norm(2)) / M_PI * 180.0;
207  const Real phi =
208  (MooseUtils::absoluteFuzzyLessThan(mesh_norm(2), 1.0) ? std::atan2(mesh_norm(1), mesh_norm(0))
209  : 0.0) /
210  M_PI * 180.0;
211  MeshTools::Modification::rotate(*mesh_1d, 90.0 - phi, theta, 0.0);
212  MeshTools::Modification::rotate(*mesh_2d, 90.0 - phi, theta, 0.0);
213  // Also rotate the 1D hole meshes if applicable
214  for (auto & hole_mesh_1d : hole_meshes_1d)
215  MeshTools::Modification::rotate(*hole_mesh_1d, 90.0 - phi, theta, 0.0);
216 
217  // Clone the 2D mesh to be used for reverse projection later
218  auto mesh_2d_xyz = dynamic_pointer_cast<MeshBase>(mesh_2d->clone());
219 
220  // Project the 2D mesh to the XY plane so that XYDelaunay can be used
221  for (const auto & node : mesh_2d->node_ptr_range())
222  (*node)(2) = 0;
223  // Project the 1D mesh to the XY plane as well
224  for (const auto & node : mesh_1d->node_ptr_range())
225  (*node)(2) = 0;
226  // Also project the 1D hole meshes to the XY plane if applicable
227  for (auto & hole_mesh_1d : hole_meshes_1d)
228  {
229  for (const auto & node : hole_mesh_1d->node_ptr_range())
230  (*node)(2) = 0;
231  }
232 
233  std::vector<libMesh::TriangulatorInterface::MeshedHole> meshed_holes;
234  std::vector<libMesh::TriangulatorInterface::Hole *> triangulator_hole_ptrs(hole_meshes_1d.size());
235  meshed_holes.reserve(hole_meshes_1d.size());
236  for (auto hole_i : index_range(hole_meshes_1d))
237  {
238  hole_meshes_1d[hole_i]->prepare_for_use();
239  meshed_holes.emplace_back(*hole_meshes_1d[hole_i]);
240  triangulator_hole_ptrs[hole_i] = &meshed_holes.back();
241  }
242 
243  // Finally, triangulation
244  std::unique_ptr<UnstructuredMesh> mesh =
245  dynamic_pointer_cast<UnstructuredMesh>(std::move(mesh_1d));
246 
247  Poly2TriTriangulator poly2tri(*mesh);
248  poly2tri.triangulation_type() = TriangulatorInterface::PSLG;
249 
250  poly2tri.set_interpolate_boundary_points(0);
251  poly2tri.set_refine_boundary_allowed(false);
252  poly2tri.set_verify_hole_boundaries(false);
253  poly2tri.desired_area() = 0;
254  poly2tri.minimum_angle() = 0; // Not yet supported
255  poly2tri.smooth_after_generating() = false;
256  if (!triangulator_hole_ptrs.empty())
257  poly2tri.attach_hole_list(&triangulator_hole_ptrs);
258  // Future TODO: correct the area function based on the local normal vector
260  poly2tri.set_auto_area_function(this->comm(),
265  poly2tri.triangulate();
266 
267  // Reverse the projection based on the original 2D mesh
268  for (const auto & node : mesh->node_ptr_range())
269  {
270  bool node_mod = false;
271  // Try to find the element in mesh_2d that contains the new node
272  for (const auto & elem : mesh_2d->active_element_ptr_range())
273  {
274  if (elem->contains_point(Point((*node)(0), (*node)(1), 0.0)))
275  {
276  // Element id
277  const auto elem_id = elem->id();
278  // element in xyz_in_xyz
279  const Elem & elem_xyz = *mesh_2d_xyz->elem_ptr(elem_id);
280 
281  const Point elem_normal = elemNormal(elem_xyz);
282  const Point & elem_p = *mesh_2d_xyz->elem_ptr(elem_id)->node_ptr(0);
283 
284  // if the x and y values of the node is the same as the elem_p's first node, we can just
285  // move it to that node's position
286  if (MooseUtils::absoluteFuzzyEqual((*node)(0), elem_p(0)) &&
287  MooseUtils::absoluteFuzzyEqual((*node)(1), elem_p(1)))
288  {
289  (*node)(2) = elem_p(2);
290  node_mod = true;
291  break;
292  }
293  // Otherwise, we need to find a position inside the 2D element
294  // It has the same x and y coordinates as the node in the projected mesh;
295  (*node)(2) = elem_p(2) - (((*node)(0) - elem_p(0)) * elem_normal(0) +
296  ((*node)(1) - elem_p(1)) * elem_normal(1)) /
297  elem_normal(2);
298  node_mod = true;
299  break;
300  }
301  }
302  if (!node_mod)
303  mooseError("Node not found in mesh_in_xy");
304  }
305 
306  // Rotate the mesh back
307  MeshTools::Modification::rotate(*mesh, 0.0, -theta, phi - 90.0);
308  // Translate the mesh back
309  MeshTools::Modification::translate(*mesh, centroid(0), centroid(1), centroid(2));
310 
311  // Correct the nodes based on the level set function
312  if (_func_level_set)
313  {
314  for (const auto & node : mesh->node_ptr_range())
315  {
316  unsigned int iter_ct = 0;
317  while (iter_ct < _max_level_set_correction_iterations &&
319  {
320  levelSetCorrection(*node);
321  ++iter_ct;
322  }
323  }
324  }
325 
326  // Assign the external boundary name if provided
327  if (!_output_external_boundary_name.empty())
328  {
329  const std::vector<BoundaryID> output_boundary_id =
331 
333  mesh->get_boundary_info().sideset_name(output_boundary_id[0]) = _output_external_boundary_name;
334  }
335 
336  mesh->unset_is_prepared();
337 
338  return mesh;
339 }
MetaPhysicL::DualNumber< V, D, asd > abs(const MetaPhysicL::DualNumber< V, D, asd > &a)
Definition: EigenADReal.h:50
Boundary2DDelaunayGenerator(const InputParameters &parameters)
Real meshNormalDeviation2D(const MeshBase &mesh, const Point &global_norm)
Calculate the maximum deviation of the normal vectors in a given mesh from a global average normal ve...
virtual const char * what() const
Get out the error message.
void paramError(const std::string &param, Args... args) const
Emits an error prefixed with the file and line number of the given param (from the input file) along ...
Definition: MooseBase.h:467
void convertBlockToMesh(MeshBase &source_mesh, MeshBase &target_mesh, const std::vector< SubdomainName > &target_blocks)
Convert a list of blocks in a given mesh to a standalone new mesh.
const std::vector< std::vector< BoundaryName > > _hole_boundary_names
The boundaries to be used as holes.
static constexpr Real TOLERANCE
Base class for Delaunay mesh generators applied to a surface.
MeshBase & mesh
The main MOOSE class responsible for handling user-defined parameters in almost every MOOSE system...
const Parallel::Communicator & comm() const
BoundaryName _output_external_boundary_name
The name of the external boundary of the mesh to generate.
Point meshNormal2D(const MeshBase &mesh)
Calculate the average normal vector of a 2D mesh based on the normal vectors of its elements using th...
std::unique_ptr< T_DEST, T_DELETER > dynamic_pointer_cast(std::unique_ptr< T_SRC, T_DELETER > &src)
These are reworked from https://stackoverflow.com/a/11003103.
Helper class to define, parameterize and create a level set function used in meshing, often to correct the position of nodes on a surface.
void addRequiredParam(const std::string &name, const std::string &doc_string)
This method adds a parameter and documentation string to the InputParameters object that will be extr...
static InputParameters validParams()
const bool _use_auto_area_func
Whether to use automatic desired area function.
std::unique_ptr< MeshBase > & _input
The input mesh name.
virtual std::unique_ptr< MeshBase > generate() override
Generate / modify the mesh.
void change_boundary_id(MeshBase &mesh, const boundary_id_type old_id, const boundary_id_type new_id)
const std::string & type() const
Get the type of this class.
Definition: MooseBase.h:93
std::unique_ptr< MeshBase > General2DDelaunay(std::unique_ptr< MeshBase > &mesh_2d, std::vector< std::unique_ptr< MeshBase >> &hole_meshes_2d)
Generate a 2D mesh using Delaunay triangulation based on the input 2D boundary mesh and the 2D hole m...
std::vector< BoundaryID > getBoundaryIDs(const libMesh::MeshBase &mesh, const std::vector< BoundaryName > &boundary_name, bool generate_unknown, const std::set< BoundaryID > &mesh_boundary_ids)
Gets the boundary IDs with their names.
const unsigned int _auto_area_function_num_points
Maximum number of points to use for the inverse distance interpolation for automatic area function...
SymFunctionPtr _func_level_set
function parser object describing the level set
void levelSetCorrection(Node &node)
Correct the position of a node based on the level set function.
Real levelSetEvaluator(const Point &point)
Evaluate the level set function at a given point.
const Real _auto_area_func_default_size_dist
Background size&#39;s effective distance for automatic desired area function.
registerMooseObject("MooseApp", Boundary2DDelaunayGenerator)
void createSubdomainFromSidesets(MeshBase &mesh, std::vector< BoundaryName > boundary_names, const SubdomainID new_subdomain_id, const SubdomainName new_subdomain_name, const std::string type_name)
Create a new subdomain by generating new side elements from a list of sidesets in a given mesh...
Provides a way for users to bail out of the current solve.
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
const Real _max_angle_deviation
Max angle deviation from the global average normal vector in the input mesh.
const Real _auto_area_function_power
Power of the polynomial used in the inverse distance interpolation for automatic area function...
const unsigned int _max_level_set_correction_iterations
Maximum number of iterations to correct the nodes based on the level set function.
Mesh generator to remesh a boundary of a volumetric mesh using triangle elements. ...
void mooseError(Args &&... args) const
Emits an error prefixed with object name and type and optionally a file path to the top-level block p...
Definition: MooseBase.h:281
void addClassDescription(const std::string &doc_string)
This method adds a description of the class that will be displayed in the input file syntax dump...
static InputParameters validParams()
void addParam(const std::string &name, const S &value, const std::string &doc_string)
These methods add an optional parameter and a documentation string to the InputParameters object...
Point meshCentroidCalculator(const MeshBase &mesh)
Calculates the centroid of a MeshBase.
std::unique_ptr< MeshBase > buildMeshBaseObject(unsigned int dim=libMesh::invalid_uint)
Build a MeshBase object whose underlying type will be determined by the Mesh input file block...
const Real _auto_area_func_default_size
Background size for automatic desired area function.
SubdomainID getNextFreeSubdomainID(MeshBase &input_mesh)
Checks input mesh and returns max(block ID) + 1, which represents a block ID that is not currently in...
BoundaryID getNextFreeBoundaryID(MeshBase &input_mesh)
Checks input mesh and returns the largest boundary ID in the mesh plus one, which is a boundary ID in...
auto index_range(const T &sizable)
const std::vector< BoundaryName > _boundary_names
The boundaries to be converted to a 2D mesh.
Point elemNormal(const Elem &elem)
Calculate the normal vector of a 2D element based the first three vertices.