NEMoSys  0.63.0
A modular, extensible resource with robust automated mesh generation, mesh quality analysis, adaptive mesh refinement, and data transfer between arbitrary meshes.
geoMeshBaseReconstructGeo.C
Go to the documentation of this file.
1 /*******************************************************************************
2 * Promesh *
3 * Copyright (C) 2022, IllinoisRocstar LLC. All rights reserved. *
4 * *
5 * Promesh is the property of IllinoisRocstar LLC. *
6 * *
7 * IllinoisRocstar LLC *
8 * Champaign, IL *
9 * www.illinoisrocstar.com *
10 * promesh@illinoisrocstar.com *
11 *******************************************************************************/
12 /*******************************************************************************
13 * This file is part of Promesh *
14 * *
15 * This version of Promesh is free software: you can redistribute it and/or *
16 * modify it under the terms of the GNU Lesser General Public License as *
17 * published by the Free Software Foundation, either version 3 of the License, *
18 * or (at your option) any later version. *
19 * *
20 * Promesh is distributed in the hope that it will be useful, but WITHOUT ANY *
21 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS *
22 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more *
23 * details. *
24 * *
25 * You should have received a copy of the GNU Lesser General Public License *
26 * along with this program. If not, see <https://www.gnu.org/licenses/>. *
27 * *
28 *******************************************************************************/
29 #if defined(_MSC_VER) && !defined(_USE_MATH_DEFINES)
30 # define _USE_MATH_DEFINES
31 #endif
32 
33 #include "Mesh/geoMeshBase.H"
34 
35 #include <algorithm>
36 #include <iostream>
37 #include <map>
38 #include <numeric>
39 #include <set>
40 
41 #include <vtkConnectivityFilter.h>
42 #include <vtkIdTypeArray.h>
43 #include <vtkIntArray.h>
44 #include <vtkMath.h>
45 #include <vtkPolyDataNormals.h>
46 #include <vtkThreshold.h>
47 #include <vtkDataSetTriangleFilter.h>
48 #include <vtkType.h> // for vtkIdType
49 
50 #include "AuxiliaryFunctions.H"
52 #include "Mesh/gmshTypes.H"
53 
54 #ifdef HAVE_GMSH
55 #include <gmsh.h>
56 #endif
57 
58 namespace NEM {
59 namespace MSH {
60 
61 int geoMeshBase::getGmshTypeFromVTKType(int vtkType) {
62  switch (vtkType) {
63  case VTK_VERTEX: return MSH_PNT;
64 
65  case VTK_LINE: return MSH_LIN_2;
66  case VTK_TRIANGLE: return MSH_TRI_3;
67  case VTK_QUAD: return MSH_QUA_4;
68  case VTK_TETRA: return MSH_TET_4;
69  case VTK_HEXAHEDRON: return MSH_HEX_8;
70  case VTK_WEDGE: return MSH_PRI_6;
71  case VTK_PYRAMID: return MSH_PYR_5;
72 
73  case VTK_QUADRATIC_EDGE: return MSH_LIN_3;
74  case VTK_QUADRATIC_TRIANGLE: return MSH_TRI_6;
75  case VTK_QUADRATIC_QUAD: return MSH_QUA_8;
76  case VTK_BIQUADRATIC_QUAD: return MSH_QUA_9;
77  case VTK_QUADRATIC_TETRA: return MSH_TET_10;
78  case VTK_QUADRATIC_HEXAHEDRON: return MSH_HEX_20;
79  case VTK_TRIQUADRATIC_HEXAHEDRON: return MSH_HEX_27;
80 
81  default: return -1;
82  }
83 }
84 
85 // Helper functions that implement geoMeshBase::reconstructGeo
86 namespace {
87 
88 // Setup for vtkConnectivityFilter
89 void addGlobalIds(vtkDataSet *dataSet) {
90  vtkNew<vtkIdTypeArray> cellIds{};
91  cellIds->SetNumberOfTuples(dataSet->GetNumberOfCells());
92  for (vtkIdType i = 0; i < dataSet->GetNumberOfCells(); ++i) {
93  cellIds->SetValue(i, i);
94  }
95  cellIds->SetName("GlobalIds");
96  dataSet->GetCellData()->AddArray(cellIds);
97  // These lines don't seem to affect vtkConnectivityFilter:
98  // dataSet->GetCellData()->SetGlobalIds(cellIds);
99  // dataSet->GetCellData()->SetCopyGlobalIds(true);
100 }
101 
102 int local2GlobalRegions(vtkIdTypeArray *globalIds,
103  vtkIdTypeArray *localRegionId,
104  vtkIntArray *globalRegionId, int oldRegion,
105  int regionOffset, int numNewRegions,
106  std::map<int, std::vector<int>> &old2NewRegion) {
107  for (vtkIdType i = 0; i < globalIds->GetNumberOfTuples(); ++i) {
108  globalRegionId->SetTypedComponent(
109  globalIds->GetTypedComponent(i, 0), 0,
110  localRegionId->GetTypedComponent(i, 0) + regionOffset);
111  }
112  auto newRegionIter = old2NewRegion.emplace(
113  std::piecewise_construct, std::forward_as_tuple(oldRegion),
114  std::forward_as_tuple(numNewRegions));
115  auto &newRegionVec = newRegionIter.first->second;
116  std::iota(newRegionVec.begin(), newRegionVec.end(), regionOffset);
117  return regionOffset + numNewRegions;
118 }
119 
120 std::map<int, std::vector<int>> getConnectedEnts(vtkUnstructuredGrid *dataSet,
121  const std::string &geoName) {
122  vtkSmartPointer<vtkDataArray> geoArray = vtkArrayDownCast<vtkDataArray>(
123  dataSet->GetCellData()->GetAbstractArray(geoName.c_str()));
124 
125  // Count unique entities.
126  std::set<int> geoEntities;
127  if (geoArray) {
128  for (vtkIdType i = 0; i < geoArray->GetNumberOfValues(); ++i) {
129  geoEntities.insert(static_cast<int>(geoArray->GetComponent(i, 0)));
130  }
131  } else {
132  std::cout << "No geometry array found." << std::endl;
133  geoEntities.insert(1);
134 
135  vtkSmartPointer<vtkIntArray> newGeoArray =
137  newGeoArray->SetNumberOfValues(dataSet->GetNumberOfCells());
138  newGeoArray->SetName(geoName.c_str());
139  for (vtkIdType i = 0; i < dataSet->GetNumberOfCells(); ++i) {
140  newGeoArray->SetTypedComponent(i, 0, 1);
141  }
142  dataSet->GetCellData()->AddArray(newGeoArray);
143  }
144 
145  vtkNew<vtkIntArray> connectedRegionId{};
146  connectedRegionId->SetNumberOfValues(dataSet->GetNumberOfCells());
147 
148  int regionOffset = 1;
149  std::map<int, std::vector<int>> geoId2regionIds;
150 
151  // Define Global Ids explicitly since vtkConnectivityFilter does not preserve
152  // the built-in GlobalIds attribute.
153  addGlobalIds(dataSet);
154 
155  // Set-up threshold
156  vtkSmartPointer<vtkThreshold> tf = vtkSmartPointer<vtkThreshold>::New();
157 
158  tf->SetInputData(dataSet);
159  tf->SetInputArrayToProcess(0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_CELLS,
160  geoName.c_str());
161 
162  // Extract connected regions from the mesh to pass to the geometry filter
163  vtkSmartPointer<vtkConnectivityFilter> cf =
165 
166  cf->SetInputConnection(tf->GetOutputPort());
167  cf->SetExtractionModeToAllRegions();
168  cf->ColorRegionsOn();
169 
170  // For each phys group, run threshold, and color connected regions,
171  for (const auto &geoEntity : geoEntities) {
172  std::cout << "Processing " << geoEntity << std::endl;
173 
174  tf->ThresholdBetween(geoEntity, geoEntity);
175 
176  // Run all the filters.
177  cf->Update();
178 
179  // bypass API based on VTK version
180  vtkSmartPointer<vtkUnstructuredGrid> ug =
181  vtkUnstructuredGrid::SafeDownCast(cf->GetOutput());
182 
183  // Get the coloring array from the connectivity filter, (1) convert it to
184  // vtkIntArray for interface filter and (2) add an offset to keep region
185  // ids unique after merging back into single dataset.
186  auto componentId = vtkArrayDownCast<vtkIdTypeArray>(
187  ug->GetCellData()->GetAbstractArray("RegionId"));
188  auto globalId = vtkArrayDownCast<vtkIdTypeArray>(
189  ug->GetCellData()->GetAbstractArray("GlobalIds"));
190 
191  // Create a map from phys group ids to vector of region ids it was split
192  // into by the connectivity filter. This will be used to define physical
193  // groups later.
194  regionOffset = local2GlobalRegions(
195  globalId, componentId, connectedRegionId, geoEntity, regionOffset,
196  cf->GetNumberOfExtractedRegions(), geoId2regionIds);
197  }
198 
199  connectedRegionId->SetName(geoName.c_str());
200  dataSet->GetCellData()->AddArray(connectedRegionId);
201  return geoId2regionIds;
202 }
203 
204 // Mimic the workflow of (vtkPolyDataNormals with splitting on,
205 // vtkConnectivityFilter) for vtkDataSet with only lines
206 int colorLinesByAngle(vtkDataSet *lines, double angleThreshold) {
207  vtkNew<vtkIdTypeArray> colorArr;
208  colorArr->SetName("RegionId");
209  colorArr->SetNumberOfTuples(lines->GetNumberOfCells());
210  colorArr->FillTypedComponent(0, -1);
211  vtkIdType nextRegion = 0;
212  std::vector<vtkIdType> cellsToVisit;
213  vtkNew<vtkIdList> neighborCells;
214  vtkNew<vtkGenericCell> tempCell;
215  for (vtkIdType i = 0; i < lines->GetNumberOfCells(); ++i) {
216  if (colorArr->GetTypedComponent(i, 0) == -1) {
217  cellsToVisit.clear();
218  cellsToVisit.emplace_back(i);
219  colorArr->SetTypedComponent(i, 0, nextRegion);
220  while (!cellsToVisit.empty()) {
221  auto cellIdx = cellsToVisit.back();
222  cellsToVisit.pop_back();
223  lines->GetCell(cellIdx, tempCell);
224  auto points = tempCell->GetPointIds();
225  assert(points->GetNumberOfIds() == 2);
226  std::array<double, 3> vec{};
227  {
228  lines->GetPoint(points->GetId(1), vec.data());
229  auto u = lines->GetPoint(points->GetId(0));
230  std::transform(vec.begin(), vec.end(), u, vec.begin(),
231  std::minus<double>());
232  }
233  for (int j = 0; j < 2; ++j) {
234  if (j == 1) {
235  std::transform(vec.begin(), vec.end(), vec.begin(),
236  [](double x) { return -1 * x; });
237  }
238  auto sharedPointId = points->GetId(j);
239  lines->GetPointCells(sharedPointId, neighborCells);
240  for (vtkIdType k = 0; k < neighborCells->GetNumberOfIds(); ++k) {
241  auto otherCellIdx = neighborCells->GetId(k);
242  if (colorArr->GetTypedComponent(otherCellIdx, 0) == -1) {
243  auto otherCellPoints =
244  lines->GetCell(otherCellIdx)->GetPointIds();
245  auto otherPointId = otherCellPoints->GetId(0) == sharedPointId
246  ? otherCellPoints->GetId(1)
247  : otherCellPoints->GetId(0);
248  std::array<double, 3> otherVec{};
249  lines->GetPoint(otherPointId, otherVec.data());
250  auto u = lines->GetPoint(sharedPointId);
251  std::transform(otherVec.begin(), otherVec.end(), u,
252  otherVec.begin(), std::minus<double>());
253  auto angle =
254  vtkMath::AngleBetweenVectors(vec.data(), otherVec.data());
255  if (std::abs(angle - vtkMath::Pi()) < angleThreshold) {
256  cellsToVisit.emplace_back(otherCellIdx);
257  colorArr->SetTypedComponent(otherCellIdx, 0, nextRegion);
258  }
259  }
260  }
261  }
262  }
263  ++nextRegion;
264  }
265  }
266  lines->GetCellData()->AddArray(colorArr);
267  return nextRegion;
268 }
269 
270 // Iterate through each of the surfaces (indexed from 1..numBoundEnts in the
271 // array given by surfaceArrayName), splitting by angleThreshold. Return map
272 // from old to new surfaces.
273 std::map<int, std::vector<int>> splitSurfaces(
274  vtkPolyData *surfaces, const std::string &surfaceArrayName, int max_dim,
275  double angleThreshold, int numBoundEnts, vtkIntArray *sideGeoEntArr) {
276  std::map<int, std::vector<int>> old2newSurfs{};
277  vtkNew<vtkThreshold> selectBoundary{};
278  selectBoundary->SetInputData(surfaces);
279  selectBoundary->SetInputArrayToProcess(0, 0, 0,
280  vtkDataObject::FIELD_ASSOCIATION_CELLS,
281  surfaceArrayName.c_str());
282 
283  vtkSmartPointer<vtkPolyDataNormals> pdn;
284  vtkSmartPointer<vtkPolyData> pdnInput;
285  vtkSmartPointer<vtkConnectivityFilter> pdConn{};
286  if (max_dim == 3) {
288  // The custom boundary filter should leave normals consistent
289  pdn->SetConsistency(false);
290  pdn->SetSplitting(true);
291  pdn->SetFeatureAngle(vtkMath::DegreesFromRadians(angleThreshold));
294  pdConn->SetExtractionModeToAllRegions();
295  pdConn->ColorRegionsOn();
296  }
297  // Start from 1 because gmsh want strictly positive entity tags
298  int regionOffset = 1;
299  for (int i = 0; i < numBoundEnts; ++i) {
300  vtkSmartPointer<vtkDataSet> coloredDS;
301  int numRegions;
302  selectBoundary->ThresholdBetween(i, i);
303  if (max_dim == 2) {
304  selectBoundary->Update();
305  coloredDS = selectBoundary->GetOutput();
306  numRegions = colorLinesByAngle(coloredDS, angleThreshold);
307  } else { // max_dim == 3
308  selectBoundary->Update();
309  auto thresholdOut = selectBoundary->GetOutput();
310  pdnInput->SetPoints(thresholdOut->GetPoints());
311  pdnInput->SetPolys(thresholdOut->GetCells());
312  pdnInput->GetCellData()->ShallowCopy(thresholdOut->GetCellData());
313 
314  pdn->SetInputData(pdnInput);
315  pdConn->SetInputConnection(pdn->GetOutputPort());
316  pdConn->Update();
317  coloredDS = pdConn->GetOutput();
318  numRegions = pdConn->GetNumberOfExtractedRegions();
319  }
320  auto globalId = vtkArrayDownCast<vtkIdTypeArray>(
321  coloredDS->GetCellData()->GetArray("GlobalIds"));
322  auto splitSurfId = vtkArrayDownCast<vtkIdTypeArray>(
323  coloredDS->GetCellData()->GetArray("RegionId"));
324  regionOffset = local2GlobalRegions(globalId, splitSurfId, sideGeoEntArr, i,
325  regionOffset, numRegions, old2newSurfs);
326  }
327  surfaces->GetCellData()->RemoveArray(surfaceArrayName.c_str());
328  surfaces->GetCellData()->AddArray(sideGeoEntArr);
329  return old2newSurfs;
330 }
331 
332 #ifdef HAVE_GMSH
333 void constructGeoGmsh(vtkDataSet *dataSet, const int dim,
334  vtkIntArray *sideGeoEntArr,
335  const std::map<int, std::vector<int>> &vol2surf,
336  const std::map<int, std::vector<int>> &material2GeoEnt) {
337  std::vector<double> coords(3 * dataSet->GetNumberOfPoints());
338  for (vtkIdType i = 0; i < dataSet->GetNumberOfPoints(); ++i) {
339  dataSet->GetPoint(i, coords.data() + 3 * i);
340  }
341  std::map<int, std::vector<std::size_t>> splitSurf2NodeTags;
342  const int numPoints = dim; // Assumes lines/triangles!
343  for (vtkIdType i = 0; i < dataSet->GetNumberOfCells(); ++i) {
344  auto &nodeTags = splitSurf2NodeTags[sideGeoEntArr->GetTypedComponent(i, 0)];
345  auto pointsBegin = dataSet->GetCell(i)->GetPointIds()->GetPointer(0);
346  std::transform(pointsBegin, pointsBegin + numPoints,
347  std::back_inserter(nodeTags),
348  [](vtkIdType x) { return x + 1; });
349  }
350  bool firstSurf = true;
351  for (const auto &surf : splitSurf2NodeTags) {
352  auto gmshTag = gmsh::model::addDiscreteEntity(dim - 1, surf.first);
353  if (firstSurf) {
354  // Assume (gmsh node tag) == (1 + vtk point id)
355  gmsh::model::mesh::addNodes(dim - 1, gmshTag, {}, coords);
356  firstSurf = false;
357  }
358  gmsh::model::mesh::addElementsByType(
359  gmshTag, dim == 2 ? MSH_LIN_2 : MSH_TRI_3, {}, surf.second);
360  }
361  gmsh::model::mesh::reclassifyNodes();
362  // Create curves and points to prepare for createGeometry
363  gmsh::model::mesh::createTopology();
364  gmsh::model::mesh::createGeometry();
365  for (const auto &vol : vol2surf) {
366  std::vector<int> postClassifySurfs;
367  if (dim == 2) {
368  auto curveLoop = gmsh::model::geo::addCurveLoop(vol.second);
369  gmsh::model::geo::addPlaneSurface({curveLoop}, vol.first);
370  } else { // dim == 3
371  auto surfLoop = gmsh::model::geo::addSurfaceLoop(vol.second);
372  gmsh::model::geo::addVolume({surfLoop}, vol.first);
373  }
374  }
375  gmsh::model::geo::synchronize();
376  for (const auto &phyGrp : material2GeoEnt) {
377  gmsh::model::addPhysicalGroup(dim, phyGrp.second, phyGrp.first);
378  }
379 }
380 #endif
381 
382 // Compute discrete geometry from dataSetCopy and name of geometry cell array
383 // geoName, with angleThreshold dihedral angle (in radians) to detect surfaces.
384 // Also sets sideSet with lower dimensional entities on boundaries (incl between
385 // different materials)
386 vtkSmartPointer<vtkPolyData> computeDiscreteGeoFromMsh(
387  vtkUnstructuredGrid *dataSetOriginal, const std::string &geoName,
388  double angleThreshold) {
389  //****************************************************************************
390  // 1. Access the geo array and count number of phys groups
391  //****************************************************************************
392  auto material2GeoEnt = getConnectedEnts(dataSetOriginal, geoName);
393 
394  // Prevent modifying the mesh any more
395  vtkNew<vtkUnstructuredGrid> dataSet;
396  dataSet->ShallowCopy(dataSetOriginal);
397 
398  //****************************************************************************
399  // 2. Run the interface filter (dataSetRegionBoundaryFilter) to extract the
400  // surfaces as PolyData. For each resulting surface, split surfaces by
401  // angle.
402  //****************************************************************************
403 
404  // The boundary filter should only be applied to one dimension.
405  int max_dim = -1;
406  for (vtkIdType i = 0; i < dataSet->GetNumberOfCells(); ++i) {
407  max_dim = std::max(dataSet->GetCell(i)->GetCellDimension(), max_dim);
408  }
409 
410  vtkNew<dataSetRegionBoundaryFilter> dsrbf;
411  // Set-up interface filter
412  dsrbf->SetMaterialArrayName(geoName);
413  dsrbf->SetDimension(max_dim);
414  dsrbf->SetInputData(dataSet);
415  dsrbf->Update();
416 
417  vtkSmartPointer<vtkPolyData> sidePD = dsrbf->GetOutput();
418  auto boundary2MeshRegion =
419  vtkArrayDownCast<vtkIntArray>(sidePD->GetFieldData()->GetAbstractArray(
420  dsrbf->GetRegionToMaterialArrayName().c_str()));
421  addGlobalIds(sidePD);
422  vtkNew<vtkIntArray> sideGeoEntArr;
423  sideGeoEntArr->SetName("GeoEnt");
424  sideGeoEntArr->SetNumberOfTuples(sidePD->GetNumberOfCells());
425  int numBoundEnts = boundary2MeshRegion->GetNumberOfTuples();
426  auto surf2ClassifiedSurfs =
427  splitSurfaces(sidePD, dsrbf->GetBoundaryRegionArrayName(), max_dim,
428  angleThreshold, numBoundEnts, sideGeoEntArr);
429  std::map<int, std::vector<int>> vol2surf;
430  for (vtkIdType i = 0; i < boundary2MeshRegion->GetNumberOfTuples(); ++i) {
431  auto &splitSurfs = surf2ClassifiedSurfs[i];
432  for (vtkIdType j = 0; j < 2; ++j) {
433  auto vol = boundary2MeshRegion->GetTypedComponent(i, j);
434  if (vol != -1) {
435  auto &boundary = vol2surf[vol];
436  boundary.insert(boundary.end(), splitSurfs.begin(), splitSurfs.end());
437  }
438  }
439  }
440  sidePD->GetFieldData()->RemoveArray(boundary2MeshRegion->GetName());
441 
442  //****************************************************************************
443  // 3. Pass to gmsh to reconstruct geometry.
444  //****************************************************************************
445 
446 #ifdef HAVE_GMSH
447  if (max_dim == 2) {
448  constructGeoGmsh(sidePD, max_dim, sideGeoEntArr, vol2surf, material2GeoEnt);
449  } else {
450  vtkNew<vtkDataSetTriangleFilter> trif;
451  trif->SetInputData(sidePD);
452  trif->Update();
453  auto triangleSurf = trif->GetOutput();
454  constructGeoGmsh(triangleSurf, max_dim,
455  vtkArrayDownCast<vtkIntArray>(
456  triangleSurf->GetCellData()->GetAbstractArray(
457  sideGeoEntArr->GetName())),
458  vol2surf, material2GeoEnt);
459  }
460 #endif
461  return sidePD;
462 }
463 
464 } // namespace
465 
467 #ifdef HAVE_GMSH
468  if (_geoMesh.geo.empty()) {
469  _geoMesh.geo = "geoMesh_" + nemAux::getRandomString(6);
470  } else {
471  gmsh::model::setCurrent(_geoMesh.geo);
472  gmsh::model::remove();
473  }
474  gmsh::model::add(_geoMesh.geo);
475  gmsh::model::setCurrent(_geoMesh.geo);
476 #endif
477  if (_geoMesh.link.empty()) {
479  }
481  computeDiscreteGeoFromMsh(_geoMesh.mesh, _geoMesh.link, _angleThreshold)};
482  assert(_geoMesh.mesh->GetPoints() == _geoMesh.sideSet.sides->GetPoints());
483 }
484 
485 } // namespace MSH
486 } // namespace NEM
vtkSmartPointer< vtkUnstructuredGrid > mesh
Definition: geoMeshBase.H:419
virtual void reconstructGeo()
Construct the geometry from the mesh alone.
static constexpr auto GEO_ENT_DEFAULT_NAME
Definition: geoMeshBase.H:446
geoMeshBase * New(MeshType meshType)
Create a new mesh object.
std::string getRandomString(int length)
vtkSmartPointer< vtkPolyData > sides
Cells represent edges/faces of some GeoMesh.
Definition: geoMeshBase.H:399
std::vector< vtkIdType > points
points given by id in .inp file
Definition: inpGeoMesh.C:133
std::map< std::string, std::vector< std::pair< vtkIdType, int > > > surfaces
Map from SURFACE name to (element id, side) (id and side both use .inp IDs)
Definition: inpGeoMesh.C:157
double _angleThreshold
Dihedral angle threshold (in radians) to classify surfaces (Default: 30 degrees)
Definition: geoMeshBase.H:554
constexpr double Pi
Definition: blockMeshGen.C:52