13 #include "GmshConfig.h"
35 #if defined(HAVE_POST)
42 #if defined(HAVE_FLTK)
46 #if defined(HAVE_QUADMESHINGTOOLS)
48 #include "qmtMeshUtils.h"
49 #include "qmtCrossField.h"
50 #include "qmtSizeMap.h"
51 #include "qmtCurveQuantization.h"
52 #include "qmtDiskQuadrangulationRemeshing.h"
53 #include "qmtQuadCavityRemeshing.h"
54 #include "qmtMeshGeometryOptimization.h"
55 #include "arrayGeometry.h"
59 using namespace ArrayGeometry;
61 template <
typename Key,
typename T,
typename Hash = robin_hood::hash<Key>,
62 typename KeyEqual = std::equal_to<Key>,
size_t MaxLoadFactor100 = 80>
66 template <
typename Key,
typename Hash = robin_hood::hash<Key>,
67 typename KeyEqual = std::equal_to<Key>,
size_t MaxLoadFactor100 = 80>
71 constexpr
int SizeMapDefault = 0;
72 constexpr
int SizeMapCrossFieldAndSmallCad = 2;
73 constexpr
int SizeMapBackgroundMesh = 3;
74 constexpr
int SizeMapCrossFieldAndBMeshOnCurves = 4;
76 const std::string BMESH_NAME =
"bmesh_quadqs";
78 constexpr
bool PARANO_QUALITY =
false;
79 constexpr
bool PARANO_VALIDITY =
false;
80 constexpr
bool DBG_EXPORT =
false;
81 constexpr
bool SHOW_DQR =
false;
85 constexpr
double VIEW_INT_SCALING = 1.e-8;
87 static int getNumThreads()
96 int buildBackgroundField(
97 GModel *gm,
const std::vector<MTriangle *> &global_triangles,
98 const std::vector<std::array<double, 9> > &global_triangle_directions,
99 const std::unordered_map<MVertex *, double> &global_size_map,
100 const std::vector<std::array<double, 5> > &global_singularity_list,
101 const std::string &viewName =
"guiding_field")
103 Msg::Info(
"Build background field (view 'guiding_field') ...");
104 if(global_triangles.size() != global_triangle_directions.size()) {
105 Msg::Error(
"build background field: incoherent sizes in input");
109 std::vector<double> datalist;
110 datalist.reserve(global_triangles.size() * 18);
111 for(
size_t i = 0; i < global_triangles.size(); ++i) {
114 for(
size_t d = 0; d < 3; ++d) {
115 for(
size_t lv = 0; lv < 3; ++lv) {
120 for(
size_t lv = 0; lv < 3; ++lv) {
122 auto it = global_size_map.find(v);
123 if(it == global_size_map.end()) {
124 Msg::Error(
"Building background field, triangle vertex not found in "
128 double siz = it->second;
129 for(
size_t d = 0; d < 3; ++d) {
130 double val = siz * global_triangle_directions[i][3 * lv + d];
131 datalist.push_back(val);
136 #if defined(HAVE_POST)
152 size_t numElements = datalist.size() / (9 + 9);
154 d->
importList(idxtypeVT, numElements, datalist,
false);
156 #if defined(HAVE_FLTK)
158 if(FlGui::available()) FlGui::instance()->updateViews(
true,
true);
162 if(global_singularity_list.size() > 0) {
164 std::vector<double> datalistSING;
165 datalistSING.reserve(global_singularity_list.size() * 6);
166 for(
size_t i = 0; i < global_singularity_list.size(); ++i) {
167 datalistSING.push_back(global_singularity_list[i][2]);
168 datalistSING.push_back(global_singularity_list[i][3]);
169 datalistSING.push_back(global_singularity_list[i][4]);
170 datalistSING.push_back(
172 double(global_singularity_list[i][0]));
173 datalistSING.push_back(VIEW_INT_SCALING *
174 double(global_singularity_list[i][1]));
175 datalistSING.push_back(0.);
177 d->
importList(idxtypeVP, datalistSING.size() / 6, datalistSING,
false);
184 const bool exportBGM =
false;
186 std::string name = gm->
getName() +
"_bgm.pos";
187 Msg::Warning(
"Exporting background field to '%s'", name.c_str());
188 view->
write(name, 0);
193 Msg::Error(
"Post-processing module required to create background field view");
198 void showCrossFieldInView(
199 const std::vector<MTriangle *> &global_triangles,
200 const std::vector<std::array<double, 9> > &global_triangle_directions,
201 const std::string &viewName =
"cross_field")
203 for(
size_t i = 0; i < global_triangles.size(); ++i) {
204 for(
size_t lv = 0; lv < 3; ++lv) {
205 MVertex *v = global_triangles[i]->getVertex(lv);
206 vec3 dir = {{global_triangle_directions[i][3 * lv + 0],
207 global_triangle_directions[i][3 * lv + 1],
208 global_triangle_directions[i][3 * lv + 2]}};
209 std::array<double, 3> pt = v->
point();
210 GeoLog::add(pt, dir, viewName);
216 void showUVParametrization(
GFace *gf,
const std::vector<MElement *> &elts,
217 const std::string &viewName =
"uv")
219 std::vector<std::vector<double> > values_u;
220 std::vector<std::vector<double> > values_v;
222 std::vector<SPoint2> tris_uvs = paramOnElement(gf, t);
223 std::vector<double> us(tris_uvs.size());
224 std::vector<double> vs(tris_uvs.size());
225 for(
size_t k = 0; k < us.size(); ++k) {
226 us[k] = tris_uvs[k][0];
227 vs[k] = tris_uvs[k][1];
229 values_u.push_back(us);
230 values_v.push_back(vs);
232 GeoLog::add(elts, values_u, viewName +
"_u");
233 GeoLog::add(elts, values_v, viewName +
"_v");
237 const std::string &viewName =
"uv")
240 GFace *gf = kv.first;
242 std::vector<MElement *> tris;
243 for(
MTriangle &t : kv.second.triangles) { tris.push_back(&t); }
244 showUVParametrization(gf, tris, viewName);
249 void printSizeMapStats(
const std::vector<MTriangle *> &triangles,
250 std::unordered_map<MVertex *, double> &sizemap)
252 double vmin = DBL_MAX;
253 double vmax = -DBL_MAX;
254 for(
auto &kv : sizemap) {
255 vmin = std::min(vmin, kv.second);
256 vmax = std::max(vmax, kv.second);
258 double integral = 0.;
260 double values[3] = {0, 0, 0};
261 for(
size_t lv = 0; lv < 3; ++lv) {
263 auto it = sizemap.find(v);
264 values[lv] = it->second;
269 a * 1. / std::pow(1. / 3. * (values[0] + values[1] + values[2]), 2);
271 Msg::Info(
"Size map statistics: min=%.3f, max=%.3f, target #elements: %.3f",
272 vmin, vmax, integral);
275 int fillSizemapFromTriangleSizes(
const std::vector<MTriangle *> &triangles,
276 std::unordered_map<MVertex *, double> &sizeMap)
279 std::unordered_map<MVertex *, std::vector<MVertex *> > v2v;
280 buildVertexToVertexMap(triangles, v2v);
281 for(
auto &kv : v2v) {
292 if(sum == 0.)
continue;
293 sizeMap[v] = avg / sum;
298 for(
size_t iter = 0; iter < iterMax; ++iter) {
299 for(
auto &kv : v2v) {
308 if(sum == 0.)
continue;
309 auto it = sizeMap.find(v);
310 if(it == sizeMap.end())
continue;
311 it->second = 0.5 * it->second + 0.5 * avg / sum;
318 int fillSizemapFromScalarBackgroundField(
319 GModel *gm,
const std::vector<MTriangle *> &triangles,
320 std::unordered_map<MVertex *, double> &sizeMap)
322 Field *field =
nullptr;
326 if(field && field->
numComponents() != 1) { field =
nullptr; }
328 if(field ==
nullptr) {
329 Msg::Error(
"Scalar background field not found");
333 for(
size_t lv = 0; lv < 3; ++lv) {
335 auto it = sizeMap.find(v);
336 if(it == sizeMap.end()) {
338 if(std::isnan(value) || value == -DBL_MAX || value == DBL_MAX)
continue;
345 std::string nameOfSizeMapMethod(
int method)
347 if(method == 0) {
return "default (" + nameOfSizeMapMethod(4) +
")"; }
348 else if(method == 1) {
349 return "cross-field conformal scaling";
351 else if(method == 2) {
352 return "cross-field conformal scaling and CAD adaptation";
354 else if(method == 3) {
355 return "background mesh sizes";
357 else if(method == 4) {
358 return "cross-field conformal scaling and CAD adaptation (clamped by "
364 bool generateMeshWithSpecialParameters(
GModel *gm,
365 double scalingOnTriangulation)
367 Msg::Debug(
"build background triangulation ...");
370 bool shouldLock =
false;
410 bool getGFaceBackgroundMeshLinesAndTriangles(
412 std::vector<MTriangle *> &triangles)
418 std::vector<GEdge *>
edges = face_edges(gf);
423 "getGFaceBackgroundMeshLinesAndTriangles: GEdge %i not found "
424 "in background mesh",
428 lines.reserve(lines.size() + it->second.lines.size());
429 for(
size_t i = 0; i < it->second.lines.size(); ++i) {
430 lines.push_back(&(it->second.lines[i]));
435 Msg::Warning(
"getGFaceBackgroundMeshLinesAndTriangles: GFace %i not found "
436 "in background mesh",
440 triangles.reserve(triangles.size() + it->second.triangles.size());
441 for(
size_t i = 0; i < it->second.triangles.size(); ++i) {
442 triangles.push_back(&(it->second.triangles[i]));
449 bool deleteGModelMeshAfter,
450 bool overwriteField,
int N)
456 if(N != 4 && N != 6) {
457 Msg::Error(
"guiding field: %i-symmetry field not supported", N);
462 if(qqsSizemapMethod == 5) {
467 bool midpointSubdivisionAfter =
true;
470 midpointSubdivisionAfter =
false;
475 Msg::Info(
"Build background mesh and guiding field ...");
476 bool externalSizemap =
false;
482 if(!overwriteField) {
484 "vector background field exists, using it as a guiding field");
489 "disabled current vector background field, building a new one");
494 if(qqsSizemapMethod == SizeMapDefault) {
495 Msg::Info(
"scalar background field exists, using it as size map");
496 externalSizemap =
true;
499 Msg::Warning(
"scalar background field exists, but ignored because "
500 "QuadqsSizemapMethod is %i",
507 if(overwriteGModelMesh) {
513 bool surfaceMeshed =
true;
519 surfaceMeshed =
false;
528 if(!surfaceMeshed) { generateMeshWithSpecialParameters(gm, edgeScaling); }
531 bool overwrite =
true;
534 Msg::Error(
"failed to import model mesh in background mesh");
538 if(deleteGModelMeshAfter) {
554 std::vector<MTriangle *> global_triangles;
555 std::vector<std::array<double, 9> > global_triangle_directions;
556 std::vector<std::pair<MVertex *, double> > global_size_map;
557 std::vector<std::array<double, 5> >
558 global_singularity_list;
562 "- quadqs sizemap method: %s (%i), expect midpoint subdivision: %i, "
563 "scaling on edge length: %f",
564 nameOfSizeMapMethod(
CTX::instance()->mesh.quadqsSizemapMethod).c_str(),
568 std::vector<GFace *>
faces = model_faces(gm);
570 for(
size_t f = 0;
f <
faces.size(); ++
f) {
574 ntris += it->second.triangles.size();
577 global_triangles.reserve(ntris);
578 global_size_map.reserve(3 * ntris);
580 int nthreads = getNumThreads();
581 #pragma omp parallel for schedule(dynamic) num_threads(nthreads)
582 for(
size_t f = 0;
f <
faces.size(); ++
f) {
594 std::vector<MLine *> lines;
595 std::vector<MTriangle *> triangles;
597 getGFaceBackgroundMeshLinesAndTriangles(bmesh, gf, lines, triangles);
598 if(!oklt && triangles.size() == 0) {
599 Msg::Error(
"- Face %i: failed to get triangles from background mesh",
605 std::vector<std::array<double, 3> > triEdgeTheta;
606 int nbDiffusionLevels = 4;
607 double thresholdNormConvergence = 1.e-2;
608 int nbBoundaryExtensionLayer = 1;
610 Msg::Info(
"- Face %i/%li: compute cross field (%li triangles, %li B.C. "
611 "edges, %i diffusion levels) ...",
612 gf->
tag(),
faces.size(), triangles.size(), lines.size(),
614 int scf = computeCrossFieldWithHeatEquation(
615 N, triangles, lines, triEdgeTheta, nbDiffusionLevels,
616 thresholdNormConvergence, nbBoundaryExtensionLayer, verbosity);
622 bool addSingularitiesAtAcuteCorners =
true;
623 double thresholdInDeg = 30.;
624 std::vector<std::pair<SPoint3, int> > singularities;
625 int scsi = detectCrossFieldSingularities(N, triangles, triEdgeTheta,
626 addSingularitiesAtAcuteCorners,
627 thresholdInDeg, singularities);
629 Msg::Warning(
"- Face %i: failed to compute cross field singularities",
632 std::vector<std::array<double, 5> > singularity_list(
633 singularities.size());
634 for(
size_t k = 0; k < singularities.size(); ++k) {
635 singularity_list[k] = {
636 double(gf->
tag()), double(singularities[k].second),
637 singularities[k].first.x(), singularities[k].first.y(),
638 singularities[k].first.z()};
641 if(SHOW_INTERMEDIATE_VIEWS) {
642 for(
auto &kv : singularities) {
643 GeoLog::add(kv.first,
double(kv.second),
"singularities");
647 std::vector<std::array<double, 9> > triangleDirections;
648 int sc = convertToPerTriangleCrossFieldDirections(
649 N, triangles, triEdgeTheta, triangleDirections);
652 "- Face %i: failed to resample cross field at triangle corners",
657 std::unordered_map<MVertex *, double> localSizemap;
658 if(externalSizemap) {
660 fillSizemapFromScalarBackgroundField(gm, triangles, localSizemap);
663 "- Face %i: failed to fill size map from scalar background field",
667 else if(qqsSizemapMethod ==
668 SizeMapBackgroundMesh) {
670 int sts = fillSizemapFromTriangleSizes(triangles, localSizemap);
672 Msg::Warning(
"- Face %i: failed to fill size map from triangle sizes",
675 else if(sts == 0 && edgeScaling > 0.) {
678 for(
auto &kv : localSizemap) {
679 kv.second /= edgeScaling;
680 if(midpointSubdivisionAfter) { kv.second *= 2; }
686 Msg::Info(
"- Face %i/%li: compute cross field conformal scaling ...",
688 int scs = computeCrossFieldConformalScaling(N, triangles, triEdgeTheta,
692 "- Face %i: failed to compute conformal scaling, use uniform size",
695 for(
size_t lv = 0; lv < 3; ++lv) {
701 Msg::Debug(
"- Face %i/%li: conformal scaling quantile filtering ...",
703 double filteringRange = 0.05;
704 quantileFiltering(localSizemap, filteringRange);
706 size_t targetNumberOfQuads = gf->
triangles.size() / 2.;
707 if(targetNumberOfQuads == 0) {
710 Msg::Error(
"- Face %i: background mesh not found", gf->
tag());
711 targetNumberOfQuads = 1000;
714 targetNumberOfQuads = 0.5 * it->second.triangles.size();
715 targetNumberOfQuads *= std::pow(edgeScaling, 2);
720 if(midpointSubdivisionAfter) { targetNumberOfQuads /= 4; }
722 if(targetNumberOfQuads == 0) targetNumberOfQuads = 1;
724 int scso = computeQuadSizeMapFromCrossFieldConformalFactor(
725 triangles, targetNumberOfQuads, localSizemap);
728 "- Face %i: failed to compute size map from conformal scaling",
735 append(global_triangles, triangles);
736 append(global_triangle_directions, triangleDirections);
737 append(global_singularity_list, singularity_list);
738 for(
auto &kv : localSizemap) {
739 global_size_map.push_back({kv.first, kv.second});
747 sort_unique(global_size_map);
754 if(SHOW_INTERMEDIATE_VIEWS) {
755 showCrossFieldInView(global_triangles, global_triangle_directions,
764 double smMin = DBL_MAX;
765 for(
auto &kv : global_size_map) { smMin = std::min(smMin, kv.second); }
767 double minSizeClampMin = factor * smMin;
770 std::unordered_map<MVertex *, double> cadMinimalSizeOnCurves;
771 if(!externalSizemap) {
772 if(qqsSizemapMethod == SizeMapDefault ||
773 qqsSizemapMethod == SizeMapCrossFieldAndBMeshOnCurves ||
774 qqsSizemapMethod == SizeMapCrossFieldAndSmallCad) {
775 bool clampMinWithTriEdges =
false;
776 if(qqsSizemapMethod == SizeMapDefault ||
777 qqsSizemapMethod == SizeMapCrossFieldAndBMeshOnCurves) {
778 clampMinWithTriEdges =
true;
781 int scad = computeMinimalSizeOnCurves(bmesh, clampMinWithTriEdges,
782 cadMinimalSizeOnCurves);
784 Msg::Warning(
"failed to compute minimal size on CAD curves");
790 std::unordered_map<MVertex *, double> sizeMap = cadMinimalSizeOnCurves;
791 for(
auto &kv : global_size_map) {
793 auto it = sizeMap.find(v);
794 if(it == sizeMap.end()) {
795 sizeMap[kv.first] = kv.second;
798 double sizeFromAdaptation = std::max(it->second, minSizeClampMin);
799 it->second = std::min(sizeFromAdaptation, kv.second);
804 if(qqsSizemapMethod != SizeMapBackgroundMesh) {
805 const double gradientMax =
807 Msg::Info(
"Sizemap smoothing (progression ratio: %.2f)", gradientMax);
808 int sop = sizeMapOneWaySmoothing(global_triangles, sizeMap, gradientMax);
810 Msg::Warning(
"failed to compute one-way size map smoothing");
817 double fs = midpointSubdivisionAfter ? 2. : 1.;
822 for(
auto &kv : sizeMap) {
823 if(kv.second < sizeMin) { kv.second = sizeMin; }
824 else if(kv.second > sizeMax) {
830 if(SHOW_INTERMEDIATE_VIEWS) {
831 std::vector<MElement *> elements =
832 dynamic_cast_vector<MTriangle *, MElement *>(global_triangles);
833 GeoLog::add(elements, sizeMap,
"size_map");
836 std::unordered_map<MVertex *, double> sizemap_init;
837 for(
auto &kv : global_size_map) {
839 auto it = sizemap_init.find(v);
840 if(it == sizemap_init.end()) {
841 sizemap_init[kv.first] = kv.second;
844 it->second = std::min(it->second, kv.second);
847 GeoLog::add(elements, sizemap_init,
"size_map_init");
851 printSizeMapStats(global_triangles, sizeMap);
855 buildBackgroundField(gm, global_triangles, global_triangle_directions,
856 sizeMap, global_singularity_list,
"guiding_field");
858 Msg::Warning(
"failed to build background guiding field");
872 if(guiding_field && guiding_field->
numComponents() == 3) { bfOk =
true; }
874 return bgmOk && bfOk;
877 bool getSingularitiesFromBackgroundField(
878 GFace *gf, std::vector<std::pair<SPoint3, int> > &singularities)
880 singularities.clear();
882 Field *field =
nullptr;
887 field = guiding_field;
890 if(field ==
nullptr) {
891 Msg::Debug(
"get singularities: face %i, failed to get background field",
896 int viewIndex = int(field->
options[
"IView"]->numericalValue());
897 PView *view =
nullptr;
898 if(viewIndex >= 0 && viewIndex < (
int)
PView::list.size()) {
902 Msg::Error(
"failed to get view for index = %i", viewIndex);
911 size_t nVP = d->
VP.size() / 6;
912 for(
size_t i = 0; i < nVP; ++i) {
913 int gfTag = int(std::round(d->
VP[6 * i + 3] / VIEW_INT_SCALING));
914 if(gfTag != gf->
tag())
continue;
915 int index = int(std::round(d->
VP[6 * i + 4] / VIEW_INT_SCALING));
916 double x = d->
VP[6 * i + 0];
917 double y = d->
VP[6 * i + 1];
918 double z = d->
VP[6 * i + 2];
919 singularities.push_back(std::make_pair(
SPoint3(x, y,
z), index));
925 bool getSingularitiesFromNewCrossFieldComputation(
927 std::vector<std::pair<SPoint3, int> > &singularities)
931 std::vector<MLine *> lines;
932 std::vector<MTriangle *> triangles;
934 getGFaceBackgroundMeshLinesAndTriangles(bmesh, gf, lines, triangles);
935 if(!oklt && triangles.size() == 0) {
936 Msg::Error(
"- Face %i: failed to get triangles from background mesh",
942 std::vector<std::array<double, 3> > triEdgeTheta;
943 int nbDiffusionLevels = 4;
944 double thresholdNormConvergence = 1.e-2;
945 int nbBoundaryExtensionLayer = 1;
947 Msg::Info(
"- Face %i: compute cross field (%li triangles, %li B.C. "
948 "edges, %i diffusion levels) ...",
949 gf->
tag(), triangles.size(), lines.size(), nbDiffusionLevels);
950 int scf = computeCrossFieldWithHeatEquation(
951 N, triangles, lines, triEdgeTheta, nbDiffusionLevels,
952 thresholdNormConvergence, nbBoundaryExtensionLayer, verbosity);
958 bool addSingularitiesAtAcuteCorners =
true;
959 double thresholdInDeg = 30.;
960 int scsi = detectCrossFieldSingularities(N, triangles, triEdgeTheta,
961 addSingularitiesAtAcuteCorners,
962 thresholdInDeg, singularities);
964 Msg::Warning(
"- Face %i: failed to compute cross field singularities",
971 void computeBdrVertexIdealValence(
const std::vector<MQuadrangle *> &quads,
972 unordered_map<MVertex *, double> &qValIdeal)
976 for(
size_t lv = 0; lv < 4; ++lv) {
980 MVertex *vPrev =
f->getVertex((4 + lv - 1) % 4);
981 MVertex *vNext =
f->getVertex((lv + 1) % 4);
985 double agl = angleVectors(pNext - pCurr, pPrev - pCurr);
986 qValIdeal[v] += agl * 2. / M_PI;
992 inline int clamp(
int val,
int lower,
int upper)
994 return std::min(upper, std::max(val, lower));
997 size_t idealBoundaryValence(
const MVertex *v,
double ideal)
1003 if(ideal <= 1.25) {
return 1; }
1004 else if(ideal <= 2.75) {
1007 else if(ideal <= 3.5) {
1013 bool getBoundaryIdealAndAllowedValences(
1016 GFaceMeshPatch &patch,
1018 const unordered_map<MVertex *, double> &qValIdeal,
1019 std::vector<int> &bndIdealValence,
1020 std::vector<std::pair<int, int> > &bndAllowedValenceRange)
1022 if(fixingDim < 0 || fixingDim > 2)
return false;
1023 size_t N = patch.bdrVertices.front().size();
1024 bndIdealValence.resize(N);
1025 bndAllowedValenceRange.resize(N);
1026 for(
size_t i = 0; i < N; ++i) {
1027 MVertex *bv = patch.bdrVertices.front()[i];
1033 auto it = qValIdeal.find(bv);
1034 if(it == qValIdeal.end()) {
1035 Msg::Error(
"getBoundaryIdealAndAllowedValences: bdr vertex %i not "
1036 "found in qValIdeal",
1040 idealTot = idealBoundaryValence(it->first, it->second);
1042 auto it = adj.find(bv);
1043 if(it == adj.end()) {
1045 "getBoundaryIdealAndAllowedValences: bdr vertex not found in adj");
1048 std::vector<MElement *> exterior = difference(it->second, patch.elements);
1049 int valExterior = (int)exterior.size();
1050 int valInterior = (int)it->second.size() - exterior.size();
1051 int idealIn = idealTot - valExterior;
1058 bndIdealValence[i] = idealIn;
1059 if(exterior.size() == 0) {
1061 bndAllowedValenceRange[i] = {idealIn, idealIn};
1063 else if(gv !=
nullptr) {
1064 bndAllowedValenceRange[i] = {idealIn, idealIn};
1066 else if(ge !=
nullptr) {
1067 if(fixingDim == 0) {
1072 bndAllowedValenceRange[i] = {idealIn, idealIn};
1075 bndAllowedValenceRange[i] = {idealIn, idealIn};
1078 else if(gf !=
nullptr) {
1079 if(valExterior <= 0) {
1080 Msg::Error(
"getBoundaryIdealAndAllowedValences: surface bdr vertex but "
1081 "exterior valence is: %i",
1085 if(fixingDim == 0 && idealTot >= 3 && it->second.size() == 4) {
1088 bndAllowedValenceRange[i] = {valInterior, valInterior};
1090 else if(fixingDim == 0 ||
1094 int lower = 3 - valExterior;
1095 int upper = 6 - valExterior;
1096 lower = clamp(lower, 1, 5);
1097 upper = clamp(upper, 1, 5);
1098 bndAllowedValenceRange[i] = {lower, upper};
1102 int lower = 3 - valExterior;
1103 int upper = 5 - valExterior;
1104 lower = clamp(lower, 1, 4);
1105 upper = clamp(upper, 1, 4);
1106 bndAllowedValenceRange[i] = {lower, upper};
1110 Msg::Error(
"getBoundaryIdealAndAllowedValences: vertex on no CAD entity, "
1111 "should not happen");
1126 bool meshOrientationIsOppositeOfCadOrientation(
GFace *gf)
1138 if(
dot(nf, ne) < 0.) { nInv += 1; }
1144 return (nInv > nOk);
1147 void updateAdjacencies(
const GFaceMeshPatch &before,
1148 const GFaceMeshPatch &after,
1152 for(
MVertex *v : before.intVertices) {
1153 auto it = adj.find(v);
1154 if(it != adj.end()) { adj.erase(it); }
1157 for(
auto &loop : before.bdrVertices) {
1159 auto it = adj.find(bv);
1160 if(it != adj.end()) {
1161 it->second = difference(it->second, before.elements);
1166 for(
MElement *e : after.elements) {
1169 adj[v].push_back(e);
1174 std::vector<MElement *>
1175 getNeighbors(
const std::vector<MElement *> &elements,
1178 std::vector<MElement *> neighbors;
1182 auto it = adj.find(v);
1183 if(it != adj.end()) {
1184 for(
MElement *e2 : it->second) { neighbors.push_back(e2); }
1188 neighbors = difference(neighbors, elements);
1193 GFace *gf,
const std::vector<MVertex *> &smoothingPool,
1198 std::vector<MElement *> elements;
1200 std::vector<MVertex *> unq = smoothingPool;
1202 elements.reserve(unq.size());
1204 auto it = adj.find(v);
1205 if(it != adj.end()) {
1208 if(it2 != gf->
mesh_vertices.end()) { append(elements, it->second); }
1212 sort_unique(elements);
1216 std::vector<MElement *> faceElements =
1217 dynamic_cast_vector<MQuadrangle *, MElement *>(gf->
quadrangles);
1221 std::vector<std::vector<MElement *> > components;
1222 bool okc = getConnectedComponents(elements, components);
1223 if(!okc)
return false;
1224 Msg::Debug(
"smooth %li connected components from %li initial elements ...",
1225 components.size(), elements.size());
1226 for(
size_t i = 0; i < components.size(); ++i) {
1227 GFaceMeshPatch patch;
1228 bool okp = patchFromElements(gf, components[i], patch);
1231 PatchGeometryBackup backup(patch);
1233 GeomOptimStats stats;
1234 double sicnBefore = 0.;
1235 if(haveNiceParametrization(gf)) {
1236 if(patch.bdrVertices.size() == 1) {
1238 patchOptimizeGeometryGlobal(patch, stats);
1239 sicnBefore = stats.sicnMinBefore;
1241 if(stats.sicnMinAfter < 0.5) {
1243 GeomOptimOptions opt;
1244 opt.invertCADNormals = invertNormalsForQuality;
1245 patchOptimizeGeometryWithKernel(patch, opt, stats);
1250 computeSICN(patch.elements, sicnBefore, sicnAvg);
1251 if(sicnBefore < 0.5) {
1253 GeomOptimOptions opt;
1254 opt.invertCADNormals = invertNormalsForQuality;
1255 patchOptimizeGeometryWithKernel(patch, opt, stats);
1260 GeomOptimOptions opt;
1262 opt.outerLoopIterMax = 20.;
1264 opt.withBackup =
true;
1265 opt.invertCADNormals = invertNormalsForQuality;
1268 opt.dxLocalMax = 1.e-4;
1269 patchOptimizeGeometryWithKernel(patch, opt, stats);
1273 if(stats.sicnMinAfter < sicnBefore) {
1275 "quality (SICN) decreased (%.3f -> %.3f), restore previous geometry",
1276 sicnBefore, stats.sicnMinAfter);
1285 bool invertNormalsForQuality =
false;
1290 size_t nCornerValFixed = 0;
1291 size_t nCurveValFixed = 0;
1292 size_t nSurfaceValFixed = 0;
1293 size_t nCornerValRemaining = 0;
1294 size_t nCurveValRemaining = 0;
1295 size_t nSurfaceValRemaining = 0;
1298 int improveCornerValences(
1300 const unordered_map<MVertex *, double>
1305 Msg::Debug(
"- Face %i: improve corner valences", gf->
tag());
1306 std::vector<MVertex *>
1312 std::vector<std::pair<MVertex *, double> > cornerAndIdeal;
1313 for(
auto &kv : qValIdeal) {
1315 if(gv !=
nullptr) { cornerAndIdeal.push_back({kv.first, kv.second}); }
1317 std::sort(cornerAndIdeal.begin(), cornerAndIdeal.end(),
1318 [](
const std::pair<MVertex *, double> &lhs,
1319 const std::pair<MVertex *, double> &rhs) {
1320 return lhs.second < rhs.second ||
1321 (lhs.second == rhs.second &&
1322 lhs.first->getNum() < rhs.first->getNum());
1325 int SMALL_PATCH = 0;
1326 int LARGER_PATCH = 1;
1329 int CORNER_PATCH_2 = 3;
1334 for(
int pass : {CORNER_PATCH, CORNER_PATCH_2, LARGER_PATCH, SMALL_PATCH}) {
1335 for(
const auto &kv : cornerAndIdeal) {
1338 if(gv ==
nullptr)
continue;
1340 auto it = adj.find(v);
1341 if(it == adj.end())
continue;
1342 const std::vector<MElement *> &quads = it->second;
1343 double ival_float = kv.second;
1344 size_t ival = idealBoundaryValence(kv.first, ival_float);
1345 if(ival == quads.size())
continue;
1346 size_t num = v->
getNum();
1350 "- Face %i: try to fix corner %i (vertex %li), val %li instead of %li",
1351 gf->
tag(), gv->
tag(), num, it->second.size(), ival);
1354 GFaceMeshPatch patch;
1355 if((pass == CORNER_PATCH || pass == CORNER_PATCH_2) &&
1356 quads.size() == 2) {
1359 std::vector<MVertex *> vert1 = {
1360 quads[0]->getVertex(0), quads[0]->getVertex(1),
1361 quads[0]->getVertex(2), quads[0]->getVertex(3)};
1362 std::vector<MVertex *> vert2 = {
1363 quads[1]->getVertex(0), quads[1]->getVertex(1),
1364 quads[1]->getVertex(2), quads[1]->getVertex(3)};
1365 std::vector<MVertex *> shared =
intersection(vert1, vert2);
1366 if(shared.size() != 2)
continue;
1368 if(shared[0] == v) { ext = shared[1]; }
1369 else if(shared[1] == v) {
1375 auto it2 = adj.find(ext);
1376 if(it2 == adj.end())
continue;
1377 std::vector<MElement *> quadsPlus = it2->second;
1378 append(quadsPlus, getNeighbors(quadsPlus, adj));
1379 if(pass == CORNER_PATCH_2) {
1380 append(quadsPlus, getNeighbors(quadsPlus, adj));
1382 bool okp = patchFromElements(gf, quadsPlus, patch);
1385 else if(pass == LARGER_PATCH) {
1387 std::vector<MElement *> quadsPlus = quads;
1388 append(quadsPlus, getNeighbors(quads, adj));
1389 bool okp = patchFromElements(gf, quadsPlus, patch);
1392 else if(pass == SMALL_PATCH) {
1393 bool okp = patchFromElements(gf, quads, patch);
1396 if(patch.bdrVertices.size() != 1) {
1397 Msg::Debug(
"patch has %li bdr loops, weird", patch.bdrVertices.size());
1400 if(patch.embVertices.size() > 0) {
1401 Msg::Debug(
"patch has %li embedded vertices loops, avoid",
1402 patch.embVertices.size());
1406 std::vector<int> bndIdealValence;
1407 std::vector<std::pair<int, int> > bndAllowedValenceRange;
1408 const int dimCorner = 0;
1409 bool okb = getBoundaryIdealAndAllowedValences(dimCorner, patch, adj,
1410 qValIdeal, bndIdealValence,
1411 bndAllowedValenceRange);
1415 std::vector<MElement *> neighborsForGeometry =
1416 getNeighbors(patch.elements, adj);
1417 double minSICNafer = 0.1;
1418 if(ival_float <= 0.5) minSICNafer = 0.001;
1419 int sdq = remeshLocalWithDiskQuadrangulation(
1420 gf, patch.elements, patch.intVertices, patch.bdrVertices.front(),
1421 bndIdealValence, bndAllowedValenceRange, neighborsForGeometry,
1422 minSICNafer, opt.invertNormalsForQuality, opt.sp, diff);
1425 GFaceMeshPatch patchBefore = diff.before;
1426 GFaceMeshPatch patchAfter = diff.after;
1431 GeoLog::add(patchBefore.elements,
"fixCornerB");
1432 GeoLog::add(patchAfter.elements,
"fixCornerA");
1433 gmsh::view::add(
"---");
1438 bool ok = diff.execute(
true);
1439 if(PARANO_VALIDITY) {
1440 errorAndAbortIfInvalidSurfaceMesh(
1441 gf,
"improveCornerValences | after execute");
1444 updateAdjacencies(patchBefore, patchAfter, adj);
1445 append(smoothingPool, patchAfter.intVertices);
1446 append(smoothingPool, patchAfter.bdrVertices.front());
1447 stats.nCornerValFixed += 1;
1456 Msg::Debug(
"-- failed to fix corner %li", num);
1460 GeoLog::add(patch.elements,
"fixCornerFAILED");
1461 gmsh::view::add(
"---");
1468 if(smoothingPool.size() > 0)
1469 smoothThePool(gf, smoothingPool, adj, opt.invertNormalsForQuality, opt.sp);
1472 for(
const auto &kv : cornerAndIdeal) {
1475 if(gv ==
nullptr)
continue;
1476 auto it = adj.find(v);
1477 if(it == adj.end())
continue;
1478 const std::vector<MElement *> &quads = it->second;
1479 size_t ival = idealBoundaryValence(kv.first, kv.second);
1480 if(ival == quads.size())
continue;
1481 stats.nCornerValRemaining += 1;
1487 int improveCurveValences(
1489 const unordered_map<MVertex *, double>
1495 std::vector<MVertex *>
1501 std::vector<std::pair<MVertex *, double> > curveVertexAndIdeal;
1502 curveVertexAndIdeal.reserve(qValIdeal.size());
1503 for(
auto &kv : qValIdeal) {
1506 if(ge !=
nullptr) { curveVertexAndIdeal.push_back({kv.first, kv.second}); }
1508 std::sort(curveVertexAndIdeal.begin(), curveVertexAndIdeal.end(),
1509 [](
const std::pair<MVertex *, double> &lhs,
1510 const std::pair<MVertex *, double> &rhs) {
1511 return lhs.second < rhs.second ||
1512 (lhs.second == rhs.second &&
1513 lhs.first->getNum() < rhs.first->getNum());
1516 for(
const auto &kv : curveVertexAndIdeal) {
1519 if(ge ==
nullptr)
continue;
1520 auto it = adj.find(v);
1521 if(it == adj.end())
continue;
1522 const std::vector<MElement *> &quads = it->second;
1523 size_t ival = idealBoundaryValence(kv.first, kv.second);
1524 if(ival == quads.size())
continue;
1525 size_t num = v->
getNum();
1528 Msg::Debug(
"- Face %i: try to fix curve vertex %li, val %li instead of %li",
1529 gf->
tag(), v->
getNum(), it->second.size(), ival);
1532 GFaceMeshPatch patch;
1533 bool okp = patchFromElements(gf, quads, patch);
1535 if(patch.bdrVertices.size() != 1) {
1536 Msg::Debug(
"patch has %li bdr loops, weird", patch.bdrVertices.size());
1539 if(patch.embVertices.size() > 0) {
1540 Msg::Debug(
"patch has %li embedded vertices loops, avoid",
1541 patch.embVertices.size());
1545 std::vector<int> bndIdealValence;
1546 std::vector<std::pair<int, int> > bndAllowedValenceRange;
1547 const int dimCurve = 1;
1548 bool okb = getBoundaryIdealAndAllowedValences(
1549 dimCurve, patch, adj, qValIdeal, bndIdealValence, bndAllowedValenceRange);
1553 std::vector<MElement *> neighborsForGeometry =
1554 getNeighbors(patch.elements, adj);
1555 double minSICNafer = 0.1;
1556 int sdq = remeshLocalWithDiskQuadrangulation(
1557 gf, patch.elements, patch.intVertices, patch.bdrVertices.front(),
1558 bndIdealValence, bndAllowedValenceRange, neighborsForGeometry,
1559 minSICNafer, opt.invertNormalsForQuality, opt.sp, diff);
1562 GFaceMeshPatch patchBefore = diff.before;
1563 GFaceMeshPatch patchAfter = diff.after;
1568 GeoLog::add(patchBefore.elements,
"fixCurveB");
1569 GeoLog::add(patchAfter.elements,
"fixCurveA");
1570 gmsh::view::add(
"---");
1575 bool ok = diff.execute(
true);
1576 if(PARANO_VALIDITY) {
1577 errorAndAbortIfInvalidSurfaceMesh(
1578 gf,
"improveCurveValences | after execute");
1581 updateAdjacencies(patchBefore, patchAfter, adj);
1582 append(smoothingPool, patchAfter.intVertices);
1583 append(smoothingPool, patchAfter.bdrVertices.front());
1584 stats.nCurveValFixed += 1;
1586 if(PARANO_QUALITY) {
1587 errorAndAbortIfNegativeElement(
1588 gf, dynamic_cast_vector<MQuadrangle *, MElement *>(gf->
quadrangles),
1596 Msg::Debug(
"-- curve vertex %li fixed", num);
1599 Msg::Debug(
"-- failed to fix curve vertex %li", num);
1601 if(PARANO_QUALITY) {
1602 errorAndAbortIfNegativeElement(
1603 gf, dynamic_cast_vector<MQuadrangle *, MElement *>(gf->
quadrangles),
1604 "after failed to fix, baaad");
1609 if(PARANO_QUALITY) {
1610 errorAndAbortIfNegativeElement(
1611 gf, dynamic_cast_vector<MQuadrangle *, MElement *>(gf->
quadrangles),
1615 if(smoothingPool.size() > 0)
1616 smoothThePool(gf, smoothingPool, adj, opt.invertNormalsForQuality, opt.sp);
1618 if(PARANO_QUALITY) {
1619 errorAndAbortIfNegativeElement(
1620 gf, dynamic_cast_vector<MQuadrangle *, MElement *>(gf->
quadrangles),
1625 for(
const auto &kv : curveVertexAndIdeal) {
1628 if(ge ==
nullptr)
continue;
1629 auto it = adj.find(v);
1630 if(it == adj.end())
continue;
1631 const std::vector<MElement *> &quads = it->second;
1632 size_t ival = idealBoundaryValence(kv.first, kv.second);
1633 if(ival == quads.size())
continue;
1634 stats.nCurveValRemaining += 1;
1640 double irregularityEnergy(
1641 const GFaceMeshPatch &patch,
1642 const unordered_map<MVertex *, double> &qValIdeal,
1647 for(
MVertex *v : patch.bdrVertices.front()) {
1648 double valIdeal = 4.;
1649 auto it = qValIdeal.find(v);
1650 if(it != qValIdeal.end()) { valIdeal = it->second; }
1651 auto it2 = adj.find(v);
1652 if(it2 == adj.end()) {
continue; }
1653 valIdeal = clamp(std::round(valIdeal), 1., 4.);
1655 double val = (double)it2->second.size();
1656 Ir += std::pow(valIdeal - val, 2);
1659 for(
MVertex *v : patch.intVertices) {
1660 double valIdeal = 4.;
1661 auto it2 = adj.find(v);
1662 if(it2 == adj.end()) {
continue; }
1663 valIdeal = clamp(std::round(valIdeal), 1., 4.);
1664 double val = (double)it2->second.size();
1665 Ir += std::pow(valIdeal - val, 2);
1670 double irregularityEnergy(
1671 GFace *gf,
const std::vector<MElement *> &quads,
1672 const unordered_map<MVertex *, double> &qValIdeal,
1675 GFaceMeshPatch patch;
1676 bool okp = patchFromElements(gf, quads, patch);
1678 return irregularityEnergy(patch, qValIdeal, adj);
1681 int improveInteriorValences(
1683 const unordered_map<MVertex *, double>
1688 Msg::Debug(
"- Face %i: improve interior valences", gf->
tag());
1690 std::vector<MVertex *>
1695 std::priority_queue<std::pair<double, MVertex *>,
1696 std::vector<std::pair<double, MVertex *> > >
1700 for(
const auto &kv : adj) {
1703 if(gf ==
nullptr)
continue;
1704 auto it = adj.find(v);
1705 if(it == adj.end())
continue;
1706 const std::vector<MElement *> &quads = it->second;
1707 const size_t val = quads.size();
1708 if(3 <= val && val <= 5)
continue;
1711 GFaceMeshPatch patch;
1712 bool okp = patchFromElements(gf, quads, patch);
1713 if(!okp || patch.bdrVertices.size() != 1)
continue;
1715 if(patch.embVertices.size() > 0) {
1716 Msg::Debug(
"patch has %li embedded vertices loops, avoid",
1717 patch.embVertices.size());
1721 double Ir = irregularityEnergy(gf, quads, qValIdeal, adj);
1722 if(Ir > 0.) Q.push({Ir, v});
1726 while(Q.size() > 0) {
1730 auto it = adj.find(v);
1732 if(it == adj.end())
continue;
1733 const std::vector<MElement *> &quads = it->second;
1734 size_t num = v->
getNum();
1737 GFaceMeshPatch patch;
1738 bool okp = patchFromElements(gf, quads, patch);
1739 if(!okp || patch.bdrVertices.size() != 1)
continue;
1742 std::vector<int> bndIdealValence;
1743 std::vector<std::pair<int, int> > bndAllowedValenceRange;
1744 const int dimCurve = 2;
1745 bool okb = getBoundaryIdealAndAllowedValences(
1746 dimCurve, patch, adj, qValIdeal, bndIdealValence, bndAllowedValenceRange);
1751 std::vector<MElement *> neighborsForGeometry =
1752 getNeighbors(patch.elements, adj);
1753 double minSICNafer = 0.1;
1754 int sdq = remeshLocalWithDiskQuadrangulation(
1755 gf, patch.elements, patch.intVertices, patch.bdrVertices.front(),
1756 bndIdealValence, bndAllowedValenceRange, neighborsForGeometry,
1757 minSICNafer, opt.invertNormalsForQuality, opt.sp, diff);
1760 GFaceMeshPatch patchBefore = diff.before;
1761 GFaceMeshPatch patchAfter = diff.after;
1766 GeoLog::add(patchBefore.elements,
"fixIntB");
1767 GeoLog::add(patchAfter.elements,
"fixIntA");
1768 gmsh::view::add(
"---");
1773 bool ok = diff.execute(
true);
1774 if(PARANO_VALIDITY) {
1775 errorAndAbortIfInvalidSurfaceMesh(
1776 gf,
"improveInteriorValences | after execute");
1779 updateAdjacencies(patchBefore, patchAfter, adj);
1780 append(smoothingPool, patchAfter.intVertices);
1781 append(smoothingPool, patchAfter.bdrVertices.front());
1782 stats.nSurfaceValFixed += 1;
1786 for(
MVertex *bv : patchAfter.bdrVertices.front()) {
1787 auto it2 = adj.find(bv);
1788 if(it2 == adj.end())
continue;
1789 const std::vector<MElement *> &quads2 = it2->second;
1790 if(quads2.size() > 5) {
1791 double Ir2 = irregularityEnergy(gf, quads2, qValIdeal, adj);
1792 if(Ir2 > 0) Q.push({Ir2, bv});
1800 Msg::Debug(
"-- interior vertex %li fixed", num);
1803 Msg::Debug(
"-- failed to fix interior vertex %li", num);
1809 if(smoothingPool.size() > 0)
1810 smoothThePool(gf, smoothingPool, adj, opt.invertNormalsForQuality, opt.sp);
1813 for(
const auto &kv : adj) {
1816 if(gf ==
nullptr)
continue;
1817 auto it = adj.find(v);
1818 if(it == adj.end())
continue;
1819 const std::vector<MElement *> &quads = it->second;
1820 const size_t val = quads.size();
1821 if(3 <= val && val <= 5)
continue;
1822 stats.nSurfaceValRemaining += 1;
1828 bool patchIsValidForDiskRequadrangulation(
1829 const GFaceMeshPatch &patch,
1832 if(patch.bdrVertices.size() != 1)
return false;
1833 for(
MVertex *v : patch.intVertices) {
1834 auto it = adj.find(v);
1835 if(it == adj.end())
return false;
1837 for(
MVertex *v : patch.bdrVertices[0]) {
1838 auto it = adj.find(v);
1839 if(it == adj.end())
return false;
1841 for(
MElement *e : patch.elements) {
1843 std::find(patch.gf->quadrangles.begin(), patch.gf->quadrangles.end(), e);
1844 if(it == patch.gf->quadrangles.end())
return false;
1846 std::vector<MVertex *> bndt;
1847 bool okbb = buildBoundary(patch.elements, bndt);
1848 if(!okbb)
return false;
1853 bool getIrregularPatchesAroundVertices(
1854 const std::vector<MVertex *> &vert,
1856 const unordered_map<MVertex *, double> &qValIdeal,
1857 std::vector<std::pair<double, GFaceMeshPatch> > &patches)
1861 if(gf ==
nullptr)
continue;
1862 auto it = adj.find(v);
1863 if(it == adj.end())
continue;
1864 const std::vector<MElement *> &quads = it->second;
1865 const size_t val = quads.size();
1866 if(val == 4)
continue;
1869 GFaceMeshPatch patch;
1870 bool okp = patchFromElements(gf, quads, patch);
1871 if(!okp || patch.bdrVertices.size() != 1)
continue;
1872 if(patch.embVertices.size() > 0) {
continue; }
1873 double Ir = irregularityEnergy(gf, quads, qValIdeal, adj);
1874 if(Ir > 0.) patches.push_back({Ir, patch});
1879 for(
size_t le = 0; le < 4; ++le) {
1880 MVertex *v1 = q->getVertex(le);
1881 MVertex *v2 = q->getVertex((le + 1) % 4);
1882 if(v1 != v && v2 != v)
continue;
1884 auto it2 = (v1 == v) ? adj.find(v2) : adj.find(v1);
1885 if(it2 == adj.end())
continue;
1886 if(it2->second.size() != 4) {
1888 std::vector<MElement *> elts = quads;
1889 append(elts, it2->second);
1890 GFaceMeshPatch patch;
1891 bool okp = patchFromElements(gf, elts, patch);
1892 if(!okp || patch.bdrVertices.size() != 1)
continue;
1893 if(patch.embVertices.size() > 0)
continue;
1894 double Ir = irregularityEnergy(gf, elts, qValIdeal, adj);
1895 if(Ir > 0.) patches.push_back({Ir, patch});
1900 auto it0 = adj.find(q->getVertex(0));
1901 auto it1 = adj.find(q->getVertex(1));
1902 auto it2 = adj.find(q->getVertex(2));
1903 auto it3 = adj.find(q->getVertex(3));
1904 if(it0 == adj.end() || it1 == adj.end() || it2 == adj.end() ||
1907 std::array<size_t, 4> vals = {it0->second.size(), it1->second.size(),
1908 it2->second.size(), it3->second.size()};
1909 for(
size_t k = 0; k < 2; ++k) {
1910 if(vals[k] != 4 && vals[k + 2] != 4) {
1912 std::vector<MElement *> elts = {q};
1913 append(elts, getNeighbors(elts, adj));
1914 GFaceMeshPatch patch;
1915 bool okp = patchFromElements(gf, elts, patch);
1916 if(!okp || patch.bdrVertices.size() != 1)
continue;
1917 if(patch.embVertices.size() > 0)
continue;
1918 double Ir = irregularityEnergy(gf, elts, qValIdeal, adj);
1919 if(Ir > 0.) patches.push_back({Ir, patch});
1929 int improveInteriorValencesV2(
1931 const unordered_map<MVertex *, double>
1936 Msg::Debug(
"- Face %i: improve interior valences", gf->
tag());
1938 std::vector<MVertex *>
1943 typedef std::pair<double, GFaceMeshPatch> Ir_and_Patch;
1944 auto comp = [](
const Ir_and_Patch &a,
const Ir_and_Patch &b) {
1945 return a.first > b.first ||
1946 (a.first == b.first &&
1947 a.second.elements.size() < b.second.elements.size());
1949 std::priority_queue<Ir_and_Patch, std::vector<Ir_and_Patch>, decltype(comp)>
1953 for(
const auto &kv : adj) {
1956 if(gf ==
nullptr)
continue;
1957 auto it = adj.find(v);
1958 if(it == adj.end())
continue;
1959 const std::vector<MElement *> &quads = it->second;
1960 const size_t val = quads.size();
1961 if(val == 4)
continue;
1963 std::vector<std::pair<double, GFaceMeshPatch> > patches;
1964 getIrregularPatchesAroundVertices({v}, adj, qValIdeal, patches);
1965 for(
size_t k = 0; k < patches.size(); ++k) { Q.push(patches[k]); }
1969 while(Q.size() > 0) {
1970 GFaceMeshPatch patch = Q.top().second;
1973 if(!patchIsValidForDiskRequadrangulation(patch, adj))
continue;
1976 std::vector<int> bndIdealValence;
1977 std::vector<std::pair<int, int> > bndAllowedValenceRange;
1978 const int dimCurve = 2;
1979 bool okb = getBoundaryIdealAndAllowedValences(
1980 dimCurve, patch, adj, qValIdeal, bndIdealValence, bndAllowedValenceRange);
1985 std::vector<MElement *> neighborsForGeometry =
1986 getNeighbors(patch.elements, adj);
1987 double minSICNafer = 0.1;
1988 int sdq = remeshLocalWithDiskQuadrangulation(
1989 gf, patch.elements, patch.intVertices, patch.bdrVertices.front(),
1990 bndIdealValence, bndAllowedValenceRange, neighborsForGeometry,
1991 minSICNafer, opt.invertNormalsForQuality, opt.sp, diff);
1994 GFaceMeshPatch patchBefore = diff.before;
1995 GFaceMeshPatch patchAfter = diff.after;
2000 GeoLog::add(patchBefore.elements,
"fixIntB");
2001 GeoLog::add(patchAfter.elements,
"fixIntA");
2002 gmsh::view::add(
"---");
2007 bool ok = diff.execute(
true);
2008 if(PARANO_VALIDITY) {
2009 errorAndAbortIfInvalidSurfaceMesh(
2010 gf,
"improveInteriorValencesV2 | after execute");
2013 updateAdjacencies(patchBefore, patchAfter, adj);
2014 append(smoothingPool, patchAfter.intVertices);
2015 append(smoothingPool, patchAfter.bdrVertices.front());
2016 stats.nSurfaceValFixed += 1;
2020 std::vector<std::pair<double, GFaceMeshPatch> > patches;
2021 getIrregularPatchesAroundVertices(patchAfter.bdrVertices.front(), adj,
2022 qValIdeal, patches);
2023 for(
size_t k = 0; k < patches.size(); ++k) { Q.push(patches[k]); }
2032 Msg::Debug(
"-- failed to fix interior patch");
2038 if(smoothingPool.size() > 0)
2039 smoothThePool(gf, smoothingPool, adj, opt.invertNormalsForQuality, opt.sp);
2042 for(
const auto &kv : adj) {
2045 if(gf ==
nullptr)
continue;
2046 auto it = adj.find(v);
2047 if(it == adj.end())
continue;
2048 const std::vector<MElement *> &quads = it->second;
2049 const size_t val = quads.size();
2050 if(3 <= val && val <= 5)
continue;
2051 stats.nSurfaceValRemaining += 1;
2057 int RefineMeshWithBackgroundMeshProjectionSimple(
GModel *gm)
2060 "Refine mesh (midpoint subdivision, with background projection) ...");
2068 std::vector<GEdge *>
edges = model_edges(gm);
2069 std::vector<GFace *>
faces = model_faces(gm);
2072 unordered_map<MVertex *, MVertex *> old2new;
2077 #pragma omp parallel for schedule(dynamic) num_threads(nthreads)
2078 for(
size_t e = 0; e <
edges.size(); ++e) {
2082 std::vector<MVertex *> toProj;
2089 Msg::Error(
"- Edge %i: vertex %li is associated to entity (%i,%i)",
2095 if(mev ==
nullptr) {
2100 toProj.push_back(v2);
2107 for(
size_t i = 0; i < toProj.size(); ++i) {
2110 tMin + (
tMax -
tMin) *
double(i + 1) / double(toProj.size());
2113 v->
setXYZ(proj.
x(), proj.
y(), proj.
z());
2117 Msg::Warning(
"- Edge %i, vertex %li: curve projection failed",
2128 Msg::Warning(
"refine mesh with background projection: no background mesh, "
2129 "using CAD projection (slow)");
2132 #pragma omp parallel for schedule(dynamic) num_threads(nthreads)
2133 for(
size_t f = 0;
f <
faces.size(); ++
f) {
2140 std::vector<MVertex *> toProj;
2145 Msg::Error(
"- Face %i: vertex %li is associated to entity (%i,%i)",
2153 if(mfv ==
nullptr) {
2155 v->
point().
z(), gf, 0., 0.);
2158 toProj.push_back(v2);
2170 Msg::Error(
"background mesh not found for face %i", gf->
tag());
2174 std::vector<MTriangle *> triangles(it2->second.triangles.size());
2175 for(
size_t i = 0; i < it2->second.triangles.size(); ++i) {
2176 triangles[i] = &(it2->second.triangles[i]);
2180 Msg::Warning(
"failed to initialize surface projector");
2191 bool evalOnCAD =
false;
2192 bool projOnCad =
false;
2196 for(
size_t i = 0; i < toProj.size(); ++i) {
2202 double uvg[2] = {0., 0.};
2207 double uvg[2] = {0., 0.};
2211 v->
setXYZ(proj.
x(), proj.
y(), proj.
z());
2216 Msg::Warning(
"- Face %i, vertex %li: surface projection failed",
2226 #pragma omp parallel for schedule(dynamic) num_threads(nthreads)
2227 for(
size_t e = 0; e <
edges.size(); ++e) {
2230 for(
size_t lv = 0; lv < 2; ++lv) {
2232 auto it = old2new.find(v);
2233 if(it != old2new.end()) { l->
setVertex(lv, it->second); }
2238 #pragma omp parallel for schedule(dynamic) num_threads(nthreads)
2239 for(
size_t f = 0;
f <
faces.size(); ++
f) {
2245 auto it2 = old2new.find(v);
2246 if(it2 != old2new.end()) { e->
setVertex(lv, it2->second); }
2251 if(PARANO_VALIDITY) {
2252 errorAndAbortIfInvalidVertexInModel(gm,
"after refine + proj");
2255 if(DBG_EXPORT) { gm->
writeMSH(
"qqs_subdiv.msh", 4.1); }
2257 optimizeGeometryQuadqs(gm);
2260 std::unordered_map<std::string, double> stats;
2261 appendQuadMeshStatistics(gm, stats,
"Mesh_");
2263 stats,
"Quad mesh after subdivision, projection and small smoothing:");
2271 return RefineMeshWithBackgroundMeshProjectionSimple(gm);
2273 const bool USE_CAD_PROJECTION =
false;
2274 if(USE_CAD_PROJECTION) {
2276 Msg::Info(
"Refine mesh (midpoint subdivision, with CAD projection) ...");
2277 bool linear =
false;
2280 std::unordered_map<std::string, double> stats;
2281 appendQuadMeshStatistics(gm, stats,
"Mesh_");
2282 printStatistics(stats,
"Quad mesh after subdivision with CAD projection:");
2288 if(SHOW_INTERMEDIATE_VIEWS) {
2289 std::vector<MElement *> elements;
2290 for(
GFace *gf : model_faces(gm)) {
2292 dynamic_cast_vector<MTriangle *, MElement *>(gf->
triangles));
2294 dynamic_cast_vector<MQuadrangle *, MElement *>(gf->
quadrangles));
2296 GeoLog::add(elements,
"qqs_quadtri");
2299 if(DBG_EXPORT) { gm->
writeMSH(
"qqs_init.msh", 4.1); }
2302 "Refine mesh (midpoint subdivision, with background projection) ...");
2308 std::unordered_map<std::string, double> stats;
2309 appendQuadMeshStatistics(gm, stats,
"MPS_");
2310 printStatistics(stats,
"Quad mesh after subdivision, before projection:");
2311 if(DBG_EXPORT) { gm->
writeMSH(
"qqs_subdiv_noproj.msh", 4.1); }
2317 std::vector<GEdge *>
edges = model_edges(gm);
2318 std::vector<GFace *>
faces = model_faces(gm);
2321 std::unordered_map<GEdge *, std::vector<MVertex *> > toProjectOnCurve;
2322 std::unordered_map<GFace *, std::vector<MVertex *> > toProjectOnFace;
2323 std::unordered_map<GEdge *, std::unordered_set<GFace *> > edgeToFaces;
2324 for(
GFace *gf : model_faces(gm)) {
2325 std::vector<GEdge *> fedges = face_edges(gf);
2326 for(
GEdge *ge : fedges) edgeToFaces[ge].insert(gf);
2329 int nthreads = getNumThreads();
2330 #pragma omp parallel for schedule(dynamic) num_threads(nthreads)
2331 for(
size_t e = 0; e <
edges.size(); ++e) {
2335 unordered_map<MVertex *, MVertex *> old2new_ge;
2336 std::vector<MVertex *> &projList = toProjectOnCurve[ge];
2341 if(mev ==
nullptr) {
2346 projList.push_back(v2);
2352 for(
size_t lv = 0; lv < 2; ++lv) {
2354 auto it = old2new_ge.find(v);
2355 if(it != old2new_ge.end()) { l->
setVertex(lv, it->second); }
2359 for(
GFace *gf : edgeToFaces[ge]) {
2364 auto it = old2new_ge.find(v);
2365 if(it != old2new_ge.end()) { e->
setVertex(lv, it->second); }
2371 #pragma omp parallel for schedule(dynamic) num_threads(nthreads)
2372 for(
size_t f = 0;
f <
faces.size(); ++
f) {
2379 unordered_map<MVertex *, MVertex *> old2new_gf;
2380 std::vector<MVertex *> &projList = toProjectOnFace[gf];
2385 Msg::Error(
"- Face %i: vertex %li is associated to entity (%i,%i)",
2391 if(mfv ==
nullptr) {
2393 v->
point().
z(), gf, 0., 0.);
2396 projList.push_back(v2);
2405 auto it2 = old2new_gf.find(v);
2406 if(it2 != old2new_gf.end()) { e->
setVertex(lv, it2->second); }
2414 #pragma omp parallel for schedule(dynamic) num_threads(nthreads)
2415 for(
size_t e = 0; e <
edges.size(); ++e) {
2417 auto it = toProjectOnCurve.find(ge);
2418 if(it == toProjectOnCurve.end())
continue;
2423 for(
size_t j = 0; j < it->second.size(); ++j) {
2426 tMin + (
tMax -
tMin) *
double(j + 1) / double(it->second.size() + 1);
2429 v->
setXYZ(proj.
x(), proj.
y(), proj.
z());
2433 Msg::Warning(
"- Edge %i, vertex %li: curve projection failed",
2444 Msg::Warning(
"refine mesh with background projection: no background mesh, "
2445 "using CAD projection (slow)");
2449 #pragma omp parallel for schedule(dynamic) num_threads(nthreads)
2450 for(
size_t f = 0;
f <
faces.size(); ++
f) {
2453 auto it = toProjectOnFace.find(gf);
2454 if(it == toProjectOnFace.end())
continue;
2456 Msg::Info(
"- Face %i: project %li vertices and smooth the quad mesh ...",
2457 gf->
tag(), it->second.size());
2463 Msg::Error(
"background mesh not found for face %i", gf->
tag());
2467 std::vector<MTriangle *> triangles(it2->second.triangles.size());
2468 for(
size_t i = 0; i < it2->second.triangles.size(); ++i) {
2469 triangles[i] = &(it2->second.triangles[i]);
2475 Msg::Warning(
"failed to initialize surface projector");
2483 bool evalOnCAD =
false;
2484 bool projOnCad =
false;
2487 for(
size_t j = 0; j < it->second.size(); ++j) {
2493 double uvg[2] = {0., 0.};
2498 double uvg[2] = {0., 0.};
2502 v->
setXYZ(proj.
x(), proj.
y(), proj.
z());
2507 Msg::Warning(
"- Face %i, vertex %li: surface projection failed",
2513 double timeMax = 0.3;
2514 optimizeGeometryQuadMesh(gf, sp, timeMax);
2515 double sicnMin, sicnAvg;
2516 computeSICN(dynamic_cast_vector<MQuadrangle *, MElement *>(gf->
quadrangles),
2519 double timeMax = 2.;
2520 optimizeGeometryQuadMesh(gf, sp, timeMax);
2526 if(SHOW_INTERMEDIATE_VIEWS) {
2527 std::vector<MElement *> elements;
2528 for(
GFace *gf : model_faces(gm)) {
2530 dynamic_cast_vector<MQuadrangle *, MElement *>(gf->
quadrangles));
2533 GeoLog::add(elements,
"qqs_quadinit");
2538 std::unordered_map<std::string, double> stats;
2539 appendQuadMeshStatistics(gm, stats,
"Mesh_");
2541 stats,
"Quad mesh after subdivision, projection and small smoothing:");
2544 if(PARANO_VALIDITY) {
2545 errorAndAbortIfInvalidVertexInModel(gm,
"after refine + proj");
2548 if(DBG_EXPORT) { gm->
writeMSH(
"qqs_subdiv.msh", 4.1); }
2553 int checkAndReplaceQuadDominantMesh(
GFace *gf,
int valenceMaxBdr = 6,
2554 int valenceMaxIn = 8)
2557 unordered_map<MVertex *, int> valence;
2559 for(
size_t lv = 0; lv < 4; ++lv) { valence[q->
getVertex(lv)] += 1; }
2561 for(
size_t lv = 0; lv < 3; ++lv) { valence[t->
getVertex(lv)] += 1; }
2564 for(
auto &kv : valence) {
2565 if(kv.first->onWhat() == gf) { vMaxInt = std::max(vMaxInt, kv.second); }
2566 else if(kv.first->onWhat()->cast2Edge() !=
nullptr) {
2567 vMaxBdr = std::max(vMaxBdr, kv.second);
2570 if(vMaxInt <= valenceMaxIn && vMaxBdr <= valenceMaxBdr)
return 0;
2572 Msg::Warning(
"- Face %i: quad-tri mesh have valence %i (interior) and %i "
2573 "(bdr), replace it with meshadapt ...",
2574 gf->
tag(), vMaxInt, vMaxBdr);
2594 std::vector<GFace *>
faces = model_faces(gm);
2596 int nthreads = getNumThreads();
2597 #pragma omp parallel for schedule(dynamic) num_threads(nthreads)
2598 for(
size_t f = 0;
f <
faces.size(); ++
f) {
2606 int st = checkAndReplaceQuadDominantMesh(gf);
2608 Msg::Warning(
"- Face %i: failed to replace bad quad-tri mesh", gf->
tag());
2615 int optimizeQuadMeshWithDiskQuadrangulationRemeshing(
GFace *gf)
2621 bool invertNormals = meshOrientationIsOppositeOfCadOrientation(gf);
2625 unordered_map<MVertex *, double> qValIdeal;
2626 computeBdrVertexIdealValence(gf->
quadrangles, qValIdeal);
2629 unordered_map<MVertex *, std::vector<MElement *> > adj;
2631 for(
size_t lv = 0; lv < 4; ++lv) {
2633 adj[v].push_back(
f);
2638 opt.invertNormalsForQuality = invertNormals;
2639 bool alwaysBuildSurfaceProjector =
true;
2640 if(alwaysBuildSurfaceProjector || !haveNiceParametrization(gf) ||
2643 bool okf = fillSurfaceProjector(gf, opt.sp);
2645 Msg::Error(
"- Face %i: failed to get a surface projector", gf->
tag());
2651 for(
auto &kv : qValIdeal) {
2652 GeoLog::add(kv.first->point(), std::round(kv.second),
2653 "f_" + std::to_string(gf->
tag()) +
"_val_ideal");
2660 const bool ENABLE_CORNER =
true;
2661 const bool ENABLE_CURVE =
true;
2664 int sc = improveCornerValences(gf, qValIdeal, adj, opt, stats);
2666 Msg::Warning(
"optimize quad topology: failed to improve corner valences");
2668 if(PARANO_QUALITY) {
2669 errorAndAbortIfNegativeElement(
2670 gf, dynamic_cast_vector<MQuadrangle *, MElement *>(gf->
quadrangles),
2676 int scu = improveCurveValences(gf, qValIdeal, adj, opt, stats);
2678 Msg::Warning(
"optimize quad topology: failed to improve curve valences");
2680 if(PARANO_QUALITY) {
2681 errorAndAbortIfNegativeElement(
2682 gf, dynamic_cast_vector<MQuadrangle *, MElement *>(gf->
quadrangles),
2687 int sci = improveInteriorValencesV2(gf, qValIdeal, adj, opt, stats);
2689 Msg::Warning(
"optimize quad topology: failed to improve interior valences");
2691 if(PARANO_QUALITY) {
2692 errorAndAbortIfNegativeElement(
2693 gf, dynamic_cast_vector<MQuadrangle *, MElement *>(gf->
quadrangles),
2698 double timeMax = 0.3;
2699 optimizeGeometryQuadMesh(gf, opt.sp, timeMax);
2708 Msg::Info(
"- Face %i: disk quadrangulation remeshing, improved valence on "
2709 "%li/%li/%li corner/curve/surface vertices (in %.3f sec), "
2710 "remaining non-ideal: %li/%li/%li",
2711 gf->
tag(), stats.nCornerValFixed, stats.nCurveValFixed,
2712 stats.nSurfaceValFixed, t2 - t1, stats.nCornerValRemaining,
2713 stats.nCurveValRemaining, stats.nSurfaceValRemaining);
2715 if(PARANO_QUALITY) {
2716 errorAndAbortIfNegativeElement(
2717 gf, dynamic_cast_vector<MQuadrangle *, MElement *>(gf->
quadrangles),
2718 "after geom optim");
2724 int insertExtrudedBoundaryLayer(
2725 GFace *gf,
const std::vector<MVertex *> &loop,
2728 if(loop.size() < 2)
return -1;
2729 unordered_map<MVertex *, MVertex *> extrusion;
2730 for(
size_t i = 0; i < loop.size(); ++i) {
2731 if(i > 0 && i == loop.size() - 1 && loop[i] == loop[0])
continue;
2733 auto it = adj.find(v);
2734 if(it == adj.end()) {
2735 Msg::Error(
"insert extruded layer: vertex not found in adj");
2744 for(
auto &q : it->second) {
2745 for(
size_t lv = 0; lv < 4; ++lv) {
2759 if(sum > 0.) pos = pos * double(1. / sum);
2760 if(sumuv > 0.) uv = uv * double(1. / sumuv);
2769 for(
auto &q : it->second) {
2770 for(
size_t lv = 0; lv < 4; ++lv) {
2778 for(
size_t i = 0; i < loop.size(); ++i) {
2780 MVertex *v1 = loop[(i + 1) % loop.size()];
2781 if(v0 == v1)
continue;
2791 int optimizeFaceQuadMeshBoundaries(
GFace *gf,
bool ignoreAcuteCorners =
false)
2798 double minSICNb = DBL_MAX;
2799 double avgSICNb = 0.;
2800 std::vector<MElement *> elts =
2801 dynamic_cast_vector<MQuadrangle *, MElement *>(gf->
quadrangles);
2802 computeSICN(elts, minSICNb, avgSICNb);
2803 std::vector<std::array<MVertex *, 4> > quadsInit(gf->
quadrangles.size());
2804 for(
size_t i = 0; i < gf->
quadrangles.size(); ++i)
2805 for(
size_t lv = 0; lv < 4; ++lv) {
2806 quadsInit[i][lv] = gf->
quadrangles[i]->getVertex(lv);
2808 std::vector<SPoint3> positionsInit(gf->
mesh_vertices.size());
2814 unordered_map<MVertex *, double> qValIdeal;
2815 computeBdrVertexIdealValence(gf->
quadrangles, qValIdeal);
2818 std::vector<std::vector<MVertex *> > loops;
2819 bool okb = buildBoundaries(elts, loops);
2826 unordered_map<MVertex *, std::vector<MElement *> > adj;
2828 for(
size_t lv = 0; lv < 4; ++lv) {
2830 adj[v].push_back(
f);
2835 for(
size_t i = 0; i < loops.size(); ++i) {
2836 bool goodAcute =
false;
2837 bool loopIsGood =
true;
2839 auto it = adj.find(v);
2840 if(it == adj.end())
continue;
2841 size_t val = it->second.size();
2843 auto iti = qValIdeal.find(v);
2844 if(iti == qValIdeal.end())
continue;
2845 size_t ival = idealBoundaryValence(iti->first, iti->second);
2847 if(val != ival) { loopIsGood =
false; }
2849 if(val == ival && iti->second < 0.5) { goodAcute =
true; }
2852 if(ignoreAcuteCorners && goodAcute)
continue;
2856 insertExtrudedBoundaryLayer(gf, loops[i], adj);
2864 fillSurfaceProjector(gf, &sp);
2865 optimizeGeometryQuadMesh(gf, &sp, timeMax);
2867 double minSICNa = DBL_MAX;
2868 double avgSICNa = 0.;
2869 std::vector<MElement *> eltsa =
2870 dynamic_cast_vector<MQuadrangle *, MElement *>(gf->
quadrangles);
2871 computeSICN(eltsa, minSICNa, avgSICNa);
2873 if(minSICNa <= 0. && minSICNa <= minSICNb) {
2874 Msg::Warning(
"- Face %i: worst quality (%.3f -> %.3f) after adding %li "
2875 "extruded boundary layers, restore",
2876 gf->
tag(), minSICNb, minSICNa, nlayer);
2892 for(
size_t i = 0; i < gf->
quadrangles.size(); ++i)
2893 for(
size_t lv = 0; lv < 4; ++lv) {
2894 gf->
quadrangles[i]->setVertex(lv, quadsInit[i][lv]);
2898 Msg::Info(
"- Face %i: added %li extruded boundary layers", gf->
tag(),
2905 int ensureEvenNumberOfEdgesOnCurvesAfterInitialSurfaceMesh(
GModel *gm)
2907 Msg::Info(
"Generating empty surface meshes ...");
2912 std::vector<GFace *>
faces = model_faces(gm);
2916 for(
size_t f = 0;
f <
faces.size(); ++
f) {
2938 if(!nPending)
break;
2944 std::vector<GEdge *>
edges;
2946 Msg::Info(
"Deleting empty surface meshes ...");
2947 for(
size_t f = 0;
f <
faces.size(); ++
f) {
2965 for(
size_t e = 0; e <
edges.size(); ++e) {
2967 if(ge->
lines.size() % 2 != 0) {
2969 "- Curve %i has %li lines (due to boundary recovery), splitting one",
2971 size_t i = ge->
lines.size() / 2;
2978 double t = 0.5 * (t1 + t2);
2985 t = 0.5 * (t1 + t2);
2990 if(mev1 && mev2) { lc = 0.5 * (mev1->
getLc() + mev2->
getLc()); }
3007 for(
size_t f = 0;
f <
faces.size(); ++
f) {
3014 gf->
edges().size() == 4) {
3015 std::vector<size_t> sizes;
3017 std::sort(sizes.begin(), sizes.end());
3018 if(sizes.size() != 4 || sizes[0] != sizes[1] || sizes[2] != sizes[3]) {
3020 "- Face %i: transfinite no longer possible, remove the attribute",
3031 double minimumQualityRequired)
3038 Msg::Debug(
"optimize topology of simple CAD faces with patterns: avoided "
3039 "because quadqsTopoOptimMethods = %i",
3044 std::vector<GFace *>
faces = model_faces(gm);
3045 Msg::Info(
"Pattern-based quad meshing of simple CAD faces ...",
faces.size());
3049 int nthreads = getNumThreads();
3050 #pragma omp parallel for schedule(dynamic) num_threads(nthreads)
3051 for(
size_t f = 0;
f <
faces.size(); ++
f) {
3062 bool invertNormals = meshOrientationIsOppositeOfCadOrientation(gf);
3063 meshFaceWithGlobalPattern(gf, invertNormals, minimumQualityRequired);
3065 if(PARANO_VALIDITY) {
3066 errorAndAbortIfInvalidSurfaceMesh(gf,
"after mesh with global pattern");
3070 std::unordered_map<std::string, double> stats;
3071 appendQuadMeshStatistics(gm, stats,
"Mesh_");
3072 printStatistics(stats,
3073 "Quad mesh after simple face pattern-based remeshing:");
3075 if(PARANO_VALIDITY) {
3076 errorAndAbortIfInvalidVertexInModel(
3077 gm,
"global check after face pattern meshing");
3080 if(DBG_EXPORT) { gm->
writeMSH(
"qqs_simplefaces.msh", 4.1); }
3092 Msg::Debug(
"optimize topology with disk quadrangulation remeshing: avoided "
3093 "because quadqsTopoOptimMethods = %i",
3099 "Optimize topology of quad meshes with disk quadrangulation remeshing ...");
3101 initDiskQuadrangulations();
3103 std::vector<GFace *>
faces = model_faces(gm);
3105 int nthreads = getNumThreads();
3106 #pragma omp parallel for schedule(dynamic) num_threads(nthreads)
3107 for(
size_t f = 0;
f <
faces.size(); ++
f) {
3116 optimizeQuadMeshWithDiskQuadrangulationRemeshing(gf);
3118 if(PARANO_VALIDITY) {
3119 errorAndAbortIfInvalidSurfaceMesh(gf,
3120 "after disk quadrangulation remeshing");
3124 std::unordered_map<std::string, double> stats;
3125 appendQuadMeshStatistics(gm, stats,
"Mesh_");
3126 printStatistics(stats,
"Quad mesh after disk quadrangulation remeshing:");
3128 if(stats[
"Mesh_SICN_min"] < 0.) {
3132 if(PARANO_VALIDITY) {
3133 errorAndAbortIfInvalidVertexInModel(
3134 gm,
"global check after disk quadrangulation remeshing");
3137 if(DBG_EXPORT) { gm->
writeMSH(
"qqs_diskrmsh.msh", 4.1); }
3149 Msg::Debug(
"optimize topology with cavity remeshing: avoided because "
3150 "quadqsTopoOptimMethods = %i",
3155 std::vector<GFace *>
faces = model_faces(gm);
3157 "Optimize topology of quad meshes with cavity remeshing (%li faces) ...",
3163 Msg::Info(
"no background mesh, creating one with the current quad mesh");
3167 Msg::Error(
"failed to import model mesh in background mesh");
3174 int nthreads = getNumThreads();
3175 #pragma omp parallel for schedule(dynamic) num_threads(nthreads)
3176 for(
size_t f = 0;
f <
faces.size(); ++
f) {
3187 std::vector<std::pair<SPoint3, int> > singularities;
3188 bool okg = getSingularitiesFromBackgroundField(gf, singularities);
3190 okg = getSingularitiesFromNewCrossFieldComputation(bmesh, gf,
3197 bool invertNormals = meshOrientationIsOppositeOfCadOrientation(gf);
3198 improveQuadMeshTopologyWithCavityRemeshing(gf, singularities,
3201 if(PARANO_VALIDITY) {
3202 errorAndAbortIfInvalidSurfaceMesh(gf,
"after cavity remeshing");
3206 std::unordered_map<std::string, double> stats;
3207 appendQuadMeshStatistics(gm, stats,
"Mesh_");
3208 printStatistics(stats,
"Quad mesh after cavity remeshing:");
3210 writeStatistics(stats,
"quadqs_statistics.json");
3212 if(PARANO_VALIDITY) {
3213 errorAndAbortIfInvalidVertexInModel(gm,
3214 "global check after cavity remeshing");
3219 if(DBG_EXPORT) { gm->
writeMSH(
"qqs_cavrmsh.msh", 4.1); }
3226 Msg::Info(
"Optimize topology of quad mesh boundaries with extrusion and "
3229 std::vector<GFace *>
faces = model_faces(gm);
3231 int nthreads = getNumThreads();
3232 #pragma omp parallel for schedule(dynamic) num_threads(nthreads)
3233 for(
size_t f = 0;
f <
faces.size(); ++
f) {
3242 optimizeFaceQuadMeshBoundaries(gf,
true);
3252 bool deleteGModelMeshAfter,
3253 bool overwriteField,
int N)
3255 Msg::Error(
"Module QUADMESHINGTOOLS required for function "
3256 "BuildBackgroundMeshAndGuidingField");
3261 Msg::Error(
"Module QUADMESHINGTOOLS required for function "
3262 "backgroundMeshAndGuidingFieldExists");
3267 Msg::Error(
"Module QUADMESHINGTOOLS required for function "
3268 "optimizeTopologyWithCavityRemeshing");
3273 Msg::Error(
"Module QUADMESHINGTOOLS required for function "
3274 "optimizeTopologyWithCavityRemeshing");
3278 double minimumQualityRequired)
3280 Msg::Error(
"Module QUADMESHINGTOOLS required for function "
3281 "quadMeshingOfSimpleFacesWithPatterns");
3286 Msg::Error(
"Module QUADMESHINGTOOLS required for function "
3287 "RefineMeshWithBackgroundMeshProjection");
3292 Msg::Error(
"Module QUADMESHINGTOOLS required for function "
3293 "replaceBadQuadDominantMeshes");
3298 Msg::Error(
"Module QUADMESHINGTOOLS required for function "
3299 "optimizeQuadMeshBoundaries");
3310 std::unordered_map<MVertex *, MVertex *> old2new;
3315 auto it = old2new.find(ov);
3316 if(it != old2new.end())
continue;
3329 for(
size_t i = 0; i < ge->
lines.size(); ++i) {
delete ge->
lines[i]; }
3335 std::vector<GEdge *> otherCurves;
3338 if(ge2->vertices().front() == ge2->vertices().back() &&
3343 otherCurves.push_back(ge2);
3345 std::sort(otherCurves.begin(), otherCurves.end());
3347 std::unique(otherCurves.begin(), otherCurves.end()),
3350 if(otherCurves.size() > 0)
continue;
3352 auto it = old2new.find(ov);
3353 if(it != old2new.end())
continue;
3355 "transfer: mesh vertex at CAD corner %i moved to face %i",
3374 if(old2new.size() > 0) {
3376 for(
size_t lv = 0; lv < 3; ++lv) {
3378 auto it = old2new.find(v);
3379 if(it != old2new.end()) {
f->setVertex(lv, it->second); }
3382 for(
size_t lv = 0; lv < 4; ++lv) {
3384 auto it = old2new.find(v);
3385 if(it != old2new.end()) {
f->setVertex(lv, it->second); }
3394 #if defined(HAVE_QUADMESHINGTOOLS)
3395 backups_int.push_back(
3397 backups_int.push_back(
3399 backups_int.push_back(
3403 backups_double.push_back(
3405 backups_double.push_back(
3407 backups_double.push_back(
3409 backups_int.push_back(
3411 backups_int.push_back(
3413 backups_int.push_back(
3417 backups_int.push_back(
3422 backups_char.push_back(
3426 backups_double.push_back(
3428 backups_double.push_back(
3432 backups_int.push_back(
3436 backups_bool.push_back(
3442 backups_int.push_back(
3444 backups_double.push_back(
3446 backups_char.push_back(
3452 backups_bool.push_back(
3454 backups_double.push_back(
3456 backups_double.push_back(
3458 backups_int.push_back(
3464 Msg::Error(
"Module QUADMESHINGTOOLS required to use QuadqsContextUpdater");
3474 Msg::Debug(
"set special quadqs options in the global context");
3494 #if defined(HAVE_QUADMESHINGTOOLS)
3495 Msg::Debug(
"restore options in the global context");
3496 for(
size_t i = 0; i < backups_char.size(); ++i) {
delete backups_char[i]; }
3497 for(
size_t i = 0; i < backups_bool.size(); ++i) {
delete backups_bool[i]; }
3498 for(
size_t i = 0; i < backups_int.size(); ++i) {
delete backups_int[i]; }
3499 for(
size_t i = 0; i < backups_double.size(); ++i) {
3500 delete backups_double[i];
3503 Msg::Error(
"Module QUADMESHINGTOOLS required to use QuadqsContextUpdater");
3509 Msg::Info(
"Cleaning quadqs background mesh and field");
3520 #if defined(HAVE_POST)
3523 #if defined(HAVE_FLTK)
3524 if(FlGui::available()) FlGui::instance()->updateViews(
true,
true);
3533 double angle_threshold_rad)
3535 std::unordered_map<MVertex *, std::vector<MVertex *> > corner2vertices;
3541 corner2vertices[v1].push_back(v2);
3544 corner2vertices[v2].push_back(v1);
3548 for(
auto &kv : corner2vertices) {
3549 if(kv.second.size() == 2) {
3554 if(agl < angle_threshold_rad) {
3555 acuteCorners[v].push_back(v2);
3556 acuteCorners[v].push_back(v3);
3565 std::unordered_map<MVertex *, std::vector<MVertex *> > acuteCorners;
3566 double threshold = 30. * M_PI / 180.;
3569 if(ge->
lines.size() == 0) {
3570 Msg::Warning(
"Optimize 1D mesh at acute corners: no lines in curve %i",
3574 Msg::Warning(
"Optimize 1D mesh at acute corners: elements in face %i",
3582 for(
auto &kv : acuteCorners) {
3587 double forcedLen = 0.;
3588 double forcedLenN = 0.;
3589 for(
MVertex *v2 : kv.second) {
3598 if(avgN > 0.) avgLen /= avgN;
3599 if(forcedLenN > 0.) forcedLen /= forcedLenN;
3606 double len = (forcedLen > 0.) ? forcedLen : avgLen;
3613 v2->
setXYZ(proj.
x(), proj.
y(), proj.
z());
3620 Msg::Debug(
"optimize mesh 1D at acute corners: moved %li curve vertices",