https://mooseframework.inl.gov
AdvancedExtruderGenerator.C
Go to the documentation of this file.
1 //* This file is part of the MOOSE framework
2 //* https://mooseframework.inl.gov
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 #include "MooseUtils.h"
12 #include "MooseMeshUtils.h"
13 #include "MathUtils.h"
14 #include "GeometryUtils.h"
15 
16 #include "libmesh/boundary_info.h"
17 #include "libmesh/function_base.h"
18 #include "libmesh/cell_prism6.h"
19 #include "libmesh/cell_prism18.h"
20 #include "libmesh/cell_prism21.h"
21 #include "libmesh/cell_hex8.h"
22 #include "libmesh/cell_hex20.h"
23 #include "libmesh/cell_hex27.h"
24 #include "libmesh/cell_c0polyhedron.h"
25 #include "libmesh/edge_edge2.h"
26 #include "libmesh/edge_edge3.h"
27 #include "libmesh/edge_edge4.h"
28 #include "libmesh/face_quad4.h"
29 #include "libmesh/face_quad8.h"
30 #include "libmesh/face_quad9.h"
31 #include "libmesh/face_tri3.h"
32 #include "libmesh/face_tri6.h"
33 #include "libmesh/face_tri7.h"
34 #include "libmesh/face_c0polygon.h"
35 #include "libmesh/libmesh_logging.h"
36 #include "libmesh/mesh_communication.h"
37 #include "libmesh/mesh_modification.h"
38 #include "libmesh/mesh_serializer.h"
39 #include "libmesh/mesh_tools.h"
40 #include "libmesh/parallel.h"
41 #include "libmesh/remote_elem.h"
42 #include "libmesh/string_to_enum.h"
43 #include "libmesh/unstructured_mesh.h"
44 #include "libmesh/point.h"
45 
46 #include <numeric>
47 #include <cmath>
48 
51  FancyExtruderGenerator,
52  "02/18/2023 24:00",
54 
57 {
59 
60  params.addClassDescription(
61  "Extrudes a 1D mesh into 2D, or a 2D mesh into 3D, and supports a variable height for each "
62  "elevation, variable number of layers within each elevation, variable growth factors of "
63  "axial element sizes within each elevation and remap subdomain_ids, boundary_ids and element "
64  "extra integers within each elevation as well as interface boundaries between neighboring "
65  "elevation layers, as well as following a 1D curve and modifying the radial (normal to "
66  "the extrusion axis) extent of the geometry.");
67 
68  params.addRequiredParam<MeshGeneratorName>("input", "The mesh to extrude");
69 
70  // User input of extrusion heights / axial discretization
71  params.addParam<std::vector<Real>>("heights", {}, "The height of each elevation");
72  params.addRangeCheckedParam<std::vector<Real>>(
73  "biases", "biases>0.0", "The axial growth factor used for mesh biasing for each elevation.");
74  params.addParam<std::vector<unsigned int>>(
75  "num_layers",
76  {},
77  "The number of layers for each elevation - must be num_elevations in length!");
78 
79  // Swaps on every height
80  params.addParam<std::vector<std::vector<subdomain_id_type>>>(
81  "subdomain_swaps",
82  {},
83  "For each row, every two entries are interpreted as a pair of "
84  "'from' and 'to' to remap the subdomains for that elevation");
85  params.addParam<std::vector<std::vector<boundary_id_type>>>(
86  "boundary_swaps",
87  {},
88  "For each row, every two entries are interpreted as a pair of "
89  "'from' and 'to' to remap the boundaries for that elevation");
90  params.addParam<std::vector<std::string>>(
91  "elem_integer_names_to_swap",
92  {},
93  "Array of element extra integer names that need to be swapped during extrusion.");
94  params.addParam<std::vector<std::vector<std::vector<dof_id_type>>>>(
95  "elem_integers_swaps",
96  {},
97  "For each row, every two entries are interpreted as a pair of 'from' and 'to' to remap the "
98  "element extra integer for that elevation. If multiple element extra integers need to be "
99  "swapped, the enties are stacked based on the order provided in "
100  "'elem_integer_names_to_swap' to form the third dimension.");
101 
102  // Direction parameter
103  params.addParam<Point>("direction",
104  "A vector that points in the direction to extrude (note, this will be "
105  "normalized internally - so don't worry about it here)");
106  params.addParam<MeshGeneratorName>(
107  "extrusion_curve",
108  "Name of the mesh generator providing the line mesh curve to be extruded along. The "
109  "extrusion path follows the node order in the line mesh");
110  params.addParam<RealVectorValue>(
111  "start_extrusion_direction",
112  "A vector that points in the starting direction for extruding along a curve. This vector "
113  "should be the tangent vector at the FIRST node of the extrusion curve. Vector will be "
114  "normalized in code, so don't worry about it here.");
115  params.addParam<RealVectorValue>(
116  "end_extrusion_direction",
117  "A vector that points in the ending direction for extruding along a curve. This vector "
118  "should be the tangent vector at the LAST node of the extrusion curve. Vector will be "
119  "normalized in code, so don't worry about it here.");
120 
121  // Boundaries and interfaces
122  params.addParam<BoundaryName>(
123  "top_boundary",
124  "The boundary name to set on the top boundary. If omitted an ID will be generated.");
125  params.addParam<BoundaryName>(
126  "bottom_boundary",
127  "The boundary name to set on the bottom boundary. If omitted an ID will be generated.");
128  params.addParam<std::vector<std::vector<subdomain_id_type>>>(
129  "upward_boundary_source_blocks", "Block ids used to generate upward interface boundaries.");
130  params.addParam<std::vector<std::vector<boundary_id_type>>>("upward_boundary_ids",
131  "Upward interface boundary ids.");
132  params.addParam<std::vector<std::vector<subdomain_id_type>>>(
133  "downward_boundary_source_blocks",
134  "Block ids used to generate downward interface boundaries.");
135  params.addParam<std::vector<std::vector<boundary_id_type>>>("downward_boundary_ids",
136  "Downward interface boundary ids.");
137 
138  // Radial transformations
139  params.addParam<Real>("twist_pitch",
140  0,
141  "Pitch for helicoidal extrusion around an axis going through the origin "
142  "following the direction vector");
143  params.addParam<Real>("end_radial_extent",
144  0,
145  "If modifying the radial extent of the extruded geometry, final radial "
146  "extent to reach at the end of the extrusion process");
147  MooseEnum radial_growth_methods("linear cubic", "linear");
148  params.addParam<MooseEnum>("radial_growth_method",
149  radial_growth_methods,
150  "Functional form to change radius while extruding along curve.");
151  params.addParam<Real>("start_radial_growth_rate", 1., "Starting rate of radial expansion.");
152  params.addParam<Real>("end_radial_growth_rate", 1., "Ending rate of radial expansion.");
153 
154  params.addParamNamesToGroup(
155  "top_boundary bottom_boundary upward_boundary_source_blocks upward_boundary_ids "
156  "downward_boundary_source_blocks downward_boundary_ids",
157  "Boundary Assignment");
158  params.addParamNamesToGroup(
159  "subdomain_swaps boundary_swaps elem_integer_names_to_swap elem_integers_swaps", "ID Swap");
160  params.addParamNamesToGroup("extrusion_curve start_extrusion_direction end_extrusion_direction",
161  "Extrusion along curve");
162  params.addParamNamesToGroup("twist_pitch end_radial_extent radial_growth_method "
163  "start_radial_growth_rate end_radial_growth_rate",
164  "Radial transformation");
165 
166  return params;
167 }
168 
170  : MeshGenerator(parameters),
171  _input(getMesh("input")),
172  _heights(getParam<std::vector<Real>>("heights")),
173  _biases(isParamValid("biases") ? getParam<std::vector<Real>>("biases")
174  : std::vector<Real>(_heights.size(), 1.0)),
175  _num_layers(getParam<std::vector<unsigned int>>("num_layers")),
176  _subdomain_swaps(getParam<std::vector<std::vector<subdomain_id_type>>>("subdomain_swaps")),
177  _boundary_swaps(getParam<std::vector<std::vector<boundary_id_type>>>("boundary_swaps")),
178  _elem_integer_names_to_swap(getParam<std::vector<std::string>>("elem_integer_names_to_swap")),
179  _elem_integers_swaps(
180  getParam<std::vector<std::vector<std::vector<dof_id_type>>>>("elem_integers_swaps")),
181  _direction(isParamValid("direction") ? getParam<Point>("direction") : Point(0, 0, 0)),
182  _extrusion_curve(getMesh("extrusion_curve", true)),
183  _start_extrusion_direction(isParamValid("start_extrusion_direction")
184  ? getParam<RealVectorValue>("start_extrusion_direction").unit()
185  : Point(0, 0, 0)),
186  _end_extrusion_direction(isParamValid("end_extrusion_direction")
187  ? getParam<RealVectorValue>("end_extrusion_direction").unit()
188  : Point(0, 0, 0)),
189  _extrude_along_curve(isParamValid("extrusion_curve")),
190  _has_top_boundary(isParamValid("top_boundary")),
191  _top_boundary(isParamValid("top_boundary") ? getParam<BoundaryName>("top_boundary") : "0"),
192  _has_bottom_boundary(isParamValid("bottom_boundary")),
193  _bottom_boundary(isParamValid("bottom_boundary") ? getParam<BoundaryName>("bottom_boundary")
194  : "0"),
195  _upward_boundary_source_blocks(
196  isParamValid("upward_boundary_source_blocks")
197  ? getParam<std::vector<std::vector<subdomain_id_type>>>("upward_boundary_source_blocks")
198  : std::vector<std::vector<subdomain_id_type>>(_heights.size(),
199  std::vector<subdomain_id_type>())),
200  _upward_boundary_ids(
201  isParamValid("upward_boundary_ids")
202  ? getParam<std::vector<std::vector<boundary_id_type>>>("upward_boundary_ids")
203  : std::vector<std::vector<boundary_id_type>>(_heights.size(),
204  std::vector<boundary_id_type>())),
205  _downward_boundary_source_blocks(isParamValid("downward_boundary_source_blocks")
206  ? getParam<std::vector<std::vector<subdomain_id_type>>>(
207  "downward_boundary_source_blocks")
208  : std::vector<std::vector<subdomain_id_type>>(
209  _heights.size(), std::vector<subdomain_id_type>())),
210  _downward_boundary_ids(
211  isParamValid("downward_boundary_ids")
212  ? getParam<std::vector<std::vector<boundary_id_type>>>("downward_boundary_ids")
213  : std::vector<std::vector<boundary_id_type>>(_heights.size(),
214  std::vector<boundary_id_type>())),
215  _twist_pitch(getParam<Real>("twist_pitch")),
216  _end_radial_extent(getParam<Real>("end_radial_extent")),
217  _radial_expansion_method(getParam<MooseEnum>("radial_growth_method")),
218  _start_radial_growth_rate(getParam<Real>("start_radial_growth_rate")),
219  _end_radial_growth_rate(getParam<Real>("end_radial_growth_rate"))
220 {
222  {
223  if (isParamSetByUser("heights"))
224  paramError("heights", "heights cannot be set if extruding along curve!");
225  if (isParamValid("biases"))
226  paramError("biases", "biases cannot be set if extruding along curve!");
227  if (isParamSetByUser("num_layers"))
228  paramError("num_layers", "num_layers cannot be set if extruding along curve!");
229  if (isParamValid("direction"))
230  paramError("direction", "direction cannot be set if extruding along curve!");
231  }
232  else
233  {
234  if (!_direction.norm())
235  paramError("direction", "Must have some length!");
236  _direction /= _direction.norm();
237  }
238 
239  unsigned int num_elevations;
241  num_elevations = 1;
242  else
243  {
244  num_elevations = _heights.size();
245  if (_num_layers.size() != num_elevations)
246  paramError(
247  "heights", "The length of 'heights' and 'num_layers' must be the same in ", name());
248  }
249 
250  if (_subdomain_swaps.size() && (_subdomain_swaps.size() != num_elevations))
251  {
253  paramError("subdomain_swaps",
254  "If specified, 'subdomain_swaps' (" + std::to_string(_subdomain_swaps.size()) +
255  ") must be equal to 1 when extruding along a curve.");
256  else
257  {
258  paramError("subdomain_swaps",
259  "If specified, 'subdomain_swaps' (" + std::to_string(_subdomain_swaps.size()) +
260  ") must be the same length as 'heights' (" + std::to_string(num_elevations) +
261  ") in ",
262  name());
263  }
264  }
265 
266  try
267  {
269  name(), "subdomain_swaps", _subdomain_swaps, _subdomain_swap_pairs);
270  }
271  catch (const MooseException & e)
272  {
273  paramError("subdomain_swaps", e.what());
274  }
275 
276  if (_boundary_swaps.size() && (_boundary_swaps.size() != num_elevations))
277  {
279  {
280  paramError("boundary_swaps",
281  "If specified, 'boundary_swaps' (" + std::to_string(_boundary_swaps.size()) +
282  ") must be the same length as 'heights' (" + std::to_string(num_elevations) +
283  ") in ",
284  name());
285  }
286  else
287  {
288  paramError("boundary_swaps",
289  "If specified, 'boundary_swaps' (" + std::to_string(_boundary_swaps.size()) +
290  ") must be equal to 1 when extruding along a curve.");
291  }
292  }
293 
294  try
295  {
297  name(), "boundary_swaps", _boundary_swaps, _boundary_swap_pairs);
298  }
299  catch (const MooseException & e)
300  {
301  paramError("boundary_swaps", e.what());
302  }
303 
304  if (_elem_integers_swaps.size() &&
306  paramError("elem_integers_swaps",
307  "If specified, 'elem_integers_swaps' must have the same length as the length of "
308  "'elem_integer_names_to_swap'.");
309 
310  for (const auto & unit_elem_integers_swaps : _elem_integers_swaps)
311  if (unit_elem_integers_swaps.size() != num_elevations)
312  paramError("elem_integers_swaps",
313  "If specified, each element of 'elem_integers_swaps' must have the same length as "
314  "the length of 'heights'.");
315 
316  try
317  {
319  num_elevations,
323  }
324  catch (const MooseException & e)
325  {
326  paramError("elem_integers_swaps", e.what());
327  }
328 
330  {
331  bool has_negative_entry = false;
332  bool has_positive_entry = false;
333  for (const auto & h : _heights)
334  {
335  if (h > 0.0)
336  has_positive_entry = true;
337  else
338  has_negative_entry = true;
339  }
340 
341  if (has_negative_entry && has_positive_entry)
342  paramError("heights", "Cannot have both positive and negative heights!");
343  if (_biases.size() != _heights.size())
344  paramError("biases", "Size of this parameter, if provided, must be the same as heights.");
345  }
346 
347  if ((_upward_boundary_source_blocks.size() || _upward_boundary_ids.size()) &&
349  _upward_boundary_ids.size() != num_elevations))
350  {
352  paramError(
353  "upward_boundary_ids",
354  "This parameter must have the same length (" +
355  std::to_string(_upward_boundary_ids.size()) + ") as upward_boundary_source_blocks (" +
356  std::to_string(_upward_boundary_source_blocks.size()) + ") and num_elevations (" +
357  std::to_string(num_elevations) +
358  "). Note that the number of heights is set to 1 when extruding along a curve.");
359  else
360  {
361  paramError("upward_boundary_ids",
362  "This parameter must have the same length (" +
363  std::to_string(_upward_boundary_ids.size()) +
364  ") as upward_boundary_source_blocks (" +
365  std::to_string(_upward_boundary_source_blocks.size()) + ") and heights (" +
366  std::to_string(num_elevations) + ")");
367  }
368  }
369 
370  for (unsigned int i = 0; i < _upward_boundary_source_blocks.size(); i++)
371  if (_upward_boundary_source_blocks[i].size() != _upward_boundary_ids[i].size())
372  paramError("upward_boundary_ids",
373  "Every element of this parameter must have the same length as the corresponding "
374  "element of upward_boundary_source_blocks.");
375 
378  _downward_boundary_ids.size() != num_elevations))
379  {
381  paramError(
382  "downward_boundary_ids",
383  "This parameter must have the same length (" +
384  std::to_string(_downward_boundary_ids.size()) +
385  ") as downward_boundary_source_blocks (" +
386  std::to_string(_downward_boundary_source_blocks.size()) + ") and (" +
387  std::to_string(num_elevations) +
388  "). Note that the number of heights is set to 1 when extruding along a curve.");
389  else
390  {
391  paramError("downward_boundary_ids",
392  "This parameter must have the same length (" +
393  std::to_string(_downward_boundary_ids.size()) +
394  ") as downward_boundary_source_blocks (" +
395  std::to_string(_downward_boundary_source_blocks.size()) + ") and heights (" +
396  std::to_string(num_elevations) + ")");
397  }
398  }
399  for (unsigned int i = 0; i < _downward_boundary_source_blocks.size(); i++)
400  if (_downward_boundary_source_blocks[i].size() != _downward_boundary_ids[i].size())
401  paramError("downward_boundary_ids",
402  "Every element of this parameter must have the same length as the corresponding "
403  "element of downward_boundary_source_blocks.");
404 }
405 
406 std::unique_ptr<MeshBase>
408 {
409  // Note: bulk of this code originally from libmesh mesh_modification.C
410  // Original copyright: Copyright (C) 2002-2019 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner
411  // Original license is LGPL so it can be used here.
412 
413  auto mesh = buildMeshBaseObject(_input->mesh_dimension() + 1);
414  mesh->set_mesh_dimension(_input->mesh_dimension() + 1);
415 
416  // Check if the element integer names are existent in the input mesh.
417  for (const auto i : make_range(_elem_integer_names_to_swap.size()))
418  if (_input->has_elem_integer(_elem_integer_names_to_swap[i]))
420  _input->get_elem_integer_index(_elem_integer_names_to_swap[i]));
421  else
422  paramError("elem_integer_names_to_swap",
423  "Element ",
424  i + 1,
425  " of 'elem_integer_names_to_swap' in is not a valid extra element integer of the "
426  "input mesh.");
427 
428  // prepare for transferring extra element integers from original mesh to the extruded mesh.
429  const unsigned int num_extra_elem_integers = _input->n_elem_integers();
430  std::vector<std::string> id_names;
431 
432  for (unsigned int i = 0; i < num_extra_elem_integers; i++)
433  {
434  id_names.push_back(_input->get_elem_integer_name(i));
435  if (!mesh->has_elem_integer(id_names[i]))
436  mesh->add_elem_integer(id_names[i]);
437  }
438 
439  // retrieve subdomain/sideset/nodeset name maps
440  if (!_input->preparation().has_cached_elem_data)
441  _input->cache_elem_data();
442  const auto & input_subdomain_map = _input->get_subdomain_name_map();
443  const auto & input_sideset_map = _input->get_boundary_info().get_sideset_name_map();
444  const auto & input_nodeset_map = _input->get_boundary_info().get_nodeset_name_map();
445 
446  // Check that the swaps source blocks are present in the mesh
447  for (const auto & swap : _subdomain_swaps)
448  for (const auto i : index_range(swap))
449  if (i % 2 == 0 && !MooseMeshUtils::hasSubdomainID(*_input, swap[i]))
450  paramError("subdomain_swaps", "The block '", swap[i], "' was not found within the mesh");
451 
452  // Check that the swaps source boundaries are present in the mesh
453  for (const auto & swap : _boundary_swaps)
454  for (const auto i : index_range(swap))
455  if (i % 2 == 0 && !MooseMeshUtils::hasBoundaryID(*_input, swap[i]))
456  paramError("boundary_swaps", "The boundary '", swap[i], "' was not found within the mesh");
457 
458  // Check that the source blocks for layer top/bottom boundaries exist in the mesh
459  for (const auto & layer_vec : _upward_boundary_source_blocks)
460  for (const auto bid : layer_vec)
462  paramError(
463  "upward_boundary_source_blocks", "The block '", bid, "' was not found within the mesh");
464  for (const auto & layer_vec : _downward_boundary_source_blocks)
465  for (const auto bid : layer_vec)
467  paramError("downward_boundary_source_blocks",
468  "The block '",
469  bid,
470  "' was not found within the mesh");
471 
472  // Move the meshes as requested by the mesh generator system
473  std::unique_ptr<MeshBase> input = std::move(_input);
474  std::unique_ptr<MeshBase> extrusion_curve;
476  extrusion_curve = std::move(_extrusion_curve);
477 
478  // We must serialize the curve to know how to extrude across all ranks
479  std::unique_ptr<libMesh::MeshSerializer> serializer;
481  serializer = std::make_unique<libMesh::MeshSerializer>(*extrusion_curve);
482 
483  // If we're using a distributed mesh... then make sure we don't have any remote elements hanging
484  // around
485  if (!input->is_serial())
486  {
487  input->delete_remote_elements();
488  // This should be a no-op, and yet, it's needed for two THM tests
489  mesh->delete_remote_elements();
490  }
491 
492  if (input->n_nodes() != input->max_node_id())
493  input->renumber_nodes_and_elements();
494 
495  if (input->n_nodes() != input->max_node_id())
496  mooseError(
497  "You must allow renumbering, because the extruded mesh should be contiguously numbered. "
498  "Alternatively, you can use a separate mesh generator (MeshRepairGenerator with the "
499  "renumber_contiguously parameter for example) to renumber the nodes contiguously.");
500 
501  unsigned int total_num_layers;
502  unsigned int total_num_elevations;
504  {
505  total_num_layers = std::accumulate(_num_layers.begin(), _num_layers.end(), 0);
506  total_num_elevations = _heights.size();
507  }
508  else
509  {
510  total_num_layers = extrusion_curve->n_elem();
511  total_num_elevations = 1;
512  }
513 
514  dof_id_type orig_elem = input->n_elem();
515  dof_id_type orig_nodes = input->n_nodes();
516 
517 #ifdef LIBMESH_ENABLE_UNIQUE_ID
518  unique_id_type orig_unique_ids = input->parallel_max_unique_id();
519  bool has_poly_midnodes = false;
520 #endif
521 
522  bool has_polygons = false;
523  unsigned int order = 1;
524 
525  BoundaryInfo & boundary_info = mesh->get_boundary_info();
526  const BoundaryInfo & input_boundary_info = input->get_boundary_info();
527 
528  // Determine boundary IDs for the new user provided boundary names
529  std::vector<BoundaryName> new_boundary_names;
531  new_boundary_names.push_back(_bottom_boundary);
532  if (_has_top_boundary)
533  new_boundary_names.push_back(_top_boundary);
534  std::vector<boundary_id_type> new_boundary_ids =
535  MooseMeshUtils::getBoundaryIDs(*input, new_boundary_names, true);
536  const auto user_bottom_boundary_id =
537  _has_bottom_boundary ? new_boundary_ids.front() : libMesh::BoundaryInfo::invalid_id;
538  const auto user_top_boundary_id =
539  _has_top_boundary ? new_boundary_ids.back() : libMesh::BoundaryInfo::invalid_id;
540 
541  // We know a priori how many elements we'll need
542  mesh->reserve_elem(total_num_layers * orig_elem);
543 
544  // Look for higher order elements which introduce an extra layer
545  std::set<ElemType> higher_orders = {EDGE3, EDGE4, TRI6, TRI7, QUAD8, QUAD9};
546  bool extruding_quad_eights = false;
547  std::vector<ElemType> types;
548  MeshTools::elem_types(*input, types);
549  for (const auto elem_type : types)
550  {
551  if (higher_orders.count(elem_type))
552  order = 2;
553  if (elem_type == QUAD8)
554  extruding_quad_eights = true;
555  }
556  mesh->comm().max(order);
557  mesh->comm().max(extruding_quad_eights);
558 
559  // Reserve for the max number possibly needed
560  mesh->reserve_nodes((order * total_num_layers + 1) * orig_nodes);
561 
562  // Container to catch the boundary IDs handed back by the BoundaryInfo object
563  std::vector<boundary_id_type> ids_to_copy;
564 
565  // We need to compute the distance to the axis
566  Real start_radial_extent = 0;
567  Point reference_point;
569  reference_point = *(extrusion_curve->node_ptr(0));
570  else if (!MooseUtils::absoluteFuzzyEqual(_twist_pitch, 0.))
571  // Backwards compatibility: we were twisting along an axis going through the origin
572  reference_point = Point(0, 0, 0);
573  else
574  reference_point = MooseMeshUtils::meshCentroidCalculator(*input);
575 
576  if (_end_radial_extent)
577  {
578  // the center is at the curve if we are extruding along a curve, and the centroid of the mesh
579  // otherwise
580  RealVectorValue reference_direction;
582  reference_direction =
585  : RealVectorValue(
586  (*(extrusion_curve->node_ptr(1)) - *(extrusion_curve->node_ptr(0))).unit());
587  else
588  reference_direction = _direction;
589 
590  // now we measure the initial radial extent
591  start_radial_extent =
592  MooseMeshUtils::computeMaxDistanceToAxis(*input, reference_point, reference_direction);
593  }
594 
595  // Compute the total extrusion distance along the axis
596  Real total_extrusion_distance_at_axis;
598  {
599  if (!extrusion_curve->is_prepared())
600  extrusion_curve->prepare_for_use();
601  total_extrusion_distance_at_axis = MeshTools::volume(*extrusion_curve);
602  }
603  else
604  total_extrusion_distance_at_axis = std::accumulate(_heights.begin(), _heights.end(), 0);
605 
606  // Create translated layers of nodes in the direction of extrusion
607  for (const auto & node : input->node_ptr_range())
608  {
609  unsigned int current_node_layer = 0;
610  Point orig_node_to_previous;
611  Point orig_node_to_current;
612  Real sum_step_sizes = 0.;
613  Real sum_step_sizes_at_axis = 0.;
614  // Initial distance from the node to the extrusion axis
615  Real start_node_radius = (*node - reference_point).norm();
616 
617  // e is the elevation layer ordering
618  for (const auto e : make_range(total_num_elevations))
619  {
620  unsigned int num_layers = libMesh::invalid_uint;
623  {
624  num_layers = extrusion_curve->n_elem();
625  }
626  else
627  {
628  num_layers = _num_layers[e];
629  height = _heights[e];
630  bias = _biases[e];
631  }
632 
633  // In first layer we also add the base nodes, hence the "plus one"
634  unsigned int num_heights_at_elevation = order * num_layers + (e == 0 ? 1 : 0);
635  // Keep track of the plane normal to the extrusion
636  RealVectorValue prev_intersecting_plane_normal_vec = _start_extrusion_direction;
637 
638  // k is the element layer ordering within each elevation layer
639  for (const auto k : make_range(num_heights_at_elevation))
640  {
641  // Compute 'orig_node_to_current', the update vector
642  // For the first layer we don't need to move
643  if (e == 0 && k == 0)
644  orig_node_to_current.zero();
645  else
646  {
647  // Shift the previous position by a certain fraction of 'height' along the extrusion
648  // direction to get the new position.
649 
650  // Compute orig_node_to_current before any transformation
651  Real step_size = 0;
652  Real step_size_at_axis = 0;
654  {
655  // current point in extrusion curve
656  const Node * P_current = extrusion_curve->node_ptr(k);
657  // previous point in extrusion curve
658  const Node * P_prev = extrusion_curve->node_ptr(k - 1);
659 
660  // Quantities for the previous position of the extruded node
661  const auto old_node = orig_node_to_previous + *node;
662  RealVectorValue b_vec = old_node - *P_prev;
663  Real node_distance_to_curve = b_vec.norm();
664 
665  // Node does not lie exactly on the extrusion curve we are following
666  if (node_distance_to_curve > libMesh::TOLERANCE)
667  {
668  // normal vector to the plane to extrude the point into
669  RealVectorValue intersecting_plane_normal_vec;
670 
671  // Select the extrusion direction based on the curve or the user parameters
672  if (k == 1)
673  {
674  const auto P_next = extrusion_curve->node_ptr(k + 1);
675  intersecting_plane_normal_vec =
676  0.5 * (_start_extrusion_direction + (*P_next - *P_current).unit());
677  }
678  else if (k < order * num_layers - 1)
679  {
680  const auto P_next = extrusion_curve->node_ptr(k + 1);
681  // this approximates the derivative at the spline point
682  intersecting_plane_normal_vec = *P_next - *P_prev;
683  }
684  else
685  {
686  intersecting_plane_normal_vec = (_end_extrusion_direction.norm_sq() > 0)
688  : RealVectorValue(*P_current - *P_prev);
689  }
690  intersecting_plane_normal_vec /= intersecting_plane_normal_vec.norm();
691 
692  Point new_node_point;
693  // If the extrusion direction and the previous plane normal are aligned,
694  // we can't define a rotation axis. We simply translate the points
695  if (MooseUtils::absoluteFuzzyEqual(
696  prev_intersecting_plane_normal_vec.cross(intersecting_plane_normal_vec)
697  .norm_sq(),
698  0))
699  new_node_point = old_node + *P_current - *P_prev;
700  // We use a rotation from the previous extrusion plane normal to the current one
701  // We have tried in the past:
702  // - assuming (P_prev, P_current, old_node, current_node) are coplanar
703  // - assuming (P_prev, P_current) and (old_node, current_node) are parallel
704  // Both result in a slight deformation of the shape during extrusion.
705  else
706  {
707  auto axis = prev_intersecting_plane_normal_vec.cross(intersecting_plane_normal_vec);
708  const auto sin_th = axis.norm();
709  axis /= sin_th;
710  Real cos_th = prev_intersecting_plane_normal_vec * intersecting_plane_normal_vec;
711  // Rodrigues formula for rotation
712  const auto new_v = cos_th * b_vec + axis.cross(b_vec) * sin_th +
713  axis * (axis * b_vec) * (1. - cos_th);
714 
715  mooseAssert(MooseUtils::absoluteFuzzyEqual(new_v.norm(), b_vec.norm()),
716  "Radial extent be conserved");
717  new_node_point = *P_current + new_v;
718  }
719 
720  orig_node_to_current = new_node_point - *node;
721 
722  step_size = (orig_node_to_current - orig_node_to_previous).norm();
723  step_size_at_axis = (*P_current - *P_prev).norm();
724  prev_intersecting_plane_normal_vec = intersecting_plane_normal_vec;
725  }
726  // Point is on the axis of the line
727  else
728  {
729  _direction = *P_current - *P_prev;
730  _direction /= _direction.norm();
731  mooseAssert(std::abs(_direction.norm() - 1.0) < libMesh::TOLERANCE,
732  "Norm of direction vector is not 1!");
733 
734  // Calculate step size.
735  // Note: orig_node_to_previous + *node is the vector description of the
736  // previously-created node
737  step_size = ((*P_current - (orig_node_to_previous + *node)) * _direction);
738  step_size_at_axis = step_size;
739  orig_node_to_current = orig_node_to_previous + _direction * step_size;
740  }
741  }
742  // Extruding in a fixed direction (not along a curve)
743  else
744  {
745  // Divide by the order to avoid applying the bias to the nodes within a higher order
746  // element
747  auto layer_index = (k - (e == 0 ? 1 : 0)) / order + 1;
748  step_size = MooseUtils::absoluteFuzzyEqual(bias, 1.0)
749  ? height / (Real)num_layers / (Real)order
750  : height * std::pow(bias, (Real)(layer_index - 1)) * (1.0 - bias) /
751  (1.0 - std::pow(bias, (Real)(num_layers))) / (Real)order;
752  step_size_at_axis = step_size;
753  orig_node_to_current =
754  orig_node_to_previous +
755  _direction * step_size; // update distance from starting node to new node
756  }
757 
758  // Keep track of the cumulative step size as an extrusion axis coordinate
759  sum_step_sizes += step_size;
760  sum_step_sizes_at_axis += step_size_at_axis;
761 
762  // Handle radial expansion. No need to perform expansion at the centerline
763  if (_end_radial_extent && start_node_radius > libMesh::TOLERANCE)
764  {
765  // How far along we are in the extrusion, measured at the axis (=curve when extruding
766  // along a curve)
767  Real tm1 =
768  (sum_step_sizes_at_axis - step_size_at_axis) / total_extrusion_distance_at_axis;
769  Real t = sum_step_sizes_at_axis / total_extrusion_distance_at_axis;
770 
771  // Direction of radial expansion.
772  RealVectorValue node_to_extrusion_axis;
774  node_to_extrusion_axis =
775  *node + orig_node_to_current - *(extrusion_curve->node_ptr(k));
776  // The radial expansion has to be performed in the rotated frame
777  else if (!MooseUtils::absoluteFuzzyEqual(_twist_pitch, 0.))
778  node_to_extrusion_axis =
779  *node + orig_node_to_current - (reference_point + _direction * sum_step_sizes);
780  // when extruding in a straight line with no twisting, we can use the original radial
781  // direction
782  else
783  node_to_extrusion_axis = *node - reference_point;
784 
785  // Fraction of the total starting radius the node lives in
786  const auto radius_scaling = start_node_radius / start_radial_extent;
787  // Calculate weighting for expansion
788  const auto radial_ratio_m1 = AdvancedExtruderGenerator::radialExpansionRatio(tm1);
789  const auto radial_ratio = AdvancedExtruderGenerator::radialExpansionRatio(t);
790 
791  // Compute change in step
792  orig_node_to_current += (start_radial_extent * (radial_ratio_m1 - radial_ratio) +
793  (radial_ratio - radial_ratio_m1) * _end_radial_extent) *
794  radius_scaling * node_to_extrusion_axis.unit();
795  }
796 
797  // Handle helicoidal extrusion
798  if (!MooseUtils::absoluteFuzzyEqual(_twist_pitch, 0.))
799  {
800  // twist 1 should be 'normal' to the extruded shape
801  RealVectorValue twist1 = _direction.cross(*node);
802  // This happens for any node on the helicoidal extrusion axis
803  if (!MooseUtils::absoluteFuzzyEqual(twist1.norm(), .0))
804  twist1 /= twist1.norm();
805  const RealVectorValue twist2 = twist1.cross(_direction);
806 
807  auto twist =
808  (cos(2. * libMesh::pi * current_node_layer * step_size / _twist_pitch) -
809  cos(2. * libMesh::pi * (current_node_layer - 1) * step_size / _twist_pitch)) *
810  twist2 +
811  (sin(2. * libMesh::pi * current_node_layer * step_size / _twist_pitch) -
812  sin(2. * libMesh::pi * (current_node_layer - 1) * step_size / _twist_pitch)) *
813  twist1;
814 
815  // If we normalize twist, we must multiply by 2 * sin(libMesh::pi * step_size /
816  // _twist_pitch) if (!MooseUtils::absoluteFuzzyEqual(twist.norm(), .0))
817  // twist /= twist.norm();
818 
819  // Get a point on the extrusion axis (around which we currently twist the geometry)
820  // at the local elevation height to be able to compute the distance to the axis
821  Point extrusion_axis_at_elevation;
822  Point prev_extrusion_axis_at_elevation;
824  {
825  extrusion_axis_at_elevation = *extrusion_curve->node_ptr(k);
826  prev_extrusion_axis_at_elevation = *extrusion_curve->node_ptr(k - 1);
827  }
828  else
829  {
830  // We can't use old and current step updates because they have been "twisted"
831  extrusion_axis_at_elevation = reference_point + _direction * sum_step_sizes;
832  prev_extrusion_axis_at_elevation =
833  reference_point + _direction * (sum_step_sizes - step_size);
834  }
835 
836  // Scale with how far the node is from the extrusion axis, before twisting
837  twist *= (*node + orig_node_to_current - extrusion_axis_at_elevation).norm();
838 
839  // No need to twist or expand the point on the axis
840  if (!MooseUtils::absoluteFuzzyEqual(twist1.norm(), .0))
841  orig_node_to_current += twist;
842  }
843  }
844 
845  // Add the new node to the mesh
846  Node * new_node = mesh->add_point(*node + orig_node_to_current,
847  node->id() + (current_node_layer * orig_nodes),
848  node->processor_id());
849 
850 #ifdef LIBMESH_ENABLE_UNIQUE_ID
851  // Let's give the base of the extruded mesh the same
852  // unique_ids as the source mesh, in case anyone finds that
853  // a useful map to preserve.
854  const unique_id_type uid = (current_node_layer == 0)
855  ? node->unique_id()
856  : orig_unique_ids +
857  (current_node_layer - 1) * (orig_elem + orig_nodes) +
858  node->id();
859  new_node->set_unique_id(uid);
860 #endif
861 
862  // Add the new node to the extruded boundaries
863  input_boundary_info.boundary_ids(node, ids_to_copy);
864  if (_boundary_swap_pairs.empty())
865  boundary_info.add_node(new_node, ids_to_copy);
866  else
867  for (const auto & id_to_copy : ids_to_copy)
868  {
869  boundary_info.add_node(new_node,
870  _boundary_swap_pairs[e].count(id_to_copy)
871  ? _boundary_swap_pairs[e][id_to_copy]
872  : id_to_copy);
873  }
874 
875  orig_node_to_previous = orig_node_to_current;
876  current_node_layer++;
877  }
878  }
879  }
880 
881  const auto & side_ids = input_boundary_info.get_side_boundary_ids();
882 
883  boundary_id_type next_side_id =
884  side_ids.empty() ? 0 : cast_int<boundary_id_type>(*side_ids.rbegin() + 1);
885 
886  // side_ids may not include ids from remote elements, in which case
887  // some processors may have underestimated the next_side_id; let's
888  // fix that.
889  input->comm().max(next_side_id);
890 
891  // Map to keep track of polygon sides of polygonal prisms
892  std::map<std::array<unsigned int, 3>, std::shared_ptr<libMesh::Polygon>> poly_extruded_sides;
893 
894  // Build the extruded elements
895  for (const auto & elem : input->element_ptr_range())
896  {
897  const ElemType etype = elem->type();
898 
899  // build_extrusion currently only works on coarse meshes
900  libmesh_assert(!elem->parent());
901 
902  unsigned int current_layer = 0;
903 
904  for (unsigned int e = 0; e != total_num_elevations; e++)
905  {
906  auto num_layers = !_extrude_along_curve ? _num_layers[e] : extrusion_curve->n_nodes() - 1;
907 
908  for (unsigned int k = 0; k != num_layers; ++k)
909  {
910  std::unique_ptr<Elem> new_elem;
911  bool is_flipped(false);
912  switch (etype)
913  {
914  case EDGE2:
915  {
916  new_elem = std::make_unique<Quad4>();
917  new_elem->set_node(
918  0, mesh->node_ptr(elem->node_ptr(0)->id() + (current_layer * orig_nodes)));
919  new_elem->set_node(
920  1, mesh->node_ptr(elem->node_ptr(1)->id() + (current_layer * orig_nodes)));
921  new_elem->set_node(
922  2, mesh->node_ptr(elem->node_ptr(1)->id() + ((current_layer + 1) * orig_nodes)));
923  new_elem->set_node(
924  3, mesh->node_ptr(elem->node_ptr(0)->id() + ((current_layer + 1) * orig_nodes)));
925 
926  if (elem->neighbor_ptr(0) == remote_elem)
927  new_elem->set_neighbor(3, const_cast<RemoteElem *>(remote_elem));
928  if (elem->neighbor_ptr(1) == remote_elem)
929  new_elem->set_neighbor(1, const_cast<RemoteElem *>(remote_elem));
930 
931  break;
932  }
933  case EDGE3:
934  {
935  new_elem = std::make_unique<Quad9>();
936  new_elem->set_node(
937  0, mesh->node_ptr(elem->node_ptr(0)->id() + (2 * current_layer * orig_nodes)));
938  new_elem->set_node(
939  1, mesh->node_ptr(elem->node_ptr(1)->id() + (2 * current_layer * orig_nodes)));
940  new_elem->set_node(
941  2,
942  mesh->node_ptr(elem->node_ptr(1)->id() + ((2 * current_layer + 2) * orig_nodes)));
943  new_elem->set_node(
944  3,
945  mesh->node_ptr(elem->node_ptr(0)->id() + ((2 * current_layer + 2) * orig_nodes)));
946  new_elem->set_node(
947  4, mesh->node_ptr(elem->node_ptr(2)->id() + (2 * current_layer * orig_nodes)));
948  new_elem->set_node(
949  5,
950  mesh->node_ptr(elem->node_ptr(1)->id() + ((2 * current_layer + 1) * orig_nodes)));
951  new_elem->set_node(
952  6,
953  mesh->node_ptr(elem->node_ptr(2)->id() + ((2 * current_layer + 2) * orig_nodes)));
954  new_elem->set_node(
955  7,
956  mesh->node_ptr(elem->node_ptr(0)->id() + ((2 * current_layer + 1) * orig_nodes)));
957  new_elem->set_node(
958  8,
959  mesh->node_ptr(elem->node_ptr(2)->id() + ((2 * current_layer + 1) * orig_nodes)));
960 
961  if (elem->neighbor_ptr(0) == remote_elem)
962  new_elem->set_neighbor(3, const_cast<RemoteElem *>(remote_elem));
963  if (elem->neighbor_ptr(1) == remote_elem)
964  new_elem->set_neighbor(1, const_cast<RemoteElem *>(remote_elem));
965 
966  break;
967  }
968  case TRI3:
969  {
970  new_elem = std::make_unique<Prism6>();
971  new_elem->set_node(
972  0, mesh->node_ptr(elem->node_ptr(0)->id() + (current_layer * orig_nodes)));
973  new_elem->set_node(
974  1, mesh->node_ptr(elem->node_ptr(1)->id() + (current_layer * orig_nodes)));
975  new_elem->set_node(
976  2, mesh->node_ptr(elem->node_ptr(2)->id() + (current_layer * orig_nodes)));
977  new_elem->set_node(
978  3, mesh->node_ptr(elem->node_ptr(0)->id() + ((current_layer + 1) * orig_nodes)));
979  new_elem->set_node(
980  4, mesh->node_ptr(elem->node_ptr(1)->id() + ((current_layer + 1) * orig_nodes)));
981  new_elem->set_node(
982  5, mesh->node_ptr(elem->node_ptr(2)->id() + ((current_layer + 1) * orig_nodes)));
983 
984  if (elem->neighbor_ptr(0) == remote_elem)
985  new_elem->set_neighbor(1, const_cast<RemoteElem *>(remote_elem));
986  if (elem->neighbor_ptr(1) == remote_elem)
987  new_elem->set_neighbor(2, const_cast<RemoteElem *>(remote_elem));
988  if (elem->neighbor_ptr(2) == remote_elem)
989  new_elem->set_neighbor(3, const_cast<RemoteElem *>(remote_elem));
990 
991  if (new_elem->volume() < 0.0)
992  {
993  MooseMeshUtils::swapNodesInElem(*new_elem, 0, 3);
994  MooseMeshUtils::swapNodesInElem(*new_elem, 1, 4);
995  MooseMeshUtils::swapNodesInElem(*new_elem, 2, 5);
996  is_flipped = true;
997  }
998 
999  break;
1000  }
1001  case TRI6:
1002  {
1003  new_elem = std::make_unique<Prism18>();
1004  new_elem->set_node(
1005  0, mesh->node_ptr(elem->node_ptr(0)->id() + (2 * current_layer * orig_nodes)));
1006  new_elem->set_node(
1007  1, mesh->node_ptr(elem->node_ptr(1)->id() + (2 * current_layer * orig_nodes)));
1008  new_elem->set_node(
1009  2, mesh->node_ptr(elem->node_ptr(2)->id() + (2 * current_layer * orig_nodes)));
1010  new_elem->set_node(
1011  3,
1012  mesh->node_ptr(elem->node_ptr(0)->id() + ((2 * current_layer + 2) * orig_nodes)));
1013  new_elem->set_node(
1014  4,
1015  mesh->node_ptr(elem->node_ptr(1)->id() + ((2 * current_layer + 2) * orig_nodes)));
1016  new_elem->set_node(
1017  5,
1018  mesh->node_ptr(elem->node_ptr(2)->id() + ((2 * current_layer + 2) * orig_nodes)));
1019  new_elem->set_node(
1020  6, mesh->node_ptr(elem->node_ptr(3)->id() + (2 * current_layer * orig_nodes)));
1021  new_elem->set_node(
1022  7, mesh->node_ptr(elem->node_ptr(4)->id() + (2 * current_layer * orig_nodes)));
1023  new_elem->set_node(
1024  8, mesh->node_ptr(elem->node_ptr(5)->id() + (2 * current_layer * orig_nodes)));
1025  new_elem->set_node(
1026  9,
1027  mesh->node_ptr(elem->node_ptr(0)->id() + ((2 * current_layer + 1) * orig_nodes)));
1028  new_elem->set_node(
1029  10,
1030  mesh->node_ptr(elem->node_ptr(1)->id() + ((2 * current_layer + 1) * orig_nodes)));
1031  new_elem->set_node(
1032  11,
1033  mesh->node_ptr(elem->node_ptr(2)->id() + ((2 * current_layer + 1) * orig_nodes)));
1034  new_elem->set_node(
1035  12,
1036  mesh->node_ptr(elem->node_ptr(3)->id() + ((2 * current_layer + 2) * orig_nodes)));
1037  new_elem->set_node(
1038  13,
1039  mesh->node_ptr(elem->node_ptr(4)->id() + ((2 * current_layer + 2) * orig_nodes)));
1040  new_elem->set_node(
1041  14,
1042  mesh->node_ptr(elem->node_ptr(5)->id() + ((2 * current_layer + 2) * orig_nodes)));
1043  new_elem->set_node(
1044  15,
1045  mesh->node_ptr(elem->node_ptr(3)->id() + ((2 * current_layer + 1) * orig_nodes)));
1046  new_elem->set_node(
1047  16,
1048  mesh->node_ptr(elem->node_ptr(4)->id() + ((2 * current_layer + 1) * orig_nodes)));
1049  new_elem->set_node(
1050  17,
1051  mesh->node_ptr(elem->node_ptr(5)->id() + ((2 * current_layer + 1) * orig_nodes)));
1052 
1053  if (elem->neighbor_ptr(0) == remote_elem)
1054  new_elem->set_neighbor(1, const_cast<RemoteElem *>(remote_elem));
1055  if (elem->neighbor_ptr(1) == remote_elem)
1056  new_elem->set_neighbor(2, const_cast<RemoteElem *>(remote_elem));
1057  if (elem->neighbor_ptr(2) == remote_elem)
1058  new_elem->set_neighbor(3, const_cast<RemoteElem *>(remote_elem));
1059 
1060  if (new_elem->volume() < 0.0)
1061  {
1062  MooseMeshUtils::swapNodesInElem(*new_elem, 0, 3);
1063  MooseMeshUtils::swapNodesInElem(*new_elem, 1, 4);
1064  MooseMeshUtils::swapNodesInElem(*new_elem, 2, 5);
1065  MooseMeshUtils::swapNodesInElem(*new_elem, 6, 12);
1066  MooseMeshUtils::swapNodesInElem(*new_elem, 7, 13);
1067  MooseMeshUtils::swapNodesInElem(*new_elem, 8, 14);
1068  is_flipped = true;
1069  }
1070 
1071  break;
1072  }
1073  case TRI7:
1074  {
1075  new_elem = std::make_unique<Prism21>();
1076  new_elem->set_node(
1077  0, mesh->node_ptr(elem->node_ptr(0)->id() + (2 * current_layer * orig_nodes)));
1078  new_elem->set_node(
1079  1, mesh->node_ptr(elem->node_ptr(1)->id() + (2 * current_layer * orig_nodes)));
1080  new_elem->set_node(
1081  2, mesh->node_ptr(elem->node_ptr(2)->id() + (2 * current_layer * orig_nodes)));
1082  new_elem->set_node(
1083  3,
1084  mesh->node_ptr(elem->node_ptr(0)->id() + ((2 * current_layer + 2) * orig_nodes)));
1085  new_elem->set_node(
1086  4,
1087  mesh->node_ptr(elem->node_ptr(1)->id() + ((2 * current_layer + 2) * orig_nodes)));
1088  new_elem->set_node(
1089  5,
1090  mesh->node_ptr(elem->node_ptr(2)->id() + ((2 * current_layer + 2) * orig_nodes)));
1091  new_elem->set_node(
1092  6, mesh->node_ptr(elem->node_ptr(3)->id() + (2 * current_layer * orig_nodes)));
1093  new_elem->set_node(
1094  7, mesh->node_ptr(elem->node_ptr(4)->id() + (2 * current_layer * orig_nodes)));
1095  new_elem->set_node(
1096  8, mesh->node_ptr(elem->node_ptr(5)->id() + (2 * current_layer * orig_nodes)));
1097  new_elem->set_node(
1098  9,
1099  mesh->node_ptr(elem->node_ptr(0)->id() + ((2 * current_layer + 1) * orig_nodes)));
1100  new_elem->set_node(
1101  10,
1102  mesh->node_ptr(elem->node_ptr(1)->id() + ((2 * current_layer + 1) * orig_nodes)));
1103  new_elem->set_node(
1104  11,
1105  mesh->node_ptr(elem->node_ptr(2)->id() + ((2 * current_layer + 1) * orig_nodes)));
1106  new_elem->set_node(
1107  12,
1108  mesh->node_ptr(elem->node_ptr(3)->id() + ((2 * current_layer + 2) * orig_nodes)));
1109  new_elem->set_node(
1110  13,
1111  mesh->node_ptr(elem->node_ptr(4)->id() + ((2 * current_layer + 2) * orig_nodes)));
1112  new_elem->set_node(
1113  14,
1114  mesh->node_ptr(elem->node_ptr(5)->id() + ((2 * current_layer + 2) * orig_nodes)));
1115  new_elem->set_node(
1116  15,
1117  mesh->node_ptr(elem->node_ptr(3)->id() + ((2 * current_layer + 1) * orig_nodes)));
1118  new_elem->set_node(
1119  16,
1120  mesh->node_ptr(elem->node_ptr(4)->id() + ((2 * current_layer + 1) * orig_nodes)));
1121  new_elem->set_node(
1122  17,
1123  mesh->node_ptr(elem->node_ptr(5)->id() + ((2 * current_layer + 1) * orig_nodes)));
1124  new_elem->set_node(
1125  18, mesh->node_ptr(elem->node_ptr(6)->id() + (2 * current_layer * orig_nodes)));
1126  new_elem->set_node(
1127  19,
1128  mesh->node_ptr(elem->node_ptr(6)->id() + ((2 * current_layer + 2) * orig_nodes)));
1129  new_elem->set_node(
1130  20,
1131  mesh->node_ptr(elem->node_ptr(6)->id() + ((2 * current_layer + 1) * orig_nodes)));
1132 
1133  if (elem->neighbor_ptr(0) == remote_elem)
1134  new_elem->set_neighbor(1, const_cast<RemoteElem *>(remote_elem));
1135  if (elem->neighbor_ptr(1) == remote_elem)
1136  new_elem->set_neighbor(2, const_cast<RemoteElem *>(remote_elem));
1137  if (elem->neighbor_ptr(2) == remote_elem)
1138  new_elem->set_neighbor(3, const_cast<RemoteElem *>(remote_elem));
1139 
1140  if (new_elem->volume() < 0.0)
1141  {
1142  MooseMeshUtils::swapNodesInElem(*new_elem, 0, 3);
1143  MooseMeshUtils::swapNodesInElem(*new_elem, 1, 4);
1144  MooseMeshUtils::swapNodesInElem(*new_elem, 2, 5);
1145  MooseMeshUtils::swapNodesInElem(*new_elem, 6, 12);
1146  MooseMeshUtils::swapNodesInElem(*new_elem, 7, 13);
1147  MooseMeshUtils::swapNodesInElem(*new_elem, 8, 14);
1148  MooseMeshUtils::swapNodesInElem(*new_elem, 18, 19);
1149  is_flipped = true;
1150  }
1151 
1152  break;
1153  }
1154  case QUAD4:
1155  {
1156  new_elem = std::make_unique<Hex8>();
1157  new_elem->set_node(
1158  0, mesh->node_ptr(elem->node_ptr(0)->id() + (current_layer * orig_nodes)));
1159  new_elem->set_node(
1160  1, mesh->node_ptr(elem->node_ptr(1)->id() + (current_layer * orig_nodes)));
1161  new_elem->set_node(
1162  2, mesh->node_ptr(elem->node_ptr(2)->id() + (current_layer * orig_nodes)));
1163  new_elem->set_node(
1164  3, mesh->node_ptr(elem->node_ptr(3)->id() + (current_layer * orig_nodes)));
1165  new_elem->set_node(
1166  4, mesh->node_ptr(elem->node_ptr(0)->id() + ((current_layer + 1) * orig_nodes)));
1167  new_elem->set_node(
1168  5, mesh->node_ptr(elem->node_ptr(1)->id() + ((current_layer + 1) * orig_nodes)));
1169  new_elem->set_node(
1170  6, mesh->node_ptr(elem->node_ptr(2)->id() + ((current_layer + 1) * orig_nodes)));
1171  new_elem->set_node(
1172  7, mesh->node_ptr(elem->node_ptr(3)->id() + ((current_layer + 1) * orig_nodes)));
1173 
1174  if (elem->neighbor_ptr(0) == remote_elem)
1175  new_elem->set_neighbor(1, const_cast<RemoteElem *>(remote_elem));
1176  if (elem->neighbor_ptr(1) == remote_elem)
1177  new_elem->set_neighbor(2, const_cast<RemoteElem *>(remote_elem));
1178  if (elem->neighbor_ptr(2) == remote_elem)
1179  new_elem->set_neighbor(3, const_cast<RemoteElem *>(remote_elem));
1180  if (elem->neighbor_ptr(3) == remote_elem)
1181  new_elem->set_neighbor(4, const_cast<RemoteElem *>(remote_elem));
1182 
1183  if (new_elem->volume() < 0.0)
1184  {
1185  MooseMeshUtils::swapNodesInElem(*new_elem, 0, 4);
1186  MooseMeshUtils::swapNodesInElem(*new_elem, 1, 5);
1187  MooseMeshUtils::swapNodesInElem(*new_elem, 2, 6);
1188  MooseMeshUtils::swapNodesInElem(*new_elem, 3, 7);
1189  is_flipped = true;
1190  }
1191 
1192  break;
1193  }
1194  case QUAD8:
1195  {
1196  new_elem = std::make_unique<Hex20>();
1197  new_elem->set_node(
1198  0, mesh->node_ptr(elem->node_ptr(0)->id() + (2 * current_layer * orig_nodes)));
1199  new_elem->set_node(
1200  1, mesh->node_ptr(elem->node_ptr(1)->id() + (2 * current_layer * orig_nodes)));
1201  new_elem->set_node(
1202  2, mesh->node_ptr(elem->node_ptr(2)->id() + (2 * current_layer * orig_nodes)));
1203  new_elem->set_node(
1204  3, mesh->node_ptr(elem->node_ptr(3)->id() + (2 * current_layer * orig_nodes)));
1205  new_elem->set_node(
1206  4,
1207  mesh->node_ptr(elem->node_ptr(0)->id() + ((2 * current_layer + 2) * orig_nodes)));
1208  new_elem->set_node(
1209  5,
1210  mesh->node_ptr(elem->node_ptr(1)->id() + ((2 * current_layer + 2) * orig_nodes)));
1211  new_elem->set_node(
1212  6,
1213  mesh->node_ptr(elem->node_ptr(2)->id() + ((2 * current_layer + 2) * orig_nodes)));
1214  new_elem->set_node(
1215  7,
1216  mesh->node_ptr(elem->node_ptr(3)->id() + ((2 * current_layer + 2) * orig_nodes)));
1217  new_elem->set_node(
1218  8, mesh->node_ptr(elem->node_ptr(4)->id() + (2 * current_layer * orig_nodes)));
1219  new_elem->set_node(
1220  9, mesh->node_ptr(elem->node_ptr(5)->id() + (2 * current_layer * orig_nodes)));
1221  new_elem->set_node(
1222  10, mesh->node_ptr(elem->node_ptr(6)->id() + (2 * current_layer * orig_nodes)));
1223  new_elem->set_node(
1224  11, mesh->node_ptr(elem->node_ptr(7)->id() + (2 * current_layer * orig_nodes)));
1225  new_elem->set_node(
1226  12,
1227  mesh->node_ptr(elem->node_ptr(0)->id() + ((2 * current_layer + 1) * orig_nodes)));
1228  new_elem->set_node(
1229  13,
1230  mesh->node_ptr(elem->node_ptr(1)->id() + ((2 * current_layer + 1) * orig_nodes)));
1231  new_elem->set_node(
1232  14,
1233  mesh->node_ptr(elem->node_ptr(2)->id() + ((2 * current_layer + 1) * orig_nodes)));
1234  new_elem->set_node(
1235  15,
1236  mesh->node_ptr(elem->node_ptr(3)->id() + ((2 * current_layer + 1) * orig_nodes)));
1237  new_elem->set_node(
1238  16,
1239  mesh->node_ptr(elem->node_ptr(4)->id() + ((2 * current_layer + 2) * orig_nodes)));
1240  new_elem->set_node(
1241  17,
1242  mesh->node_ptr(elem->node_ptr(5)->id() + ((2 * current_layer + 2) * orig_nodes)));
1243  new_elem->set_node(
1244  18,
1245  mesh->node_ptr(elem->node_ptr(6)->id() + ((2 * current_layer + 2) * orig_nodes)));
1246  new_elem->set_node(
1247  19,
1248  mesh->node_ptr(elem->node_ptr(7)->id() + ((2 * current_layer + 2) * orig_nodes)));
1249 
1250  if (elem->neighbor_ptr(0) == remote_elem)
1251  new_elem->set_neighbor(1, const_cast<RemoteElem *>(remote_elem));
1252  if (elem->neighbor_ptr(1) == remote_elem)
1253  new_elem->set_neighbor(2, const_cast<RemoteElem *>(remote_elem));
1254  if (elem->neighbor_ptr(2) == remote_elem)
1255  new_elem->set_neighbor(3, const_cast<RemoteElem *>(remote_elem));
1256  if (elem->neighbor_ptr(3) == remote_elem)
1257  new_elem->set_neighbor(4, const_cast<RemoteElem *>(remote_elem));
1258 
1259  if (new_elem->volume() < 0.0)
1260  {
1261  MooseMeshUtils::swapNodesInElem(*new_elem, 0, 4);
1262  MooseMeshUtils::swapNodesInElem(*new_elem, 1, 5);
1263  MooseMeshUtils::swapNodesInElem(*new_elem, 2, 6);
1264  MooseMeshUtils::swapNodesInElem(*new_elem, 3, 7);
1265  MooseMeshUtils::swapNodesInElem(*new_elem, 8, 16);
1266  MooseMeshUtils::swapNodesInElem(*new_elem, 9, 17);
1267  MooseMeshUtils::swapNodesInElem(*new_elem, 10, 18);
1268  MooseMeshUtils::swapNodesInElem(*new_elem, 11, 19);
1269  is_flipped = true;
1270  }
1271 
1272  break;
1273  }
1274  case QUAD9:
1275  {
1276  new_elem = std::make_unique<Hex27>();
1277  new_elem->set_node(
1278  0, mesh->node_ptr(elem->node_ptr(0)->id() + (2 * current_layer * orig_nodes)));
1279  new_elem->set_node(
1280  1, mesh->node_ptr(elem->node_ptr(1)->id() + (2 * current_layer * orig_nodes)));
1281  new_elem->set_node(
1282  2, mesh->node_ptr(elem->node_ptr(2)->id() + (2 * current_layer * orig_nodes)));
1283  new_elem->set_node(
1284  3, mesh->node_ptr(elem->node_ptr(3)->id() + (2 * current_layer * orig_nodes)));
1285  new_elem->set_node(
1286  4,
1287  mesh->node_ptr(elem->node_ptr(0)->id() + ((2 * current_layer + 2) * orig_nodes)));
1288  new_elem->set_node(
1289  5,
1290  mesh->node_ptr(elem->node_ptr(1)->id() + ((2 * current_layer + 2) * orig_nodes)));
1291  new_elem->set_node(
1292  6,
1293  mesh->node_ptr(elem->node_ptr(2)->id() + ((2 * current_layer + 2) * orig_nodes)));
1294  new_elem->set_node(
1295  7,
1296  mesh->node_ptr(elem->node_ptr(3)->id() + ((2 * current_layer + 2) * orig_nodes)));
1297  new_elem->set_node(
1298  8, mesh->node_ptr(elem->node_ptr(4)->id() + (2 * current_layer * orig_nodes)));
1299  new_elem->set_node(
1300  9, mesh->node_ptr(elem->node_ptr(5)->id() + (2 * current_layer * orig_nodes)));
1301  new_elem->set_node(
1302  10, mesh->node_ptr(elem->node_ptr(6)->id() + (2 * current_layer * orig_nodes)));
1303  new_elem->set_node(
1304  11, mesh->node_ptr(elem->node_ptr(7)->id() + (2 * current_layer * orig_nodes)));
1305  new_elem->set_node(
1306  12,
1307  mesh->node_ptr(elem->node_ptr(0)->id() + ((2 * current_layer + 1) * orig_nodes)));
1308  new_elem->set_node(
1309  13,
1310  mesh->node_ptr(elem->node_ptr(1)->id() + ((2 * current_layer + 1) * orig_nodes)));
1311  new_elem->set_node(
1312  14,
1313  mesh->node_ptr(elem->node_ptr(2)->id() + ((2 * current_layer + 1) * orig_nodes)));
1314  new_elem->set_node(
1315  15,
1316  mesh->node_ptr(elem->node_ptr(3)->id() + ((2 * current_layer + 1) * orig_nodes)));
1317  new_elem->set_node(
1318  16,
1319  mesh->node_ptr(elem->node_ptr(4)->id() + ((2 * current_layer + 2) * orig_nodes)));
1320  new_elem->set_node(
1321  17,
1322  mesh->node_ptr(elem->node_ptr(5)->id() + ((2 * current_layer + 2) * orig_nodes)));
1323  new_elem->set_node(
1324  18,
1325  mesh->node_ptr(elem->node_ptr(6)->id() + ((2 * current_layer + 2) * orig_nodes)));
1326  new_elem->set_node(
1327  19,
1328  mesh->node_ptr(elem->node_ptr(7)->id() + ((2 * current_layer + 2) * orig_nodes)));
1329  new_elem->set_node(
1330  20, mesh->node_ptr(elem->node_ptr(8)->id() + (2 * current_layer * orig_nodes)));
1331  new_elem->set_node(
1332  21,
1333  mesh->node_ptr(elem->node_ptr(4)->id() + ((2 * current_layer + 1) * orig_nodes)));
1334  new_elem->set_node(
1335  22,
1336  mesh->node_ptr(elem->node_ptr(5)->id() + ((2 * current_layer + 1) * orig_nodes)));
1337  new_elem->set_node(
1338  23,
1339  mesh->node_ptr(elem->node_ptr(6)->id() + ((2 * current_layer + 1) * orig_nodes)));
1340  new_elem->set_node(
1341  24,
1342  mesh->node_ptr(elem->node_ptr(7)->id() + ((2 * current_layer + 1) * orig_nodes)));
1343  new_elem->set_node(
1344  25,
1345  mesh->node_ptr(elem->node_ptr(8)->id() + ((2 * current_layer + 2) * orig_nodes)));
1346  new_elem->set_node(
1347  26,
1348  mesh->node_ptr(elem->node_ptr(8)->id() + ((2 * current_layer + 1) * orig_nodes)));
1349 
1350  if (elem->neighbor_ptr(0) == remote_elem)
1351  new_elem->set_neighbor(1, const_cast<RemoteElem *>(remote_elem));
1352  if (elem->neighbor_ptr(1) == remote_elem)
1353  new_elem->set_neighbor(2, const_cast<RemoteElem *>(remote_elem));
1354  if (elem->neighbor_ptr(2) == remote_elem)
1355  new_elem->set_neighbor(3, const_cast<RemoteElem *>(remote_elem));
1356  if (elem->neighbor_ptr(3) == remote_elem)
1357  new_elem->set_neighbor(4, const_cast<RemoteElem *>(remote_elem));
1358 
1359  if (new_elem->volume() < 0.0)
1360  {
1361  MooseMeshUtils::swapNodesInElem(*new_elem, 0, 4);
1362  MooseMeshUtils::swapNodesInElem(*new_elem, 1, 5);
1363  MooseMeshUtils::swapNodesInElem(*new_elem, 2, 6);
1364  MooseMeshUtils::swapNodesInElem(*new_elem, 3, 7);
1365  MooseMeshUtils::swapNodesInElem(*new_elem, 8, 16);
1366  MooseMeshUtils::swapNodesInElem(*new_elem, 9, 17);
1367  MooseMeshUtils::swapNodesInElem(*new_elem, 10, 18);
1368  MooseMeshUtils::swapNodesInElem(*new_elem, 11, 19);
1369  MooseMeshUtils::swapNodesInElem(*new_elem, 20, 25);
1370  is_flipped = true;
1371  }
1372 
1373  break;
1374  }
1375  case libMesh::C0POLYGON:
1376  {
1377  has_polygons = true;
1378  const auto num_sides = elem->n_sides();
1379  std::vector<std::shared_ptr<libMesh::Polygon>> sides;
1380  sides.reserve(2 + num_sides);
1381  if (2 * elem->n_nodes() > libMesh::C0Polygon::max_n_nodes)
1382  mooseError("Too many nodes in polygons to extrude it. Max number of the prism "
1383  "polyhedral nodes after extrusion: " +
1384  std::to_string(libMesh::C0Polygon::max_n_nodes));
1385  // Make a copy of the original element to use as a side
1386  auto new_ptr = std::make_shared<libMesh::C0Polygon>(num_sides);
1387  for (const auto node_i : make_range(elem->n_nodes()))
1388  {
1389  // This one will be oriented outwards, it should be OK though, polyhedron code does
1390  // not mind
1391  new_ptr->set_node(
1392  node_i,
1393  mesh->node_ptr(elem->node_ptr(node_i)->id() + (current_layer * orig_nodes)));
1394  }
1395  sides.push_back(new_ptr);
1396  // Form the next horizontal side
1397  auto translated_side = std::make_shared<libMesh::C0Polygon>(num_sides);
1398  for (const auto node_i : make_range(elem->n_nodes()))
1399  translated_side->set_node(node_i,
1400  mesh->node_ptr(elem->node_ptr(node_i)->id() +
1401  ((current_layer + 1) * orig_nodes)));
1402  sides.push_back(translated_side);
1403 
1404  // Form the vertical sides
1405  for (const auto side_i : make_range(num_sides))
1406  {
1407  // If the side already exists, use that
1408  std::array<unsigned int, 3> side_key = {
1409  current_layer,
1410  static_cast<unsigned int>(
1411  std::min(elem->node_ptr(side_i)->id(),
1412  elem->node_ptr((side_i + 1) % num_sides)->id())),
1413  static_cast<unsigned int>(
1414  std::max(elem->node_ptr(side_i)->id(),
1415  elem->node_ptr((side_i + 1) % num_sides)->id()))};
1416  if (poly_extruded_sides.count(side_key))
1417  {
1418  sides.push_back(poly_extruded_sides[side_key]);
1419  continue;
1420  }
1421 
1422  // They are all quads, but constructor expects polygons
1423  auto vert_side = std::make_shared<libMesh::C0Polygon>(4);
1424  vert_side->set_node(
1425  0, mesh->node_ptr(elem->node_ptr(side_i)->id() + (current_layer * orig_nodes)));
1426  vert_side->set_node(1,
1427  mesh->node_ptr(elem->node_ptr((side_i + 1) % num_sides)->id() +
1428  (current_layer * orig_nodes)));
1429  vert_side->set_node(2,
1430  mesh->node_ptr(elem->node_ptr((side_i + 1) % num_sides)->id() +
1431  ((current_layer + 1) * orig_nodes)));
1432  vert_side->set_node(3,
1433  mesh->node_ptr(elem->node_ptr(side_i)->id() +
1434  ((current_layer + 1) * orig_nodes)));
1435  sides.push_back(vert_side);
1436 
1437  poly_extruded_sides.insert(std::make_pair(side_key, vert_side));
1438  }
1439  mooseAssert(sides.size() == 2 + num_sides, "Unexpected size of side vector");
1440 
1441  // Create the element from the sides, let libMesh figure out the orientation
1442  std::unique_ptr<libMesh::Node> mid_elem_node;
1443  new_elem = std::make_unique<libMesh::C0Polyhedron>(sides, mid_elem_node);
1444  if (mid_elem_node)
1445  {
1446 #ifdef LIBMESH_ENABLE_UNIQUE_ID
1447  // Number it at the end for convenience
1448  // use the element ID to be able to set in parallel
1449  unsigned int total_new_node_layers = total_num_layers * order;
1450  unsigned int last_uid = orig_unique_ids + (total_new_node_layers - 1) * orig_elem +
1451  total_new_node_layers * orig_nodes + elem->unique_id();
1452  mid_elem_node->set_unique_id(last_uid);
1453  has_poly_midnodes = true;
1454 #endif
1455  mesh->add_node(std::move(mid_elem_node));
1456  }
1457 
1458  break;
1459  }
1460  default:
1461  mooseError("Extrusion is not implemented for element type " + Moose::stringify(etype));
1462  }
1463 
1464  new_elem->set_id(elem->id() + (current_layer * orig_elem));
1465  new_elem->processor_id() = elem->processor_id();
1466 
1467 #ifdef LIBMESH_ENABLE_UNIQUE_ID
1468  // Let's give the base of the extruded mesh the same
1469  // unique_ids as the source mesh, in case anyone finds that
1470  // a useful map to preserve.
1471  const unique_id_type uid = (current_layer == 0)
1472  ? elem->unique_id()
1473  : orig_unique_ids +
1474  (current_layer - 1) * (orig_elem + orig_nodes) +
1475  orig_nodes + elem->id();
1476 
1477  new_elem->set_unique_id(uid);
1478 #endif
1479 
1480  // maintain the subdomain_id
1481  new_elem->subdomain_id() = elem->subdomain_id();
1482 
1483  // define upward boundaries
1484  if (k == num_layers - 1)
1485  {
1486  // Identify the side index of the new element that is part of the upward boundary
1487  const unsigned short top_id =
1488  new_elem->dim() == 3 ? cast_int<unsigned short>(elem->n_sides() + 1) : 2;
1489  // Assign sideset id to the side if the element belongs to a specified
1490  // upward_boundary_source_blocks
1492  for (unsigned int i = 0; i < _upward_boundary_source_blocks[e].size(); i++)
1493  if (new_elem->subdomain_id() == _upward_boundary_source_blocks[e][i])
1494  boundary_info.add_side(
1495  new_elem.get(), is_flipped ? 0 : top_id, _upward_boundary_ids[e][i]);
1496  }
1497  // define downward boundaries
1498  if (k == 0)
1499  {
1500  const unsigned short top_id =
1501  new_elem->dim() == 3 ? cast_int<unsigned short>(elem->n_sides() + 1) : 2;
1503  for (unsigned int i = 0; i < _downward_boundary_source_blocks[e].size(); i++)
1504  if (new_elem->subdomain_id() == _downward_boundary_source_blocks[e][i])
1505  boundary_info.add_side(
1506  new_elem.get(), is_flipped ? top_id : 0, _downward_boundary_ids[e][i]);
1507  }
1508 
1509  // perform subdomain swaps
1510  if (_subdomain_swap_pairs.size())
1511  {
1512  auto & elevation_swap_pairs = _subdomain_swap_pairs[e];
1513 
1514  auto new_id_it = elevation_swap_pairs.find(elem->subdomain_id());
1515 
1516  if (new_id_it != elevation_swap_pairs.end())
1517  new_elem->subdomain_id() = new_id_it->second;
1518  }
1519 
1520  Elem * added_elem = mesh->add_elem(std::move(new_elem));
1521 
1522  // maintain extra integers
1523  for (unsigned int i = 0; i < num_extra_elem_integers; i++)
1524  added_elem->set_extra_integer(i, elem->get_extra_integer(i));
1525 
1526  if (_elem_integers_swap_pairs.size())
1527  {
1528  for (unsigned int i = 0; i < _elem_integer_indices_to_swap.size(); i++)
1529  {
1530  auto & elevation_extra_swap_pairs = _elem_integers_swap_pairs[i * _heights.size() + e];
1531 
1532  auto new_extra_id_it = elevation_extra_swap_pairs.find(
1533  elem->get_extra_integer(_elem_integer_indices_to_swap[i]));
1534 
1535  if (new_extra_id_it != elevation_extra_swap_pairs.end())
1536  added_elem->set_extra_integer(_elem_integer_indices_to_swap[i],
1537  new_extra_id_it->second);
1538  }
1539  }
1540 
1541  // Copy any old boundary ids on all sides
1542  for (auto s : elem->side_index_range())
1543  {
1544  input_boundary_info.boundary_ids(elem, s, ids_to_copy);
1545 
1546  if (added_elem->dim() == 3)
1547  {
1548  // For 2D->3D extrusion, we give the boundary IDs
1549  // for side s on the old element to side s+1 on the
1550  // new element. This is just a happy coincidence as
1551  // far as I can tell...
1552  if (_boundary_swap_pairs.empty())
1553  boundary_info.add_side(added_elem, cast_int<unsigned short>(s + 1), ids_to_copy);
1554  else
1555  for (const auto & id_to_copy : ids_to_copy)
1556  boundary_info.add_side(added_elem,
1557  cast_int<unsigned short>(s + 1),
1558  _boundary_swap_pairs[e].count(id_to_copy)
1559  ? _boundary_swap_pairs[e][id_to_copy]
1560  : id_to_copy);
1561  }
1562  else
1563  {
1564  // For 1D->2D extrusion, the boundary IDs map as:
1565  // Old elem -> New elem
1566  // 0 -> 3
1567  // 1 -> 1
1568  libmesh_assert_less(s, 2);
1569  const unsigned short sidemap[2] = {3, 1};
1570  if (_boundary_swap_pairs.empty())
1571  boundary_info.add_side(added_elem, sidemap[s], ids_to_copy);
1572  else
1573  for (const auto & id_to_copy : ids_to_copy)
1574  boundary_info.add_side(added_elem,
1575  sidemap[s],
1576  _boundary_swap_pairs[e].count(id_to_copy)
1577  ? _boundary_swap_pairs[e][id_to_copy]
1578  : id_to_copy);
1579  }
1580  }
1581 
1582  // Give new boundary ids to bottom and top
1583  if (current_layer == 0)
1584  {
1585  const unsigned short top_id =
1586  added_elem->dim() == 3 ? cast_int<unsigned short>(elem->n_sides() + 1) : 2;
1588  {
1589  mooseAssert(user_bottom_boundary_id != libMesh::BoundaryInfo::invalid_id,
1590  "We should have retrieved a proper boundary ID");
1591  boundary_info.add_side(added_elem, is_flipped ? top_id : 0, user_bottom_boundary_id);
1592  }
1593  else
1594  boundary_info.add_side(added_elem, is_flipped ? top_id : 0, next_side_id);
1595  }
1596 
1597  if (current_layer == total_num_layers - 1)
1598  {
1599  // For 2D->3D extrusion, the "top" ID is 1+the original
1600  // element's number of sides. For 1D->2D extrusion, the
1601  // "top" ID is side 2.
1602  const unsigned short top_id =
1603  added_elem->dim() == 3 ? cast_int<unsigned short>(elem->n_sides() + 1) : 2;
1604 
1605  if (_has_top_boundary)
1606  {
1607  mooseAssert(user_top_boundary_id != libMesh::BoundaryInfo::invalid_id,
1608  "We should have retrieved a proper boundary ID");
1609  boundary_info.add_side(added_elem, is_flipped ? 0 : top_id, user_top_boundary_id);
1610  }
1611  else
1612  boundary_info.add_side(
1613  added_elem, is_flipped ? 0 : top_id, cast_int<boundary_id_type>(next_side_id + 1));
1614  }
1615 
1616  current_layer++;
1617  }
1618  }
1619  }
1620 
1621  if (has_polygons && !input->is_serial())
1622  mooseError("Distributed meshes are not supported when extruding polygons at this time.");
1623 
1624 #ifdef LIBMESH_ENABLE_UNIQUE_ID
1625  // Update the value of next_unique_id based on newly created nodes and elements
1626  // Note: Number of element layers is one less than number of node layers
1627  unsigned int total_new_node_layers = total_num_layers * order;
1628  unsigned int new_unique_ids = orig_unique_ids + (total_new_node_layers - 1) * orig_elem +
1629  total_new_node_layers * orig_nodes;
1630  // Maximum case for the unique ids: all poly elements have a midnode
1631  if (has_poly_midnodes)
1632  new_unique_ids += orig_elem;
1633  mesh->set_next_unique_id(new_unique_ids);
1634 #endif
1635 
1636  // Copy all the subdomain/sideset/nodeset name maps to the extruded mesh
1637  if (!input_subdomain_map.empty())
1638  mesh->set_subdomain_name_map().insert(input_subdomain_map.begin(), input_subdomain_map.end());
1639  if (!input_sideset_map.empty())
1640  mesh->get_boundary_info().set_sideset_name_map().insert(input_sideset_map.begin(),
1641  input_sideset_map.end());
1642  if (!input_nodeset_map.empty())
1643  mesh->get_boundary_info().set_nodeset_name_map().insert(input_nodeset_map.begin(),
1644  input_nodeset_map.end());
1645 
1647  boundary_info.sideset_name(new_boundary_ids.front()) = new_boundary_names.front();
1648  if (_has_top_boundary)
1649  boundary_info.sideset_name(new_boundary_ids.back()) = new_boundary_names.back();
1650 
1651  mesh->unset_is_prepared();
1652  // Creating the layered meshes creates a lot of leftover nodes, notably in the boundary_info,
1653  // which will crash both paraview and trigger exodiff. Best to be safe.
1654  if (extruding_quad_eights)
1655  mesh->prepare_for_use();
1656 
1657  return mesh;
1658 }
1659 
1660 Real
1662 {
1663  // NOTE: All functions added to this method must obey the following: f(0)=0, f(1)=1 for all t in
1664  // [0,1].
1665  switch (_radial_expansion_method)
1666  {
1667  case 0:
1668  return t;
1669  // Only linear and cubic implemented
1670  default:
1673  MathUtils::pow(t, 2) +
1675  }
1676 }
void swapNodesInElem(Elem &elem, const unsigned int nd1, const unsigned int nd2)
Swap two nodes within an element.
std::vector< std::unordered_map< boundary_id_type, boundary_id_type > > _boundary_swap_pairs
Easier to work with version of _boundary_swaps.
MetaPhysicL::DualNumber< V, D, asd > abs(const MetaPhysicL::DualNumber< V, D, asd > &a)
Definition: EigenADReal.h:50
ElemType
std::unique_ptr< MeshBase > & _input
Mesh that comes from another generator.
const RealVectorValue _end_extrusion_direction
Extrusion direction for the final layer of the extrusion.
CTSub CT_OPERATOR_BINARY CTMul CTCompareLess CTCompareGreater CTCompareEqual _arg template * sin(_arg) *_arg.template D< dtag >()) CT_SIMPLE_UNARY_FUNCTION(tan
virtual const char * what() const
Get out the error message.
auto norm() const
const unsigned int invalid_uint
QUAD8
const Real _end_radial_extent
Radial extent of the extruded geometry at the end of the extrusion.
std::unique_ptr< MeshBase > & _extrusion_curve
Curve mesh to extrude along.
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
std::unique_ptr< MeshBase > generate() override
Generate / modify the mesh.
static constexpr Real TOLERANCE
EDGE4
void swap(std::vector< T > &data, const std::size_t idx0, const std::size_t idx1, const libMesh::Parallel::Communicator &comm)
Swap function for serial or distributed vector of data.
Definition: Shuffle.h:495
std::vector< unsigned int > _elem_integer_indices_to_swap
Point _direction
The direction of the extrusion.
MeshBase & mesh
const std::vector< Real > & _heights
Height of each elevation.
The main MOOSE class responsible for handling user-defined parameters in almost every MOOSE system...
AdvancedExtruderGenerator(const InputParameters &parameters)
bool hasBoundaryID(const MeshBase &input_mesh, const BoundaryID id)
Whether a particular boundary ID exists in the mesh.
const Real _twist_pitch
Axial pitch for a full rotation.
const std::vector< std::vector< subdomain_id_type > > _downward_boundary_source_blocks
The list of input mesh&#39;s blocks that need to be assigned downward boundary interfaces for each layer ...
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...
auto max(const L &left, const R &right)
TRI3
const std::vector< std::vector< boundary_id_type > > _upward_boundary_ids
Upward boundary interfaces for each layer of elevation.
QUAD4
bool hasSubdomainID(const MeshBase &input_mesh, const SubdomainID &id)
Whether a particular subdomain ID exists in the mesh.
TypeVector< Real > unit() const
const Real _start_radial_growth_rate
Derivative of the radial expansion function at the beginning of the extrusion.
auto norm_sq() const
const std::string & name() const
Get the name of the class.
Definition: MooseBase.h:103
Extrudes a mesh to another dimension.
int8_t boundary_id_type
static const boundary_id_type invalid_id
CTSub CT_OPERATOR_BINARY CTMul CTCompareLess CTCompareGreater CTCompareEqual _arg template cos(_arg) *_arg.template D< dtag >()) CT_SIMPLE_UNARY_FUNCTION(cos
char ** sides
TRI6
const std::vector< std::vector< boundary_id_type > > _downward_boundary_ids
Downward boundary interfaces for each layer of elevation.
libmesh_assert(ctx)
This is a "smart" enum class intended to replace many of the shortcomings in the C++ enum type It sho...
Definition: MooseEnum.h:54
void idSwapParametersProcessor(const std::string &class_name, const std::string &id_name, const std::vector< std::vector< T >> &id_swaps, std::vector< std::unordered_map< T, T >> &id_swap_pairs, const unsigned int row_index_shift=0)
Reprocess the swap related input parameters to make pairs out of them to ease further processing...
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.
std::vector< std::unordered_map< subdomain_id_type, subdomain_id_type > > _subdomain_swap_pairs
Easier to work with version of _sudomain_swaps.
TypeVector< typename CompareTypes< Real, T2 >::supertype > cross(const TypeVector< T2 > &v) const
static InputParameters validParams()
Definition: MeshGenerator.C:23
const Real _end_radial_growth_rate
Derivative of the radial expansion function at the end of the extrusion.
std::string stringify(const T &t)
conversion to string
Definition: Conversion.h:64
const std::vector< std::vector< subdomain_id_type > > & _subdomain_swaps
Subdomains to swap out for each elevation.
registerMooseObjectRenamed("MooseApp", FancyExtruderGenerator, "02/18/2023 24:00", AdvancedExtruderGenerator)
const std::vector< std::vector< boundary_id_type > > & _boundary_swaps
Boundaries to swap out for each elevation.
const boundary_id_type top_id
bool _extrude_along_curve
Whether we are extruding along a curve.
Provides a way for users to bail out of the current solve.
Real radialExpansionRatio(const Real t) const
Calculate the share of the radial expansion to apply at the local node This share goes from 0 at the ...
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
const std::vector< std::vector< std::vector< dof_id_type > > > & _elem_integers_swaps
Extra element integers to swap out for each elevation and each element interger name.
EDGE2
auto norm(const T &a)
const std::vector< Real > _biases
Bias growth factor of each elevation.
IntRange< T > make_range(T beg, T end)
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
TRI7
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...
registerMooseObject("MooseApp", AdvancedExtruderGenerator)
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...
QUAD9
const std::vector< std::vector< subdomain_id_type > > _upward_boundary_source_blocks
The list of input mesh&#39;s blocks that need to be assigned upward boundary interfaces for each layer of...
const std::vector< unsigned int > & _num_layers
Number of layers in each elevation.
void extraElemIntegerSwapParametersProcessor(const std::string &class_name, const unsigned int num_sections, const unsigned int num_integers, const std::vector< std::vector< std::vector< dof_id_type >>> &elem_integers_swaps, std::vector< std::unordered_map< dof_id_type, dof_id_type >> &elem_integers_swap_pairs)
Reprocess the elem_integers_swaps into maps so they are easier to use.
void addRangeCheckedParam(const std::string &name, const T &value, const std::string &parsed_function, const std::string &doc_string)
bool isParamValid(const std::string &name) const
Test if the supplied parameter is valid.
Definition: MooseBase.h:209
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...
Real computeMaxDistanceToAxis(const MeshBase &mesh, const Point &origin, const RealVectorValue &direction)
Computes the maximum distance from all nodes of a mesh to a general axis.
T pow(T x, int e)
Definition: MathUtils.h:91
const MooseEnum _radial_expansion_method
Function type for modifying the radial extent of the extruded shape.
static InputParameters validParams()
auto min(const L &left, const R &right)
EDGE3
const std::vector< std::string > & _elem_integer_names_to_swap
Names and indices of extra element integers to swap.
bool isParamSetByUser(const std::string &name) const
Test if the supplied parameter is set by a user, as opposed to not set or set to default.
Definition: MooseBase.h:215
MooseUnits pow(const MooseUnits &, int)
Definition: Units.C:537
MeshGenerators are objects that can modify or add to an existing mesh.
Definition: MeshGenerator.h:33
uint8_t unique_id_type
void ErrorVector unsigned int
auto index_range(const T &sizable)
std::vector< std::unordered_map< dof_id_type, dof_id_type > > _elem_integers_swap_pairs
Easier to work with version of _elem_integers_swaps.
static const unsigned int max_n_nodes
const RealVectorValue _start_extrusion_direction
Extrusion direction to follow at the start (first layer) of the extrusion.
uint8_t dof_id_type
const Real pi
void addParamNamesToGroup(const std::string &space_delim_names, const std::string group_name)
This method takes a space delimited list of parameter names and adds them to the specified group name...
const RemoteElem * remote_elem