https://mooseframework.inl.gov
Loading...
Searching...
No Matches
Functions
BoundaryLayerUtils Namespace Reference

Functions

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 (or a 1D loop).
 
std::vector< Point > generateOffsetPolyline (MeshGenerator *mg, std::unique_ptr< libMesh::UnstructuredMesh > &ply_mesh_u, std::vector< Point > &points, std::vector< Point > &mid_points, const bool outward, const Real thickness)
 Generates a list of points offset from the input boundary polyline by a specified thickness in either outward/inward direction.
 
void collectExteriorVertexPointsFromMesh (libMesh::TriangulatorInterface::MeshedHole &bdry_mh, std::vector< Point > &points, std::vector< Point > &mid_points, const bool skip_node_reduction=false)
 Collects key vertex points (and optional midpoints) from a meshed hole, optionally discarding colinear vertices.
 
Point getKeyNormal (const Elem *elem, const unsigned int s, const unsigned int node_index)
 Extracts the normal vector of the EDGE3 side of a quadratic element at a given node index.
 
std::unique_ptr< MeshBasebuildBoundaryLayerRing (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)
 
std::vector< PointgenerateOffsetPolyline (MeshGenerator *mg, std::unique_ptr< libMesh::UnstructuredMesh > &ply_mesh_u, std::vector< Point > &points, std::vector< Point > &mid_points, const bool outward, const Real thickness)
 
void collectExteriorVertexPointsFromMesh (libMesh::TriangulatorInterface::MeshedHole &bdry_mh, std::vector< Point > &points, std::vector< Point > &mid_points, const bool skip_node_reduction)
 
Point getKeyNormal (const Elem *elem, const unsigned int s, const unsigned int node_index)
 

Function Documentation

◆ buildBoundaryLayerRing() [1/2]

std::unique_ptr< MeshBase > BoundaryLayerUtils::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 (or a 1D loop).

Generates num_layers + 1 parallel polylines (geometric progression of thicknesses with the given bias), triangulates each annulus with triangulateWithDelaunay, and sequentially stitches them. The output mesh carries 2 * num_layers boundary ids, with the innermost being id 1 and the outermost being id (num_layers - 1) * 2.

Parameters
mgThe calling mesh generator (for paramError reporting + buildMeshBaseObject)
input_meshThe 2D-XY input mesh or 1D closed loop providing the seed boundary
boundary_namesSubset of boundary names on input_mesh defining the seed boundary; if empty, the external boundary of input_mesh is auto-detected via MeshedHole
num_layersNumber of element layers to generate
thicknessTotal boundary-layer thickness
layer_biasGeometric growth factor between successive layer thicknesses (1.0 = uniform)
outwardIf true, the layer grows outward from the seed boundary; else inward
tri_elem_typeTriangle element type ("TRI3", "TRI6", "TRI7", or "DEFAULT")
output_subdomain_idSubdomain id assigned to all generated triangles (0 = default)
output_subdomain_nameSubdomain name assigned to output_subdomain_id (empty = unnamed)
Returns
The stitched boundary-layer ring mesh

Referenced by XYDelaunayGenerator::generate(), and XYTriangleBoundaryLayerGenerator::generate().

◆ buildBoundaryLayerRing() [2/2]

std::unique_ptr< MeshBase > BoundaryLayerUtils::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 
)

Definition at line 32 of file BoundaryLayerUtils.C.

42{
43 // Extract seed boundary polyline points (vertices + optional midpoints) from input_mesh.
44 std::set<std::size_t> bdry_id_set;
45 if (!boundary_names.empty())
46 {
47 auto ids =
48 MooseMeshUtils::getBoundaryIDs(input_mesh, boundary_names, /*generate unknown*/ false);
49 bdry_id_set.insert(ids.begin(), ids.end());
50 }
51 TriangulatorInterface::MeshedHole bdry_mh(input_mesh, bdry_id_set);
52 std::vector<Point> cur_pts;
53 std::vector<Point> cur_mids;
54 // Preserve every input boundary node (including colinear interior nodes on straight edges) so
55 // the ring's innermost polyline can be stitched back to the input mesh's boundary exactly when
56 // keep_input is requested by a downstream caller.
57 collectExteriorVertexPointsFromMesh(bdry_mh,
58 cur_pts,
59 cur_mids,
60 /*skip_node_reduction=*/true);
61
62 // Geometric progression of incremental thicknesses. layer_thicknesses[i] is the offset
63 // distance from polyline i-1 to polyline i (for i >= 1); layer_thicknesses[0] is unused.
64 std::vector<Real> layer_thicknesses(num_layers + 1);
65 const Real unit_thickness =
66 (layer_bias == 1.0)
67 ? (thickness / num_layers)
68 : (thickness / (std::pow(layer_bias, num_layers) - 1.0) * (layer_bias - 1.0));
69 layer_thicknesses[0] = 0.0;
70 for (auto i : make_range(std::vector<Real>::size_type(1), layer_thicknesses.size()))
71 layer_thicknesses[i] = unit_thickness * std::pow(layer_bias, i - 1);
72
73 // Generate the N+1 polyline meshes. Polylines are indexed so that polyline 0 is geometrically
74 // innermost (smallest) and polyline N is outermost (largest). Iteration order depends on
75 // direction: for outward, build polyline 0 first (= input boundary); for inward, build polyline
76 // N first.
77 std::vector<std::unique_ptr<MeshBase>> polylines(num_layers + 1);
78 for (auto layer_i : make_range(num_layers + 1))
79 {
80 const unsigned int layer_index = outward ? layer_i : (num_layers - layer_i);
81
82 // Build the polyline mesh for storage as polylines[layer_index].
83 auto ply = std::make_unique<ReplicatedMesh>(mg.comm());
85 cur_pts,
86 cur_mids,
87 /*loop=*/true,
88 BoundaryName(),
89 BoundaryName(),
90 std::vector<unsigned int>({1}));
91 polylines[layer_index] = std::move(ply);
92
93 if (layer_i + 1 < num_layers + 1)
94 {
95 // Build a sacrificial polyline mesh to feed generateOffsetPolyline (it triangulates the
96 // input mesh in place to compute side normals; we discard it afterwards).
97 auto ply_for_offset = std::make_unique<ReplicatedMesh>(mg.comm());
99 cur_pts,
100 cur_mids,
101 /*loop=*/true,
102 BoundaryName(),
103 BoundaryName(),
104 std::vector<unsigned int>({1}));
105 std::unique_ptr<UnstructuredMesh> ply_for_offset_u =
106 dynamic_pointer_cast<UnstructuredMesh>(std::move(ply_for_offset));
107
108 std::vector<Point> next_combined = generateOffsetPolyline(
109 &mg, ply_for_offset_u, cur_pts, cur_mids, outward, layer_thicknesses[layer_i + 1]);
110 if (cur_mids.empty())
111 cur_pts = std::move(next_combined);
112 else
113 {
114 const auto n_vert = cur_pts.size();
115 cur_pts.assign(next_combined.begin(), next_combined.begin() + n_vert);
116 cur_mids.assign(next_combined.begin() + n_vert, next_combined.end());
117 }
118 }
119 }
120
121 // Triangulate each annulus (between polylines[i] and polylines[i+1]). Stitch each annulus to
122 // the accumulating ring (except the first one).
123 std::unique_ptr<MeshBase> ring;
124 for (auto i : make_range(num_layers))
125 {
127 xyd_opts.refine_bdy = false;
128 xyd_opts.verify_holes = false;
129 xyd_opts.stitch_holes = {i > 0};
130 xyd_opts.refine_holes = {false};
131 xyd_opts.tri_elem_type = std::string(tri_elem_type);
132 if (output_subdomain_id != 0)
133 {
134 xyd_opts.has_output_subdomain_id = true;
135 xyd_opts.output_subdomain_id = output_subdomain_id;
136 }
137 if (output_subdomain_name.size())
138 {
139 xyd_opts.has_output_subdomain_name = true;
140 xyd_opts.output_subdomain_name = output_subdomain_name;
141 }
142
143 std::vector<std::unique_ptr<MeshBase>> holes;
144 holes.reserve(1);
145 if (i == 0)
146 holes.push_back(std::move(polylines[0]));
147 else
148 holes.push_back(std::move(ring));
149
151 mg, std::move(polylines[i + 1]), std::move(holes), xyd_opts);
152 }
153 // We now have 2 * num_layers boundaries
154 // Let's only keep the innermost (1) and outermost (2 * num_layers) boundaries, and remove all
155 // intermediate ring bcids
156 std::vector<BoundaryID> bids_to_delete;
157 for (const auto b : make_range(2 * num_layers))
158 {
159 if (b == 1 || b == (num_layers - 1) * 2)
160 continue;
161 bids_to_delete.push_back(b);
162 }
163 auto & bi = ring->get_boundary_info();
164 for (auto b : bids_to_delete)
165 bi.remove_id(b);
166
167 return ring;
168}
const Parallel::Communicator & comm() const
std::vector< Point > generateOffsetPolyline(MeshGenerator *mg, std::unique_ptr< libMesh::UnstructuredMesh > &ply_mesh_u, std::vector< Point > &points, std::vector< Point > &mid_points, const bool outward, const Real thickness)
Generates a list of points offset from the input boundary polyline by a specified thickness in either...
std::unique_ptr< MeshBase > triangulateWithDelaunay(MeshGenerator &mg, std::unique_ptr< MeshBase > boundary_mesh, std::vector< std::unique_ptr< MeshBase > > hole_meshes, const XYDelaunayOptions &xyd_opts)
Performs a 2D Delaunay triangulation (via libMesh::Poly2TriTriangulator) inside a closed boundary mes...
void buildPolyLineMesh(MeshBase &mesh, const std::vector< Point > &points, const bool loop, const BoundaryName &start_boundary, const BoundaryName &end_boundary, const std::vector< unsigned int > &nums_edges_between_points)
Generates meshes from edges connecting a list of points.
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.
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
IntRange< T > make_range(T beg, T end)
MooseUnits pow(const MooseUnits &, int)
Definition Units.C:537
Bundle of inputs for triangulateWithDelaunay.
const unsigned int n_vert

◆ collectExteriorVertexPointsFromMesh() [1/2]

void BoundaryLayerUtils::collectExteriorVertexPointsFromMesh ( libMesh::TriangulatorInterface::MeshedHole bdry_mh,
std::vector< Point > &  points,
std::vector< Point > &  mid_points,
const bool  skip_node_reduction 
)

Definition at line 323 of file BoundaryLayerUtils.C.

327{
328 for (const auto i : make_range(bdry_mh.n_points()))
329 {
330 if (skip_node_reduction || !geom_utils::arePointsColinear(
331 bdry_mh.point((i - 1 + bdry_mh.n_points()) % bdry_mh.n_points()),
332 bdry_mh.point(i),
333 bdry_mh.point((i + 1) % bdry_mh.n_points())))
334 {
335 points.push_back(bdry_mh.point(i));
336 if (bdry_mh.n_midpoints() == 1 && skip_node_reduction)
337 mid_points.push_back(bdry_mh.midpoint(0, i));
338 }
339 }
340}
virtual Point point(const unsigned int n) const override
virtual Point midpoint(const unsigned int m, const unsigned int n) const override
virtual unsigned int n_points() const override
virtual unsigned int n_midpoints() const override
bool arePointsColinear(const Point &p1, const Point &p2, const Point &p3)
Check if three points are colinear.

◆ collectExteriorVertexPointsFromMesh() [2/2]

void BoundaryLayerUtils::collectExteriorVertexPointsFromMesh ( libMesh::TriangulatorInterface::MeshedHole bdry_mh,
std::vector< Point > &  points,
std::vector< Point > &  mid_points,
const bool  skip_node_reduction = false 
)

Collects key vertex points (and optional midpoints) from a meshed hole, optionally discarding colinear vertices.

Parameters
bdry_mhThe 2D MeshedHole object from which to collect key points
pointsThe vector to which collected vertex points are appended (in order)
mid_pointsThe vector to which collected midpoints are appended; only populated when the MeshedHole reports a midpoint per side AND skip_node_reduction is true
skip_node_reductionIf true, retain every vertex and midpoint; if false, drop vertices that are colinear with their two neighbors and do not collect midpoints

Referenced by buildBoundaryLayerRing(), and generateOffsetPolyline().

◆ generateOffsetPolyline() [1/2]

std::vector< Point > BoundaryLayerUtils::generateOffsetPolyline ( MeshGenerator mg,
std::unique_ptr< libMesh::UnstructuredMesh > &  ply_mesh_u,
std::vector< Point > &  points,
std::vector< Point > &  mid_points,
const bool  outward,
const Real  thickness 
)

Generates a list of points offset from the input boundary polyline by a specified thickness in either outward/inward direction.

Parameters
mgThe mesh generator calling this function, used for paramError reporting
ply_mesh_uThe 1D loop polyline mesh of the original boundary; the volume it encloses will be triangulated in-place to compute the normal and define the inward/outward directions
pointsThe vertex points of the original polyline. If empty, populated from the input mesh via collectExteriorVertexPointsFromMesh.
mid_pointsThe midpoints of the original polyline (optional to enable quadratic elements). If both this and points are empty, populated from the input mesh.
outwardWhether to offset in the outward (true) or inward (false) direction
thicknessThe offset distance (>= 0)
Returns
Offset points: vertices first, then midpoints (length = points.size() + mid_points.size())

Referenced by buildBoundaryLayerRing().

◆ generateOffsetPolyline() [2/2]

std::vector< Point > BoundaryLayerUtils::generateOffsetPolyline ( MeshGenerator mg,
std::unique_ptr< libMesh::UnstructuredMesh > &  ply_mesh_u,
std::vector< Point > &  points,
std::vector< Point > &  mid_points,
const bool  outward,
const Real  thickness 
)

Definition at line 171 of file BoundaryLayerUtils.C.

177{
178 // If the input points are empty, we will extract them from the input 1D mesh
179 if (points.empty())
180 {
181 mooseAssert(mid_points.empty(),
182 "If the input points are empty, the input mid_points must be also empty.");
183
184 TriangulatorInterface::MeshedHole bdry_mh(*ply_mesh_u);
185 collectExteriorVertexPointsFromMesh(bdry_mh, points, mid_points);
186 }
187 // Generate a very simple triangulation mesh so that we can get the outward normal vectors
188 libMesh::Poly2TriTriangulator poly2tri(*ply_mesh_u);
189 poly2tri.triangulation_type() = libMesh::TriangulatorInterface::PSLG;
190
191 poly2tri.set_interpolate_boundary_points(0);
192 poly2tri.set_refine_boundary_allowed(false);
193 poly2tri.set_verify_hole_boundaries(false);
194 poly2tri.desired_area() = 0;
195 poly2tri.minimum_angle() = 0; // Not yet supported
196 poly2tri.smooth_after_generating() = false;
197 if (mid_points.size())
198 poly2tri.elem_type() = libMesh::ElemType::TRI6;
199 poly2tri.triangulate();
200
201 // We need to serialize the mesh for next steps
202 libMesh::MeshSerializer serial(*ply_mesh_u);
203 // The mesh now only contains one side set that corresponds to the outer boundary with an ID of 0
204 auto bdry_list(ply_mesh_u->get_boundary_info().build_side_list());
205
206 // For each vertex, the shifting direction to form the offset is defined by the normal vectors of
207 // the two sides that contain the vertex.
208 // We gather the normals for each node on the boundary here, which are all vertices because of
209 // the pre-selection of nodes.
210 std::map<dof_id_type, std::vector<Point>> node_normal_map;
211 std::map<dof_id_type, Point> mid_node_normal_map;
212 for (const auto & bside : bdry_list)
213 {
214 const auto & side = ply_mesh_u->elem_ptr(std::get<0>(bside))->side_ptr(std::get<1>(bside));
215 // For linear elements, the side normal is constant and can be obtained straightforwardly;
216 // For quadratic elements, we need the normal at the shared vertices
217 const Point side_normal_0 =
218 mid_points.size()
219 ? getKeyNormal(ply_mesh_u->elem_ptr(std::get<0>(bside)), std::get<1>(bside), 0)
220 : ply_mesh_u->elem_ptr(std::get<0>(bside))
221 ->side_vertex_average_normal(std::get<1>(bside));
222 const Point side_normal_1 =
223 mid_points.size()
224 ? getKeyNormal(ply_mesh_u->elem_ptr(std::get<0>(bside)), std::get<1>(bside), 1)
225 : side_normal_0;
226
227 if (node_normal_map.count(side->node_ptr(0)->id()))
228 node_normal_map[side->node_ptr(0)->id()].push_back(side_normal_0);
229 else
230 node_normal_map[side->node_ptr(0)->id()] = {side_normal_0};
231 if (node_normal_map.count(side->node_ptr(1)->id()))
232 node_normal_map[side->node_ptr(1)->id()].push_back(side_normal_1);
233 else
234 node_normal_map[side->node_ptr(1)->id()] = {side_normal_1};
235
236 if (mid_points.size())
237 {
238 const Point mid_node_normal =
239 getKeyNormal(ply_mesh_u->elem_ptr(std::get<0>(bside)), std::get<1>(bside), 2);
240 mid_node_normal_map
241 [ply_mesh_u->elem_ptr(std::get<0>(bside))->node_ptr(std::get<1>(bside) + 3)->id()] =
242 mid_node_normal;
243 }
244 }
245
246 std::vector<Point> mod_reduced_pts_list(points);
247 for (const auto & [node_id, normal_vecs] : node_normal_map)
248 {
249 mooseAssert(normal_vecs.size() == 2,
250 "Each vertex should be connected to exactly two sides in a polygon.");
251
252 const Point original_pt = *(ply_mesh_u->node_ptr(node_id));
253 // Form an average normal at the vertex from the two connected sides' normals
254 const Point move_dir =
255 (normal_vecs.front() + normal_vecs.back()).unit() * (outward ? 1.0 : -1.0);
256 // Consider four points of interest to determine the moving distance
257 // 1. the vertex point
258 // 2. point along normal_vecs.front() from the vertex with a distance thickness
259 // 3. point along normal_vecs.back() from the vertex with a distance thickness
260 // 4. point along move_dir from the vertex with a distance mov_dist
261 // The four points form a kite shape with its symmetry axis along 1-4 direction
262 // Angle 1-2-4 and angle 1-3-4 are right angles
263 // Angle 2-1-3 (i.e., theta) can be calculated using the interior product of
264 // v1 = normal_vecs.front() and v2 = normal_vecs.back():
265 // cos(theta) = (v1 . v2) / (|v1| * |v2|)
266 // Because of the right angles, the distance between 1 and 4 can be calculated as:
267 // mov_dist = thickness / cos(theta/2)
268 // and cos(theta/2) = sqrt((1 + cos(theta)) / 2)
269 const Real mov_dist =
270 thickness / std::sqrt((1.0 + (normal_vecs.front() * normal_vecs.back()) /
271 (normal_vecs.front().norm() * normal_vecs.back().norm())) /
272 2.0);
273 mooseAssert(std::count(points.begin(), points.end(), original_pt) == 1,
274 "The original point should be found exactly once in the reduced points list.");
275 mod_reduced_pts_list[std::distance(points.begin(),
276 std::find(points.begin(), points.end(), original_pt))] =
277 original_pt + move_dir * mov_dist;
278 }
279
280 // To ensure no overlapping, we need to check set of four points
281 // p1 and p2 should be a pair of points before and after shifting
282 // p3 and p4 should be a pair of adjacent shifted points that are neither not p2
283 for (const auto & i_node_1 : make_range(mod_reduced_pts_list.size()))
284 {
285 const Point & p1 = points[i_node_1];
286 const Point & p2 = mod_reduced_pts_list[i_node_1];
287 for (const auto & i_node_2 : make_range(mod_reduced_pts_list.size()))
288 {
289 if (i_node_2 == i_node_1 || (i_node_2 + 1) % mod_reduced_pts_list.size() == i_node_1)
290 continue;
291 const Point & p3 = mod_reduced_pts_list[i_node_2];
292 const Point & p4 = mod_reduced_pts_list[(i_node_2 + 1) % mod_reduced_pts_list.size()];
293 if (thickness > 0)
294 if (geom_utils::segmentsIntersect(p1, p2, p3, p4))
295 mg->paramError(
296 "thickness",
297 "The thickness is so large that the mesh is tangled because the offset nodes "
298 "are no longer in the same order when following the original boundary. Please "
299 "reduce the thickness value.");
300 }
301 }
302
303 std::vector<Point> mid_mod_reduced_pts_list(mid_points.size());
304 for (const auto & [node_id, normal_vec] : mid_node_normal_map)
305 {
306 const Point original_pt = *(ply_mesh_u->node_ptr(node_id));
307 const Point move_dir = normal_vec.unit() * (outward ? 1.0 : -1.0);
308 mid_mod_reduced_pts_list[std::distance(
309 mid_points.begin(), std::find(mid_points.begin(), mid_points.end(), original_pt))] =
310 original_pt + move_dir * thickness;
311 }
312
313 // combine mod_reduced_pts_list and mid_mod_reduced_pts_list to get the final list of points for
314 // the layer mesh
315 std::vector<Point> layer_pts_list(mod_reduced_pts_list);
316 layer_pts_list.insert(
317 layer_pts_list.end(), mid_mod_reduced_pts_list.begin(), mid_mod_reduced_pts_list.end());
318
319 return layer_pts_list;
320}
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
TypeVector< Real > unit() const
Point getKeyNormal(const Elem *elem, const unsigned int s, const unsigned int node_index)
Extracts the normal vector of the EDGE3 side of a quadratic element at a given node index.
void collectExteriorVertexPointsFromMesh(libMesh::TriangulatorInterface::MeshedHole &bdry_mh, std::vector< Point > &points, std::vector< Point > &mid_points, const bool skip_node_reduction=false)
Collects key vertex points (and optional midpoints) from a meshed hole, optionally discarding colinea...
bool segmentsIntersect(const Point &p1, const Point &p2, const Point &p3, const Point &p4)
Check if the line segment p1-p2 intersects with line segment p3-p4 (only working in 2D (x-y plane)).

◆ getKeyNormal() [1/2]

Point BoundaryLayerUtils::getKeyNormal ( const Elem *  elem,
const unsigned int  s,
const unsigned int  node_index 
)

Extracts the normal vector of the EDGE3 side of a quadratic element at a given node index.

Parameters
elemThe element containing the side of interest
sThe side index of interest
node_indexThe index of the node on the side at which to extract the normal vector
Returns
The normal vector at the node index on the side of interest

Referenced by generateOffsetPolyline().

◆ getKeyNormal() [2/2]

Point BoundaryLayerUtils::getKeyNormal ( const Elem elem,
const unsigned int  s,
const unsigned int  node_index 
)

Definition at line 343 of file BoundaryLayerUtils.C.

344{
345 const std::unique_ptr<const Elem> face = elem->build_side_ptr(s);
346 mooseAssert(face->type() == ElemType::EDGE3,
347 "Only elements with EDGE3 sides are supported in this function.");
348 mooseAssert(node_index < 3,
349 "The node index for an EDGE3 side should be 0, 1, or 2 (for the two vertices and the "
350 "midpoint).");
351 std::unique_ptr<libMesh::FEBase> fe(
353 const std::vector<Point> & normals = fe->get_normals();
354 std::vector<Point> ref_pts = {face->reference_elem()->point(node_index)};
355 fe->reinit(elem, s, TOLERANCE, &ref_pts);
356 return normals[0];
357}
virtual Order default_order() const=0
virtual std::unique_ptr< Elem > build_side_ptr(const unsigned int i)=0
std::unique_ptr< FEGenericBase< Real > > build(const unsigned int dim, const FEType &fet)
static constexpr Real TOLERANCE