/*---------------------------------------------------------------------------*\
========= |
\\ / F ield | foam-extend: Open Source CFD
\\ / O peration | Version: 3.2
\\ / A nd | Web: http://www.foam-extend.org
\\/ M anipulation | For copyright notice see file Copyright
-------------------------------------------------------------------------------
License
This file is part of foam-extend.
foam-extend 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.
foam-extend 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 foam-extend. If not, see .
Description
Utility to refine cells near to a surface.
\*---------------------------------------------------------------------------*/
#include "argList.H"
#include "objectRegistry.H"
#include "Time.H"
#include "polyMesh.H"
#include "twoDPointCorrector.H"
#include "OFstream.H"
#include "multiDirRefinement.H"
#include "triSurface.H"
#include "triSurfaceSearch.H"
#include "cellSet.H"
#include "pointSet.H"
#include "surfaceToCell.H"
#include "surfaceToPoint.H"
#include "cellToPoint.H"
#include "pointToCell.H"
#include "cellToCell.H"
#include "surfaceSets.H"
#include "directTopoChange.H"
#include "mapPolyMesh.H"
#include "labelIOList.H"
#include "emptyPolyPatch.H"
#include "removeCells.H"
#include "meshSearch.H"
using namespace Foam;
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
// Max cos angle for edges to be considered aligned with axis.
static const scalar edgeTol = 1E-3;
void writeSet(const cellSet& cells, const string& msg)
{
Info<< "Writing " << msg << " (" << cells.size() << ") to cellSet "
<< cells.instance()/cells.local()/cells.name()
<< endl;
cells.write();
}
direction getNormalDir(const twoDPointCorrector* correct2DPtr)
{
direction dir = 3;
if (correct2DPtr)
{
const vector& normal = correct2DPtr->planeNormal();
if (mag(normal & vector(1, 0, 0)) > 1-edgeTol)
{
dir = 0;
}
else if (mag(normal & vector(0, 1, 0)) > 1-edgeTol)
{
dir = 1;
}
else if (mag(normal & vector(0, 0, 1)) > 1-edgeTol)
{
dir = 2;
}
}
return dir;
}
// Calculate some edge statistics on mesh. Return min. edge length over all
// directions but exclude component (0=x, 1=y, 2=z, other=none)
scalar getEdgeStats(const primitiveMesh& mesh, const direction excludeCmpt)
{
label nX = 0;
label nY = 0;
label nZ = 0;
scalar minX = GREAT;
scalar maxX = -GREAT;
vector x(1, 0, 0);
scalar minY = GREAT;
scalar maxY = -GREAT;
vector y(0, 1, 0);
scalar minZ = GREAT;
scalar maxZ = -GREAT;
vector z(0, 0, 1);
scalar minOther = GREAT;
scalar maxOther = -GREAT;
const edgeList& edges = mesh.edges();
forAll(edges, edgeI)
{
const edge& e = edges[edgeI];
vector eVec(e.vec(mesh.points()));
scalar eMag = mag(eVec);
eVec /= eMag;
if (mag(eVec & x) > 1-edgeTol)
{
minX = min(minX, eMag);
maxX = max(maxX, eMag);
nX++;
}
else if (mag(eVec & y) > 1-edgeTol)
{
minY = min(minY, eMag);
maxY = max(maxY, eMag);
nY++;
}
else if (mag(eVec & z) > 1-edgeTol)
{
minZ = min(minZ, eMag);
maxZ = max(maxZ, eMag);
nZ++;
}
else
{
minOther = min(minOther, eMag);
maxOther = max(maxOther, eMag);
}
}
Info<< "Mesh bounding box:" << boundBox(mesh.points()) << nl << nl
<< "Mesh edge statistics:" << nl
<< " x aligned : number:" << nX << "\tminLen:" << minX
<< "\tmaxLen:" << maxX << nl
<< " y aligned : number:" << nY << "\tminLen:" << minY
<< "\tmaxLen:" << maxY << nl
<< " z aligned : number:" << nZ << "\tminLen:" << minZ
<< "\tmaxLen:" << maxZ << nl
<< " other : number:" << mesh.nEdges() - nX - nY - nZ
<< "\tminLen:" << minOther
<< "\tmaxLen:" << maxOther << nl << endl;
if (excludeCmpt == 0)
{
return min(minY, min(minZ, minOther));
}
else if (excludeCmpt == 1)
{
return min(minX, min(minZ, minOther));
}
else if (excludeCmpt == 2)
{
return min(minX, min(minY, minOther));
}
else
{
return min(minX, min(minY, min(minZ, minOther)));
}
}
// Adds empty patch if not yet there. Returns patchID.
label addPatch(polyMesh& mesh, const word& patchName)
{
label patchI = mesh.boundaryMesh().findPatchID(patchName);
if (patchI == -1)
{
const polyBoundaryMesh& patches = mesh.boundaryMesh();
List newPatches(patches.size() + 1);
// Add empty patch as 0th entry (Note: only since subsetMesh wants this)
patchI = 0;
newPatches[patchI] =
new emptyPolyPatch
(
Foam::word(patchName),
0,
mesh.nInternalFaces(),
patchI,
patches
);
forAll(patches, i)
{
const polyPatch& pp = patches[i];
newPatches[i+1] =
pp.clone
(
patches,
i+1,
pp.size(),
pp.start()
).ptr();
}
mesh.removeBoundary();
mesh.addPatches(newPatches);
Info<< "Created patch oldInternalFaces at " << patchI << endl;
}
else
{
Info<< "Reusing patch oldInternalFaces at " << patchI << endl;
}
return patchI;
}
// Take surface and select cells based on surface curvature.
void selectCurvatureCells
(
const polyMesh& mesh,
const fileName& surfName,
const triSurfaceSearch& querySurf,
const scalar nearDist,
const scalar curvature,
cellSet& cells
)
{
// Use surfaceToCell to do actual calculation.
// Since we're adding make sure set is on disk.
cells.write();
// Take centre of cell 0 as outside point since info not used.
surfaceToCell cutSource
(
mesh,
surfName,
querySurf.surface(),
querySurf,
pointField(1, mesh.cellCentres()[0]),
false,
false,
false,
nearDist,
curvature
);
cutSource.applyToSet(topoSetSource::ADD, cells);
}
// cutCells contains currently selected cells to be refined. Add neighbours
// on the inside or outside to them.
void addCutNeighbours
(
const polyMesh& mesh,
const bool selectInside,
const bool selectOutside,
const labelHashSet& inside,
const labelHashSet& outside,
labelHashSet& cutCells
)
{
// Pick up face neighbours of cutCells
labelHashSet addCutFaces(cutCells.size());
for
(
labelHashSet::const_iterator iter = cutCells.begin();
iter != cutCells.end();
++iter
)
{
label cellI = iter.key();
const labelList& cFaces = mesh.cells()[cellI];
forAll(cFaces, i)
{
label faceI = cFaces[i];
if (mesh.isInternalFace(faceI))
{
label nbr = mesh.faceOwner()[faceI];
if (nbr == cellI)
{
nbr = mesh.faceNeighbour()[faceI];
}
if (selectInside && inside.found(nbr))
{
addCutFaces.insert(nbr);
}
else if (selectOutside && outside.found(nbr))
{
addCutFaces.insert(nbr);
}
}
}
}
Info<< " Selected an additional " << addCutFaces.size()
<< " neighbours of cutCells to refine" << endl;
for
(
labelHashSet::const_iterator iter = addCutFaces.begin();
iter != addCutFaces.end();
++iter
)
{
cutCells.insert(iter.key());
}
}
// Return true if any cells had to be split to keep a difference between
// neighbouring refinement levels < limitDiff.
// Gets cells which will be refined (so increase the refinement level) and
// updates it.
bool limitRefinementLevel
(
const primitiveMesh& mesh,
const label limitDiff,
const labelHashSet& excludeCells,
const labelList& refLevel,
labelHashSet& cutCells
)
{
// Do simple check on validity of refinement level.
forAll(refLevel, cellI)
{
if (!excludeCells.found(cellI))
{
const labelList& cCells = mesh.cellCells()[cellI];
forAll(cCells, i)
{
label nbr = cCells[i];
if (!excludeCells.found(nbr))
{
if (refLevel[cellI] - refLevel[nbr] >= limitDiff)
{
FatalErrorIn("limitRefinementLevel")
<< "Level difference between neighbouring cells "
<< cellI << " and " << nbr
<< " greater than or equal to " << limitDiff << endl
<< "refLevels:" << refLevel[cellI] << ' '
<< refLevel[nbr] << abort(FatalError);
}
}
}
}
}
labelHashSet addCutCells(cutCells.size());
for
(
labelHashSet::const_iterator iter = cutCells.begin();
iter != cutCells.end();
++iter
)
{
// cellI will be refined.
label cellI = iter.key();
const labelList& cCells = mesh.cellCells()[cellI];
forAll(cCells, i)
{
label nbr = cCells[i];
if (!excludeCells.found(nbr) && !cutCells.found(nbr))
{
if (refLevel[cellI] + 1 - refLevel[nbr] >= limitDiff)
{
addCutCells.insert(nbr);
}
}
}
}
if (addCutCells.size())
{
// Add cells to cutCells.
Info<< "Added an additional " << addCutCells.size() << " cells"
<< " to satisfy 1:" << limitDiff << " refinement level"
<< endl;
for
(
labelHashSet::const_iterator iter = addCutCells.begin();
iter != addCutCells.end();
++iter
)
{
cutCells.insert(iter.key());
}
return true;
}
else
{
Info<< "Added no additional cells"
<< " to satisfy 1:" << limitDiff << " refinement level"
<< endl;
return false;
}
}
// Do all refinement (i.e. refCells) according to refineDict and update
// refLevel afterwards for added cells
void doRefinement
(
polyMesh& mesh,
const dictionary& refineDict,
const labelHashSet& refCells,
labelList& refLevel
)
{
label oldCells = mesh.nCells();
// Multi-iteration, multi-direction topology modifier.
multiDirRefinement multiRef
(
mesh,
refCells.toc(),
refineDict
);
//
// Update refLevel for split cells
//
refLevel.setSize(mesh.nCells());
for (label cellI = oldCells; cellI < mesh.nCells(); cellI++)
{
refLevel[cellI] = 0;
}
const labelListList& addedCells = multiRef.addedCells();
forAll(addedCells, oldCellI)
{
const labelList& added = addedCells[oldCellI];
if (added.size())
{
// Give all cells resulting from split the refinement level
// of the master.
label masterLevel = ++refLevel[oldCellI];
forAll(added, i)
{
refLevel[added[i]] = masterLevel;
}
}
}
}
// Subset mesh and update refLevel and cutCells
void subsetMesh
(
polyMesh& mesh,
const label writeMesh,
const label patchI, // patchID for exposed faces
const labelHashSet& cellsToRemove,
cellSet& cutCells,
labelIOList& refLevel
)
{
removeCells cellRemover(mesh);
labelList cellLabels(cellsToRemove.toc());
Info<< "Mesh has:" << mesh.nCells() << " cells."
<< " Removing:" << cellLabels.size() << " cells" << endl;
// exposed faces
labelList exposedFaces(cellRemover.getExposedFaces(cellLabels));
directTopoChange meshMod(mesh);
cellRemover.setRefinement
(
cellLabels,
exposedFaces,
labelList(exposedFaces.size(), patchI),
meshMod
);
// Do all changes
Info<< "Morphing ..." << endl;
const Time& runTime = mesh.time();
autoPtr morphMap = meshMod.changeMesh(mesh, false);
if (morphMap().hasMotionPoints())
{
mesh.movePoints(morphMap().preMotionPoints());
}
// Update topology on cellRemover
cellRemover.updateMesh(morphMap());
// Update refLevel for removed cells.
const labelList& cellMap = morphMap().cellMap();
labelList newRefLevel(cellMap.size());
forAll(cellMap, i)
{
newRefLevel[i] = refLevel[cellMap[i]];
}
// Transfer back to refLevel
refLevel.transfer(newRefLevel);
if (writeMesh)
{
Info<< "Writing refined mesh to time " << runTime.timeName() << nl
<< endl;
IOstream::defaultPrecision(10);
mesh.write();
refLevel.write();
}
// Update cutCells for removed cells.
cutCells.updateMesh(morphMap());
}
// Divide the cells according to status compared to surface. Constructs sets:
// - cutCells : all cells cut by surface
// - inside : all cells inside surface
// - outside : ,, outside ,,
// and a combined set:
// - selected : sum of above according to selectCut/Inside/Outside flags.
void classifyCells
(
const polyMesh& mesh,
const fileName& surfName,
const triSurfaceSearch& querySurf,
const pointField& outsidePts,
const bool selectCut,
const bool selectInside,
const bool selectOutside,
const label nCutLayers,
cellSet& inside,
cellSet& outside,
cellSet& cutCells,
cellSet& selected
)
{
// Cut faces with surface and classify cells
surfaceSets::getSurfaceSets
(
mesh,
surfName,
querySurf.surface(),
querySurf,
outsidePts,
nCutLayers,
inside,
outside,
cutCells
);
// Combine wanted parts into selected
if (selectCut)
{
selected.addSet(cutCells);
}
if (selectInside)
{
selected.addSet(inside);
}
if (selectOutside)
{
selected.addSet(outside);
}
Info<< "Determined cell status:" << endl
<< " inside :" << inside.size() << endl
<< " outside :" << outside.size() << endl
<< " cutCells:" << cutCells.size() << endl
<< " selected:" << selected.size() << endl
<< endl;
writeSet(inside, "inside cells");
writeSet(outside, "outside cells");
writeSet(cutCells, "cut cells");
writeSet(selected, "selected cells");
}
// Main program:
int main(int argc, char *argv[])
{
argList::noParallel();
# include "setRootCase.H"
# include "createTime.H"
# include "createPolyMesh.H"
// If nessecary add oldInternalFaces patch
label newPatchI = addPatch(mesh, "oldInternalFaces");
//
// Read motionProperties dictionary
//
Info<< "Checking for motionProperties\n" << endl;
IOobject motionObj
(
"motionProperties",
runTime.constant(),
mesh,
IOobject::MUST_READ,
IOobject::NO_WRITE
);
// corrector for mesh motion
twoDPointCorrector* correct2DPtr = NULL;
if (motionObj.headerOk())
{
Info<< "Reading " << runTime.constant() / "motionProperties"
<< endl << endl;
IOdictionary motionProperties(motionObj);
Switch twoDMotion(motionProperties.lookup("twoDMotion"));
if (twoDMotion)
{
Info<< "Correcting for 2D motion" << endl << endl;
correct2DPtr = new twoDPointCorrector(mesh);
}
}
IOdictionary refineDict
(
IOobject
(
"autoRefineMeshDict",
runTime.system(),
mesh,
IOobject::MUST_READ,
IOobject::NO_WRITE
)
);
fileName surfName(refineDict.lookup("surface"));
surfName.expand();
label nCutLayers(readLabel(refineDict.lookup("nCutLayers")));
label cellLimit(readLabel(refineDict.lookup("cellLimit")));
bool selectCut(readBool(refineDict.lookup("selectCut")));
bool selectInside(readBool(refineDict.lookup("selectInside")));
bool selectOutside(readBool(refineDict.lookup("selectOutside")));
bool selectHanging(readBool(refineDict.lookup("selectHanging")));
scalar minEdgeLen(readScalar(refineDict.lookup("minEdgeLen")));
scalar maxEdgeLen(readScalar(refineDict.lookup("maxEdgeLen")));
scalar curvature(readScalar(refineDict.lookup("curvature")));
scalar curvDist(readScalar(refineDict.lookup("curvatureDistance")));
pointField outsidePts(refineDict.lookup("outsidePoints"));
label refinementLimit(readLabel(refineDict.lookup("splitLevel")));
bool writeMesh(readBool(refineDict.lookup("writeMesh")));
Info<< "Cells to be used for meshing (0=false, 1=true):" << nl
<< " cells cut by surface : " << selectCut << nl
<< " cells inside of surface : " << selectInside << nl
<< " cells outside of surface : " << selectOutside << nl
<< " hanging cells : " << selectHanging << nl
<< endl;
if (nCutLayers > 0 && selectInside)
{
WarningIn(args.executable())
<< "Illogical settings : Both nCutLayers>0 and selectInside true."
<< endl
<< "This would mean that inside cells get removed but should be"
<< " included in final mesh" << endl;
}
// Surface.
triSurface surf(surfName);
// Search engine on surface
triSurfaceSearch querySurf(surf);
// Search engine on mesh. No face decomposition since mesh unwarped.
meshSearch queryMesh(mesh, false);
// Check all 'outside' points
forAll(outsidePts, outsideI)
{
const point& outsidePoint = outsidePts[outsideI];
if (queryMesh.findCell(outsidePoint, -1, false) == -1)
{
FatalErrorIn(args.executable())
<< "outsidePoint " << outsidePoint
<< " is not inside any cell"
<< exit(FatalError);
}
}
// Current refinement level. Read if present.
labelIOList refLevel
(
IOobject
(
"refinementLevel",
runTime.timeName(),
polyMesh::defaultRegion,
mesh,
IOobject::READ_IF_PRESENT,
IOobject::AUTO_WRITE
),
labelList(mesh.nCells(), 0)
);
label maxLevel = max(refLevel);
if (maxLevel > 0)
{
Info<< "Read existing refinement level from file "
<< refLevel.objectPath() << nl
<< " min level : " << min(refLevel) << nl
<< " max level : " << maxLevel << nl
<< endl;
}
else
{
Info<< "Created zero refinement level in file "
<< refLevel.objectPath() << nl
<< endl;
}
// Print edge stats on original mesh. Leave out 2d-normal direction
direction normalDir(getNormalDir(correct2DPtr));
scalar meshMinEdgeLen = getEdgeStats(mesh, normalDir);
while (meshMinEdgeLen > minEdgeLen)
{
// Get inside/outside/cutCells cellSets.
cellSet inside(mesh, "inside", mesh.nCells()/10);
cellSet outside(mesh, "outside", mesh.nCells()/10);
cellSet cutCells(mesh, "cutCells", mesh.nCells()/10);
cellSet selected(mesh, "selected", mesh.nCells()/10);
classifyCells
(
mesh,
surfName,
querySurf,
outsidePts,
selectCut, // for determination of selected
selectInside, // for determination of selected
selectOutside, // for determination of selected
nCutLayers, // How many layers of cutCells to include
inside,
outside,
cutCells,
selected // not used but determined anyway.
);
Info<< " Selected " << cutCells.name() << " with "
<< cutCells.size() << " cells" << endl;
if ((curvDist > 0) && (meshMinEdgeLen < maxEdgeLen))
{
// Done refining enough close to surface. Now switch to more
// intelligent refinement where only cutCells on surface curvature
// are refined.
cellSet curveCells(mesh, "curveCells", mesh.nCells()/10);
selectCurvatureCells
(
mesh,
surfName,
querySurf,
maxEdgeLen,
curvature,
curveCells
);
Info<< " Selected " << curveCells.name() << " with "
<< curveCells.size() << " cells" << endl;
// Add neighbours to cutCells. This is if selectCut is not
// true and so outside and/or inside cells get exposed there is
// also refinement in them.
if (!selectCut)
{
addCutNeighbours
(
mesh,
selectInside,
selectOutside,
inside,
outside,
cutCells
);
}
// Subset cutCells to only curveCells
cutCells.subset(curveCells);
Info<< " Removed cells not on surface curvature. Selected "
<< cutCells.size() << endl;
}
if (nCutLayers > 0)
{
// Subset mesh to remove inside cells altogether. Updates cutCells,
// refLevel.
subsetMesh(mesh, writeMesh, newPatchI, inside, cutCells, refLevel);
}
// Added cells from 2:1 refinement level restriction.
while
(
limitRefinementLevel
(
mesh,
refinementLimit,
labelHashSet(),
refLevel,
cutCells
)
)
{}
Info<< " Current cells : " << mesh.nCells() << nl
<< " Selected for refinement :" << cutCells.size() << nl
<< endl;
if (cutCells.empty())
{
Info<< "Stopping refining since 0 cells selected to be refined ..."
<< nl << endl;
break;
}
if ((mesh.nCells() + 8*cutCells.size()) > cellLimit)
{
Info<< "Stopping refining since cell limit reached ..." << nl
<< "Would refine from " << mesh.nCells()
<< " to " << mesh.nCells() + 8*cutCells.size() << " cells."
<< nl << endl;
break;
}
doRefinement
(
mesh,
refineDict,
cutCells,
refLevel
);
Info<< " After refinement:" << mesh.nCells() << nl
<< endl;
if (writeMesh)
{
Info<< " Writing refined mesh to time " << runTime.timeName()
<< nl << endl;
IOstream::defaultPrecision(10);
mesh.write();
refLevel.write();
}
// Update mesh edge stats.
meshMinEdgeLen = getEdgeStats(mesh, normalDir);
}
if (selectHanging)
{
// Get inside/outside/cutCells cellSets.
cellSet inside(mesh, "inside", mesh.nCells()/10);
cellSet outside(mesh, "outside", mesh.nCells()/10);
cellSet cutCells(mesh, "cutCells", mesh.nCells()/10);
cellSet selected(mesh, "selected", mesh.nCells()/10);
classifyCells
(
mesh,
surfName,
querySurf,
outsidePts,
selectCut,
selectInside,
selectOutside,
nCutLayers,
inside,
outside,
cutCells,
selected
);
// Find any cells which have all their points on the outside of the
// selected set and refine them
labelHashSet hanging = surfaceSets::getHangingCells(mesh, selected);
Info<< "Detected " << hanging.size() << " hanging cells"
<< " (cells with all points on"
<< " outside of cellSet 'selected').\nThese would probably result"
<< " in flattened cells when snapping the mesh to the surface"
<< endl;
Info<< "Refining " << hanging.size() << " hanging cells" << nl
<< endl;
// Added cells from 2:1 refinement level restriction.
while
(
limitRefinementLevel
(
mesh,
refinementLimit,
labelHashSet(),
refLevel,
hanging
)
)
{}
doRefinement(mesh, refineDict, hanging, refLevel);
Info<< "Writing refined mesh to time " << runTime.timeName() << nl
<< endl;
// Write final mesh
IOstream::defaultPrecision(10);
mesh.write();
refLevel.write();
}
else if (!writeMesh)
{
Info<< "Writing refined mesh to time " << runTime.timeName() << nl
<< endl;
// Write final mesh. (will have been written already if writeMesh=true)
IOstream::defaultPrecision(10);
mesh.write();
refLevel.write();
}
Info << "End\n" << endl;
return 0;
}
// ************************************************************************* //