surfaceCheck.C
Go to the documentation of this file.
1 /*---------------------------------------------------------------------------*\
2  ========= |
3  \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
4  \\ / O peration |
5  \\ / A nd | www.openfoam.com
6  \\/ M anipulation |
7 -------------------------------------------------------------------------------
8  Copyright (C) 2011-2016 OpenFOAM Foundation
9  Copyright (C) 2016-2020 OpenCFD Ltd.
10 -------------------------------------------------------------------------------
11 License
12  This file is part of OpenFOAM.
13 
14  OpenFOAM is free software: you can redistribute it and/or modify it
15  under the terms of the GNU General Public License as published by
16  the Free Software Foundation, either version 3 of the License, or
17  (at your option) any later version.
18 
19  OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
20  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
21  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
22  for more details.
23 
24  You should have received a copy of the GNU General Public License
25  along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
26 
27 Application
28  surfaceCheck
29 
30 Group
31  grpSurfaceUtilities
32 
33 Description
34  Check geometric and topological quality of a surface.
35 
36 Usage
37  \b surfaceCheck [OPTION] surfaceFile
38 
39  Options:
40  - \par -checkSelfIntersection
41  Check for self-intersection.
42 
43  - \par -splitNonManifold
44  Split surface along non-manifold edges.
45 
46  - \par -verbose
47  Extra verbosity.
48 
49  - \par -blockMesh
50  Write vertices/blocks for tight-fitting 1 cell blockMeshDict.
51 
52  - \par -outputThreshold <num files>
53  Upper limit on the number of files written.
54  Prevent surfaces with many disconnected parts from writing many files.
55  The default is 10. A value of 0 suppresses file writing.
56 
57 \*---------------------------------------------------------------------------*/
58 
59 #include "triangle.H"
60 #include "edgeHashes.H"
61 #include "triSurface.H"
62 #include "triSurfaceSearch.H"
63 #include "triSurfaceTools.H"
64 #include "argList.H"
65 #include "OFstream.H"
66 #include "OBJstream.H"
67 #include "SortableList.H"
68 #include "PatchTools.H"
69 #include "vtkSurfaceWriter.H"
70 #include "functionObject.H"
71 #include "DynamicField.H"
72 #include "edgeMesh.H"
73 
74 using namespace Foam;
75 
76 labelList countBins
77 (
78  const scalar min,
79  const scalar max,
80  const label nBins,
81  const scalarField& vals
82 )
83 {
84  scalar dist = nBins/(max - min);
85 
86  labelList binCount(nBins, Zero);
87 
88  forAll(vals, i)
89  {
90  scalar val = vals[i];
91 
92  label index = -1;
93 
94  if (Foam::mag(val - min) < SMALL)
95  {
96  index = 0;
97  }
98  else if (val >= max - SMALL)
99  {
100  index = nBins - 1;
101  }
102  else
103  {
104  index = label((val - min)*dist);
105 
106  if ((index < 0) || (index >= nBins))
107  {
109  << "value " << val << " at index " << i
110  << " outside range " << min << " .. " << max << endl;
111 
112  if (index < 0)
113  {
114  index = 0;
115  }
116  else
117  {
118  index = nBins - 1;
119  }
120  }
121  }
122  binCount[index]++;
123  }
124 
125  return binCount;
126 }
127 
128 
129 
130 void writeZoning
131 (
133  const triSurface& surf,
134  const labelList& faceZone,
135  const word& fieldName,
136  const fileName& surfFilePath,
137  const fileName& surfFileNameBase
138 )
139 {
140  // Transcribe faces
141  faceList faces;
142  surf.triFaceFaces(faces);
143 
144  writer.open
145  (
146  surf.points(),
147  faces,
148  (surfFilePath / surfFileNameBase),
149  false // serial - already merged
150  );
151 
152  fileName outputName = writer.write(fieldName, labelField(faceZone));
153 
154  writer.clear();
155 
156  Info<< "Wrote zoning to " << outputName << nl << endl;
157 }
158 
159 
160 void writeParts
161 (
162  const triSurface& surf,
163  const label nFaceZones,
164  const labelList& faceZone,
165  const fileName& surfFilePath,
166  const fileName& surfFileNameBase
167 )
168 {
169  for (label zone = 0; zone < nFaceZones; zone++)
170  {
171  boolList includeMap(surf.size(), false);
172 
173  forAll(faceZone, facei)
174  {
175  if (faceZone[facei] == zone)
176  {
177  includeMap[facei] = true;
178  }
179  }
180 
181  triSurface subSurf(surf.subsetMesh(includeMap));
182 
183  fileName subName
184  (
185  surfFilePath
186  / surfFileNameBase + "_" + name(zone) + ".obj"
187  );
188 
189  Info<< "writing part " << zone << " size " << subSurf.size()
190  << " to " << subName << endl;
191 
192  subSurf.write(subName);
193  }
194 }
195 
196 
197 void syncEdges(const triSurface& p, labelHashSet& markedEdges)
198 {
199  // See comment below about having duplicate edges
200 
201  const edgeList& edges = p.edges();
202  edgeHashSet edgeSet(2*markedEdges.size());
203 
204  for (const label edgei : markedEdges)
205  {
206  edgeSet.insert(edges[edgei]);
207  }
208 
209  forAll(edges, edgei)
210  {
211  if (edgeSet.found(edges[edgei]))
212  {
213  markedEdges.insert(edgei);
214  }
215  }
216 }
217 
218 
219 void syncEdges(const triSurface& p, boolList& isMarkedEdge)
220 {
221  // See comment below about having duplicate edges
222 
223  const edgeList& edges = p.edges();
224 
225  label n = 0;
226  forAll(isMarkedEdge, edgei)
227  {
228  if (isMarkedEdge[edgei])
229  {
230  n++;
231  }
232  }
233 
234  edgeHashSet edgeSet(2*n);
235 
236  forAll(isMarkedEdge, edgei)
237  {
238  if (isMarkedEdge[edgei])
239  {
240  edgeSet.insert(edges[edgei]);
241  }
242  }
243 
244  forAll(edges, edgei)
245  {
246  if (edgeSet.found(edges[edgei]))
247  {
248  isMarkedEdge[edgei] = true;
249  }
250  }
251 }
252 
253 
254 void writeEdgeSet
255 (
256  const word& setName,
257  const triSurface& surf,
258  const labelUList& edgeSet
259 )
260 {
261  // Get compact edge mesh
262  labelList pointToCompact(surf.nPoints(), -1);
263  DynamicField<point> compactPoints(edgeSet.size());
264  DynamicList<edge> compactEdges(edgeSet.size());
265  for (label edgei : edgeSet)
266  {
267  const edge& e = surf.edges()[edgei];
268  edge compactEdge(-1, -1);
269  forAll(e, ep)
270  {
271  label& compacti = pointToCompact[e[ep]];
272  if (compacti == -1)
273  {
274  compacti = compactPoints.size();
275  label pointi = surf.meshPoints()[e[ep]];
276  compactPoints.append(surf.points()[pointi]);
277  }
278  compactEdge[ep] = compacti;
279  }
280  compactEdges.append(compactEdge);
281  }
282 
283  edgeMesh eMesh(std::move(compactPoints), std::move(compactEdges));
284  eMesh.write(setName);
285 }
286 
287 
288 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
289 
290 int main(int argc, char *argv[])
291 {
293  (
294  "Check geometric and topological quality of a surface"
295  );
296 
298  argList::addArgument("input", "The input surface file");
299 
301  (
302  "checkSelfIntersection",
303  "Also check for self-intersection"
304  );
306  (
307  "splitNonManifold",
308  "Split surface along non-manifold edges "
309  "(default split is fully disconnected)"
310  );
312  (
313  "verbose",
314  "Additional verbosity"
315  );
317  (
318  "blockMesh",
319  "Write vertices/blocks for blockMeshDict"
320  );
322  (
323  "outputThreshold",
324  "number",
325  "Upper limit on the number of files written. "
326  "Default is 10, using 0 suppresses file writing."
327  );
329  (
330  "writeSets",
331  "surfaceFormat",
332  "Reconstruct and write problem triangles/edges in selected format"
333  );
334 
335 
336  argList args(argc, argv);
337 
338  const fileName surfFileName = args[1];
339  const bool checkSelfIntersect = args.found("checkSelfIntersection");
340  const bool splitNonManifold = args.found("splitNonManifold");
341  const label outputThreshold =
342  args.getOrDefault<label>("outputThreshold", 10);
343  const word surfaceFormat = args.getOrDefault<word>("writeSets", "");
344  const bool writeSets = !surfaceFormat.empty();
345 
346  autoPtr<surfaceWriter> surfWriter;
347  word edgeFormat;
348  if (writeSets)
349  {
350  surfWriter = surfaceWriter::New(surfaceFormat);
351 
352  // Option1: hard-coded format
353  edgeFormat = "obj";
356  //edgeFormat = surfaceFormat;
357  //if (!edgeMesh::canWriteType(edgeFormat))
358  //{
359  // edgeFormat = "obj";
360  //}
361  }
362 
363 
364  Info<< "Reading surface from " << surfFileName << " ..." << nl << endl;
365 
366  // Read
367  // ~~~~
368 
369  triSurface surf(surfFileName);
370 
371 
372  Info<< "Statistics:" << endl;
373  surf.writeStats(Info);
374  Info<< endl;
375 
376 
377  // Determine path and extension
378  fileName surfFileNameBase(surfFileName.name());
379  const word fileType = surfFileNameBase.ext();
380  // Strip extension
381  surfFileNameBase = surfFileNameBase.lessExt();
382  // If extension was .gz strip original extension
383  if (fileType == "gz")
384  {
385  surfFileNameBase = surfFileNameBase.lessExt();
386  }
387  const fileName surfFilePath(surfFileName.path());
388 
389 
390  // write bounding box corners
391  if (args.found("blockMesh"))
392  {
393  pointField cornerPts(boundBox(surf.points(), false).points());
394 
395  Info<< "// blockMeshDict info" << nl << nl;
396 
397  Info<< "vertices\n(" << nl;
398  forAll(cornerPts, pti)
399  {
400  Info<< " " << cornerPts[pti] << nl;
401  }
402 
403  // number of divisions needs adjustment later
404  Info<< ");\n" << nl
405  << "blocks\n"
406  << "(\n"
407  << " hex (0 1 2 3 4 5 6 7) (10 10 10) simpleGrading (1 1 1)\n"
408  << ");\n" << nl;
409 
410  Info<< "edges\n();" << nl
411  << "patches\n();" << endl;
412 
413  Info<< nl << "// end blockMeshDict info" << nl << endl;
414  }
415 
416 
417  // Region sizes
418  // ~~~~~~~~~~~~
419 
420  {
421  labelList regionSize(surf.patches().size(), Zero);
422 
423  forAll(surf, facei)
424  {
425  label region = surf[facei].region();
426 
427  if (region < 0 || region >= regionSize.size())
428  {
430  << "Triangle " << facei << " vertices " << surf[facei]
431  << " has region " << region << " which is outside the range"
432  << " of regions 0.." << surf.patches().size()-1
433  << endl;
434  }
435  else
436  {
437  regionSize[region]++;
438  }
439  }
440 
441  Info<< "Region\tSize" << nl
442  << "------\t----" << nl;
443  forAll(surf.patches(), patchi)
444  {
445  Info<< surf.patches()[patchi].name() << '\t'
446  << regionSize[patchi] << nl;
447  }
448  Info<< nl << endl;
449  }
450 
451 
452  // Check triangles
453  // ~~~~~~~~~~~~~~~
454 
455  {
456  DynamicList<label> illegalFaces(surf.size()/100 + 1);
457 
458  forAll(surf, facei)
459  {
460  // Check silently
461  if (!triSurfaceTools::validTri(surf, facei, false))
462  {
463  illegalFaces.append(facei);
464  }
465  }
466 
467  if (illegalFaces.size())
468  {
469  Info<< "Surface has " << illegalFaces.size()
470  << " illegal triangles." << endl;
471 
472  if (surfWriter.valid())
473  {
474  boolList isIllegalFace(surf.size(), false);
475  UIndirectList<bool>(isIllegalFace, illegalFaces) = true;
476 
477  triSurface subSurf(surf.subsetMesh(isIllegalFace));
478 
479 
480  // Transcribe faces
481  faceList faces;
482  subSurf.triFaceFaces(faces);
483 
484  surfWriter->open
485  (
486  subSurf.points(),
487  faces,
488  (surfFilePath / surfFileNameBase),
489  false // serial - already merged
490  );
491 
492  fileName outputName = surfWriter->write
493  (
494  "illegal",
495  scalarField(subSurf.size(), Zero)
496  );
497 
498  surfWriter->clear();
499 
500  Info<< "Wrote illegal triangles to "
501  << outputName << nl << endl;
502  }
503  else if (outputThreshold > 0)
504  {
505  forAll(illegalFaces, i)
506  {
507  // Force warning message
508  triSurfaceTools::validTri(surf, illegalFaces[i], true);
509  if (i >= outputThreshold)
510  {
511  Info<< "Suppressing further warning messages."
512  << " Use -outputThreshold to increase number"
513  << " of warnings." << endl;
514  break;
515  }
516  }
517 
518  OFstream str("illegalFaces");
519  Info<< "Dumping conflicting face labels to " << str.name()
520  << endl
521  << "Paste this into the input for surfaceSubset" << endl;
522  str << illegalFaces;
523  }
524  }
525  else
526  {
527  Info<< "Surface has no illegal triangles." << endl;
528  }
529  Info<< endl;
530  }
531 
532 
533 
534  // Triangle quality
535  // ~~~~~~~~~~~~~~~~
536 
537  {
538  scalarField triQ(surf.size(), Zero);
539  forAll(surf, facei)
540  {
541  const labelledTri& f = surf[facei];
542 
543  if (f[0] == f[1] || f[0] == f[2] || f[1] == f[2])
544  {
545  //WarningInFunction
546  // << "Illegal triangle " << facei << " vertices " << f
547  // << " coords " << f.points(surf.points()) << endl;
548  }
549  else
550  {
551  triQ[facei] = triPointRef
552  (
553  surf.points()[f[0]],
554  surf.points()[f[1]],
555  surf.points()[f[2]]
556  ).quality();
557  }
558  }
559 
560  labelList binCount = countBins(0, 1, 20, triQ);
561 
562  Info<< "Triangle quality (equilateral=1, collapsed=0):"
563  << endl;
564 
565 
566  OSstream& os = Info;
567  os.width(4);
568 
569  scalar dist = (1.0 - 0.0)/20.0;
570  scalar min = 0;
571  forAll(binCount, bini)
572  {
573  Info<< " " << min << " .. " << min+dist << " : "
574  << 1.0/surf.size() * binCount[bini]
575  << endl;
576  min += dist;
577  }
578  Info<< endl;
579 
580  labelPair minMaxIds = findMinMax(triQ);
581 
582  const label minIndex = minMaxIds.first();
583  const label maxIndex = minMaxIds.second();
584 
585  Info<< " min " << triQ[minIndex] << " for triangle " << minIndex
586  << nl
587  << " max " << triQ[maxIndex] << " for triangle " << maxIndex
588  << nl
589  << endl;
590 
591 
592  if (triQ[minIndex] < SMALL)
593  {
595  << "Minimum triangle quality is "
596  << triQ[minIndex] << ". This might give problems in"
597  << " self-intersection testing later on." << endl;
598  }
599 
600  // Dump for subsetting
601  if (surfWriter.valid())
602  {
603  // Transcribe faces
604  faceList faces(surf.size());
605  surf.triFaceFaces(faces);
606 
607  surfWriter->open
608  (
609  surf.points(),
610  faces,
611  (surfFilePath / surfFileNameBase),
612  false // serial - already merged
613  );
614 
615  fileName outputName = surfWriter->write("quality", triQ);
616 
617  surfWriter->clear();
618 
619  Info<< "Wrote triangle-quality to "
620  << outputName << nl << endl;
621  }
622  else if (outputThreshold > 0)
623  {
624  DynamicList<label> problemFaces(surf.size()/100+1);
625 
626  forAll(triQ, facei)
627  {
628  if (triQ[facei] < 1e-11)
629  {
630  problemFaces.append(facei);
631  }
632  }
633 
634  if (!problemFaces.empty())
635  {
636  OFstream str("badFaces");
637 
638  Info<< "Dumping bad quality faces to " << str.name() << endl
639  << "Paste this into the input for surfaceSubset" << nl
640  << nl << endl;
641 
642  str << problemFaces;
643  }
644  }
645  }
646 
647 
648 
649  // Edges
650  // ~~~~~
651  {
652  const edgeList& edges = surf.edges();
653  const pointField& localPoints = surf.localPoints();
654 
655  scalarField edgeMag(edges.size());
656 
657  forAll(edges, edgei)
658  {
659  edgeMag[edgei] = edges[edgei].mag(localPoints);
660  }
661 
662  labelPair minMaxIds = findMinMax(edgeMag);
663 
664  const label minEdgei = minMaxIds.first();
665  const label maxEdgei = minMaxIds.second();
666 
667  const edge& minE = edges[minEdgei];
668  const edge& maxE = edges[maxEdgei];
669 
670 
671  Info<< "Edges:" << nl
672  << " min " << edgeMag[minEdgei] << " for edge " << minEdgei
673  << " points " << localPoints[minE[0]] << localPoints[minE[1]]
674  << nl
675  << " max " << edgeMag[maxEdgei] << " for edge " << maxEdgei
676  << " points " << localPoints[maxE[0]] << localPoints[maxE[1]]
677  << nl
678  << endl;
679  }
680 
681 
682 
683  // Close points
684  // ~~~~~~~~~~~~
685  {
686  const edgeList& edges = surf.edges();
687  const pointField& localPoints = surf.localPoints();
688 
689  const boundBox bb(localPoints);
690  scalar smallDim = 1e-6 * bb.mag();
691 
692  Info<< "Checking for points less than 1e-6 of bounding box ("
693  << bb.span() << " metre) apart."
694  << endl;
695 
696  // Sort points
697  SortableList<scalar> sortedMag(mag(localPoints));
698 
699  label nClose = 0;
700 
701  for (label i = 1; i < sortedMag.size(); i++)
702  {
703  label pti = sortedMag.indices()[i];
704 
705  label prevPti = sortedMag.indices()[i-1];
706 
707  if (mag(localPoints[pti] - localPoints[prevPti]) < smallDim)
708  {
709  // Check if neighbours.
710  const labelList& pEdges = surf.pointEdges()[pti];
711 
712  label edgei = -1;
713 
714  forAll(pEdges, i)
715  {
716  const edge& e = edges[pEdges[i]];
717 
718  if (e[0] == prevPti || e[1] == prevPti)
719  {
720  // point1 and point0 are connected through edge.
721  edgei = pEdges[i];
722 
723  break;
724  }
725  }
726 
727  if (nClose < outputThreshold)
728  {
729  if (edgei == -1)
730  {
731  Info<< " close unconnected points "
732  << pti << ' ' << localPoints[pti]
733  << " and " << prevPti << ' '
734  << localPoints[prevPti]
735  << " distance:"
736  << mag(localPoints[pti] - localPoints[prevPti])
737  << endl;
738  }
739  else
740  {
741  Info<< " small edge between points "
742  << pti << ' ' << localPoints[pti]
743  << " and " << prevPti << ' '
744  << localPoints[prevPti]
745  << " distance:"
746  << mag(localPoints[pti] - localPoints[prevPti])
747  << endl;
748  }
749  }
750  else if (nClose == outputThreshold)
751  {
752  Info<< "Suppressing further warning messages."
753  << " Use -outputThreshold to increase number"
754  << " of warnings." << endl;
755  }
756 
757  nClose++;
758  }
759  }
760 
761  Info<< "Found " << nClose << " nearby points." << nl
762  << endl;
763  }
764 
765 
766 
767  // Check manifold
768  // ~~~~~~~~~~~~~~
769 
770  DynamicList<label> problemFaces(surf.size()/100 + 1);
771  DynamicList<label> openEdges(surf.nEdges()/100 + 1);
772  DynamicList<label> multipleEdges(surf.nEdges()/100 + 1);
773 
774  const labelListList& edgeFaces = surf.edgeFaces();
775 
776  forAll(edgeFaces, edgei)
777  {
778  const labelList& myFaces = edgeFaces[edgei];
779 
780  if (myFaces.size() == 1)
781  {
782  problemFaces.append(myFaces[0]);
783  openEdges.append(edgei);
784  }
785  }
786 
787  forAll(edgeFaces, edgei)
788  {
789  const labelList& myFaces = edgeFaces[edgei];
790 
791  if (myFaces.size() > 2)
792  {
793  forAll(myFaces, myFacei)
794  {
795  problemFaces.append(myFaces[myFacei]);
796  }
797  multipleEdges.append(edgei);
798  }
799  }
800  problemFaces.shrink();
801 
802  if (openEdges.size() || multipleEdges.size())
803  {
804  Info<< "Surface is not closed since not all edges ("
805  << edgeFaces.size() << ") connected to "
806  << "two faces:" << endl
807  << " connected to one face : " << openEdges.size() << endl
808  << " connected to >2 faces : " << multipleEdges.size() << endl;
809 
810  Info<< "Conflicting face labels:" << problemFaces.size() << endl;
811 
812  if (!edgeFormat.empty() && openEdges.size())
813  {
814  const fileName openName
815  (
816  surfFileName.lessExt()
817  + "_open."
818  + edgeFormat
819  );
820  Info<< "Writing open edges to " << openName << " ..." << endl;
821  writeEdgeSet(openName, surf, openEdges);
822  }
823  if (!edgeFormat.empty() && multipleEdges.size())
824  {
825  const fileName multName
826  (
827  surfFileName.lessExt()
828  + "_multiply."
829  + edgeFormat
830  );
831  Info<< "Writing multiply connected edges to "
832  << multName << " ..." << endl;
833  writeEdgeSet(multName, surf, multipleEdges);
834  }
835  }
836  else
837  {
838  Info<< "Surface is closed. All edges connected to two faces." << endl;
839  }
840  Info<< endl;
841 
842 
843 
844  // Check singly connected domain
845  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
846 
847  {
848  boolList borderEdge(surf.nEdges(), false);
849  if (splitNonManifold)
850  {
851  forAll(edgeFaces, edgei)
852  {
853  if (edgeFaces[edgei].size() > 2)
854  {
855  borderEdge[edgei] = true;
856  }
857  }
858  syncEdges(surf, borderEdge);
859  }
860 
862  label numZones = surf.markZones(borderEdge, faceZone);
863 
864  Info<< "Number of unconnected parts : " << numZones << endl;
865 
866  if (numZones > 1 && outputThreshold > 0)
867  {
868  Info<< "Splitting surface into parts ..." << endl << endl;
869 
870  if (!surfWriter.valid())
871  {
872  surfWriter.reset(new surfaceWriters::vtkWriter());
873  }
874 
875  writeZoning
876  (
877  *surfWriter,
878  surf,
879  faceZone,
880  "zone",
881  surfFilePath,
882  surfFileNameBase
883  );
884 
885  if (numZones > outputThreshold)
886  {
887  Info<< "Limiting number of files to " << outputThreshold
888  << endl;
889  }
890  writeParts
891  (
892  surf,
893  min(outputThreshold, numZones),
894  faceZone,
895  surfFilePath,
896  surfFileNameBase
897  );
898  }
899  }
900 
901 
902 
903  // Check orientation
904  // ~~~~~~~~~~~~~~~~~
905 
906  labelHashSet borderEdge(surf.size()/1000);
907  PatchTools::checkOrientation(surf, false, &borderEdge);
908 
909  // Bit strange: if a triangle has two same vertices (illegal!) it will
910  // still have three distinct edges (two of which have the same vertices).
911  // In this case the faceEdges addressing is not symmetric, i.e. a
912  // neighbouring, valid, triangle will have correct addressing so 3 distinct
913  // edges so it will miss one of those two identical edges.
914  // - we don't want to fix this in PrimitivePatch since it is too specific
915  // - instead just make sure we mark all identical edges consistently
916  // when we use them for marking.
917 
918  syncEdges(surf, borderEdge);
919 
920  //
921  // Colour all faces into zones using borderEdge
922  //
923  labelList normalZone;
924  label numNormalZones = PatchTools::markZones(surf, borderEdge, normalZone);
925 
926  Info<< endl
927  << "Number of zones (connected area with consistent normal) : "
928  << numNormalZones << endl;
929 
930  if (numNormalZones > 1)
931  {
932  Info<< "More than one normal orientation." << endl;
933 
934  if (outputThreshold > 0)
935  {
936  if (!surfWriter.valid())
937  {
938  surfWriter.reset(new surfaceWriters::vtkWriter());
939  }
940 
941  writeZoning
942  (
943  *surfWriter,
944  surf,
945  normalZone,
946  "normal",
947  surfFilePath,
948  surfFileNameBase
949  );
950 
951  if (numNormalZones > outputThreshold)
952  {
953  Info<< "Limiting number of files to " << outputThreshold
954  << endl;
955  }
956  writeParts
957  (
958  surf,
959  min(outputThreshold, numNormalZones),
960  normalZone,
961  surfFilePath,
962  surfFileNameBase + "_normal"
963  );
964  }
965  }
966  Info<< endl;
967 
968 
969 
970  // Check self-intersection
971  // ~~~~~~~~~~~~~~~~~~~~~~~
972 
973  if (checkSelfIntersect)
974  {
975  Info<< "Checking self-intersection." << endl;
976 
977  triSurfaceSearch querySurf(surf);
978 
979  const indexedOctree<treeDataTriSurface>& tree = querySurf.tree();
980 
981  autoPtr<OBJstream> intStreamPtr;
982  if (outputThreshold > 0)
983  {
984  intStreamPtr.reset(new OBJstream("selfInterPoints.obj"));
985  }
986 
987  label nInt = 0;
988 
989  forAll(surf.edges(), edgei)
990  {
991  const edge& e = surf.edges()[edgei];
992  const point& start = surf.points()[surf.meshPoints()[e[0]]];
993  const point& end = surf.points()[surf.meshPoints()[e[1]]];
994 
995  // Exclude hits of connected triangles
996  treeDataTriSurface::findSelfIntersectOp exclOp(tree, edgei);
997 
998  pointIndexHit hitInfo(tree.findLineAny(start, end, exclOp));
999 
1000  if (hitInfo.hit())
1001  {
1002  nInt++;
1003 
1004  if (intStreamPtr.valid())
1005  {
1006  intStreamPtr().write(hitInfo.hitPoint());
1007  }
1008 
1009  // Try and find from other side.
1010  pointIndexHit hitInfo2(tree.findLineAny(end, start, exclOp));
1011 
1012  if (hitInfo2.hit() && hitInfo.index() != hitInfo2.index())
1013  {
1014  nInt++;
1015 
1016  if (intStreamPtr.valid())
1017  {
1018  intStreamPtr().write(hitInfo2.hitPoint());
1019  }
1020  }
1021  }
1022  }
1023 
1025  //{
1026  // const pointField& localPoints = surf.localPoints();
1027  //
1028  // const boundBox bb(localPoints);
1029  // scalar smallDim = 1e-6 * bb.mag();
1030  // scalar smallDimSqr = Foam::sqr(smallDim);
1031  //
1032  // const pointField& faceCentres = surf.faceCentres();
1033  // forAll(faceCentres, faceI)
1034  // {
1035  // const point& fc = faceCentres[faceI];
1036  // pointIndexHit hitInfo
1037  // (
1038  // tree.findNearest
1039  // (
1040  // fc,
1041  // smallDimSqr,
1042  // findSelfNearOp(tree, faceI)
1043  // )
1044  // );
1045  //
1046  // if (hitInfo.hit() && intStreamPtr.valid())
1047  // {
1048  // intStreamPtr().write(hitInfo.hitPoint());
1049  //
1050  // label nearFaceI = hitInfo.index();
1051  // triPointRef nearTri(surf[nearFaceI].tri(surf.points()));
1052  // triStreamPtr().write
1053  // (
1054  // surf[faceI].tri(surf.points()),
1055  // false
1056  // );
1057  // triStreamPtr().write(nearTri, false);
1058  // nInt++;
1059  // }
1060  // }
1061  //}
1062 
1063 
1064  if (nInt == 0)
1065  {
1066  Info<< "Surface is not self-intersecting" << endl;
1067  }
1068  else
1069  {
1070  Info<< "Surface is self-intersecting at " << nInt
1071  << " locations." << endl;
1072 
1073  if (intStreamPtr.valid())
1074  {
1075  Info<< "Writing intersection points to "
1076  << intStreamPtr().name() << endl;
1077  }
1078  }
1079  Info<< endl;
1080  }
1081 
1082 
1083  Info<< "\nEnd\n" << endl;
1084 
1085  return 0;
1086 }
1087 
1088 
1089 // ************************************************************************* //
Foam::triSurface::writeStats
void writeStats(Ostream &os) const
Write some statistics.
Definition: triSurfaceIO.C:353
Foam::Pair::second
const T & second() const noexcept
Return second element, which is also the last element.
Definition: PairI.H:122
Foam::triSurface::subsetMesh
triSurface subsetMesh(const UList< bool > &include, labelList &pointMap, labelList &faceMap) const
Return a new surface subsetted on the selected faces.
Definition: triSurface.C:840
Foam::HashTable::size
label size() const noexcept
The number of elements in table.
Definition: HashTableI.H:52
Foam::autoPtr::reset
void reset(T *p=nullptr) noexcept
Delete managed object and set to new given pointer.
Definition: autoPtrI.H:109
Foam::scalarField
Field< scalar > scalarField
Specialisation of Field<T> for scalar.
Definition: primitiveFieldsFwd.H:52
p
volScalarField & p
Definition: createFieldRefs.H:8
Foam::PrimitivePatch::edgeFaces
const labelListList & edgeFaces() const
Return edge-face addressing.
Definition: PrimitivePatch.C:242
Foam::word
A class for handling words, derived from Foam::string.
Definition: word.H:62
Foam::surfaceWriter::write
virtual fileName write()=0
Write separate surface geometry to file.
Foam::surfaceWriter
Base class for surface writers.
Definition: surfaceWriter.H:111
Foam::fileName
A class for handling file names.
Definition: fileName.H:69
Foam::OBJstream
OFstream that keeps track of vertices.
Definition: OBJstream.H:58
Foam::PrimitivePatch::edges
const edgeList & edges() const
Return list of edges, address into LOCAL point list.
Definition: PrimitivePatch.C:190
Foam::argList::getOrDefault
T getOrDefault(const word &optName, const T &deflt) const
Get a value from the named option if present, or return default.
Definition: argListI.H:286
triangle.H
Foam::fileName::path
static std::string path(const std::string &str)
Return directory path name (part before last /)
Definition: fileNameI.H:186
Foam::Zero
static constexpr const zero Zero
Global zero (0)
Definition: zero.H:131
PatchTools.H
Foam::DynamicList
A 1D vector of objects of type <T> that resizes itself as necessary to accept the new objects.
Definition: DynamicList.H:55
Foam::triSurface::triFaceFaces
void triFaceFaces(List< face > &plainFaceList) const
Create a list of faces from the triFaces.
Definition: triSurface.C:684
Foam::PrimitivePatch::nEdges
label nEdges() const
Return number of edges in patch.
Definition: PrimitivePatch.H:322
Foam::fileName::name
static std::string name(const std::string &str)
Return basename (part beyond last /), including its extension.
Definition: fileNameI.H:209
Foam::OSstream::width
virtual int width() const
Get width of output field.
Definition: OSstream.C:308
Foam::surfaceWriter::clear
virtual void clear()
Definition: surfaceWriter.C:313
Foam::edge
An edge is a list of two point labels. The functionality it provides supports the discretisation on a...
Definition: edge.H:63
Foam::zone
Base class for mesh zones.
Definition: zone.H:63
Foam::writer::write
virtual void write(const coordSet &, const wordList &, const List< const Field< Type > * > &, Ostream &) const =0
General entry point for writing.
Foam::argList::addNote
static void addNote(const string &note)
Add extra notes for the usage information.
Definition: argList.C:413
Foam::argList
Extract command arguments and options from the supplied argc and argv parameters.
Definition: argList.H:123
Foam::List::append
void append(const T &val)
Append an element at the end of the list.
Definition: ListI.H:182
Foam::autoPtr::valid
bool valid() const noexcept
True if the managed pointer is non-null.
Definition: autoPtr.H:148
Foam::endl
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:350
Foam::fileName::lessExt
fileName lessExt() const
Return file name without extension (part before last .)
Definition: fileNameI.H:240
Foam::indexedOctree::findLineAny
pointIndexHit findLineAny(const point &start, const point &end) const
Find any intersection of line between start and end.
Definition: indexedOctree.C:2509
triSurface.H
Foam::triSurface::patches
const geometricSurfacePatchList & patches() const
Definition: triSurface.H:399
Foam::HashSet< label, Hash< label > >
Foam::triSurfaceSearch
Helper class to search on triSurface.
Definition: triSurfaceSearch.H:58
Foam::min
label min(const labelHashSet &set, label minValue=labelMax)
Find the min value in labelHashSet, optionally limited by second argument.
Definition: hashSets.C:33
forAll
#define forAll(list, i)
Loop across all elements in list.
Definition: stdFoam.H:296
OFstream.H
Foam::DynamicField
Dynamically sized Field.
Definition: DynamicField.H:51
Foam::argList::addArgument
static void addArgument(const string &argName, const string &usage="")
Append a (mandatory) argument to validArgs.
Definition: argList.C:302
n
label n
Definition: TABSMDCalcMethod2.H:31
Foam::findMinMax
labelPair findMinMax(const ListType &input, label start=0)
SortableList.H
Foam::PointIndexHit
This class describes the interaction of (usually) a face and a point. It carries the info of a succes...
Definition: PointIndexHit.H:55
Foam::surfaceWriters::vtkWriter
A surfaceWriter for VTK legacy (.vtk) or XML (.vtp) format.
Definition: vtkSurfaceWriter.H:114
Foam::Field< scalar >
Foam::labelField
Field< label > labelField
Specialisation of Field<T> for label.
Definition: labelField.H:52
Foam::triSurface
Triangulated surface description with patch information.
Definition: triSurface.H:76
Foam::faceZone
A subset of mesh faces organised as a primitive patch.
Definition: faceZone.H:65
edgeMesh.H
Foam::Info
messageStream Info
Information stream (uses stdout - output is on the master only)
Foam::name
word name(const complex &c)
Return string representation of complex.
Definition: complex.C:76
Foam::DynamicList::append
DynamicList< T, SizeMin > & append(const T &val)
Append an element to the end of this list.
Definition: DynamicListI.H:472
argList.H
Foam::PrimitivePatch::pointEdges
const labelListList & pointEdges() const
Return point-edge addressing.
Definition: PrimitivePatch.C:268
Foam::indexedOctree
Non-pointer based hierarchical recursive searching.
Definition: treeDataEdge.H:50
Foam::treeDataPrimitivePatch::findSelfIntersectOp
Definition: treeDataPrimitivePatch.H:170
Foam::OSstream
Generic output stream using a standard (STL) stream.
Definition: OSstream.H:54
Foam::max
label max(const labelHashSet &set, label maxValue=labelMin)
Find the max value in labelHashSet, optionally limited by second argument.
Definition: hashSets.C:47
Foam::PrimitivePatch::nPoints
label nPoints() const
Return number of points supporting patch faces.
Definition: PrimitivePatch.H:316
Foam::PrimitivePatch::points
const Field< point_type > & points() const
Return reference to global points.
Definition: PrimitivePatch.H:305
Foam::SortableList
A list that is sorted upon construction or when explicitly requested with the sort() method.
Definition: List.H:63
stdFoam::end
constexpr auto end(C &c) -> decltype(c.end())
Return iterator to the end of the container c.
Definition: stdFoam.H:121
Foam::writer
Base class for graphics format writing. Entry points are.
Definition: writer.H:80
Foam::Ostream::write
virtual bool write(const token &tok)=0
Write token to stream or otherwise handle it.
Foam::fileName::ext
word ext() const
Return file name extension (part after last .)
Definition: fileNameI.H:228
Foam
Namespace for OpenFOAM.
Definition: atmBoundaryLayer.C:33
Foam::PrimitivePatch::localPoints
const Field< point_type > & localPoints() const
Return pointField of points in patch.
Definition: PrimitivePatch.C:339
Foam::OFstream
Output to file stream, using an OSstream.
Definition: OFstream.H:87
Foam::argList::addBoolOption
static void addBoolOption(const word &optName, const string &usage="", bool advanced=false)
Add a bool option to validOptions with usage information.
Definition: argList.C:325
Foam::autoPtr
Pointer management similar to std::unique_ptr, with some additional methods and type checking.
Definition: HashPtrTable.H:53
Foam::triSurface::markZones
label markZones(const boolList &borderEdge, labelList &faceZone) const
(size and) fills faceZone with zone of face. Zone is area
Definition: triSurface.C:757
DynamicField.H
edgeHashes.H
Foam::nl
constexpr char nl
Definition: Ostream.H:385
Foam::Pair< label >
f
labelList f(nPoints)
Foam::surfaceWriter::New
static autoPtr< surfaceWriter > New(const word &writeType)
Return a reference to the selected surfaceWriter.
Definition: surfaceWriter.C:64
Foam::Vector< scalar >
vtkSurfaceWriter.H
Foam::List< label >
Foam::mag
dimensioned< typename typeOfMag< Type >::type > mag(const dimensioned< Type > &dt)
Foam::UList< label >
points
const pointField & points
Definition: gmvOutputHeader.H:1
Foam::constant::electromagnetic::e
const dimensionedScalar e
Elementary charge.
Definition: createFields.H:11
Foam::HashSet::insert
bool insert(const Key &key)
Insert a new entry, not overwriting existing entries.
Definition: HashSet.H:181
Foam::labelledTri
Triangle with additional region number.
Definition: labelledTri.H:58
triSurfaceSearch.H
Foam::boundBox
A bounding box defined in terms of min/max extrema points.
Definition: boundBox.H:63
Foam::UList::size
void size(const label n) noexcept
Override size to be inconsistent with allocated storage.
Definition: UListI.H:360
Foam::UIndirectList
A List with indirect addressing.
Definition: fvMatrix.H:109
Foam::PatchTools::markZones
static label markZones(const PrimitivePatch< FaceList, PointField > &, const BoolListType &borderEdge, labelList &faceZone)
Size and fills faceZone with zone of face.
Foam::PatchTools::checkOrientation
static bool checkOrientation(const PrimitivePatch< FaceList, PointField > &, const bool report=false, labelHashSet *marked=0)
Check for orientation issues.
Definition: PatchToolsCheck.C:36
Foam::argList::noParallel
static void noParallel()
Remove the parallel options.
Definition: argList.C:491
Foam::triSurfaceTools::validTri
static bool validTri(const triSurface &, const label facei, const bool verbose=true)
Check single triangle for (topological) validity.
Definition: triSurfaceTools.C:2699
Foam::PrimitivePatch::meshPoints
const labelList & meshPoints() const
Return labelList of mesh points in patch.
Definition: PrimitivePatch.C:310
functionObject.H
Foam::argList::addOption
static void addOption(const word &optName, const string &param="", const string &usage="", bool advanced=false)
Add an option to validOptions with usage information.
Definition: argList.C:336
args
Foam::argList args(argc, argv)
Foam::edgeMesh
Mesh data needed to do the Finite Area discretisation.
Definition: edgeFaMesh.H:52
triSurfaceTools.H
WarningInFunction
#define WarningInFunction
Report a warning using Foam::Warning.
Definition: messageStream.H:298
Foam::surfaceWriter::open
virtual void open(const fileName &outputPath)
Open for output on specified path, using existing surface.
Definition: surfaceWriter.C:247
OBJstream.H
Foam::triPointRef
triangle< point, const point & > triPointRef
Definition: triangle.H:78
Foam::argList::found
bool found(const word &optName) const
Return true if the named option is found.
Definition: argListI.H:157