combinePatchFaces.C
Go to the documentation of this file.
1 /*---------------------------------------------------------------------------*\
2  ========= |
3  \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
4  \\ / O peration | Website: https://openfoam.org
5  \\ / A nd | Copyright (C) 2011-2023 OpenFOAM Foundation
6  \\/ M anipulation |
7 -------------------------------------------------------------------------------
8 License
9  This file is part of OpenFOAM.
10 
11  OpenFOAM is free software: you can redistribute it and/or modify it
12  under the terms of the GNU General Public License as published by
13  the Free Software Foundation, either version 3 of the License, or
14  (at your option) any later version.
15 
16  OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
17  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
18  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
19  for more details.
20 
21  You should have received a copy of the GNU General Public License
22  along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
23 
24 Application
25  combinePatchFaces
26 
27 Description
28  Checks for multiple patch faces on same cell and combines them.
29  Multiple patch faces can result from e.g. removal of refined
30  neighbouring cells, leaving 4 exposed faces with same owner.
31 
32  Rules for merging:
33  - only boundary faces (since multiple internal faces between two cells
34  not allowed anyway)
35  - faces have to have same owner
36  - faces have to be connected via edge which are not features (so angle
37  between them < feature angle)
38  - outside of faces has to be single loop
39  - outside of face should not be (or just slightly) concave (so angle
40  between consecutive edges < concaveangle
41 
42  E.g. to allow all faces on same patch to be merged:
43 
44  combinePatchFaces 180 -concaveAngle 90
45 
46 \*---------------------------------------------------------------------------*/
47 
48 #include "PstreamReduceOps.H"
49 #include "argList.H"
50 #include "Time.H"
51 #include "polyTopoChange.H"
52 #include "polyModifyFace.H"
53 #include "polyAddFace.H"
54 #include "combineFaces.H"
55 #include "removePoints.H"
56 #include "polyMesh.H"
57 #include "polyTopoChangeMap.H"
58 #include "unitConversion.H"
59 #include "motionSmoother.H"
60 
61 using namespace Foam;
62 
63 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
64 
65 // Merge faces on the same patch (usually from exposing refinement)
66 // Can undo merges if these cause problems.
67 label mergePatchFaces
68 (
69  const scalar minCos,
70  const scalar concaveSin,
71  const autoPtr<IOdictionary>& qualDictPtr,
72  const Time& runTime,
73  polyMesh& mesh
74 )
75 {
76  // Patch face merging engine
77  combineFaces faceCombiner(mesh);
78 
79  // Get all sets of faces that can be merged
80  labelListList allFaceSets(faceCombiner.getMergeSets(minCos, concaveSin));
81 
82  label nFaceSets = returnReduce(allFaceSets.size(), sumOp<label>());
83 
84  Info<< "Merging " << nFaceSets << " sets of faces." << endl;
85 
86  if (nFaceSets > 0)
87  {
88  // Store the faces of the face sets
89  List<faceList> allFaceSetsFaces(allFaceSets.size());
90  forAll(allFaceSets, setI)
91  {
92  allFaceSetsFaces[setI] = UIndirectList<face>
93  (
94  mesh.faces(),
95  allFaceSets[setI]
96  );
97  }
98 
100  {
101  // Topology changes container
102  polyTopoChange meshMod(mesh);
103 
104  // Merge all faces of a set into the first face of the set.
105  faceCombiner.setRefinement(allFaceSets, meshMod);
106 
107  // Change the mesh (no inflation)
108  map = meshMod.changeMesh(mesh, false, true);
109 
110  // Update fields
111  mesh.topoChange(map);
112 
113  // Move mesh (since morphing does not do this)
114  if (map().hasMotionPoints())
115  {
116  mesh.setPoints(map().preMotionPoints());
117  }
118  else
119  {
120  // Delete mesh volumes. No other way to do this?
121  mesh.clearOut();
122  }
123  }
124 
125 
126  // Check for errors and undo
127  // ~~~~~~~~~~~~~~~~~~~~~~~~~
128 
129  // Faces in error.
130  labelHashSet errorFaces;
131 
132  if (qualDictPtr.valid())
133  {
134  motionSmoother::checkMesh(false, mesh, qualDictPtr(), errorFaces);
135  }
136  else
137  {
138  mesh.checkFacePyramids(false, -small, &errorFaces);
139  }
140 
141  // Sets where the master is in error
142  labelHashSet errorSets;
143 
144  forAll(allFaceSets, setI)
145  {
146  label newMasterI = map().reverseFaceMap()[allFaceSets[setI][0]];
147 
148  if (errorFaces.found(newMasterI))
149  {
150  errorSets.insert(setI);
151  }
152  }
153  label nErrorSets = returnReduce(errorSets.size(), sumOp<label>());
154 
155  Info<< "Detected " << nErrorSets
156  << " error faces on boundaries that have been merged."
157  << " These will be restored to their original faces."
158  << endl;
159 
160  if (nErrorSets > 0)
161  {
162  // Renumber stored faces to new vertex numbering.
163  forAllConstIter(labelHashSet, errorSets, iter)
164  {
165  label setI = iter.key();
166 
167  faceList& setFaceVerts = allFaceSetsFaces[setI];
168 
169  forAll(setFaceVerts, i)
170  {
171  inplaceRenumber(map().reversePointMap(), setFaceVerts[i]);
172 
173  // Debug: check that all points are still there.
174  forAll(setFaceVerts[i], j)
175  {
176  label newVertI = setFaceVerts[i][j];
177 
178  if (newVertI < 0)
179  {
181  << "In set:" << setI << " old face labels:"
182  << allFaceSets[setI] << " new face vertices:"
183  << setFaceVerts[i] << " are unmapped vertices!"
184  << abort(FatalError);
185  }
186  }
187  }
188  }
189 
190 
191  // Topology changes container
192  polyTopoChange meshMod(mesh);
193 
194 
195  // Restore faces
196  forAllConstIter(labelHashSet, errorSets, iter)
197  {
198  label setI = iter.key();
199 
200  const labelList& setFaces = allFaceSets[setI];
201  const faceList& setFaceVerts = allFaceSetsFaces[setI];
202 
203  label newMasterI = map().reverseFaceMap()[setFaces[0]];
204 
205  // Restore. Get face properties.
206 
207  label own = mesh.faceOwner()[newMasterI];
208  label zoneID = mesh.faceZones().whichZone(newMasterI);
209  bool zoneFlip = false;
210  if (zoneID >= 0)
211  {
212  const faceZone& fZone = mesh.faceZones()[zoneID];
213  zoneFlip = fZone.flipMap()[fZone.whichFace(newMasterI)];
214  }
215  label patchID = mesh.boundaryMesh().whichPatch(newMasterI);
216 
217  Pout<< "Restoring new master face " << newMasterI
218  << " to vertices " << setFaceVerts[0] << endl;
219 
220  // Modify the master face.
221  meshMod.setAction
222  (
224  (
225  setFaceVerts[0], // original face
226  newMasterI, // label of face
227  own, // owner
228  -1, // neighbour
229  false, // face flip
230  patchID, // patch for face
231  false, // remove from zone
232  zoneID, // zone for face
233  zoneFlip // face flip in zone
234  )
235  );
236 
237 
238  // Add the previously removed faces
239  for (label i = 1; i < setFaces.size(); i++)
240  {
241  Pout<< "Restoring removed face " << setFaces[i]
242  << " with vertices " << setFaceVerts[i] << endl;
243 
244  meshMod.setAction
245  (
247  (
248  setFaceVerts[i], // vertices
249  own, // owner,
250  -1, // neighbour,
251  -1, // masterPointID,
252  -1, // masterEdgeID,
253  newMasterI, // masterFaceID,
254  false, // flipFaceFlux,
255  patchID, // patchID,
256  zoneID, // zoneID,
257  zoneFlip // zoneFlip
258  )
259  );
260  }
261  }
262 
263  // Change the mesh (no inflation)
264  map = meshMod.changeMesh(mesh, false, true);
265 
266  // Update fields
267  mesh.topoChange(map);
268 
269  // Move mesh (since morphing does not do this)
270  if (map().hasMotionPoints())
271  {
272  mesh.setPoints(map().preMotionPoints());
273  }
274  else
275  {
276  // Delete mesh volumes. No other way to do this?
277  mesh.clearOut();
278  }
279  }
280  }
281  else
282  {
283  Info<< "No faces merged ..." << endl;
284  }
285 
286  return nFaceSets;
287 }
288 
289 
290 // Remove points not used by any face or points used by only two faces where
291 // the edges are in line
292 label mergeEdges(const scalar minCos, polyMesh& mesh)
293 {
294  Info<< "Merging all points on surface that" << nl
295  << "- are used by only two boundary faces and" << nl
296  << "- make an angle with a cosine of more than " << minCos
297  << "." << nl << endl;
298 
299  // Point removal analysis engine
300  removePoints pointRemover(mesh);
301 
302  // Count usage of points
303  boolList pointCanBeDeleted;
304  label nRemove = pointRemover.countPointUsage(minCos, pointCanBeDeleted);
305 
306  if (nRemove > 0)
307  {
308  Info<< "Removing " << nRemove
309  << " straight edge points ..." << endl;
310 
311  // Topology changes container
312  polyTopoChange meshMod(mesh);
313 
314  pointRemover.setRefinement(pointCanBeDeleted, meshMod);
315 
316  // Change the mesh (no inflation)
317  autoPtr<polyTopoChangeMap> map = meshMod.changeMesh(mesh, false, true);
318 
319  // Update fields
320  mesh.topoChange(map);
321 
322  // Move mesh (since morphing does not do this)
323  if (map().hasMotionPoints())
324  {
325  mesh.setPoints(map().preMotionPoints());
326  }
327  else
328  {
329  // Delete mesh volumes. No other way to do this?
330  mesh.clearOut();
331  }
332  }
333  else
334  {
335  Info<< "No straight edges simplified and no points removed ..." << endl;
336  }
337 
338  return nRemove;
339 }
340 
341 
342 
343 int main(int argc, char *argv[])
344 {
345  #include "addOverwriteOption.H"
346 
347  argList::validArgs.append("featureAngle [0..180]");
349  (
350  "concaveAngle",
351  "degrees",
352  "specify concave angle [0..180] (default: 30 degrees)"
353  );
355  (
356  "meshQuality",
357  "read user-defined mesh quality criterions from system/meshQualityDict"
358  );
359 
360  #include "setRootCase.H"
362  #include "createPolyMesh.H"
363  const word oldInstance = mesh.pointsInstance();
364 
365  const scalar featureAngle = args.argRead<scalar>(1);
366  const scalar minCos = Foam::cos(degToRad(featureAngle));
367 
368  // Sin of angle between two consecutive edges on a face.
369  // If sin(angle) larger than this the face will be considered concave.
370  scalar concaveAngle = args.optionLookupOrDefault("concaveAngle", 30.0);
371  scalar concaveSin = Foam::sin(degToRad(concaveAngle));
372 
373  const bool overwrite = args.optionFound("overwrite");
374  const bool meshQuality = args.optionFound("meshQuality");
375 
376  Info<< "Merging all faces of a cell" << nl
377  << " - which are on the same patch" << nl
378  << " - which make an angle < " << featureAngle << " degrees"
379  << nl
380  << " (cos:" << minCos << ')' << nl
381  << " - even when resulting face becomes concave by more than "
382  << concaveAngle << " degrees" << nl
383  << " (sin:" << concaveSin << ')' << nl
384  << endl;
385 
386  autoPtr<IOdictionary> qualDict;
387  if (meshQuality)
388  {
389  Info<< "Enabling user-defined geometry checks." << nl << endl;
390 
391  qualDict.reset
392  (
393  new IOdictionary
394  (
395  IOobject
396  (
397  "meshQualityDict",
398  mesh.time().system(),
399  mesh,
402  )
403  )
404  );
405  }
406 
407 
408  if (!overwrite)
409  {
410  runTime++;
411  }
412 
413 
414 
415  // Merge faces on same patch
416  label nChanged = mergePatchFaces
417  (
418  minCos,
419  concaveSin,
420  qualDict,
421  runTime,
422  mesh
423  );
424 
425  // Merge points on straight edges and remove unused points
426  if (qualDict.valid())
427  {
428  Info<< "Merging all 'loose' points on surface edges, "
429  << "regardless of the angle they make." << endl;
430 
431  // Surface bnound to be used to extrude. Merge all loose points.
432  nChanged += mergeEdges(-1, mesh);
433  }
434  else
435  {
436  nChanged += mergeEdges(minCos, mesh);
437  }
438 
439  if (nChanged > 0)
440  {
441  if (overwrite)
442  {
443  mesh.setInstance(oldInstance);
444  }
445 
446  Info<< "Writing morphed mesh to time " << runTime.name() << endl;
447 
448  mesh.write();
449  }
450  else
451  {
452  Info<< "Mesh unchanged." << endl;
453  }
454 
455  Info<< "\nEnd\n" << endl;
456 
457  return 0;
458 }
459 
460 
461 // ************************************************************************* //
Inter-processor communication reduction functions.
#define forAll(list, i)
Loop across all elements in list.
Definition: UList.H:434
#define forAllConstIter(Container, container, iter)
Iterate across all elements in the container object of type.
Definition: UList.H:477
bool insert(const Key &key)
Insert a new entry.
Definition: HashSet.H:111
label size() const
Return number of elements in table.
Definition: HashTableI.H:65
bool found(const Key &) const
Return true if hashedEntry is found in table.
Definition: HashTable.C:113
IOdictionary is derived from dictionary and IOobject to give the dictionary automatic IO functionalit...
Definition: IOdictionary.H:57
IOobject defines the attributes of an object for which implicit objectRegistry management is supporte...
Definition: IOobject.H:99
void size(const label)
Override size to be inconsistent with allocated storage.
Definition: ListI.H:164
label whichZone(const label objectIndex) const
Given a global object index, return the zone it is in.
Definition: MeshZones.C:221
static const word & system()
Return system name.
Definition: TimePaths.H:112
Class to control time during OpenFOAM simulations that is also the top-level objectRegistry.
Definition: Time.H:76
A List with indirect addressing.
Definition: UIndirectList.H:60
static void addOption(const word &opt, const string &param="", const string &usage="")
Add to an option to validOptions with usage information.
Definition: argList.C:128
static void addBoolOption(const word &opt, const string &usage="")
Add to a bool option to validOptions with usage information.
Definition: argList.C:118
bool optionFound(const word &opt) const
Return true if the named option is found.
Definition: argListI.H:114
static SLList< string > validArgs
A list of valid (mandatory) arguments.
Definition: argList.H:153
T argRead(const label index) const
Read a value from the argument at index.
Definition: argListI.H:183
T optionLookupOrDefault(const word &opt, const T &deflt) const
Read a value from the named option if present.
Definition: argListI.H:243
An auto-pointer similar to the STL auto_ptr but with automatic casting to a reference to the type and...
Definition: autoPtr.H:51
bool valid() const
Return true if the autoPtr valid (ie, the pointer is set)
Definition: autoPtrI.H:83
void reset(T *=nullptr)
If object pointer already set, delete object and set to given.
Definition: autoPtrI.H:114
Combines boundary faces into single face. The faces get the patch of the first face ('the master')
Definition: combineFaces.H:56
const word & name() const
Return const reference to name.
A subset of mesh faces organised as a primitive patch.
Definition: faceZone.H:68
const boolList & flipMap() const
Return face flip map.
Definition: faceZone.H:249
label whichFace(const label globalCellID) const
Helper function to re-direct to zone::localID(...)
Definition: faceZone.C:307
static bool checkMesh(const bool report, const polyMesh &mesh, const dictionary &dict, labelHashSet &wrongFaces)
Check mesh with mesh settings in dict. Collects incorrect faces.
const Time & time() const
Return time.
A face addition data class. A face can be inflated either from a point or from another face and can e...
Definition: polyAddFace.H:54
label whichPatch(const label faceIndex) const
Return patch index for a given face label.
Mesh consisting of general polyhedral cells.
Definition: polyMesh.H:80
const meshFaceZones & faceZones() const
Return face zones.
Definition: polyMesh.H:447
virtual const faceList & faces() const
Return raw faces.
Definition: polyMesh.C:1373
virtual const labelList & faceOwner() const
Return face owner.
Definition: polyMesh.C:1386
virtual void topoChange(const polyTopoChangeMap &)
Update topology using the given map.
const fileName & pointsInstance() const
Return the current instance directory for points.
Definition: polyMesh.C:1019
const polyBoundaryMesh & boundaryMesh() const
Return boundary mesh.
Definition: polyMesh.H:405
void setInstance(const fileName &)
Set the instance for mesh files.
Definition: polyMeshIO.C:78
void clearOut()
Clear all geometry and addressing unnecessary for CFD.
virtual void setPoints(const pointField &)
Reset the points.
Definition: polyMesh.C:1448
Class describing modification of a face.
Direct mesh changes based on v1.3 polyTopoChange syntax.
bool checkFacePyramids(const bool report=false, const scalar minPyrVol=-small, labelHashSet *setPtr=nullptr) const
Check face pyramid volume.
virtual bool write(const bool write=true) const
Write using setting from DB.
Removes selected points from mesh and updates faces using these points.
Definition: removePoints.H:59
A class for handling words, derived from string.
Definition: word.H:62
#define FatalErrorInFunction
Report an error message using Foam::FatalError.
Definition: error.H:306
int main(int argc, char *argv[])
Definition: financialFoam.C:44
Namespace for OpenFOAM.
intWM_LABEL_SIZE_t label
A label is an int32_t or int64_t as specified by the pre-processor macro WM_LABEL_SIZE.
Definition: label.H:59
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:251
errorManip< error > abort(error &err)
Definition: errorManip.H:131
dimensionedScalar sin(const dimensionedScalar &ds)
messageStream Info
T returnReduce(const T &Value, const BinaryOp &bop, const int tag=Pstream::msgType(), const label comm=UPstream::worldComm)
void inplaceRenumber(const labelUList &oldToNew, ListType &)
Inplace renumber the values of a list.
prefixOSstream Pout(cout, "Pout")
Definition: IOstreams.H:53
error FatalError
static const char nl
Definition: Ostream.H:260
dimensionedScalar cos(const dimensionedScalar &ds)
scalar degToRad(const scalar deg)
Conversion from degrees to radians.
Foam::argList args(argc, argv)
Unit conversion functions.