/*---------------------------------------------------------------------------*\ ========= | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox \\ / O peration | \\ / A nd | www.openfoam.com \\/ M anipulation | ------------------------------------------------------------------------------- Copyright (C) 2018-2025 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 . \*---------------------------------------------------------------------------*/ #include "meshRefinement.H" #include "fvMesh.H" #include "Time.H" #include "refinementSurfaces.H" #include "removeCells.H" #include "unitConversion.H" #include "bitSet.H" #include "volFields.H" // Leak path #include "shortestPathSet.H" #include "meshSearch.H" #include "topoDistanceData.H" #include "FaceCellWave.H" #include "removeCells.H" #include "regionSplit.H" #include "volFields.H" #include "wallPoints.H" #include "searchableSurfaces.H" #include "distributedTriSurfaceMesh.H" #include "holeToFace.H" #include "refinementParameters.H" #include "indirectPrimitivePatch.H" #include "OBJstream.H" #include "PatchTools.H" // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * // //Foam::label Foam::meshRefinement::markFakeGapRefinement //( // const scalar planarCos, // // const label nAllowRefine, // const labelList& neiLevel, // const pointField& neiCc, // // labelList& refineCell, // label& nRefine //) const //{ // label oldNRefine = nRefine; // // // // Collect candidate faces (i.e. intersecting any surface and // // owner/neighbour not yet refined. // const labelList testFaces(getRefineCandidateFaces(refineCell)); // // // Collect segments // pointField start(testFaces.size()); // pointField end(testFaces.size()); // labelList minLevel(testFaces.size()); // // calcCellCellRays // ( // neiCc, // neiLevel, // testFaces, // start, // end, // minLevel // ); // // // // Re-use the gap shooting methods. This needs: // // - shell gapLevel : faked. Set to 0,labelMax // // - surface gapLevel : faked by overwriting // // // List>& surfGapLevel = const_cast // < // List>& // >(surfaces_.extendedGapLevel()); // // List& surfGapMode = const_cast // < // List& // >(surfaces_.extendedGapMode()); // // const List> surfOldLevel(surfGapLevel); // const List surfOldMode(surfGapMode); // // // Set the extended gap levels // forAll(surfaces_.gapLevel(), regioni) // { // surfGapLevel[regioni] = FixedList // ({ // 3, // -1, // surfaces_.gapLevel()[regioni]+1 // }); // } // surfGapMode = volumeType::MIXED; // //Pout<< "gapLevel was:" << surfOldLevel << endl; //Pout<< "gapLevel now:" << surfGapLevel << endl; //Pout<< "gapMode was:" << surfOldMode << endl; //Pout<< "gapMode now:" << surfGapMode << endl; //Pout<< "nRefine was:" << oldNRefine << endl; // // // // List>>& shellGapLevel = const_cast // < // List>>& // >(shells_.extendedGapLevel()); // // List>& shellGapMode = const_cast // < // List>& // >(shells_.extendedGapMode()); // // const List>> shellOldLevel(shellGapLevel); // const List> shellOldMode(shellGapMode); // // // Set the extended gap levels // forAll(shellGapLevel, shelli) // { // shellGapLevel[shelli] = FixedList({3, -1, labelMax}); // shellGapMode[shelli] = volumeType::MIXED; // } //Pout<< "shellLevel was:" << shellOldLevel << endl; //Pout<< "shellLevel now:" << shellGapLevel << endl; // // const label nAdditionalRefined = markSurfaceGapRefinement // ( // planarCos, // // nAllowRefine, // neiLevel, // neiCc, // // refineCell, // nRefine // ); // //Pout<< "nRefine now:" << nRefine << endl; // // // Restore // surfGapLevel = surfOldLevel; // surfGapMode = surfOldMode; // shellGapLevel = shellOldLevel; // shellGapMode = shellOldMode; // // return nAdditionalRefined; //} void Foam::meshRefinement::markOutsideFaces ( const labelList& cellLevel, const labelList& neiLevel, const labelList& refineCell, bitSet& isOutsideFace ) const { // Get faces: // - on outside of cell set // - inbetween same cell level (i.e. quads) isOutsideFace.setSize(mesh_.nFaces()); isOutsideFace = Zero; forAll(mesh_.faceNeighbour(), facei) { label own = mesh_.faceOwner()[facei]; label nei = mesh_.faceNeighbour()[facei]; if ( (cellLevel[own] == cellLevel[nei]) && ( (refineCell[own] != -1) != (refineCell[nei] != -1) ) ) { isOutsideFace.set(facei); } } { const label nBnd = mesh_.nBoundaryFaces(); labelList neiRefineCell(nBnd); syncTools::swapBoundaryCellList(mesh_, refineCell, neiRefineCell); for (label bFacei = 0; bFacei < nBnd; ++bFacei) { label facei = mesh_.nInternalFaces()+bFacei; label own = mesh_.faceOwner()[facei]; if ( (cellLevel[own] == neiLevel[bFacei]) && ( (refineCell[own] != -1) != (neiRefineCell[bFacei] != -1) ) ) { isOutsideFace.set(facei); } } } } Foam::label Foam::meshRefinement::countFaceDirs ( const bitSet& isOutsideFace, const label celli ) const { const cell& cFaces = mesh_.cells()[celli]; const vectorField& faceAreas = mesh_.faceAreas(); Vector haveDirs(vector::uniform(false)); forAll(cFaces, cFacei) { const label facei = cFaces[cFacei]; if (isOutsideFace[facei]) { const vector& n = faceAreas[facei]; scalar magSqrN = magSqr(n); if (magSqrN > ROOTVSMALL) { for ( direction dir = 0; dir < pTraits::nComponents; dir++ ) { if (Foam::sqr(n[dir]) > 0.99*magSqrN) { haveDirs[dir] = true; break; } } } } } label nDirs = 0; forAll(haveDirs, dir) { if (haveDirs[dir]) { nDirs++; } } return nDirs; } void Foam::meshRefinement::growSet ( const labelList& neiLevel, const bitSet& isOutsideFace, labelList& refineCell, label& nRefine ) const { // Get cells with three or more outside faces const cellList& cells = mesh_.cells(); forAll(cells, celli) { if (refineCell[celli] == -1) { if (countFaceDirs(isOutsideFace, celli) == 3) { // Mark cell with any value refineCell[celli] = 0; nRefine++; } } } } //void Foam::meshRefinement::markMultiRegionCell //( // const label celli, // const FixedList& surface, // // Map>& cellToRegions, // bitSet& isMultiRegion //) const //{ // if (!isMultiRegion[celli]) // { // Map>::iterator fnd = cellToRegions.find(celli); // if (!fnd.good()) // { // cellToRegions.insert(celli, surface); // } // else if (fnd() != surface) // { // // Found multiple intersections on cell // isMultiRegion.set(celli); // } // } //} //void Foam::meshRefinement::detectMultiRegionCells //( // const labelListList& faceZones, // const labelList& testFaces, // // const labelList& surface1, // const List& hit1, // const labelList& region1, // // const labelList& surface2, // const List& hit2, // const labelList& region2, // // bitSet& isMultiRegion //) const //{ // isMultiRegion.clear(); // isMultiRegion.setSize(mesh_.nCells()); // // Map> cellToRegions(testFaces.size()); // // forAll(testFaces, i) // { // const pointIndexHit& info1 = hit1[i]; // if (info1.hit()) // { // const label facei = testFaces[i]; // const labelList& fz1 = faceZones[surface1[i]]; // const FixedList surfaceInfo1 // ({ // surface1[i], // region1[i], // (fz1.size() ? fz1[info1.index()] : region1[i]) // }); // // markMultiRegionCell // ( // mesh_.faceOwner()[facei], // surfaceInfo1, // cellToRegions, // isMultiRegion // ); // if (mesh_.isInternalFace(facei)) // { // markMultiRegionCell // ( // mesh_.faceNeighbour()[facei], // surfaceInfo1, // cellToRegions, // isMultiRegion // ); // } // // const pointIndexHit& info2 = hit2[i]; // // if (info2.hit() && info1 != info2) // { // const labelList& fz2 = faceZones[surface2[i]]; // const FixedList surfaceInfo2 // ({ // surface2[i], // region2[i], // (fz2.size() ? fz2[info2.index()] : region2[i]) // }); // // markMultiRegionCell // ( // mesh_.faceOwner()[facei], // surfaceInfo2, // cellToRegions, // isMultiRegion // ); // if (mesh_.isInternalFace(facei)) // { // markMultiRegionCell // ( // mesh_.faceNeighbour()[facei], // surfaceInfo2, // cellToRegions, // isMultiRegion // ); // } // } // } // } // // // if (debug&meshRefinement::MESH) // { // volScalarField multiCell // ( // IOobject // ( // "multiCell", // mesh_.time().timeName(), // mesh_, // IOobject::NO_READ, // IOobject::NO_WRITE, // IOobject::NO_REGISTER // ), // mesh_, // dimensionedScalar // ( // "zero", // dimensionSet(0, 1, 0, 0, 0), // 0.0 // ) // ); // forAll(isMultiRegion, celli) // { // if (isMultiRegion[celli]) // { // multiCell[celli] = 1.0; // } // } // // Info<< "Writing all multi cells to " << multiCell.name() << endl; // multiCell.write(); // } //} void Foam::meshRefinement::markProximityCandidateFaces ( const labelList& blockedSurfaces, const List& regionToBlockSize, const labelList& neiLevel, const pointField& neiCc, labelList& testFaces ) const { labelListList faceZones(surfaces_.surfaces().size()); { // If triSurface do additional zoning based on connectivity for (const label surfi : blockedSurfaces) { const label geomi = surfaces_.surfaces()[surfi]; const searchableSurface& s = surfaces_.geometry()[geomi]; if (isA(s) && !isA(s)) { const triSurfaceMesh& surf = refCast(s); const labelListList& edFaces = surf.edgeFaces(); boolList isOpenEdge(edFaces.size(), false); forAll(edFaces, i) { if (edFaces[i].size() == 1) { isOpenEdge[i] = true; } } labelList faceZone; const label nZones = surf.markZones(isOpenEdge, faceZone); if (nZones > 1) { faceZones[surfi].transfer(faceZone); } } } } // Clever limiting of the number of iterations (= max cells in the channel) // since it has too many problematic issues, e.g. with volume refinement // and the real check uses the proper distance anyway just disable. const label nIters = mesh_.globalData().nTotalCells(); // Collect candidate faces (i.e. intersecting any surface and // owner/neighbour not yet refined) //const labelList testFaces(getRefineCandidateFaces(refineCell)); // Collect segments pointField start(testFaces.size()); pointField end(testFaces.size()); labelList minLevel(testFaces.size()); calcCellCellRays ( neiCc, neiLevel, testFaces, start, end, minLevel ); // TBD. correct nIters for actual cellLevel (since e.g. volume refinement // might add to cell level). Unfortunately we only have minLevel, // not maxLevel. Problem: what if volume refinement only in middle of // channel? Say channel 1m wide with a 0.1m sphere of refinement // Workaround: have dummy surface with e.g. maxLevel 100 to // force nIters to be high enough. // Test for all intersections (with surfaces of higher gap level than // minLevel) and cache per cell the max surface level and the local normal // on that surface. labelList surface1; List hit1; labelList region1; vectorField normal1; labelList surface2; List hit2; labelList region2; vectorField normal2; surfaces_.findNearestIntersection ( blockedSurfaces, start, end, surface1, hit1, region1, // local region normal1, surface2, hit2, region2, // local region normal2 ); // Detect cells that are using multiple surface regions //bitSet isMultiRegion; //detectMultiRegionCells //( // faceZones, // testFaces, // // surface1, // hit1, // region1, // // surface2, // hit2, // region2, // // isMultiRegion //); label n = 0; forAll(testFaces, i) { if (hit1[i].hit()) { n++; } } List faceDist(n); labelList changedFaces(n); n = 0; DynamicList originLocation(2); DynamicList originDistSqr(2); DynamicList> originSurface(2); //DynamicList originNormal(2); //- To avoid walking through surfaces we mark all faces that have been // intersected. We can either mark only those faces intersecting // blockedSurfaces (i.e. with a 'blockLevel') or mark faces intersecting // any (refinement) surface (this includes e.g. faceZones). This is // much easier since that information is already cached // (meshRefinement::intersectedFaces()) //bitSet isBlockedFace(mesh_.nFaces()); forAll(testFaces, i) { if (hit1[i].hit()) { const label facei = testFaces[i]; //isBlockedFace.set(facei); const point& fc = mesh_.faceCentres()[facei]; const labelList& fz1 = faceZones[surface1[i]]; originLocation.clear(); originDistSqr.clear(); //originNormal.clear(); originSurface.clear(); originLocation.append(hit1[i].point()); originDistSqr.append(hit1[i].point().distSqr(fc)); //originNormal.append(normal1[i]); originSurface.append ( FixedList ({ surface1[i], region1[i], (fz1.size() ? fz1[hit1[i].index()] : region1[i]) }) ); if (hit2[i].hit() && hit1[i] != hit2[i]) { const labelList& fz2 = faceZones[surface2[i]]; originLocation.append(hit2[i].point()); originDistSqr.append(hit2[i].point().distSqr(fc)); //originNormal.append(normal2[i]); originSurface.append ( FixedList ({ surface2[i], region2[i], (fz2.size() ? fz2[hit2[i].index()] : region2[i]) }) ); } // Collect all seed data. Currently walking does not look at // surface direction - if so pass in surface normal as well faceDist[n] = wallPoints ( originLocation, // origin originDistSqr, // distance to origin originSurface // surface+region+zone //originNormal // normal at origin ); changedFaces[n] = facei; n++; } } // Clear intersection info surface1.clear(); hit1.clear(); region1.clear(); normal1.clear(); surface2.clear(); hit2.clear(); region2.clear(); normal2.clear(); List allFaceInfo(mesh_.nFaces()); List allCellInfo(mesh_.nCells()); // Any refinement surface (even a faceZone) should stop the gap walking. // This is exactly the information which is cached in the surfaceIndex_ // field. const bitSet isBlockedFace(intersectedFaces()); wallPoints::trackData td(isBlockedFace, regionToBlockSize); FaceCellWave wallDistCalc ( mesh_, changedFaces, faceDist, allFaceInfo, allCellInfo, 0, // max iterations td ); wallDistCalc.iterate(nIters); // Extract faces of visited cells bitSet isProximityFace(mesh_.nFaces(), false); // Make sure to include initial intersected ones isProximityFace.set(testFaces); forAll(allCellInfo, celli) { if (allCellInfo[celli].valid(wallDistCalc.data())) { isProximityFace.set(mesh_.cells()[celli]); } } syncTools::syncFaceList ( mesh_, isProximityFace, orEqOp(), 0u ); testFaces = isProximityFace.toc(); } Foam::label Foam::meshRefinement::markFakeGapRefinement ( const scalar planarCos, const labelList& blockedSurfaces, const label nAllowRefine, const labelList& neiLevel, const pointField& neiCc, labelList& refineCell, label& nRefine ) const { // Re-work the blockLevel of the blockedSurfaces into a length scale // and a number of cells to cross List regionToBlockSize(surfaces_.surfaces().size()); scalar maxBlockSize(-1); for (const label surfi : blockedSurfaces) { const label geomi = surfaces_.surfaces()[surfi]; const searchableSurface& s = surfaces_.geometry()[geomi]; const label nRegions = s.regions().size(); regionToBlockSize[surfi].setSize(nRegions, -1); for (label regioni = 0; regioni < nRegions; regioni++) { const label globalRegioni = surfaces_.globalRegion(surfi, regioni); const label bLevel = surfaces_.blockLevel()[globalRegioni]; // If valid blockLevel specified if (bLevel != labelMax) { regionToBlockSize[surfi][regioni] = meshCutter_.level0EdgeLength()/pow(2.0, bLevel); } maxBlockSize = max(maxBlockSize, regionToBlockSize[surfi][regioni]); //const label mLevel = surfaces_.maxLevel()[globalRegioni]; //// TBD: check for higher cached level of surface due to vol //// refinement. Problem: might still miss refinement bubble //// fully inside thin channel //if (isA(s) && !isA(s)) //{ // const triSurfaceMesh& surf = refCast(s); //} } } // Collect candidate faces (i.e. intersecting any surface and // owner/neighbour not yet refined) and their cells labelList cellMap; { labelList testFaces(getRefineCandidateFaces(refineCell)); // Extend the set by faceCellFace wave upto given 3*blockLevel size markProximityCandidateFaces ( blockedSurfaces, regionToBlockSize, neiLevel, neiCc, testFaces ); bitSet isTestCell(mesh_.nCells()); for (const label facei : testFaces) { const label own = mesh_.faceOwner()[facei]; if (refineCell[own] == -1) { isTestCell.set(own); } if (mesh_.isInternalFace(facei)) { const label nei = mesh_.faceNeighbour()[facei]; if (refineCell[nei] == -1) { isTestCell.set(nei); } } } cellMap = isTestCell.sortedToc(); } // Shoot rays in all 6 directions // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // // Almost exact copy of meshRefinement::markInternalGapRefinement. // Difference is only in the length scale (now based on blockLevel) // Find per cell centre the nearest point and normal on the surfaces List nearInfo; vectorField nearNormal; labelList nearSurface; labelList nearRegion; { surfaces_.findNearestRegion ( blockedSurfaces, pointField(mesh_.cellCentres(), cellMap), scalarField(cellMap.size(), maxBlockSize), // use uniform for now nearSurface, nearInfo, nearRegion, nearNormal ); } DynamicList