31 MeshBase & input_mesh,
32 const std::vector<BoundaryName> & boundary_names,
33 unsigned int num_layers,
39 const SubdomainName & output_subdomain_name)
42 std::set<std::size_t> bdry_id_set;
43 if (!boundary_names.empty())
47 bdry_id_set.insert(ids.begin(), ids.end());
49 TriangulatorInterface::MeshedHole bdry_mh(input_mesh, bdry_id_set);
50 std::vector<Point> cur_pts;
51 std::vector<Point> cur_mids;
62 std::vector<Real> layer_thicknesses(num_layers + 1);
63 const Real unit_thickness =
65 ? (thickness / num_layers)
66 : (thickness / (
std::pow(layer_bias, num_layers) - 1.0) * (layer_bias - 1.0));
67 layer_thicknesses[0] = 0.0;
68 for (
auto i : make_range(std::vector<Real>::size_type(1), layer_thicknesses.size()))
69 layer_thicknesses[i] = unit_thickness *
std::pow(layer_bias, i - 1);
75 std::vector<std::unique_ptr<MeshBase>> polylines(num_layers + 1);
76 for (
auto layer_i : make_range(num_layers + 1))
78 const unsigned int layer_index = outward ? layer_i : (num_layers - layer_i);
81 auto ply = std::make_unique<ReplicatedMesh>(mg.
comm());
88 std::vector<unsigned int>({1}));
89 polylines[layer_index] = std::move(ply);
91 if (layer_i + 1 < num_layers + 1)
95 auto ply_for_offset = std::make_unique<ReplicatedMesh>(mg.
comm());
102 std::vector<unsigned int>({1}));
103 std::unique_ptr<UnstructuredMesh> ply_for_offset_u =
104 dynamic_pointer_cast<UnstructuredMesh>(std::move(ply_for_offset));
107 &mg, ply_for_offset_u, cur_pts, cur_mids, outward, layer_thicknesses[layer_i + 1]);
108 if (cur_mids.empty())
109 cur_pts = std::move(next_combined);
112 const auto n_vert = cur_pts.size();
113 cur_pts.assign(next_combined.begin(), next_combined.begin() +
n_vert);
114 cur_mids.assign(next_combined.begin() +
n_vert, next_combined.end());
121 std::unique_ptr<MeshBase> ring;
122 for (
auto i : make_range(num_layers))
130 if (output_subdomain_id != 0)
135 if (output_subdomain_name.size())
141 std::vector<std::unique_ptr<MeshBase>> holes;
144 holes.push_back(std::move(polylines[0]));
146 holes.push_back(std::move(ring));
149 mg, std::move(polylines[i + 1]), std::move(holes), xyd_opts);
154 std::vector<BoundaryID> bids_to_delete;
155 for (
const auto b : make_range(2 * num_layers))
157 if (b == 1 || b == (num_layers - 1) * 2)
159 bids_to_delete.push_back(b);
161 auto & bi = ring->get_boundary_info();
162 for (
auto b : bids_to_delete)
170 std::unique_ptr<libMesh::UnstructuredMesh> & ply_mesh_u,
171 std::vector<Point> & points,
172 std::vector<Point> & mid_points,
174 const Real thickness)
179 mooseAssert(mid_points.empty(),
180 "If the input points are empty, the input mid_points must be also empty.");
182 TriangulatorInterface::MeshedHole bdry_mh(*ply_mesh_u);
195 if (mid_points.size())
196 poly2tri.
elem_type() = libMesh::ElemType::TRI6;
202 auto bdry_list(ply_mesh_u->get_boundary_info().build_side_list());
208 std::map<dof_id_type, std::vector<Point>> node_normal_map;
209 std::map<dof_id_type, Point> mid_node_normal_map;
210 for (
const auto & bside : bdry_list)
212 const auto & side = ply_mesh_u->elem_ptr(std::get<0>(bside))->side_ptr(std::get<1>(bside));
215 const Point side_normal_0 =
217 ?
getKeyNormal(ply_mesh_u->elem_ptr(std::get<0>(bside)), std::get<1>(bside), 0)
218 : ply_mesh_u->elem_ptr(std::get<0>(bside))
219 ->side_vertex_average_normal(std::get<1>(bside));
220 const Point side_normal_1 =
222 ?
getKeyNormal(ply_mesh_u->elem_ptr(std::get<0>(bside)), std::get<1>(bside), 1)
225 if (node_normal_map.count(side->node_ptr(0)->id()))
226 node_normal_map[side->node_ptr(0)->id()].push_back(side_normal_0);
228 node_normal_map[side->node_ptr(0)->id()] = {side_normal_0};
229 if (node_normal_map.count(side->node_ptr(1)->id()))
230 node_normal_map[side->node_ptr(1)->id()].push_back(side_normal_1);
232 node_normal_map[side->node_ptr(1)->id()] = {side_normal_1};
234 if (mid_points.size())
236 const Point mid_node_normal =
237 getKeyNormal(ply_mesh_u->elem_ptr(std::get<0>(bside)), std::get<1>(bside), 2);
239 [ply_mesh_u->elem_ptr(std::get<0>(bside))->node_ptr(std::get<1>(bside) + 3)->id()] =
244 std::vector<Point> mod_reduced_pts_list(points);
245 for (
const auto & [node_id, normal_vecs] : node_normal_map)
247 mooseAssert(normal_vecs.size() == 2,
248 "Each vertex should be connected to exactly two sides in a polygon.");
250 const Point original_pt = *(ply_mesh_u->node_ptr(node_id));
252 const Point move_dir =
253 (normal_vecs.front() + normal_vecs.back()).unit() * (outward ? 1.0 : -1.0);
267 const Real mov_dist =
268 thickness / std::sqrt((1.0 + (normal_vecs.front() * normal_vecs.back()) /
269 (normal_vecs.front().norm() * normal_vecs.back().norm())) /
271 mooseAssert(std::count(points.begin(), points.end(), original_pt) == 1,
272 "The original point should be found exactly once in the reduced points list.");
273 mod_reduced_pts_list[std::distance(points.begin(),
274 std::find(points.begin(), points.end(), original_pt))] =
275 original_pt + move_dir * mov_dist;
281 for (
const auto & i_node_1 : make_range(mod_reduced_pts_list.size()))
283 const Point & p1 = points[i_node_1];
284 const Point & p2 = mod_reduced_pts_list[i_node_1];
285 for (
const auto & i_node_2 : make_range(mod_reduced_pts_list.size()))
287 if (i_node_2 == i_node_1 || (i_node_2 + 1) % mod_reduced_pts_list.size() == i_node_1)
289 const Point & p3 = mod_reduced_pts_list[i_node_2];
290 const Point & p4 = mod_reduced_pts_list[(i_node_2 + 1) % mod_reduced_pts_list.size()];
295 "The thickness is so large that the mesh is tangled because the offset nodes "
296 "are no longer in the same order when following the original boundary. Please "
297 "reduce the thickness value.");
301 std::vector<Point> mid_mod_reduced_pts_list(mid_points.size());
302 for (
const auto & [node_id, normal_vec] : mid_node_normal_map)
304 const Point original_pt = *(ply_mesh_u->node_ptr(node_id));
305 const Point move_dir = normal_vec.unit() * (outward ? 1.0 : -1.0);
306 mid_mod_reduced_pts_list[std::distance(
307 mid_points.begin(), std::find(mid_points.begin(), mid_points.end(), original_pt))] =
308 original_pt + move_dir * thickness;
313 std::vector<Point> layer_pts_list(mod_reduced_pts_list);
314 layer_pts_list.insert(
315 layer_pts_list.end(), mid_mod_reduced_pts_list.begin(), mid_mod_reduced_pts_list.end());
317 return layer_pts_list;
341getKeyNormal(
const Elem * elem,
const unsigned int s,
const unsigned int node_index)
343 const std::unique_ptr<const Elem> face = elem->build_side_ptr(s);
344 mooseAssert(face->type() == ElemType::EDGE3,
345 "Only elements with EDGE3 sides are supported in this function.");
346 mooseAssert(node_index < 3,
347 "The node index for an EDGE3 side should be 0, 1, or 2 (for the two vertices and the "
349 std::unique_ptr<libMesh::FEBase> fe(
351 const std::vector<Point> & normals = fe->get_normals();
352 std::vector<Point> ref_pts = {face->reference_elem()->point(node_index)};
353 fe->reinit(elem, s, TOLERANCE, &ref_pts);
std::unique_ptr< MeshBase > buildBoundaryLayerRing(MeshGenerator &mg, MeshBase &input_mesh, const std::vector< BoundaryName > &boundary_names, unsigned int num_layers, Real thickness, Real layer_bias, bool outward, const MooseEnum &tri_elem_type, SubdomainID output_subdomain_id, const SubdomainName &output_subdomain_name)
Builds a conformal boundary-layer ring of triangulated annuli along a boundary of an input 2D mesh (o...