Skip to content

Commit bb5a861

Browse files
committed
Implement dfAutoPatch: A tool to divide external faces into independent patches.
1 parent 6f8fc9f commit bb5a861

3 files changed

Lines changed: 367 additions & 0 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
dfAutoPatch.C
2+
3+
EXE = $(DF_APPBIN)/dfAutoPatch
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
EXE_INC = \
2+
-I$(LIB_SRC)/meshTools/lnInclude \
3+
-I$(LIB_SRC)/dynamicMesh/lnInclude
4+
5+
EXE_LIBS = \
6+
-ldynamicMesh \
7+
-lmeshTools
Lines changed: 357 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,357 @@
1+
/*---------------------------------------------------------------------------*\
2+
========= |
3+
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
4+
\\ / O peration | Website: https://openfoam.org
5+
\\ / A nd | Copyright (C) 2011-2018 OpenFOAM Foundation
6+
\\/ M anipulation |
7+
-------------------------------------------------------------------------------
8+
License
9+
This file is part of OpenFOAM.
10+
11+
OpenFOAM is free software: you can redistribute it and/or modify it
12+
under the terms of the GNU General Public License as published by
13+
the Free Software Foundation, either version 3 of the License, or
14+
(at your option) any later version.
15+
16+
OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
17+
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
18+
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
19+
for more details.
20+
21+
You should have received a copy of the GNU General Public License
22+
along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
23+
24+
Application
25+
dfAutoPatch
26+
27+
Description
28+
An extension of autoPatch to work with polyhedral meshes.
29+
This utility divides external faces into patches based on (user supplied) feature
30+
angle and the source patches can be limited to user supplied patches if desired.
31+
The resulting patches are renamed to <originalName>_autoN if only a subset of
32+
patches is processed, otherwise to autoN.
33+
34+
Example usage:
35+
dfAutoPatch -onlyPatches "(box)" 45
36+
37+
Authorship
38+
Author: Teng Zhang @ AISI, <ztnuaa0211@gmail.com>
39+
Date: 2025-10-22
40+
\*---------------------------------------------------------------------------*/
41+
42+
#include "argList.H"
43+
#include "polyMesh.H"
44+
#include "Time.H"
45+
#include "boundaryMesh.H"
46+
#include "repatchPolyTopoChanger.H"
47+
#include "unitConversion.H"
48+
#include "OFstream.H"
49+
#include "ListOps.H"
50+
#include "wordList.H"
51+
52+
using namespace Foam;
53+
54+
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
55+
56+
// Get all feature edges.
57+
void collectFeatureEdges(const boundaryMesh& bMesh, labelList& markedEdges)
58+
{
59+
markedEdges.setSize(bMesh.mesh().nEdges());
60+
61+
label markedI = 0;
62+
63+
forAll(bMesh.featureSegments(), i)
64+
{
65+
const labelList& segment = bMesh.featureSegments()[i];
66+
67+
forAll(segment, j)
68+
{
69+
label featEdgeI = segment[j];
70+
71+
label meshEdgeI = bMesh.featureToEdge()[featEdgeI];
72+
73+
markedEdges[markedI++] = meshEdgeI;
74+
}
75+
}
76+
markedEdges.setSize(markedI);
77+
}
78+
79+
80+
81+
int main(int argc, char *argv[])
82+
{
83+
#include "addOverwriteOption.H"
84+
argList::noParallel();
85+
argList::validArgs.append("feature angle[0-180]");
86+
argList::addOption
87+
(
88+
"onlyPatches",
89+
"wordList of boundary patch names to be repatched"
90+
);
91+
92+
#include "setRootCase.H"
93+
#include "createTime.H"
94+
runTime.functionObjects().off();
95+
#include "createPolyMesh.H"
96+
const word oldInstance = mesh.pointsInstance();
97+
98+
wordList onlyPatches;
99+
100+
if (args.optionFound("onlyPatches"))
101+
{
102+
onlyPatches = wordList(args.optionLookup("onlyPatches")());
103+
104+
Info<< "Limiting autoPatch to patches: " << onlyPatches
105+
<< nl << endl;
106+
}
107+
108+
Info<< "Mesh read in = "
109+
<< runTime.cpuTimeIncrement()
110+
<< " s\n" << endl << endl;
111+
112+
113+
const scalar featureAngle = args.argRead<scalar>(1);
114+
const bool overwrite = args.optionFound("overwrite");
115+
116+
const scalar minCos = Foam::cos(degToRad(featureAngle));
117+
118+
Info<< "Feature:" << featureAngle << endl
119+
<< "minCos :" << minCos << endl
120+
<< endl;
121+
122+
//
123+
// Use boundaryMesh to reuse all the featureEdge stuff in there.
124+
//
125+
126+
boundaryMesh bMesh;
127+
bMesh.read(mesh);
128+
129+
// Set feature angle (calculate feature edges)
130+
bMesh.setFeatureEdges(minCos);
131+
132+
// Collect all feature edges as edge labels
133+
labelList markedEdges;
134+
135+
collectFeatureEdges(bMesh, markedEdges);
136+
137+
138+
139+
const labelList& boundaryFaces = bMesh.meshFace();
140+
const polyBoundaryMesh& originalBoundary = mesh.boundaryMesh();
141+
const label nBoundaryFaces = boundaryFaces.size();
142+
143+
// (new) patch ID for every face in mesh.
144+
labelList patchIDs(nBoundaryFaces, -1);
145+
boolList processFace(nBoundaryFaces, false);
146+
labelList originalPatchIDs(nBoundaryFaces, -1);
147+
148+
// Track per-patch suffix when only a subset is processed.
149+
labelList patchSuffixCount(originalBoundary.size(), 0);
150+
151+
const bool limitToPatches = onlyPatches.size() != 0;
152+
153+
// Determine which faces are allowed to be repatched.
154+
forAll(boundaryFaces, facei)
155+
{
156+
const label meshFacei = boundaryFaces[facei];
157+
const label patchi = originalBoundary.whichPatch(meshFacei);
158+
const word& patchName = originalBoundary[patchi].name();
159+
160+
originalPatchIDs[facei] = patchi;
161+
patchIDs[facei] = patchi;
162+
163+
if (!limitToPatches || findIndex(onlyPatches, patchName) != -1)
164+
{
165+
patchIDs[facei] = -1;
166+
processFace[facei] = true;
167+
}
168+
}
169+
170+
if (limitToPatches)
171+
{
172+
label selectedFaceCount = 0;
173+
174+
forAll(processFace, facei)
175+
{
176+
if (processFace[facei])
177+
{
178+
++selectedFaceCount;
179+
}
180+
}
181+
182+
if (!selectedFaceCount)
183+
{
184+
Info<< "No faces found for the supplied onlyPatches option." << nl
185+
<< endl;
186+
}
187+
}
188+
189+
//
190+
// Fill patchIDs with values for every face by floodfilling without
191+
// crossing feature edge.
192+
//
193+
194+
// Current patch number.
195+
label newPatchi = bMesh.patches().size();
196+
197+
label suffix = 0;
198+
199+
while (true)
200+
{
201+
// Find first unset face respecting the optional patch filter.
202+
label unsetFacei = -1;
203+
204+
forAll(patchIDs, facei)
205+
{
206+
if (patchIDs[facei] == -1 && processFace[facei])
207+
{
208+
unsetFacei = facei;
209+
break;
210+
}
211+
}
212+
213+
if (unsetFacei == -1)
214+
{
215+
// All faces have patchID set. Exit.
216+
break;
217+
}
218+
219+
// Found unset face. Create patch for it.
220+
word patchName;
221+
if (limitToPatches)
222+
{
223+
const label basePatchi = originalPatchIDs[unsetFacei];
224+
const word& baseName = originalBoundary[basePatchi].name();
225+
label& nextSuffix = patchSuffixCount[basePatchi];
226+
227+
do
228+
{
229+
patchName = baseName + "_auto" + name(nextSuffix++);
230+
}
231+
while (bMesh.findPatchID(patchName) != -1);
232+
}
233+
else
234+
{
235+
do
236+
{
237+
patchName = "auto" + name(suffix++);
238+
}
239+
while (bMesh.findPatchID(patchName) != -1);
240+
}
241+
242+
bMesh.addPatch(patchName);
243+
244+
bMesh.changePatchType(patchName, "patch");
245+
246+
247+
// Fill visited with all faces reachable from unsetFacei.
248+
boolList visited(nBoundaryFaces, false);
249+
250+
bMesh.markFaces(markedEdges, unsetFacei, visited);
251+
252+
253+
// Assign all visited faces to current patch
254+
label nVisited = 0;
255+
256+
forAll(visited, facei)
257+
{
258+
if (visited[facei] && processFace[facei])
259+
{
260+
nVisited++;
261+
262+
patchIDs[facei] = newPatchi;
263+
}
264+
}
265+
266+
Info<< "Assigned " << nVisited << " faces to patch " << patchName
267+
<< endl << endl;
268+
269+
newPatchi++;
270+
}
271+
272+
273+
274+
const PtrList<boundaryPatch>& patches = bMesh.patches();
275+
276+
// Create new list of patches with old ones first
277+
List<polyPatch*> newPatchPtrList(patches.size());
278+
279+
newPatchi = 0;
280+
281+
// Copy old patches
282+
forAll(mesh.boundaryMesh(), patchi)
283+
{
284+
const polyPatch& patch = mesh.boundaryMesh()[patchi];
285+
286+
newPatchPtrList[newPatchi] =
287+
patch.clone
288+
(
289+
mesh.boundaryMesh(),
290+
newPatchi,
291+
patch.size(),
292+
patch.start()
293+
).ptr();
294+
295+
newPatchi++;
296+
}
297+
298+
// Add new ones with empty size.
299+
for (label patchi = newPatchi; patchi < patches.size(); patchi++)
300+
{
301+
const boundaryPatch& bp = patches[patchi];
302+
303+
newPatchPtrList[newPatchi] = polyPatch::New
304+
(
305+
polyPatch::typeName,
306+
bp.name(),
307+
0,
308+
mesh.nFaces(),
309+
newPatchi,
310+
mesh.boundaryMesh()
311+
).ptr();
312+
313+
newPatchi++;
314+
}
315+
316+
if (!overwrite)
317+
{
318+
runTime++;
319+
}
320+
321+
322+
// Change patches
323+
repatchPolyTopoChanger polyMeshRepatcher(mesh);
324+
polyMeshRepatcher.changePatches(newPatchPtrList);
325+
326+
327+
// Change face ordering
328+
329+
// Since bMesh read from mesh there is one to one mapping so we don't
330+
// have to do the geometric stuff.
331+
forAll(patchIDs, facei)
332+
{
333+
label meshFacei = boundaryFaces[facei];
334+
335+
polyMeshRepatcher.changePatchID(meshFacei, patchIDs[facei]);
336+
}
337+
338+
polyMeshRepatcher.repatch();
339+
340+
// Write resulting mesh
341+
if (overwrite)
342+
{
343+
mesh.setInstance(oldInstance);
344+
}
345+
346+
// Set the precision of the points data to 10
347+
IOstream::defaultPrecision(max(10u, IOstream::defaultPrecision()));
348+
349+
mesh.write();
350+
351+
Info<< "End\n" << endl;
352+
353+
return 0;
354+
}
355+
356+
357+
// ************************************************************************* //

0 commit comments

Comments
 (0)