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-2022 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 word& surfFileStem
138 )
139 {
140  // Transcribe faces
141  faceList faces;
142  surf.triFaceFaces(faces);
143 
144  writer.open
145  (
146  surf.points(),
147  faces,
148  (surfFilePath / surfFileStem),
149  false // serial - already merged
150  );
151 
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 word& surfFileStem
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  / surfFileStem + "_" + 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  );
313  (
314  "blockMesh",
315  "Write vertices/blocks for blockMeshDict"
316  );
318  (
319  "outputThreshold",
320  "number",
321  "Upper limit on the number of files written. "
322  "Default is 10, using 0 suppresses file writing."
323  );
325  (
326  "writeSets",
327  "surfaceFormat",
328  "Reconstruct and write problem triangles/edges in selected format"
329  );
330 
331 
332  argList args(argc, argv);
333 
334  const auto surfName = args.get<fileName>(1);
335  const bool checkSelfIntersect = args.found("checkSelfIntersection");
336  const bool splitNonManifold = args.found("splitNonManifold");
337  const label outputThreshold =
338  args.getOrDefault<label>("outputThreshold", 10);
339  const word surfaceFormat = args.getOrDefault<word>("writeSets", "");
340  const bool writeSets = !surfaceFormat.empty();
341 
342  autoPtr<surfaceWriter> surfWriter;
343  word edgeFormat;
344  if (writeSets)
345  {
346  surfWriter = surfaceWriter::New(surfaceFormat);
347 
348  // Option1: hard-coded format
349  edgeFormat = "obj";
352  //edgeFormat = surfaceFormat;
353  //if (!edgeMesh::canWriteType(edgeFormat))
354  //{
355  // edgeFormat = "obj";
356  //}
357  }
358 
359 
360  Info<< "Reading surface from "
361  << args.relativePath(surfName) << " ..." << nl << endl;
362 
363  // Read
364  // ~~~~
365 
366  triSurface surf(surfName);
367 
368 
369  Info<< "Statistics:" << endl;
370  surf.writeStats(Info);
371  Info<< endl;
372 
373 
374  // Split into path and stem (no extension)
375  const fileName surfFilePath(surfName.path());
376  word surfFileStem(surfName.stem());
377 
378  // If originally ".gz", need to strip extension again
379  if (surfName.has_ext("gz"))
380  {
381  surfFileStem.remove_ext();
382  }
383 
384 
385  // write bounding box corners
386  if (args.found("blockMesh"))
387  {
388  pointField cornerPts(boundBox(surf.points(), false).points());
389 
390  Info<< "// blockMeshDict info" << nl << nl;
391 
392  Info<< "vertices\n(" << nl;
393  forAll(cornerPts, pti)
394  {
395  Info<< " " << cornerPts[pti] << nl;
396  }
397 
398  // number of divisions needs adjustment later
399  Info<< ");\n" << nl
400  << "blocks\n"
401  << "(\n"
402  << " hex (0 1 2 3 4 5 6 7) (10 10 10) simpleGrading (1 1 1)\n"
403  << ");\n" << nl;
404 
405  Info<< "edges\n();" << nl
406  << "patches\n();" << endl;
407 
408  Info<< nl << "// end blockMeshDict info" << nl << endl;
409  }
410 
411 
412  // Region sizes
413  // ~~~~~~~~~~~~
414 
415  {
416  labelList regionSize(surf.patches().size(), Zero);
417 
418  forAll(surf, facei)
419  {
420  label region = surf[facei].region();
421 
422  if (region < 0 || region >= regionSize.size())
423  {
425  << "Triangle " << facei << " vertices " << surf[facei]
426  << " has region " << region << " which is outside the range"
427  << " of regions 0.." << surf.patches().size()-1
428  << endl;
429  }
430  else
431  {
432  regionSize[region]++;
433  }
434  }
435 
436  Info<< "Region\tSize" << nl
437  << "------\t----" << nl;
438  forAll(surf.patches(), patchi)
439  {
440  Info<< surf.patches()[patchi].name() << '\t'
441  << regionSize[patchi] << nl;
442  }
443  Info<< nl << endl;
444  }
445 
446 
447  // Check triangles
448  // ~~~~~~~~~~~~~~~
449 
450  {
451  DynamicList<label> illegalFaces(surf.size()/100 + 1);
452 
453  forAll(surf, facei)
454  {
455  // Check silently
456  if (!triSurfaceTools::validTri(surf, facei, false))
457  {
458  illegalFaces.append(facei);
459  }
460  }
461 
462  if (illegalFaces.size())
463  {
464  Info<< "Surface has " << illegalFaces.size()
465  << " illegal triangles." << endl;
466 
467  if (surfWriter)
468  {
469  boolList isIllegalFace(surf.size(), false);
470  UIndirectList<bool>(isIllegalFace, illegalFaces) = true;
471 
472  triSurface subSurf(surf.subsetMesh(isIllegalFace));
473 
474 
475  // Transcribe faces
476  faceList faces;
477  subSurf.triFaceFaces(faces);
478 
479  surfWriter->open
480  (
481  subSurf.points(),
482  faces,
483  (surfFilePath / surfFileStem),
484  false // serial - already merged
485  );
486 
487  fileName outputName = surfWriter->write
488  (
489  "illegal",
490  scalarField(subSurf.size(), Zero)
491  );
492 
493  surfWriter->clear();
494 
495  Info<< "Wrote illegal triangles to "
496  << outputName << nl << endl;
497  }
498  else if (outputThreshold > 0)
499  {
500  forAll(illegalFaces, i)
501  {
502  // Force warning message
503  triSurfaceTools::validTri(surf, illegalFaces[i], true);
504  if (i >= outputThreshold)
505  {
506  Info<< "Suppressing further warning messages."
507  << " Use -outputThreshold to increase number"
508  << " of warnings." << endl;
509  break;
510  }
511  }
512 
513  OFstream str("illegalFaces");
514  Info<< "Dumping conflicting face labels to " << str.name()
515  << endl
516  << "Paste this into the input for surfaceSubset" << endl;
517  str << illegalFaces;
518  }
519  }
520  else
521  {
522  Info<< "Surface has no illegal triangles." << endl;
523  }
524  Info<< endl;
525  }
526 
527 
528 
529  // Triangle quality
530  // ~~~~~~~~~~~~~~~~
531 
532  {
533  scalarField triQ(surf.size(), Zero);
534  forAll(surf, facei)
535  {
536  const labelledTri& f = surf[facei];
537 
538  if (f[0] == f[1] || f[0] == f[2] || f[1] == f[2])
539  {
540  //WarningInFunction
541  // << "Illegal triangle " << facei << " vertices " << f
542  // << " coords " << f.points(surf.points()) << endl;
543  }
544  else
545  {
546  triQ[facei] = triPointRef
547  (
548  surf.points()[f[0]],
549  surf.points()[f[1]],
550  surf.points()[f[2]]
551  ).quality();
552  }
553  }
554 
555  labelList binCount = countBins(0, 1, 20, triQ);
556 
557  Info<< "Triangle quality (equilateral=1, collapsed=0):"
558  << endl;
559 
560 
561  OSstream& os = Info;
562  os.width(4);
563 
564  scalar dist = (1.0 - 0.0)/20.0;
565  scalar min = 0;
566  forAll(binCount, bini)
567  {
568  Info<< " " << min << " .. " << min+dist << " : "
569  << 1.0/surf.size() * binCount[bini]
570  << endl;
571  min += dist;
572  }
573  Info<< endl;
574 
575  labelPair minMaxIds = findMinMax(triQ);
576 
577  const label minIndex = minMaxIds.first();
578  const label maxIndex = minMaxIds.second();
579 
580  Info<< " min " << triQ[minIndex] << " for triangle " << minIndex
581  << nl
582  << " max " << triQ[maxIndex] << " for triangle " << maxIndex
583  << nl
584  << endl;
585 
586 
587  if (triQ[minIndex] < SMALL)
588  {
590  << "Minimum triangle quality is "
591  << triQ[minIndex] << ". This might give problems in"
592  << " self-intersection testing later on." << endl;
593  }
594 
595  // Dump for subsetting
596  if (surfWriter)
597  {
598  // Transcribe faces
599  faceList faces(surf.size());
600  surf.triFaceFaces(faces);
601 
602  surfWriter->open
603  (
604  surf.points(),
605  faces,
606  (surfFilePath / surfFileStem),
607  false // serial - already merged
608  );
609 
610  fileName outputName = surfWriter->write("quality", triQ);
611 
612  surfWriter->clear();
613 
614  Info<< "Wrote triangle-quality to "
615  << outputName << nl << endl;
616  }
617  else if (outputThreshold > 0)
618  {
619  DynamicList<label> problemFaces(surf.size()/100+1);
620 
621  forAll(triQ, facei)
622  {
623  if (triQ[facei] < 1e-11)
624  {
625  problemFaces.append(facei);
626  }
627  }
628 
629  if (!problemFaces.empty())
630  {
631  OFstream str("badFaces");
632 
633  Info<< "Dumping bad quality faces to " << str.name() << endl
634  << "Paste this into the input for surfaceSubset" << nl
635  << nl << endl;
636 
637  str << problemFaces;
638  }
639  }
640  }
641 
642 
643 
644  // Edges
645  // ~~~~~
646  {
647  const edgeList& edges = surf.edges();
648  const pointField& localPoints = surf.localPoints();
649 
650  scalarField edgeMag(edges.size());
651 
652  forAll(edges, edgei)
653  {
654  edgeMag[edgei] = edges[edgei].mag(localPoints);
655  }
656 
657  labelPair minMaxIds = findMinMax(edgeMag);
658 
659  const label minEdgei = minMaxIds.first();
660  const label maxEdgei = minMaxIds.second();
661 
662  const edge& minE = edges[minEdgei];
663  const edge& maxE = edges[maxEdgei];
664 
665 
666  Info<< "Edges:" << nl
667  << " min " << edgeMag[minEdgei] << " for edge " << minEdgei
668  << " points " << localPoints[minE[0]] << localPoints[minE[1]]
669  << nl
670  << " max " << edgeMag[maxEdgei] << " for edge " << maxEdgei
671  << " points " << localPoints[maxE[0]] << localPoints[maxE[1]]
672  << nl
673  << endl;
674  }
675 
676 
677 
678  // Close points
679  // ~~~~~~~~~~~~
680  {
681  const edgeList& edges = surf.edges();
682  const pointField& localPoints = surf.localPoints();
683 
684  const boundBox bb(localPoints);
685  scalar smallDim = 1e-6 * bb.mag();
686 
687  Info<< "Checking for points less than 1e-6 of bounding box ("
688  << bb.span() << " metre) apart."
689  << endl;
690 
691  // Sort points
692  SortableList<scalar> sortedMag(mag(localPoints));
693 
694  label nClose = 0;
695 
696  for (label i = 1; i < sortedMag.size(); i++)
697  {
698  label pti = sortedMag.indices()[i];
699 
700  label prevPti = sortedMag.indices()[i-1];
701 
702  if (mag(localPoints[pti] - localPoints[prevPti]) < smallDim)
703  {
704  // Check if neighbours.
705  const labelList& pEdges = surf.pointEdges()[pti];
706 
707  label edgei = -1;
708 
709  forAll(pEdges, i)
710  {
711  const edge& e = edges[pEdges[i]];
712 
713  if (e[0] == prevPti || e[1] == prevPti)
714  {
715  // point1 and point0 are connected through edge.
716  edgei = pEdges[i];
717 
718  break;
719  }
720  }
721 
722  if (nClose < outputThreshold)
723  {
724  if (edgei == -1)
725  {
726  Info<< " close unconnected points "
727  << pti << ' ' << localPoints[pti]
728  << " and " << prevPti << ' '
729  << localPoints[prevPti]
730  << " distance:"
731  << mag(localPoints[pti] - localPoints[prevPti])
732  << endl;
733  }
734  else
735  {
736  Info<< " small edge between points "
737  << pti << ' ' << localPoints[pti]
738  << " and " << prevPti << ' '
739  << localPoints[prevPti]
740  << " distance:"
741  << mag(localPoints[pti] - localPoints[prevPti])
742  << endl;
743  }
744  }
745  else if (nClose == outputThreshold)
746  {
747  Info<< "Suppressing further warning messages."
748  << " Use -outputThreshold to increase number"
749  << " of warnings." << endl;
750  }
751 
752  nClose++;
753  }
754  }
755 
756  Info<< "Found " << nClose << " nearby points." << nl
757  << endl;
758  }
759 
760 
761 
762  // Check manifold
763  // ~~~~~~~~~~~~~~
764 
765  DynamicList<label> problemFaces(surf.size()/100 + 1);
766  DynamicList<label> openEdges(surf.nEdges()/100 + 1);
767  DynamicList<label> multipleEdges(surf.nEdges()/100 + 1);
768 
769  const labelListList& edgeFaces = surf.edgeFaces();
770 
771  forAll(edgeFaces, edgei)
772  {
773  const labelList& myFaces = edgeFaces[edgei];
774 
775  if (myFaces.size() == 1)
776  {
777  problemFaces.append(myFaces[0]);
778  openEdges.append(edgei);
779  }
780  }
781 
782  forAll(edgeFaces, edgei)
783  {
784  const labelList& myFaces = edgeFaces[edgei];
785 
786  if (myFaces.size() > 2)
787  {
788  forAll(myFaces, myFacei)
789  {
790  problemFaces.append(myFaces[myFacei]);
791  }
792  multipleEdges.append(edgei);
793  }
794  }
795  problemFaces.shrink();
796 
797  if (openEdges.size() || multipleEdges.size())
798  {
799  Info<< "Surface is not closed since not all edges ("
800  << edgeFaces.size() << ") connected to "
801  << "two faces:" << endl
802  << " connected to one face : " << openEdges.size() << endl
803  << " connected to >2 faces : " << multipleEdges.size() << endl;
804 
805  Info<< "Conflicting face labels:" << problemFaces.size() << endl;
806 
807  if (!edgeFormat.empty() && openEdges.size())
808  {
809  const fileName outputName
810  (
811  surfName.lessExt()
812  + "_open."
813  + edgeFormat
814  );
815  Info<< "Writing open edges to "
816  << args.relativePath(outputName) << " ..." << endl;
817  writeEdgeSet(outputName, surf, openEdges);
818  }
819  if (!edgeFormat.empty() && multipleEdges.size())
820  {
821  const fileName outputName
822  (
823  surfName.lessExt()
824  + "_multiply."
825  + edgeFormat
826  );
827  Info<< "Writing multiply connected edges to "
828  << args.relativePath(outputName) << " ..." << endl;
829  writeEdgeSet(outputName, surf, multipleEdges);
830  }
831  }
832  else
833  {
834  Info<< "Surface is closed. All edges connected to two faces." << endl;
835  }
836  Info<< endl;
837 
838 
839 
840  // Check singly connected domain
841  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
842 
843  {
844  boolList borderEdge(surf.nEdges(), false);
845  if (splitNonManifold)
846  {
847  forAll(edgeFaces, edgei)
848  {
849  if (edgeFaces[edgei].size() > 2)
850  {
851  borderEdge[edgei] = true;
852  }
853  }
854  syncEdges(surf, borderEdge);
855  }
856 
858  label numZones = surf.markZones(borderEdge, faceZone);
859 
860  Info<< "Number of unconnected parts : " << numZones << endl;
861 
862  if (numZones > 1 && outputThreshold > 0)
863  {
864  Info<< "Splitting surface into parts ..." << endl << endl;
865 
866  if (!surfWriter)
867  {
868  surfWriter.reset(new surfaceWriters::vtkWriter());
869  }
870 
871  writeZoning
872  (
873  *surfWriter,
874  surf,
875  faceZone,
876  "zone",
877  surfFilePath,
878  surfFileStem
879  );
880 
881  if (numZones > outputThreshold)
882  {
883  Info<< "Limiting number of files to " << outputThreshold
884  << endl;
885  }
886  writeParts
887  (
888  surf,
889  min(outputThreshold, numZones),
890  faceZone,
891  surfFilePath,
892  surfFileStem
893  );
894  }
895  }
896 
897 
898 
899  // Check orientation
900  // ~~~~~~~~~~~~~~~~~
901 
902  labelHashSet borderEdge(surf.size()/1000);
903  PatchTools::checkOrientation(surf, false, &borderEdge);
904 
905  // Bit strange: if a triangle has two same vertices (illegal!) it will
906  // still have three distinct edges (two of which have the same vertices).
907  // In this case the faceEdges addressing is not symmetric, i.e. a
908  // neighbouring, valid, triangle will have correct addressing so 3 distinct
909  // edges so it will miss one of those two identical edges.
910  // - we don't want to fix this in PrimitivePatch since it is too specific
911  // - instead just make sure we mark all identical edges consistently
912  // when we use them for marking.
913 
914  syncEdges(surf, borderEdge);
915 
916  //
917  // Colour all faces into zones using borderEdge
918  //
919  labelList normalZone;
920  label numNormalZones = PatchTools::markZones(surf, borderEdge, normalZone);
921 
922  Info<< endl
923  << "Number of zones (connected area with consistent normal) : "
924  << numNormalZones << endl;
925 
926  if (numNormalZones > 1)
927  {
928  Info<< "More than one normal orientation." << endl;
929 
930  if (outputThreshold > 0)
931  {
932  if (!surfWriter)
933  {
934  surfWriter.reset(new surfaceWriters::vtkWriter());
935  }
936 
937  writeZoning
938  (
939  *surfWriter,
940  surf,
941  normalZone,
942  "normal",
943  surfFilePath,
944  surfFileStem
945  );
946 
947  if (numNormalZones > outputThreshold)
948  {
949  Info<< "Limiting number of files to " << outputThreshold
950  << endl;
951  }
952  writeParts
953  (
954  surf,
955  min(outputThreshold, numNormalZones),
956  normalZone,
957  surfFilePath,
958  surfFileStem + "_normal"
959  );
960  }
961  }
962  Info<< endl;
963 
964 
965 
966  // Check self-intersection
967  // ~~~~~~~~~~~~~~~~~~~~~~~
968 
969  if (checkSelfIntersect)
970  {
971  Info<< "Checking self-intersection." << endl;
972 
973  triSurfaceSearch querySurf(surf);
974 
975  const indexedOctree<treeDataTriSurface>& tree = querySurf.tree();
976 
977  autoPtr<OBJstream> intStreamPtr;
978  if (outputThreshold > 0)
979  {
980  intStreamPtr.reset(new OBJstream("selfInterPoints.obj"));
981  }
982 
983  label nInt = 0;
984 
985  forAll(surf.edges(), edgei)
986  {
987  const edge& e = surf.edges()[edgei];
988  const point& start = surf.points()[surf.meshPoints()[e[0]]];
989  const point& end = surf.points()[surf.meshPoints()[e[1]]];
990 
991  // Exclude hits of connected triangles
993 
994  pointIndexHit hitInfo(tree.findLineAny(start, end, exclOp));
995 
996  if (hitInfo.hit())
997  {
998  nInt++;
999 
1000  if (intStreamPtr)
1001  {
1002  intStreamPtr().write(hitInfo.point());
1003  }
1004 
1005  // Try and find from other side.
1006  pointIndexHit hitInfo2(tree.findLineAny(end, start, exclOp));
1007 
1008  if (hitInfo2.hit() && hitInfo.index() != hitInfo2.index())
1009  {
1010  nInt++;
1011 
1012  if (intStreamPtr)
1013  {
1014  intStreamPtr().write(hitInfo2.point());
1015  }
1016  }
1017  }
1018  }
1019 
1021  //{
1022  // const pointField& localPoints = surf.localPoints();
1023  //
1024  // const boundBox bb(localPoints);
1025  // scalar smallDim = 1e-6 * bb.mag();
1026  // scalar smallDimSqr = Foam::sqr(smallDim);
1027  //
1028  // const pointField& faceCentres = surf.faceCentres();
1029  // forAll(faceCentres, faceI)
1030  // {
1031  // const point& fc = faceCentres[faceI];
1032  // pointIndexHit hitInfo
1033  // (
1034  // tree.findNearest
1035  // (
1036  // fc,
1037  // smallDimSqr,
1038  // findSelfNearOp(tree, faceI)
1039  // )
1040  // );
1041  //
1042  // if (hitInfo.hit() && intStreamPtr)
1043  // {
1044  // intStreamPtr().write(hitInfo.point());
1045  //
1046  // label nearFaceI = hitInfo.index();
1047  // triPointRef nearTri(surf[nearFaceI].tri(surf.points()));
1048  // triStreamPtr().write
1049  // (
1050  // surf[faceI].tri(surf.points()),
1051  // false
1052  // );
1053  // triStreamPtr().write(nearTri, false);
1054  // nInt++;
1055  // }
1056  // }
1057  //}
1058 
1059 
1060  if (nInt == 0)
1061  {
1062  Info<< "Surface is not self-intersecting" << endl;
1063  }
1064  else
1065  {
1066  Info<< "Surface is self-intersecting at " << nInt
1067  << " locations." << endl;
1068 
1069  if (intStreamPtr)
1070  {
1071  Info<< "Writing intersection points to "
1072  << intStreamPtr().name() << endl;
1073  }
1074  }
1075  Info<< endl;
1076  }
1077 
1078 
1079  Info<< "\nEnd\n" << endl;
1080 
1081  return 0;
1082 }
1083 
1084 
1085 // ************************************************************************* //
const labelListList & pointEdges() const
Return point-edge addressing.
static bool checkOrientation(const PrimitivePatch< FaceList, PointField > &, const bool report=false, labelHashSet *marked=0)
Check for orientation issues.
const T & first() const noexcept
Access the first element.
Definition: Pair.H:137
label nPoints() const
Number of points supporting patch faces.
triSurface subsetMesh(const UList< bool > &include, labelList &pointMap, labelList &faceMap) const
Return a new surface subsetted on the selected faces.
Definition: triSurface.C:872
Generic output stream using a standard (STL) stream.
Definition: OSstream.H:50
Field< label > labelField
Specialisation of Field<T> for label.
Definition: labelField.H:48
static void addNote(const string &note)
Add extra notes for the usage information.
Definition: argList.C:462
void size(const label n)
Older name for setAddressableSize.
Definition: UList.H:116
labelPair findMinMax(const ListType &input, label start=0)
Linear search for the index of the min/max element, similar to std::minmax_element but for lists and ...
A class for handling file names.
Definition: fileName.H:72
A surfaceWriter for VTK legacy (.vtk) or XML (.vtp) format.
dimensioned< typename typeOfMag< Type >::type > mag(const dimensioned< Type > &dt)
const Field< point_type > & localPoints() const
Return pointField of points in patch.
vtk::lineWriter writer(edgeCentres, edgeList::null(), fileName(aMesh.time().globalPath()/"finiteArea-edgesCentres"))
void writeStats(Ostream &os) const
Write some statistics.
Definition: triSurfaceIO.C:348
void append(const T &val)
Append an element at the end of the list.
Definition: List.H:517
A list that is sorted upon construction or when explicitly requested with the sort() method...
Definition: SortableList.H:56
label max(const labelHashSet &set, label maxValue=labelMin)
Find the max value in labelHashSet, optionally limited by second argument.
Definition: hashSets.C:40
virtual void open(const fileName &outputPath)
Open for output on specified path, using existing surface.
Output to file stream, using an OSstream.
Definition: OFstream.H:49
constexpr char nl
The newline &#39;\n&#39; character (0x0a)
Definition: Ostream.H:50
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:531
static void addBoolOption(const word &optName, const string &usage="", bool advanced=false)
Add a bool option to validOptions with usage information.
Definition: argList.C:374
static void noParallel()
Remove the parallel options.
Definition: argList.C:584
A bounding box defined in terms of min/max extrema points.
Definition: boundBox.H:63
This class describes the interaction of an object (often a face) and a point. It carries the info of ...
Definition: pointIndexHit.H:44
T getOrDefault(const word &optName, const T &deflt) const
Get a value from the named option if present, or return default.
Definition: argListI.H:300
bool insert(const Key &key)
Insert a new entry, not overwriting existing entries.
Definition: HashSet.H:232
void write(const word &fieldName, const UList< Type > &field)
Write primitive field of CellData (Poly or Line) or PointData values.
const labelList & meshPoints() const
Return labelList of mesh points in patch.
static label markZones(const PrimitivePatch< FaceList, PointField > &, const BoolListType &borderEdge, labelList &faceZone)
Size and fills faceZone with zone of face.
#define forAll(list, i)
Loop across all elements in list.
Definition: stdFoam.H:421
void reset(T *p=nullptr) noexcept
Delete managed object and set to new given pointer.
Definition: autoPtrI.H:37
virtual void clear()
Close any open output, remove association with a surface and expire the writer. The parallel flag rem...
label size() const noexcept
The number of elements in table.
Definition: HashTable.H:342
Helper class to search on triSurface.
const geometricSurfacePatchList & patches() const noexcept
Definition: triSurface.H:509
word outputName("finiteArea-edges.obj")
const dimensionedScalar e
Elementary charge.
Definition: createFields.H:11
word name(const expressions::valueTypeCode typeCode)
A word representation of a valueTypeCode. Empty for expressions::valueTypeCode::INVALID.
Definition: exprTraits.C:127
const pointField & points
A 1D vector of objects of type <T> that resizes itself as necessary to accept the new objects...
Definition: DynamicList.H:51
An edge is a list of two vertex labels. This can correspond to a directed graph edge or an edge on a ...
Definition: edge.H:59
Base class for mesh zones.
Definition: zone.H:59
A class for handling words, derived from Foam::string.
Definition: word.H:63
Field< scalar > scalarField
Specialisation of Field<T> for scalar.
Extract command arguments and options from the supplied argc and argv parameters. ...
Definition: argList.H:118
Tree tree(triangles.begin(), triangles.end())
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:385
virtual bool write(const token &tok)=0
Write token to stream or otherwise handle it.
const labelListList & edgeFaces() const
Return edge-face addressing.
const edgeList & edges() const
Return list of edges, address into LOCAL point list.
Dynamically sized Field.
Definition: DynamicField.H:45
A triFace with additional (region) index.
Definition: labelledTri.H:53
const Field< point_type > & points() const noexcept
Return reference to global points.
void append(const T &val)
Copy append an element to the end of this list.
Definition: DynamicList.H:584
label min(const labelHashSet &set, label minValue=labelMax)
Find the min value in labelHashSet, optionally limited by second argument.
Definition: hashSets.C:26
fileName relativePath(const fileName &input, const bool caseTag=false) const
Return the input relative to the globalPath by stripping off a leading value of the globalPath...
Definition: argListI.H:87
static void addVerboseOption(const string &usage="", bool advanced=false)
Enable a &#39;verbose&#39; bool option, with usage information.
Definition: argList.C:520
label nEdges() const
Number of edges in patch.
A Vector of values with scalar precision, where scalar is float/double depending on the compilation f...
An OFstream that keeps track of vertices and provides convenience output methods for OBJ files...
Definition: OBJstream.H:55
void triFaceFaces(List< face > &plainFaceList) const
Create a list of faces from the triFaces.
Definition: triSurface.C:716
constexpr auto end(C &c) -> decltype(c.end())
Return iterator to the end of the container c.
Definition: stdFoam.H:201
OBJstream os(runTime.globalPath()/outputName)
virtual bool open(const fileName &file, bool parallel=UPstream::parRun())
Open file for writing (creates parent directory).
Mesh data needed to do the Finite Area discretisation.
Definition: edgeFaMesh.H:47
labelList f(nPoints)
static bool validTri(const triSurface &, const label facei, const bool verbose=true)
Check single triangle for (topological) validity.
T get(const label index) const
Get a value from the argument at index.
Definition: argListI.H:271
Non-pointer based hierarchical recursive searching.
#define WarningInFunction
Report a warning using Foam::Warning.
A List with indirect addressing. Like IndirectList but does not store addressing. ...
Definition: faMatrix.H:56
static void addArgument(const string &argName, const string &usage="")
Append a (mandatory) argument to validArgs.
Definition: argList.C:351
triangle< point, const point & > triPointRef
A triangle using referred points.
Definition: triangleFwd.H:39
messageStream Info
Information stream (stdout output on master, null elsewhere)
label n
Base class for surface writers.
Pointer management similar to std::unique_ptr, with some additional methods and type checking...
Definition: HashPtrTable.H:48
virtual fileName write()=0
Write separate surface geometry to file.
A subset of mesh faces organised as a primitive patch.
Definition: faceZone.H:60
label markZones(const boolList &borderEdge, labelList &faceZone) const
(size and) fills faceZone with zone of face. Zone is area
Definition: triSurface.C:789
virtual int width() const override
Get width of output field.
Definition: OSstream.C:322
volScalarField & p
const T & second() const noexcept
Access the second element.
Definition: Pair.H:147
Triangulated surface description with patch information.
Definition: triSurface.H:71
Foam::argList args(argc, argv)
static autoPtr< surfaceWriter > New(const word &writeType)
Return a reference to the selected surfaceWriter.
Definition: surfaceWriter.C:80
bool found(const word &optName) const
Return true if the named option is found.
Definition: argListI.H:171
bool remove_ext()
Remove extension, return true if string changed.
Definition: stringI.H:93
Namespace for OpenFOAM.
static constexpr const zero Zero
Global zero (0)
Definition: zero.H:127