openfoam/applications/utilities/mesh/generation/snappyHexMesh/snappyHexMesh.C
Mark Olesen 62fc3bbc33 STYLE: broadcast instead of combineScatter/listCombineScatter/mapCombineScatter
- these are the same thing now and 'broadcast' expresses the intention
  more directly/consistently
2022-03-12 21:16:30 +01:00

2058 lines
56 KiB
C

/*---------------------------------------------------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | www.openfoam.com
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2011-2016 OpenFOAM Foundation
Copyright (C) 2015-2022 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
OpenFOAM is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
Application
snappyHexMesh
Group
grpMeshGenerationUtilities
Description
Automatic split hex mesher. Refines and snaps to surface.
\*---------------------------------------------------------------------------*/
#include "argList.H"
#include "Time.H"
#include "fvMesh.H"
#include "snappyRefineDriver.H"
#include "snappySnapDriver.H"
#include "snappyLayerDriver.H"
#include "searchableSurfaces.H"
#include "refinementSurfaces.H"
#include "refinementFeatures.H"
#include "shellSurfaces.H"
#include "decompositionMethod.H"
#include "fvMeshDistribute.H"
#include "wallPolyPatch.H"
#include "refinementParameters.H"
#include "snapParameters.H"
#include "layerParameters.H"
#include "vtkCoordSetWriter.H"
#include "faceSet.H"
#include "motionSmoother.H"
#include "polyTopoChange.H"
#include "uindirectPrimitivePatch.H"
#include "surfZoneIdentifierList.H"
#include "UnsortedMeshedSurface.H"
#include "MeshedSurface.H"
#include "globalIndex.H"
#include "IOmanip.H"
#include "decompositionModel.H"
#include "fvMeshTools.H"
#include "profiling.H"
#include "processorMeshes.H"
#include "snappyVoxelMeshDriver.H"
using namespace Foam;
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
// Convert size (as fraction of defaultCellSize) to refinement level
label sizeCoeffToRefinement
(
const scalar level0Coeff, // ratio of hex cell size v.s. defaultCellSize
const scalar sizeCoeff
)
{
return round(::log(level0Coeff/sizeCoeff)/::log(2));
}
autoPtr<refinementSurfaces> createRefinementSurfaces
(
const searchableSurfaces& allGeometry,
const dictionary& surfacesDict,
const dictionary& shapeControlDict,
const label gapLevelIncrement,
const scalar level0Coeff
)
{
autoPtr<refinementSurfaces> surfacePtr;
// Count number of surfaces.
label surfi = 0;
forAll(allGeometry.names(), geomi)
{
const word& geomName = allGeometry.names()[geomi];
if (surfacesDict.found(geomName))
{
surfi++;
}
}
labelList surfaces(surfi);
wordList names(surfi);
PtrList<surfaceZonesInfo> surfZones(surfi);
labelList regionOffset(surfi);
labelList globalMinLevel(surfi, Zero);
labelList globalMaxLevel(surfi, Zero);
labelList globalLevelIncr(surfi, Zero);
PtrList<dictionary> globalPatchInfo(surfi);
List<Map<label>> regionMinLevel(surfi);
List<Map<label>> regionMaxLevel(surfi);
List<Map<label>> regionLevelIncr(surfi);
List<Map<scalar>> regionAngle(surfi);
List<Map<autoPtr<dictionary>>> regionPatchInfo(surfi);
wordHashSet unmatchedKeys(surfacesDict.toc());
surfi = 0;
forAll(allGeometry.names(), geomi)
{
const word& geomName = allGeometry.names()[geomi];
const entry* ePtr = surfacesDict.findEntry(geomName, keyType::REGEX);
if (ePtr)
{
const dictionary& shapeDict = ePtr->dict();
unmatchedKeys.erase(ePtr->keyword());
names[surfi] = geomName;
surfaces[surfi] = geomi;
const searchableSurface& surface = allGeometry[geomi];
// Find the index in shapeControlDict
// Invert surfaceCellSize to get the refinementLevel
const word scsFuncName =
shapeDict.get<word>("surfaceCellSizeFunction");
const dictionary& scsDict =
shapeDict.optionalSubDict(scsFuncName + "Coeffs");
const scalar surfaceCellSize =
scsDict.get<scalar>("surfaceCellSizeCoeff");
const label refLevel = sizeCoeffToRefinement
(
level0Coeff,
surfaceCellSize
);
globalMinLevel[surfi] = refLevel;
globalMaxLevel[surfi] = refLevel;
globalLevelIncr[surfi] = gapLevelIncrement;
// Surface zones
surfZones.set
(
surfi,
new surfaceZonesInfo
(
surface,
shapeDict,
allGeometry.regionNames()[surfaces[surfi]]
)
);
// Global perpendicular angle
if (shapeDict.found("patchInfo"))
{
globalPatchInfo.set
(
surfi,
shapeDict.subDict("patchInfo").clone()
);
}
// Per region override of patchInfo
if (shapeDict.found("regions"))
{
const dictionary& regionsDict = shapeDict.subDict("regions");
const wordList& regionNames =
allGeometry[surfaces[surfi]].regions();
forAll(regionNames, regioni)
{
if (regionsDict.found(regionNames[regioni]))
{
// Get the dictionary for region
const dictionary& regionDict = regionsDict.subDict
(
regionNames[regioni]
);
if (regionDict.found("patchInfo"))
{
regionPatchInfo[surfi].insert
(
regioni,
regionDict.subDict("patchInfo").clone()
);
}
}
}
}
// Per region override of cellSize
if (shapeDict.found("regions"))
{
const dictionary& shapeControlRegionsDict =
shapeDict.subDict("regions");
const wordList& regionNames =
allGeometry[surfaces[surfi]].regions();
forAll(regionNames, regioni)
{
if (shapeControlRegionsDict.found(regionNames[regioni]))
{
const dictionary& shapeControlRegionDict =
shapeControlRegionsDict.subDict
(
regionNames[regioni]
);
const word scsFuncName =
shapeControlRegionDict.get<word>
(
"surfaceCellSizeFunction"
);
const dictionary& scsDict =
shapeControlRegionDict.subDict
(
scsFuncName + "Coeffs"
);
const scalar surfaceCellSize =
scsDict.get<scalar>("surfaceCellSizeCoeff");
const label refLevel = sizeCoeffToRefinement
(
level0Coeff,
surfaceCellSize
);
regionMinLevel[surfi].insert(regioni, refLevel);
regionMaxLevel[surfi].insert(regioni, refLevel);
regionLevelIncr[surfi].insert(regioni, 0);
}
}
}
surfi++;
}
}
// Calculate local to global region offset
label nRegions = 0;
forAll(surfaces, surfi)
{
regionOffset[surfi] = nRegions;
nRegions += allGeometry[surfaces[surfi]].regions().size();
}
// Rework surface specific information into information per global region
labelList minLevel(nRegions, Zero);
labelList maxLevel(nRegions, Zero);
labelList gapLevel(nRegions, -1);
PtrList<dictionary> patchInfo(nRegions);
forAll(globalMinLevel, surfi)
{
label nRegions = allGeometry[surfaces[surfi]].regions().size();
// Initialise to global (i.e. per surface)
for (label i = 0; i < nRegions; i++)
{
label globalRegioni = regionOffset[surfi] + i;
minLevel[globalRegioni] = globalMinLevel[surfi];
maxLevel[globalRegioni] = globalMaxLevel[surfi];
gapLevel[globalRegioni] =
maxLevel[globalRegioni]
+ globalLevelIncr[surfi];
if (globalPatchInfo.set(surfi))
{
patchInfo.set
(
globalRegioni,
globalPatchInfo[surfi].clone()
);
}
}
// Overwrite with region specific information
forAllConstIters(regionMinLevel[surfi], iter)
{
label globalRegioni = regionOffset[surfi] + iter.key();
minLevel[globalRegioni] = iter();
maxLevel[globalRegioni] = regionMaxLevel[surfi][iter.key()];
gapLevel[globalRegioni] =
maxLevel[globalRegioni]
+ regionLevelIncr[surfi][iter.key()];
}
const Map<autoPtr<dictionary>>& localInfo = regionPatchInfo[surfi];
forAllConstIters(localInfo, iter)
{
label globalRegioni = regionOffset[surfi] + iter.key();
patchInfo.set(globalRegioni, iter()().clone());
}
}
surfacePtr.reset
(
new refinementSurfaces
(
allGeometry,
surfaces,
names,
surfZones,
regionOffset,
minLevel,
maxLevel,
gapLevel,
scalarField(nRegions, -GREAT), //perpendicularAngle,
patchInfo,
false //dryRun
)
);
const refinementSurfaces& rf = surfacePtr();
// Determine maximum region name length
label maxLen = 0;
forAll(rf.surfaces(), surfi)
{
label geomi = rf.surfaces()[surfi];
const wordList& regionNames = allGeometry.regionNames()[geomi];
forAll(regionNames, regioni)
{
maxLen = Foam::max(maxLen, label(regionNames[regioni].size()));
}
}
Info<< setw(maxLen) << "Region"
<< setw(10) << "Min Level"
<< setw(10) << "Max Level"
<< setw(10) << "Gap Level" << nl
<< setw(maxLen) << "------"
<< setw(10) << "---------"
<< setw(10) << "---------"
<< setw(10) << "---------" << endl;
forAll(rf.surfaces(), surfi)
{
label geomi = rf.surfaces()[surfi];
Info<< rf.names()[surfi] << ':' << nl;
const wordList& regionNames = allGeometry.regionNames()[geomi];
forAll(regionNames, regioni)
{
label globali = rf.globalRegion(surfi, regioni);
Info<< setw(maxLen) << regionNames[regioni]
<< setw(10) << rf.minLevel()[globali]
<< setw(10) << rf.maxLevel()[globali]
<< setw(10) << rf.gapLevel()[globali] << endl;
}
}
return surfacePtr;
}
void extractSurface
(
const polyMesh& mesh,
const Time& runTime,
const labelHashSet& includePatches,
const fileName& outFileName
)
{
const polyBoundaryMesh& bMesh = mesh.boundaryMesh();
// Collect sizes. Hash on names to handle local-only patches (e.g.
// processor patches)
HashTable<label> patchSize(1024);
label nFaces = 0;
for (const label patchi : includePatches)
{
const polyPatch& pp = bMesh[patchi];
patchSize.insert(pp.name(), pp.size());
nFaces += pp.size();
}
Pstream::mapCombineGather(patchSize, plusEqOp<label>());
// Allocate zone/patch for all patches
HashTable<label> compactZoneID(1024);
forAllConstIters(patchSize, iter)
{
label sz = compactZoneID.size();
compactZoneID.insert(iter.key(), sz);
}
Pstream::broadcast(compactZoneID);
// Rework HashTable into labelList just for speed of conversion
labelList patchToCompactZone(bMesh.size(), -1);
forAllConstIters(compactZoneID, iter)
{
label patchi = bMesh.findPatchID(iter.key());
if (patchi != -1)
{
patchToCompactZone[patchi] = iter();
}
}
// Collect faces on zones
DynamicList<label> faceLabels(nFaces);
DynamicList<label> compactZones(nFaces);
for (const label patchi : includePatches)
{
const polyPatch& pp = bMesh[patchi];
forAll(pp, i)
{
faceLabels.append(pp.start()+i);
compactZones.append(patchToCompactZone[pp.index()]);
}
}
// Addressing engine for all faces
uindirectPrimitivePatch allBoundary
(
UIndirectList<face>(mesh.faces(), faceLabels),
mesh.points()
);
// Find correspondence to master points
labelList pointToGlobal;
labelList uniqueMeshPoints;
autoPtr<globalIndex> globalNumbers = mesh.globalData().mergePoints
(
allBoundary.meshPoints(),
allBoundary.meshPointMap(),
pointToGlobal,
uniqueMeshPoints
);
// Gather all unique points on master
List<pointField> gatheredPoints(Pstream::nProcs());
gatheredPoints[Pstream::myProcNo()] = pointField
(
mesh.points(),
uniqueMeshPoints
);
Pstream::gatherList(gatheredPoints);
// Gather all faces
List<faceList> gatheredFaces(Pstream::nProcs());
gatheredFaces[Pstream::myProcNo()] = allBoundary.localFaces();
forAll(gatheredFaces[Pstream::myProcNo()], i)
{
inplaceRenumber(pointToGlobal, gatheredFaces[Pstream::myProcNo()][i]);
}
Pstream::gatherList(gatheredFaces);
// Gather all ZoneIDs
List<labelList> gatheredZones(Pstream::nProcs());
gatheredZones[Pstream::myProcNo()].transfer(compactZones);
Pstream::gatherList(gatheredZones);
// On master combine all points, faces, zones
if (Pstream::master())
{
pointField allPoints = ListListOps::combine<pointField>
(
gatheredPoints,
accessOp<pointField>()
);
gatheredPoints.clear();
faceList allFaces = ListListOps::combine<faceList>
(
gatheredFaces,
accessOp<faceList>()
);
gatheredFaces.clear();
labelList allZones = ListListOps::combine<labelList>
(
gatheredZones,
accessOp<labelList>()
);
gatheredZones.clear();
// Zones
surfZoneIdentifierList surfZones(compactZoneID.size());
forAllConstIters(compactZoneID, iter)
{
surfZones[iter()] = surfZoneIdentifier(iter.key(), iter());
Info<< "surfZone " << iter() << " : " << surfZones[iter()].name()
<< endl;
}
UnsortedMeshedSurface<face> unsortedFace
(
std::move(allPoints),
std::move(allFaces),
std::move(allZones),
surfZones
);
MeshedSurface<face> sortedFace(unsortedFace);
fileName globalCasePath
(
runTime.processorCase()
? runTime.globalPath()/outFileName
: runTime.path()/outFileName
);
globalCasePath.clean(); // Remove unneeded ".."
Info<< "Writing merged surface to " << globalCasePath << endl;
sortedFace.write(globalCasePath);
}
}
label checkAlignment(const polyMesh& mesh, const scalar tol, Ostream& os)
{
// Check all edges aligned with one of the coordinate axes
const faceList& faces = mesh.faces();
const pointField& points = mesh.points();
label nUnaligned = 0;
forAll(faces, facei)
{
const face& f = faces[facei];
forAll(f, fp)
{
label fp1 = f.fcIndex(fp);
const linePointRef e(edge(f[fp], f[fp1]).line(points));
const vector v(e.vec());
const scalar magV(mag(v));
if (magV > ROOTVSMALL)
{
for
(
direction dir = 0;
dir < pTraits<vector>::nComponents;
++dir
)
{
const scalar s(mag(v[dir]));
if (s > magV*tol && s < magV*(1-tol))
{
++nUnaligned;
break;
}
}
}
}
}
reduce(nUnaligned, sumOp<label>());
if (nUnaligned)
{
os << "Initial mesh has " << nUnaligned
<< " edges unaligned with any of the coordinate axes" << nl << endl;
}
return nUnaligned;
}
// Check writing tolerance before doing any serious work
scalar getMergeDistance
(
const polyMesh& mesh,
const scalar mergeTol,
const bool dryRun
)
{
const boundBox& meshBb = mesh.bounds();
scalar mergeDist = mergeTol * meshBb.mag();
Info<< nl
<< "Overall mesh bounding box : " << meshBb << nl
<< "Relative tolerance : " << mergeTol << nl
<< "Absolute matching distance : " << mergeDist << nl
<< endl;
// check writing tolerance
if (mesh.time().writeFormat() == IOstream::ASCII && !dryRun)
{
const scalar writeTol = std::pow
(
scalar(10),
-scalar(IOstream::defaultPrecision())
);
if (mergeTol < writeTol)
{
FatalErrorInFunction
<< "Your current settings specify ASCII writing with "
<< IOstream::defaultPrecision() << " digits precision." << nl
<< "Your merging tolerance (" << mergeTol
<< ") is finer than this." << nl
<< "Change to binary writeFormat, "
<< "or increase the writePrecision" << endl
<< "or adjust the merge tolerance (mergeTol)."
<< exit(FatalError);
}
}
return mergeDist;
}
void removeZeroSizedPatches(fvMesh& mesh)
{
// Remove any zero-sized ones. Assumes
// - processor patches are already only there if needed
// - all other patches are available on all processors
// - but coupled ones might still be needed, even if zero-size
// (e.g. processorCyclic)
// See also logic in createPatch.
const polyBoundaryMesh& pbm = mesh.boundaryMesh();
labelList oldToNew(pbm.size(), -1);
label newPatchi = 0;
forAll(pbm, patchi)
{
const polyPatch& pp = pbm[patchi];
if (!isA<processorPolyPatch>(pp))
{
if
(
isA<coupledPolyPatch>(pp)
|| returnReduce(pp.size(), sumOp<label>())
)
{
// Coupled (and unknown size) or uncoupled and used
oldToNew[patchi] = newPatchi++;
}
}
}
forAll(pbm, patchi)
{
const polyPatch& pp = pbm[patchi];
if (isA<processorPolyPatch>(pp))
{
oldToNew[patchi] = newPatchi++;
}
}
const label nKeepPatches = newPatchi;
// Shuffle unused ones to end
if (nKeepPatches != pbm.size())
{
Info<< endl
<< "Removing zero-sized patches:" << endl << incrIndent;
forAll(oldToNew, patchi)
{
if (oldToNew[patchi] == -1)
{
Info<< indent << pbm[patchi].name()
<< " type " << pbm[patchi].type()
<< " at position " << patchi << endl;
oldToNew[patchi] = newPatchi++;
}
}
Info<< decrIndent;
fvMeshTools::reorderPatches(mesh, oldToNew, nKeepPatches, true);
Info<< endl;
}
}
// Write mesh and additional information
void writeMesh
(
const string& msg,
const meshRefinement& meshRefiner,
const meshRefinement::debugType debugLevel,
const meshRefinement::writeType writeLevel
)
{
const fvMesh& mesh = meshRefiner.mesh();
meshRefiner.printMeshInfo(debugLevel, msg);
Info<< "Writing mesh to time " << meshRefiner.timeName() << endl;
processorMeshes::removeFiles(mesh);
if (!debugLevel && !(writeLevel&meshRefinement::WRITELAYERSETS))
{
topoSet::removeFiles(mesh);
}
refinementHistory::removeFiles(mesh);
meshRefiner.write
(
debugLevel,
meshRefinement::writeType(writeLevel | meshRefinement::WRITEMESH),
mesh.time().path()/meshRefiner.timeName()
);
Info<< "Wrote mesh in = "
<< mesh.time().cpuTimeIncrement() << " s." << endl;
}
int main(int argc, char *argv[])
{
argList::addNote
(
"Automatic split hex mesher. Refines and snaps to surface"
);
#include "addRegionOption.H"
#include "addOverwriteOption.H"
#include "addProfilingOption.H"
argList::addBoolOption
(
"checkGeometry",
"Check all surface geometry for quality"
);
argList::addDryRunOption
(
"Check case set-up only using a single time step"
);
argList::addOption
(
"surfaceSimplify",
"boundBox",
"Simplify the surface using snappyHexMesh starting from a boundBox"
);
argList::addOption
(
"patches",
"(patch0 .. patchN)",
"Only triangulate selected patches (wildcards supported)"
);
argList::addOption
(
"outFile",
"file",
"Name of the file to save the simplified surface to"
);
argList::addOption("dict", "file", "Alternative snappyHexMeshDict");
argList::noFunctionObjects(); // Never use function objects
#include "setRootCase.H"
#include "createTime.H"
const bool overwrite = args.found("overwrite");
const bool checkGeometry = args.found("checkGeometry");
const bool surfaceSimplify = args.found("surfaceSimplify");
const bool dryRun = args.dryRun();
if (dryRun)
{
Info<< "Operating in dry-run mode to detect set-up errors"
<< nl << endl;
}
#include "createNamedMesh.H"
Info<< "Read mesh in = "
<< runTime.cpuTimeIncrement() << " s" << endl;
// Check patches and faceZones are synchronised
mesh.boundaryMesh().checkParallelSync(true);
meshRefinement::checkCoupledFaceZones(mesh);
if (dryRun)
{
// Check if mesh aligned with cartesian axes
checkAlignment(mesh, 1e-6, Pout); //FatalIOError);
}
// Read meshing dictionary
const word dictName("snappyHexMeshDict");
#include "setSystemMeshDictionaryIO.H"
const IOdictionary meshDict(dictIO);
// all surface geometry
const dictionary& geometryDict =
meshRefinement::subDict(meshDict, "geometry", dryRun);
// refinement parameters
const dictionary& refineDict =
meshRefinement::subDict(meshDict, "castellatedMeshControls", dryRun);
// mesh motion and mesh quality parameters
const dictionary& motionDict =
meshRefinement::subDict(meshDict, "meshQualityControls", dryRun);
// snap-to-surface parameters
const dictionary& snapDict =
meshRefinement::subDict(meshDict, "snapControls", dryRun);
// layer addition parameters
const dictionary& layerDict =
meshRefinement::subDict(meshDict, "addLayersControls", dryRun);
// absolute merge distance
const scalar mergeDist = getMergeDistance
(
mesh,
meshRefinement::get<scalar>
(
meshDict,
"mergeTolerance",
dryRun
),
dryRun
);
const bool keepPatches(meshDict.getOrDefault("keepPatches", false));
// Writer for writing lines
autoPtr<coordSetWriter> setFormatter;
{
const word setFormat
(
meshDict.getOrDefault<word>
(
"setFormat",
coordSetWriters::vtkWriter::typeName // Default: "vtk"
)
);
setFormatter = coordSetWriter::New
(
setFormat,
meshDict.subOrEmptyDict("formatOptions").optionalSubDict(setFormat)
);
}
const scalar maxSizeRatio
(
meshDict.getOrDefault<scalar>("maxSizeRatio", 100)
);
// Read decomposePar dictionary
dictionary decomposeDict;
if (Pstream::parRun())
{
// Ensure demand-driven decompositionMethod finds alternative
// decomposeParDict location properly.
IOdictionary* dictPtr = new IOdictionary
(
IOobject::selectIO
(
IOobject
(
decompositionModel::canonicalName,
runTime.system(),
runTime,
IOobject::MUST_READ,
IOobject::NO_WRITE
),
args.getOrDefault<fileName>("decomposeParDict", "")
)
);
// Store it on the object registry, but to be found it must also
// have the expected "decomposeParDict" name.
dictPtr->rename(decompositionModel::canonicalName);
runTime.store(dictPtr);
decomposeDict = *dictPtr;
}
else
{
decomposeDict.add("method", "none");
decomposeDict.add("numberOfSubdomains", 1);
}
// Debug
// ~~~~~
// Set debug level
meshRefinement::debugType debugLevel = meshRefinement::debugType
(
meshDict.getOrDefault<label>
(
"debug",
0
)
);
{
wordList flags;
if (meshDict.readIfPresent("debugFlags", flags))
{
debugLevel = meshRefinement::debugType
(
meshRefinement::readFlags
(
meshRefinement::debugTypeNames,
flags
)
);
}
}
if (debugLevel > 0)
{
meshRefinement::debug = debugLevel;
snappyRefineDriver::debug = debugLevel;
snappySnapDriver::debug = debugLevel;
snappyLayerDriver::debug = debugLevel;
}
// Set file writing level
{
wordList flags;
if (meshDict.readIfPresent("writeFlags", flags))
{
meshRefinement::writeLevel
(
meshRefinement::writeType
(
meshRefinement::readFlags
(
meshRefinement::writeTypeNames,
flags
)
)
);
}
}
//// Set output level
//{
// wordList flags;
// if (meshDict.readIfPresent("outputFlags", flags))
// {
// meshRefinement::outputLevel
// (
// meshRefinement::outputType
// (
// meshRefinement::readFlags
// (
// meshRefinement::outputTypeNames,
// flags
// )
// )
// );
// }
//}
// for the impatient who want to see some output files:
profiling::writeNow();
// Read geometry
// ~~~~~~~~~~~~~
searchableSurfaces allGeometry
(
IOobject
(
"abc", // dummy name
mesh.time().constant(), // instance
//mesh.time().findInstance("triSurface", word::null),// instance
"triSurface", // local
mesh.time(), // registry
IOobject::MUST_READ,
IOobject::NO_WRITE
),
geometryDict,
meshDict.getOrDefault("singleRegionName", true)
);
// Read refinement surfaces
// ~~~~~~~~~~~~~~~~~~~~~~~~
autoPtr<refinementSurfaces> surfacesPtr;
Info<< "Reading refinement surfaces." << endl;
if (surfaceSimplify)
{
addProfiling(surfaceSimplify, "snappyHexMesh::surfaceSimplify");
IOdictionary foamyHexMeshDict
(
IOobject
(
"foamyHexMeshDict",
runTime.system(),
runTime,
IOobject::MUST_READ_IF_MODIFIED,
IOobject::NO_WRITE
)
);
const dictionary& conformationDict =
foamyHexMeshDict.subDict("surfaceConformation").subDict
(
"geometryToConformTo"
);
const dictionary& motionDict =
foamyHexMeshDict.subDict("motionControl");
const dictionary& shapeControlDict =
motionDict.subDict("shapeControlFunctions");
// Calculate current ratio of hex cells v.s. wanted cell size
const scalar defaultCellSize =
motionDict.get<scalar>("defaultCellSize");
const scalar initialCellSize = ::pow(mesh.V()[0], 1.0/3.0);
//Info<< "Wanted cell size = " << defaultCellSize << endl;
//Info<< "Current cell size = " << initialCellSize << endl;
//Info<< "Fraction = " << initialCellSize/defaultCellSize
// << endl;
surfacesPtr =
createRefinementSurfaces
(
allGeometry,
conformationDict,
shapeControlDict,
refineDict.getOrDefault("gapLevelIncrement", 0),
initialCellSize/defaultCellSize
);
profiling::writeNow();
}
else
{
surfacesPtr.reset
(
new refinementSurfaces
(
allGeometry,
meshRefinement::subDict
(
refineDict,
"refinementSurfaces",
dryRun
),
refineDict.getOrDefault("gapLevelIncrement", 0),
dryRun
)
);
Info<< "Read refinement surfaces in = "
<< mesh.time().cpuTimeIncrement() << " s" << nl << endl;
}
refinementSurfaces& surfaces = surfacesPtr();
// Checking only?
if (checkGeometry)
{
// Check geometry amongst itself (e.g. intersection, size differences)
// Extract patchInfo
List<wordList> patchTypes(allGeometry.size());
const PtrList<dictionary>& patchInfo = surfaces.patchInfo();
const labelList& surfaceGeometry = surfaces.surfaces();
forAll(surfaceGeometry, surfi)
{
label geomi = surfaceGeometry[surfi];
const wordList& regNames = allGeometry.regionNames()[geomi];
patchTypes[geomi].setSize(regNames.size());
forAll(regNames, regioni)
{
label globalRegioni = surfaces.globalRegion(surfi, regioni);
if (patchInfo.set(globalRegioni))
{
patchTypes[geomi][regioni] =
meshRefinement::get<word>
(
patchInfo[globalRegioni],
"type",
dryRun,
keyType::REGEX,
word::null
);
}
else
{
patchTypes[geomi][regioni] = wallPolyPatch::typeName;
}
}
}
// Write some stats
allGeometry.writeStats(patchTypes, Info);
// Check topology
allGeometry.checkTopology(true);
// Check geometry
allGeometry.checkGeometry
(
maxSizeRatio, // max size ratio
1e-9, // intersection tolerance
setFormatter,
0.01, // min triangle quality
true
);
if (!dryRun)
{
return 0;
}
}
if (dryRun)
{
// Check geometry to mesh bounding box
Info<< "Checking for geometry size relative to mesh." << endl;
const boundBox& meshBb = mesh.bounds();
forAll(allGeometry, geomi)
{
const searchableSurface& s = allGeometry[geomi];
const boundBox& bb = s.bounds();
scalar ratio = bb.mag() / meshBb.mag();
if (ratio > maxSizeRatio || ratio < 1.0/maxSizeRatio)
{
Warning
<< " " << allGeometry.names()[geomi]
<< " bounds differ from mesh"
<< " by more than a factor " << maxSizeRatio << ":" << nl
<< " bounding box : " << bb << nl
<< " mesh bounding box : " << meshBb
<< endl;
}
if (!meshBb.contains(bb))
{
Warning
<< " " << allGeometry.names()[geomi]
<< " bounds not fully contained in mesh" << nl
<< " bounding box : " << bb << nl
<< " mesh bounding box : " << meshBb
<< endl;
}
}
Info<< endl;
}
// Read refinement shells
// ~~~~~~~~~~~~~~~~~~~~~~
Info<< "Reading refinement shells." << endl;
shellSurfaces shells
(
allGeometry,
meshRefinement::subDict(refineDict, "refinementRegions", dryRun),
dryRun
);
Info<< "Read refinement shells in = "
<< mesh.time().cpuTimeIncrement() << " s" << nl << endl;
Info<< "Setting refinement level of surface to be consistent"
<< " with shells." << endl;
surfaces.setMinLevelFields(shells);
Info<< "Checked shell refinement in = "
<< mesh.time().cpuTimeIncrement() << " s" << nl << endl;
// Optionally read limit shells
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
const dictionary limitDict(refineDict.subOrEmptyDict("limitRegions"));
if (!limitDict.empty())
{
Info<< "Reading limit shells." << endl;
}
shellSurfaces limitShells(allGeometry, limitDict, dryRun);
if (!limitDict.empty())
{
Info<< "Read limit shells in = "
<< mesh.time().cpuTimeIncrement() << " s" << nl << endl;
}
if (dryRun)
{
// Check for use of all geometry
const wordList& allGeomNames = allGeometry.names();
labelHashSet unusedGeometries(identity(allGeomNames.size()));
unusedGeometries.erase(surfaces.surfaces());
unusedGeometries.erase(shells.shells());
unusedGeometries.erase(limitShells.shells());
if (unusedGeometries.size())
{
IOWarningInFunction(geometryDict)
<< "The following geometry entries are not used:" << nl;
for (const label geomi : unusedGeometries)
{
Info<< " " << allGeomNames[geomi] << nl;
}
Info<< endl;
}
}
// Read feature meshes
// ~~~~~~~~~~~~~~~~~~~
Info<< "Reading features." << endl;
refinementFeatures features
(
mesh,
PtrList<dictionary>
(
meshRefinement::lookup(refineDict, "features", dryRun)
),
dryRun
);
Info<< "Read features in = "
<< mesh.time().cpuTimeIncrement() << " s" << nl << endl;
if (dryRun)
{
// Check geometry to mesh bounding box
Info<< "Checking for line geometry size relative to surface geometry."
<< endl;
OStringStream os;
bool hasErrors = features.checkSizes
(
maxSizeRatio, //const scalar maxRatio,
mesh.bounds(),
true, //const bool report,
os //FatalIOError
);
if (hasErrors)
{
Warning<< os.str() << endl;
}
}
// Refinement engine
// ~~~~~~~~~~~~~~~~~
Info<< nl
<< "Determining initial surface intersections" << nl
<< "-----------------------------------------" << nl
<< endl;
// Main refinement engine
meshRefinement meshRefiner
(
mesh,
mergeDist, // tolerance used in sorting coordinates
overwrite, // overwrite mesh files?
surfaces, // for surface intersection refinement
features, // for feature edges/point based refinement
shells, // for volume (inside/outside) refinement
limitShells, // limit of volume refinement
labelList(), // initial faces to test
dryRun
);
if (!dryRun)
{
meshRefiner.updateIntersections(identity(mesh.nFaces()));
Info<< "Calculated surface intersections in = "
<< mesh.time().cpuTimeIncrement() << " s" << nl << endl;
}
// Some stats
meshRefiner.printMeshInfo(debugLevel, "Initial mesh");
meshRefiner.write
(
meshRefinement::debugType(debugLevel&meshRefinement::OBJINTERSECTIONS),
meshRefinement::writeType(0),
mesh.time().path()/meshRefiner.timeName()
);
// Refinement parameters
const refinementParameters refineParams(refineDict, dryRun);
// Snap parameters
const snapParameters snapParams(snapDict, dryRun);
// Add all the cellZones and faceZones
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 1. cellZones relating to surface (faceZones added later)
const labelList namedSurfaces
(
surfaceZonesInfo::getNamedSurfaces(surfaces.surfZones())
);
labelList surfaceToCellZone = surfaceZonesInfo::addCellZonesToMesh
(
surfaces.surfZones(),
namedSurfaces,
mesh
);
// 2. cellZones relating to locations
refineParams.addCellZonesToMesh(mesh);
// Add all the surface regions as patches
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//- Global surface region to patch (non faceZone surface) or patches
// (faceZone surfaces)
labelList globalToMasterPatch;
labelList globalToSlavePatch;
{
Info<< nl
<< "Adding patches for surface regions" << nl
<< "----------------------------------" << nl
<< endl;
// From global region number to mesh patch.
globalToMasterPatch.setSize(surfaces.nRegions(), -1);
globalToSlavePatch.setSize(surfaces.nRegions(), -1);
if (!dryRun)
{
Info<< setf(ios_base::left)
<< setw(6) << "Patch"
<< setw(20) << "Type"
<< setw(30) << "Region" << nl
<< setw(6) << "-----"
<< setw(20) << "----"
<< setw(30) << "------" << endl;
}
const labelList& surfaceGeometry = surfaces.surfaces();
const PtrList<dictionary>& surfacePatchInfo = surfaces.patchInfo();
const polyBoundaryMesh& pbm = mesh.boundaryMesh();
forAll(surfaceGeometry, surfi)
{
label geomi = surfaceGeometry[surfi];
const wordList& regNames = allGeometry.regionNames()[geomi];
if (!dryRun)
{
Info<< surfaces.names()[surfi] << ':' << nl << nl;
}
const wordList& fzNames =
surfaces.surfZones()[surfi].faceZoneNames();
if (fzNames.empty())
{
// 'Normal' surface
forAll(regNames, i)
{
label globalRegioni = surfaces.globalRegion(surfi, i);
label patchi;
if (surfacePatchInfo.set(globalRegioni))
{
patchi = meshRefiner.addMeshedPatch
(
regNames[i],
surfacePatchInfo[globalRegioni]
);
}
else
{
dictionary patchInfo;
patchInfo.set("type", wallPolyPatch::typeName);
patchi = meshRefiner.addMeshedPatch
(
regNames[i],
patchInfo
);
}
if (!dryRun)
{
Info<< setf(ios_base::left)
<< setw(6) << patchi
<< setw(20) << pbm[patchi].type()
<< setw(30) << regNames[i] << nl;
}
globalToMasterPatch[globalRegioni] = patchi;
globalToSlavePatch[globalRegioni] = patchi;
}
}
else
{
// Zoned surface
forAll(regNames, i)
{
label globalRegioni = surfaces.globalRegion(surfi, i);
// Add master side patch
{
label patchi;
if (surfacePatchInfo.set(globalRegioni))
{
patchi = meshRefiner.addMeshedPatch
(
regNames[i],
surfacePatchInfo[globalRegioni]
);
}
else
{
dictionary patchInfo;
patchInfo.set("type", wallPolyPatch::typeName);
patchi = meshRefiner.addMeshedPatch
(
regNames[i],
patchInfo
);
}
if (!dryRun)
{
Info<< setf(ios_base::left)
<< setw(6) << patchi
<< setw(20) << pbm[patchi].type()
<< setw(30) << regNames[i] << nl;
}
globalToMasterPatch[globalRegioni] = patchi;
}
// Add slave side patch
{
const word slaveName = regNames[i] + "_slave";
label patchi;
if (surfacePatchInfo.set(globalRegioni))
{
patchi = meshRefiner.addMeshedPatch
(
slaveName,
surfacePatchInfo[globalRegioni]
);
}
else
{
dictionary patchInfo;
patchInfo.set("type", wallPolyPatch::typeName);
patchi = meshRefiner.addMeshedPatch
(
slaveName,
patchInfo
);
}
if (!dryRun)
{
Info<< setf(ios_base::left)
<< setw(6) << patchi
<< setw(20) << pbm[patchi].type()
<< setw(30) << slaveName << nl;
}
globalToSlavePatch[globalRegioni] = patchi;
}
}
// For now: have single faceZone per surface. Use first
// region in surface for patch for zoning
if (regNames.size())
{
forAll(fzNames, fzi)
{
const word& fzName = fzNames[fzi];
label globalRegioni = surfaces.globalRegion(surfi, fzi);
meshRefiner.addFaceZone
(
fzName,
pbm[globalToMasterPatch[globalRegioni]].name(),
pbm[globalToSlavePatch[globalRegioni]].name(),
surfaces.surfZones()[surfi].faceType()
);
}
}
}
if (!dryRun)
{
Info<< nl;
}
}
Info<< "Added patches in = "
<< mesh.time().cpuTimeIncrement() << " s" << nl << endl;
}
// Add all information for all the remaining faceZones
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
HashTable<Pair<word>> faceZoneToPatches;
forAll(mesh.faceZones(), zonei)
{
const word& fzName = mesh.faceZones()[zonei].name();
label mpI, spI;
surfaceZonesInfo::faceZoneType fzType;
bool hasInfo = meshRefiner.getFaceZoneInfo(fzName, mpI, spI, fzType);
if (!hasInfo)
{
// faceZone does not originate from a surface but presumably
// from a cellZone pair instead
string::size_type i = fzName.find("_to_");
if (i != string::npos)
{
word cz0 = fzName.substr(0, i);
word cz1 = fzName.substr(i+4, fzName.size()-i+4);
word slaveName(cz1 + "_to_" + cz0);
faceZoneToPatches.insert(fzName, Pair<word>(fzName, slaveName));
}
else
{
// Add as fzName + fzName_slave
const word slaveName = fzName + "_slave";
faceZoneToPatches.insert(fzName, Pair<word>(fzName, slaveName));
}
}
}
if (faceZoneToPatches.size())
{
snappyRefineDriver::addFaceZones
(
meshRefiner,
refineParams,
faceZoneToPatches
);
}
// Re-do intersections on meshed boundaries since they use an extrapolated
// other side
{
const labelList adaptPatchIDs(meshRefiner.meshedPatches());
const polyBoundaryMesh& pbm = mesh.boundaryMesh();
label nFaces = 0;
forAll(adaptPatchIDs, i)
{
nFaces += pbm[adaptPatchIDs[i]].size();
}
labelList faceLabels(nFaces);
nFaces = 0;
forAll(adaptPatchIDs, i)
{
const polyPatch& pp = pbm[adaptPatchIDs[i]];
forAll(pp, i)
{
faceLabels[nFaces++] = pp.start()+i;
}
}
meshRefiner.updateIntersections(faceLabels);
}
// Parallel
// ~~~~~~~~
// Construct decomposition engine. Note: cannot use decompositionModel
// MeshObject since we're clearing out the mesh inside the mesh generation.
autoPtr<decompositionMethod> decomposerPtr
(
decompositionMethod::New
(
decomposeDict
)
);
decompositionMethod& decomposer = *decomposerPtr;
if (Pstream::parRun() && !decomposer.parallelAware())
{
FatalErrorInFunction
<< "You have selected decomposition method "
<< decomposer.typeName
<< " which is not parallel aware." << endl
<< "Please select one that is (hierarchical, ptscotch)"
<< exit(FatalError);
}
// Mesh distribution engine (uses tolerance to reconstruct meshes)
fvMeshDistribute distributor(mesh);
// Now do the real work -refinement -snapping -layers
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
const bool wantRefine
(
meshRefinement::get<bool>(meshDict, "castellatedMesh", dryRun)
);
const bool wantSnap
(
meshRefinement::get<bool>(meshDict, "snap", dryRun)
);
const bool wantLayers
(
meshRefinement::get<bool>(meshDict, "addLayers", dryRun)
);
if (dryRun)
{
string errorMsg(FatalError.message());
string IOerrorMsg(FatalIOError.message());
if (errorMsg.size() || IOerrorMsg.size())
{
//errorMsg = "[dryRun] " + errorMsg;
//errorMsg.replaceAll("\n", "\n[dryRun] ");
//IOerrorMsg = "[dryRun] " + IOerrorMsg;
//IOerrorMsg.replaceAll("\n", "\n[dryRun] ");
Warning
<< nl
<< "Missing/incorrect required dictionary entries:" << nl
<< nl
<< IOerrorMsg.c_str() << nl
<< errorMsg.c_str() << nl << nl
<< "Exiting dry-run" << nl << endl;
FatalError.clear();
FatalIOError.clear();
return 0;
}
}
// How to treat co-planar faces
meshRefinement::FaceMergeType mergeType =
meshRefinement::FaceMergeType::GEOMETRIC;
{
const bool mergePatchFaces
(
meshDict.getOrDefault("mergePatchFaces", true)
);
if (!mergePatchFaces)
{
Info<< "Not merging patch-faces of cell to preserve"
<< " (split)hex cell shape."
<< nl << endl;
mergeType = meshRefinement::FaceMergeType::NONE;
}
else
{
const bool mergeAcrossPatches
(
meshDict.getOrDefault("mergeAcrossPatches", false)
);
if (mergeAcrossPatches)
{
Info<< "Merging co-planar patch-faces of cells"
<< ", regardless of patch assignment"
<< nl << endl;
mergeType = meshRefinement::FaceMergeType::IGNOREPATCH;
}
}
}
if (wantRefine)
{
cpuTime timer;
snappyRefineDriver refineDriver
(
meshRefiner,
decomposer,
distributor,
globalToMasterPatch,
globalToSlavePatch,
setFormatter(),
dryRun
);
if (!overwrite && !debugLevel)
{
const_cast<Time&>(mesh.time())++;
}
refineDriver.doRefine
(
refineDict,
refineParams,
snapParams,
refineParams.handleSnapProblems(),
mergeType,
motionDict
);
// Remove zero sized patches originating from faceZones
if (!keepPatches && !wantSnap && !wantLayers)
{
fvMeshTools::removeEmptyPatches(mesh, true);
}
if (!dryRun)
{
writeMesh
(
"Refined mesh",
meshRefiner,
debugLevel,
meshRefinement::writeLevel()
);
}
Info<< "Mesh refined in = "
<< timer.cpuTimeIncrement() << " s." << endl;
profiling::writeNow();
}
if (wantSnap)
{
cpuTime timer;
snappySnapDriver snapDriver
(
meshRefiner,
globalToMasterPatch,
globalToSlavePatch,
dryRun
);
if (!overwrite && !debugLevel)
{
const_cast<Time&>(mesh.time())++;
}
// Use the resolveFeatureAngle from the refinement parameters
scalar curvature = refineParams.curvature();
scalar planarAngle = refineParams.planarAngle();
snapDriver.doSnap
(
snapDict,
motionDict,
mergeType,
curvature,
planarAngle,
snapParams
);
// Remove zero sized patches originating from faceZones
if (!keepPatches && !wantLayers)
{
fvMeshTools::removeEmptyPatches(mesh, true);
}
if (!dryRun)
{
writeMesh
(
"Snapped mesh",
meshRefiner,
debugLevel,
meshRefinement::writeLevel()
);
}
Info<< "Mesh snapped in = "
<< timer.cpuTimeIncrement() << " s." << endl;
profiling::writeNow();
}
if (wantLayers)
{
cpuTime timer;
// Layer addition parameters
const layerParameters layerParams
(
layerDict,
mesh.boundaryMesh(),
dryRun
);
snappyLayerDriver layerDriver
(
meshRefiner,
globalToMasterPatch,
globalToSlavePatch,
dryRun
);
// Use the maxLocalCells from the refinement parameters
bool preBalance = returnReduce
(
(mesh.nCells() >= refineParams.maxLocalCells()),
orOp<bool>()
);
if (!overwrite && !debugLevel)
{
const_cast<Time&>(mesh.time())++;
}
layerDriver.doLayers
(
layerDict,
motionDict,
layerParams,
mergeType,
preBalance,
decomposer,
distributor
);
// Remove zero sized patches originating from faceZones
if (!keepPatches)
{
fvMeshTools::removeEmptyPatches(mesh, true);
}
if (!dryRun)
{
writeMesh
(
"Layer mesh",
meshRefiner,
debugLevel,
meshRefinement::writeLevel()
);
}
Info<< "Layers added in = "
<< timer.cpuTimeIncrement() << " s." << endl;
profiling::writeNow();
}
{
addProfiling(checkMesh, "snappyHexMesh::checkMesh");
// Check final mesh
Info<< "Checking final mesh ..." << endl;
faceSet wrongFaces(mesh, "wrongFaces", mesh.nFaces()/100);
motionSmoother::checkMesh(false, mesh, motionDict, wrongFaces, dryRun);
const label nErrors = returnReduce
(
wrongFaces.size(),
sumOp<label>()
);
if (nErrors > 0)
{
Info<< "Finished meshing with " << nErrors << " illegal faces"
<< " (concave, zero area or negative cell pyramid volume)"
<< endl;
wrongFaces.write();
}
else
{
Info<< "Finished meshing without any errors" << endl;
}
profiling::writeNow();
}
if (surfaceSimplify)
{
addProfiling(surfaceSimplify, "snappyHexMesh::surfaceSimplify");
const polyBoundaryMesh& bMesh = mesh.boundaryMesh();
labelHashSet includePatches(bMesh.size());
if (args.found("patches"))
{
includePatches = bMesh.patchSet
(
args.getList<wordRe>("patches")
);
}
else
{
forAll(bMesh, patchi)
{
const polyPatch& patch = bMesh[patchi];
if (!isA<processorPolyPatch>(patch))
{
includePatches.insert(patchi);
}
}
}
fileName outFileName
(
args.getOrDefault<fileName>
(
"outFile",
"constant/triSurface/simplifiedSurface.stl"
)
);
extractSurface
(
mesh,
runTime,
includePatches,
outFileName
);
pointIOField cellCentres
(
IOobject
(
"internalCellCentres",
runTime.timeName(),
mesh,
IOobject::NO_READ,
IOobject::AUTO_WRITE
),
mesh.cellCentres()
);
cellCentres.write();
}
profiling::writeNow();
Info<< "Finished meshing in = "
<< runTime.elapsedCpuTime() << " s." << endl;
if (dryRun)
{
string errorMsg(FatalError.message());
string IOerrorMsg(FatalIOError.message());
if (errorMsg.size() || IOerrorMsg.size())
{
//errorMsg = "[dryRun] " + errorMsg;
//errorMsg.replaceAll("\n", "\n[dryRun] ");
//IOerrorMsg = "[dryRun] " + IOerrorMsg;
//IOerrorMsg.replaceAll("\n", "\n[dryRun] ");
Perr<< nl
<< "Missing/incorrect required dictionary entries:" << nl
<< nl
<< IOerrorMsg.c_str() << nl
<< errorMsg.c_str() << nl << nl
<< "Exiting dry-run" << nl << endl;
FatalError.clear();
FatalIOError.clear();
return 0;
}
}
Info<< "End\n" << endl;
return 0;
}
// ************************************************************************* //