https://mooseframework.inl.gov
Loading...
Searching...
No Matches
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
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
60std::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
117std::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 {
127 if (std::abs(levelSetEvaluator(*node)) > libMesh::TOLERANCE)
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
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}
registerMooseObject("MooseApp", Boundary2DDelaunayGenerator)
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application.
Definition MooseError.h:311
Mesh generator to remesh a boundary of a volumetric mesh using triangle elements.
const std::vector< std::vector< BoundaryName > > _hole_boundary_names
The boundaries to be used as holes.
Boundary2DDelaunayGenerator(const InputParameters &parameters)
const std::vector< BoundaryName > _boundary_names
The boundaries to be converted to a 2D mesh.
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...
BoundaryName _output_external_boundary_name
The name of the external boundary of the mesh to generate.
virtual std::unique_ptr< MeshBase > generate() override
Generate / modify the mesh.
std::unique_ptr< MeshBase > & _input
The input mesh name.
The main MOOSE class responsible for handling user-defined parameters in almost every MOOSE system.
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.
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...
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.
Helper class to define, parameterize and create a level set function used in meshing,...
void levelSetCorrection(Node &node)
Correct the position of a node based on the level set function.
static InputParameters validParams()
const unsigned int _max_level_set_correction_iterations
Maximum number of iterations to correct the nodes based on the level set function.
SymFunctionPtr _func_level_set
function parser object describing the level set
Real levelSetEvaluator(const Point &point)
Evaluate the level set function at a given point.
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 std::string & type() const
Get the type of this class.
Definition MooseBase.h:93
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:457
Provides a way for users to bail out of the current solve.
virtual const char * what() const
Get out the error message.
Base class for Delaunay mesh generators applied to a surface.
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...
Point meshNormal2D(const MeshBase &mesh)
Calculate the average normal vector of a 2D mesh based on the normal vectors of its elements using th...
const Real _auto_area_func_default_size_dist
Background size's effective distance for automatic desired area function.
const Real _auto_area_func_default_size
Background size for automatic desired area function.
Point elemNormal(const Elem &elem)
Calculate the normal vector of a 2D element based the first three vertices.
const Real _max_angle_deviation
Max angle deviation from the global average normal vector in the input mesh.
const bool _use_auto_area_func
Whether to use automatic desired area function.
const Real _auto_area_function_power
Power of the polynomial used in the inverse distance interpolation for automatic area function.
const unsigned int _auto_area_function_num_points
Maximum number of points to use for the inverse distance interpolation for automatic area function.
const Parallel::Communicator & comm() const
MeshBase & mesh
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.
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...
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.
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.
SubdomainID getNextFreeSubdomainID(MeshBase &input_mesh)
Checks input mesh and returns max(block ID) + 1, which represents a block ID that is not currently in...
Point meshCentroidCalculator(const MeshBase &mesh)
Calculates the centroid of a MeshBase.
void change_boundary_id(MeshBase &mesh, const boundary_id_type old_id, const boundary_id_type new_id)
static constexpr Real TOLERANCE