Files
OpenFOAM-12/applications/utilities/mesh/manipulation/polyDualMesh/polyDualMesh.C
Henry Weller cf3d6cd1e9 fvMeshMovers, fvMeshTopoChangers: General mesh motion and topology change replacement for dynamicFvMesh
Mesh motion and topology change are now combinable run-time selectable options
within fvMesh, replacing the restrictive dynamicFvMesh which supported only
motion OR topology change.

All solvers which instantiated a dynamicFvMesh now instantiate an fvMesh which
reads the optional constant/dynamicFvMeshDict to construct an fvMeshMover and/or
an fvMeshTopoChanger.  These two are specified within the optional mover and
topoChanger sub-dictionaries of dynamicFvMeshDict.

When the fvMesh is updated the fvMeshTopoChanger is first executed which can
change the mesh topology in anyway, adding or removing points as required, for
example for automatic mesh refinement/unrefinement, and all registered fields
are mapped onto the updated mesh.  The fvMeshMover is then executed which moved
the points only and calculates the cell volume change and corresponding
mesh-fluxes for conservative moving mesh transport.  If multiple topological
changes or movements are required these would be combined into special
fvMeshMovers and fvMeshTopoChangers which handle the processing of a list of
changes, e.g. solidBodyMotionFunctions:multiMotion.

The tutorials/multiphase/interFoam/laminar/sloshingTank3D3DoF case has been
updated to demonstrate this new functionality by combining solid-body motion
with mesh refinement/unrefinement:

/*--------------------------------*- C++ -*----------------------------------*\
  =========                 |
  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
   \\    /   O peration     | Website:  https://openfoam.org
    \\  /    A nd           | Version:  dev
     \\/     M anipulation  |
\*---------------------------------------------------------------------------*/
FoamFile
{
    format      ascii;
    class       dictionary;
    location    "constant";
    object      dynamicMeshDict;
}
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //

mover
{
    type    motionSolver;

    libs    ("libfvMeshMovers.so" "libfvMotionSolvers.so");

    motionSolver    solidBody;

    solidBodyMotionFunction SDA;

    CofG            (0 0 0);
    lamda           50;
    rollAmax        0.2;
    rollAmin        0.1;
    heaveA          4;
    swayA           2.4;
    Q               2;
    Tp              14;
    Tpn             12;
    dTi             0.06;
    dTp             -0.001;
}

topoChanger
{
    type    refiner;

    libs    ("libfvMeshTopoChangers.so");

    // How often to refine
    refineInterval  1;

    // Field to be refinement on
    field           alpha.water;

    // Refine field in between lower..upper
    lowerRefineLevel 0.001;
    upperRefineLevel 0.999;

    // Have slower than 2:1 refinement
    nBufferLayers   1;

    // Refine cells only up to maxRefinement levels
    maxRefinement   1;

    // Stop refinement if maxCells reached
    maxCells        200000;

    // Flux field and corresponding velocity field. Fluxes on changed
    // faces get recalculated by interpolating the velocity. Use 'none'
    // on surfaceScalarFields that do not need to be reinterpolated.
    correctFluxes
    (
        (phi none)
        (nHatf none)
        (rhoPhi none)
        (alphaPhi.water none)
        (meshPhi none)
        (meshPhi_0 none)
        (ghf none)
    );

    // Write the refinement level as a volScalarField
    dumpLevel       true;
}

// ************************************************************************* //

Note that currently this is the only working combination of mesh-motion with
topology change within the new framework and further development is required to
update the set of topology changers so that topology changes with mapping are
separated from the mesh-motion so that they can be combined with any of the
other movements or topology changes in any manner.

All of the solvers and tutorials have been updated to use the new form of
dynamicMeshDict but backward-compatibility was not practical due to the complete
reorganisation of the mesh change structure.
2021-10-01 15:50:06 +01:00

543 lines
16 KiB
C++

/*---------------------------------------------------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration | Website: https://openfoam.org
\\ / A nd | Copyright (C) 2011-2021 OpenFOAM Foundation
\\/ M anipulation |
-------------------------------------------------------------------------------
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
polyDualMesh
Description
Calculates the dual of a polyMesh. Adheres to all the feature and patch
edges.
Usage
\b polyDualMesh featureAngle
Detects any boundary edge > angle and creates multiple boundary faces
for it. Normal behaviour is to have each point become a cell
(1.5 behaviour)
Options:
- \par -concaveMultiCells
Creates multiple cells for each point on a concave edge. Might limit
the amount of distortion on some meshes.
- \par -splitAllFaces
Normally only constructs a single face between two cells. This single
face might be too distorted. splitAllFaces will create a single face for
every original cell the face passes through. The mesh will thus have
multiple faces in between two cells! (so is not strictly
upper-triangular anymore - checkMesh will complain)
- \par -doNotPreserveFaceZones:
By default all faceZones are preserved by marking all faces, edges and
points on them as features. The -doNotPreserveFaceZones disables this
behaviour.
Note
It is just a driver for meshDualiser. Substitute your own simpleMarkFeatures
to have different behaviour.
\*---------------------------------------------------------------------------*/
#include "argList.H"
#include "Time.H"
#include "fvMesh.H"
#include "unitConversion.H"
#include "polyTopoChange.H"
#include "mapPolyMesh.H"
#include "PackedBoolList.H"
#include "meshTools.H"
#include "OFstream.H"
#include "meshDualiser.H"
#include "ReadFields.H"
#include "volFields.H"
#include "surfaceFields.H"
#include "pointFields.H"
using namespace Foam;
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
// Naive feature detection. All boundary edges with angle > featureAngle become
// feature edges. All points on feature edges become feature points. All
// boundary faces become feature faces.
void simpleMarkFeatures
(
const polyMesh& mesh,
const PackedBoolList& isBoundaryEdge,
const scalar featureAngle,
const bool concaveMultiCells,
const bool doNotPreserveFaceZones,
labelList& featureFaces,
labelList& featureEdges,
labelList& singleCellFeaturePoints,
labelList& multiCellFeaturePoints
)
{
scalar minCos = Foam::cos(degToRad(featureAngle));
const polyBoundaryMesh& patches = mesh.boundaryMesh();
// Working sets
labelHashSet featureEdgeSet;
labelHashSet singleCellFeaturePointSet;
labelHashSet multiCellFeaturePointSet;
// 1. Mark all edges between patches
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
forAll(patches, patchi)
{
const polyPatch& pp = patches[patchi];
const labelList& meshEdges = pp.meshEdges();
// All patch corner edges. These need to be feature points & edges!
for (label edgeI = pp.nInternalEdges(); edgeI < pp.nEdges(); edgeI++)
{
label meshEdgeI = meshEdges[edgeI];
featureEdgeSet.insert(meshEdgeI);
singleCellFeaturePointSet.insert(mesh.edges()[meshEdgeI][0]);
singleCellFeaturePointSet.insert(mesh.edges()[meshEdgeI][1]);
}
}
// 2. Mark all geometric feature edges
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Make distinction between convex features where the boundary point becomes
// a single cell and concave features where the boundary point becomes
// multiple 'half' cells.
// Addressing for all outside faces
primitivePatch allBoundary
(
SubList<face>
(
mesh.faces(),
mesh.nFaces()-mesh.nInternalFaces(),
mesh.nInternalFaces()
),
mesh.points()
);
// Check for non-manifold points (surface pinched at point)
allBoundary.checkPointManifold(false, &singleCellFeaturePointSet);
// Check for non-manifold edges (surface pinched at edge)
const labelListList& edgeFaces = allBoundary.edgeFaces();
const labelList& meshPoints = allBoundary.meshPoints();
forAll(edgeFaces, edgeI)
{
const labelList& eFaces = edgeFaces[edgeI];
if (eFaces.size() > 2)
{
const edge& e = allBoundary.edges()[edgeI];
// Info<< "Detected non-manifold boundary edge:" << edgeI
// << " coords:"
// << allBoundary.points()[meshPoints[e[0]]]
// << allBoundary.points()[meshPoints[e[1]]] << endl;
singleCellFeaturePointSet.insert(meshPoints[e[0]]);
singleCellFeaturePointSet.insert(meshPoints[e[1]]);
}
}
// Check for features.
forAll(edgeFaces, edgeI)
{
const labelList& eFaces = edgeFaces[edgeI];
if (eFaces.size() == 2)
{
label f0 = eFaces[0];
label f1 = eFaces[1];
// check angle
const vector& n0 = allBoundary.faceNormals()[f0];
const vector& n1 = allBoundary.faceNormals()[f1];
if ((n0 & n1) < minCos)
{
const edge& e = allBoundary.edges()[edgeI];
label v0 = meshPoints[e[0]];
label v1 = meshPoints[e[1]];
label meshEdgeI = meshTools::findEdge(mesh, v0, v1);
featureEdgeSet.insert(meshEdgeI);
// Check if convex or concave by looking at angle
// between face centres and normal
vector c1c0
(
allBoundary[f1].centre(allBoundary.points())
- allBoundary[f0].centre(allBoundary.points())
);
if (concaveMultiCells && (c1c0 & n0) > small)
{
// Found concave edge. Make into multiCell features
Info<< "Detected concave feature edge:" << edgeI
<< " cos:" << (c1c0 & n0)
<< " coords:"
<< allBoundary.points()[v0]
<< allBoundary.points()[v1]
<< endl;
singleCellFeaturePointSet.erase(v0);
multiCellFeaturePointSet.insert(v0);
singleCellFeaturePointSet.erase(v1);
multiCellFeaturePointSet.insert(v1);
}
else
{
// Convex. singleCell feature.
if (!multiCellFeaturePointSet.found(v0))
{
singleCellFeaturePointSet.insert(v0);
}
if (!multiCellFeaturePointSet.found(v1))
{
singleCellFeaturePointSet.insert(v1);
}
}
}
}
}
// 3. Mark all feature faces
// ~~~~~~~~~~~~~~~~~~~~~~~~~
// Face centres that need inclusion in the dual mesh
labelHashSet featureFaceSet(mesh.nFaces()-mesh.nInternalFaces());
// A. boundary faces.
for (label facei = mesh.nInternalFaces(); facei < mesh.nFaces(); facei++)
{
featureFaceSet.insert(facei);
}
// B. face zones.
const meshFaceZones& faceZones = mesh.faceZones();
if (doNotPreserveFaceZones)
{
if (faceZones.size() > 0)
{
WarningInFunction
<< "Detected " << faceZones.size()
<< " faceZones. These will not be preserved."
<< endl;
}
}
else
{
if (faceZones.size() > 0)
{
Info<< "Detected " << faceZones.size()
<< " faceZones. Preserving these by marking their"
<< " points, edges and faces as features." << endl;
}
forAll(faceZones, zoneI)
{
const faceZone& fz = faceZones[zoneI];
Info<< "Inserting all faces in faceZone " << fz.name()
<< " as features." << endl;
forAll(fz, i)
{
label facei = fz[i];
const face& f = mesh.faces()[facei];
const labelList& fEdges = mesh.faceEdges()[facei];
featureFaceSet.insert(facei);
forAll(f, fp)
{
// Mark point as multi cell point (since both sides of
// face should have different cells)
singleCellFeaturePointSet.erase(f[fp]);
multiCellFeaturePointSet.insert(f[fp]);
// Make sure there are points on the edges.
featureEdgeSet.insert(fEdges[fp]);
}
}
}
}
// Transfer to arguments
featureFaces = featureFaceSet.toc();
featureEdges = featureEdgeSet.toc();
singleCellFeaturePoints = singleCellFeaturePointSet.toc();
multiCellFeaturePoints = multiCellFeaturePointSet.toc();
}
// Dump features to .obj files
void dumpFeatures
(
const polyMesh& mesh,
const labelList& featureFaces,
const labelList& featureEdges,
const labelList& singleCellFeaturePoints,
const labelList& multiCellFeaturePoints
)
{
{
OFstream str("featureFaces.obj");
Info<< "Dumping centres of featureFaces to obj file " << str.name()
<< endl;
forAll(featureFaces, i)
{
meshTools::writeOBJ(str, mesh.faceCentres()[featureFaces[i]]);
}
}
{
OFstream str("featureEdges.obj");
Info<< "Dumping featureEdges to obj file " << str.name() << endl;
label vertI = 0;
forAll(featureEdges, i)
{
const edge& e = mesh.edges()[featureEdges[i]];
meshTools::writeOBJ(str, mesh.points()[e[0]]);
vertI++;
meshTools::writeOBJ(str, mesh.points()[e[1]]);
vertI++;
str<< "l " << vertI-1 << ' ' << vertI << nl;
}
}
{
OFstream str("singleCellFeaturePoints.obj");
Info<< "Dumping featurePoints that become a single cell to obj file "
<< str.name() << endl;
forAll(singleCellFeaturePoints, i)
{
meshTools::writeOBJ(str, mesh.points()[singleCellFeaturePoints[i]]);
}
}
{
OFstream str("multiCellFeaturePoints.obj");
Info<< "Dumping featurePoints that become multiple cells to obj file "
<< str.name() << endl;
forAll(multiCellFeaturePoints, i)
{
meshTools::writeOBJ(str, mesh.points()[multiCellFeaturePoints[i]]);
}
}
}
int main(int argc, char *argv[])
{
#include "addOverwriteOption.H"
argList::noParallel();
argList::validArgs.append("featureAngle [0-180]");
argList::addBoolOption
(
"splitAllFaces",
"have multiple faces in between cells"
);
argList::addBoolOption
(
"concaveMultiCells",
"split cells on concave boundary edges into multiple cells"
);
argList::addBoolOption
(
"doNotPreserveFaceZones",
"disable the default behaviour of preserving faceZones by having"
" multiple faces in between cells"
);
argList::addBoolOption
(
"noFields",
"do not update fields"
);
#include "setRootCase.H"
#include "createTime.H"
#include "createMeshNoChangers.H"
const word oldInstance = mesh.pointsInstance();
// Mark boundary edges and points.
// (Note: in 1.4.2 we can use the built-in mesh point ordering
// facility instead)
PackedBoolList isBoundaryEdge(mesh.nEdges());
for (label facei = mesh.nInternalFaces(); facei < mesh.nFaces(); facei++)
{
const labelList& fEdges = mesh.faceEdges()[facei];
forAll(fEdges, i)
{
isBoundaryEdge.set(fEdges[i], 1);
}
}
const scalar featureAngle = args.argRead<scalar>(1);
const scalar minCos = Foam::cos(degToRad(featureAngle));
Info<< "Feature:" << featureAngle << endl
<< "minCos :" << minCos << endl
<< endl;
const bool splitAllFaces = args.optionFound("splitAllFaces");
if (splitAllFaces)
{
Info<< "Splitting all internal faces to create multiple faces"
<< " between two cells." << nl
<< endl;
}
const bool overwrite = args.optionFound("overwrite");
const bool doNotPreserveFaceZones = args.optionFound
(
"doNotPreserveFaceZones"
);
const bool concaveMultiCells = args.optionFound("concaveMultiCells");
if (concaveMultiCells)
{
Info<< "Generating multiple cells for points on concave feature edges."
<< nl << endl;
}
const bool fields = !args.optionFound("noFields");
// Face(centre)s that need inclusion in the dual mesh
labelList featureFaces;
// Edge(centre)s ,,
labelList featureEdges;
// Points (that become a single cell) that need inclusion in the dual mesh
labelList singleCellFeaturePoints;
// Points (that become a multiple cells) ,,
labelList multiCellFeaturePoints;
// Sample implementation of feature detection.
simpleMarkFeatures
(
mesh,
isBoundaryEdge,
featureAngle,
concaveMultiCells,
doNotPreserveFaceZones,
featureFaces,
featureEdges,
singleCellFeaturePoints,
multiCellFeaturePoints
);
// If we want to split all polyMesh faces into one dualface per cell
// we are passing through we also need a point
// at the polyMesh facecentre and edgemid of the faces we want to
// split.
if (splitAllFaces)
{
featureEdges = identity(mesh.nEdges());
featureFaces = identity(mesh.nFaces());
}
// Write obj files for debugging
dumpFeatures
(
mesh,
featureFaces,
featureEdges,
singleCellFeaturePoints,
multiCellFeaturePoints
);
// Read objects in time directory
IOobjectList objects(mesh, runTime.timeName());
if (fields) Info<< "Reading geometric fields" << nl << endl;
#include "readVolFields.H"
#include "readSurfaceFields.H"
#include "readPointFields.H"
Info<< endl;
// Topo change container
polyTopoChange meshMod(mesh.boundaryMesh().size());
// Mesh dualiser engine
meshDualiser dualMaker(mesh);
// Insert all commands into polyTopoChange to create dual of mesh. This does
// all the hard work.
dualMaker.setRefinement
(
splitAllFaces,
featureFaces,
featureEdges,
singleCellFeaturePoints,
multiCellFeaturePoints,
meshMod
);
// Create mesh, return map from old to new mesh.
autoPtr<mapPolyMesh> map = meshMod.changeMesh(mesh, false);
// Update fields
mesh.updateMesh(map);
// Optionally inflate mesh
if (map().hasMotionPoints())
{
mesh.movePoints(map().preMotionPoints());
}
if (!overwrite)
{
runTime++;
}
else
{
mesh.setInstance(oldInstance);
}
Info<< "Writing dual mesh to " << runTime.timeName() << endl;
mesh.write();
Info<< "End\n" << endl;
return 0;
}
// ************************************************************************* //