gmsh-TingyuanDoc  0.1
An Open-Source Timing-driven Analytical Mixed-size FPGA Placer
meshQuadQuasiStructured.cpp
Go to the documentation of this file.
1 // Gmsh - Copyright (C) 1997-2022 C. Geuzaine, J.-F. Remacle
2 //
3 // See the LICENSE.txt file in the Gmsh root directory for license information.
4 // Please report all issues on https://gitlab.onelab.info/gmsh/gmsh/issues.
5 //
6 // Author: Maxence Reberol
7 
9 
10 #include <array>
11 #include <queue>
12 
13 #include "GmshConfig.h"
14 #include "GmshMessage.h"
15 #include "Numeric.h"
16 #include "Context.h"
17 #include "OS.h"
18 #include "GModel.h"
19 #include "meshGEdge.h"
20 #include "meshGFace.h"
21 #include "meshGFaceOptimize.h"
22 #include "meshGRegion.h"
23 #include "BackgroundMesh.h"
24 #include "Generator.h"
25 #include "Field.h"
26 #include "MElement.h"
27 #include "MTriangle.h"
28 #include "MQuadrangle.h"
29 #include "MLine.h"
30 #include "gmsh.h"
31 #include "meshRefine.h"
32 #include "meshOctreeLibOL.h"
33 #include "robin_hood.h"
34 
35 #if defined(HAVE_POST)
36 #include "PView.h"
37 #include "PViewData.h"
38 #include "PViewDataList.h"
39 #include "PViewDataGModel.h"
40 #include "PViewOptions.h"
41 #endif
42 #if defined(HAVE_FLTK)
43 #include "FlGui.h"
44 #endif
45 
46 #if defined(HAVE_QUADMESHINGTOOLS)
47 #include "cppUtils.h"
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"
56 #include "geolog.h"
57 
58 using namespace CppUtils;
59 using namespace ArrayGeometry;
60 
61 template <typename Key, typename T, typename Hash = robin_hood::hash<Key>,
62  typename KeyEqual = std::equal_to<Key>, size_t MaxLoadFactor100 = 80>
63 using unordered_map =
65 
66 template <typename Key, typename Hash = robin_hood::hash<Key>,
67  typename KeyEqual = std::equal_to<Key>, size_t MaxLoadFactor100 = 80>
68 using unordered_set =
70 
71 constexpr int SizeMapDefault = 0;
72 constexpr int SizeMapCrossFieldAndSmallCad = 2;
73 constexpr int SizeMapBackgroundMesh = 3;
74 constexpr int SizeMapCrossFieldAndBMeshOnCurves = 4;
75 
76 const std::string BMESH_NAME = "bmesh_quadqs";
77 
78 constexpr bool PARANO_QUALITY = false;
79 constexpr bool PARANO_VALIDITY = false;
80 constexpr bool DBG_EXPORT = false;
81 constexpr bool SHOW_DQR = false;
82 
83 /* scaling applied on integer values stored in view (background field),
84  * so the visualization is not broken by the integers */
85 constexpr double VIEW_INT_SCALING = 1.e-8;
86 
87 static int getNumThreads()
88 {
89  int nthreads = CTX::instance()->numThreads;
90  if(CTX::instance()->mesh.maxNumThreads2D > 0)
91  nthreads = CTX::instance()->mesh.maxNumThreads2D;
92  if(!nthreads) nthreads = Msg::GetMaxThreads();
93  return nthreads;
94 }
95 
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")
102 {
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");
106  return -1;
107  }
108 
109  std::vector<double> datalist;
110  datalist.reserve(global_triangles.size() * 18);
111  for(size_t i = 0; i < global_triangles.size(); ++i) {
112  MTriangle *t = global_triangles[i];
113  /* Triangle coordinates */
114  for(size_t d = 0; d < 3; ++d) {
115  for(size_t lv = 0; lv < 3; ++lv) {
116  datalist.push_back(t->getVertex(lv)->point().data()[d]);
117  }
118  }
119  /* Vector field */
120  for(size_t lv = 0; lv < 3; ++lv) {
121  MVertex *v = t->getVertex(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 "
125  "global size map");
126  return -1;
127  }
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);
132  }
133  }
134  }
135 
136 #if defined(HAVE_POST)
137  PView *view = PView::getViewByName(viewName);
138  if(view == NULL) {
139  view = new PView();
140  view->getData()->setName(viewName);
141  }
142  PViewDataList *d = dynamic_cast<PViewDataList *>(view->getData());
143  if(!d) { // change the view type
144  std::string name = view->getData()->getName();
145  delete view->getData();
146  d = new PViewDataList();
147  d->setName(name);
148  d->setFileName(name + ".pos");
149  view->setData(d);
150  }
151 
152  size_t numElements = datalist.size() / (9 + 9);
153  int idxtypeVT = 7; /* Post type: VT */
154  d->importList(idxtypeVT, numElements, datalist, false);
155 
156 #if defined(HAVE_FLTK)
157  view->getOptions()->visible = 0;
158  if(FlGui::available()) FlGui::instance()->updateViews(true, true);
159 #endif
160 
161  /* singularities */
162  if(global_singularity_list.size() > 0) {
163  int idxtypeVP = 1; /* Post type: VP */
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]); /* x */
168  datalistSING.push_back(global_singularity_list[i][3]); /* y */
169  datalistSING.push_back(global_singularity_list[i][4]); /* z */
170  datalistSING.push_back(
171  VIEW_INT_SCALING *
172  double(global_singularity_list[i][0])); /* gf->tag() */
173  datalistSING.push_back(VIEW_INT_SCALING *
174  double(global_singularity_list[i][1])); /* index */
175  datalistSING.push_back(0.); /* empty */
176  }
177  d->importList(idxtypeVP, datalistSING.size() / 6, datalistSING, false);
178  }
179 
180  d->finalize();
181 
182  gm->getFields()->setBackgroundMesh(view->getIndex());
183 
184  const bool exportBGM = false;
185  if(exportBGM || Msg::GetVerbosity() >= 99) {
186  std::string name = gm->getName() + "_bgm.pos";
187  Msg::Warning("Exporting background field to '%s'", name.c_str());
188  view->write(name, 0);
189  }
190 
191  return 0;
192 #else
193  Msg::Error("Post-processing module required to create background field view");
194  return -1;
195 #endif
196 }
197 
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")
202 {
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);
211  }
212  }
213  GeoLog::flush();
214 }
215 
216 void showUVParametrization(GFace *gf, const std::vector<MElement *> &elts,
217  const std::string &viewName = "uv")
218 {
219  std::vector<std::vector<double> > values_u;
220  std::vector<std::vector<double> > values_v;
221  for(MElement *t : elts) {
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];
228  }
229  values_u.push_back(us);
230  values_v.push_back(vs);
231  }
232  GeoLog::add(elts, values_u, viewName + "_u");
233  GeoLog::add(elts, values_v, viewName + "_v");
234 }
235 
236 void showUVParametrization(GlobalBackgroundMesh &bgm,
237  const std::string &viewName = "uv")
238 {
239  for(auto &kv : bgm.faceBackgroundMeshes) {
240  GFace *gf = kv.first;
241  if(!gf->haveParametrization()) continue;
242  std::vector<MElement *> tris;
243  for(MTriangle &t : kv.second.triangles) { tris.push_back(&t); }
244  showUVParametrization(gf, tris, viewName);
245  }
246  GeoLog::flush();
247 }
248 
249 void printSizeMapStats(const std::vector<MTriangle *> &triangles,
250  std::unordered_map<MVertex *, double> &sizemap)
251 {
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);
257  }
258  double integral = 0.;
259  for(MTriangle *t : triangles) {
260  double values[3] = {0, 0, 0};
261  for(size_t lv = 0; lv < 3; ++lv) {
262  MVertex *v = t->getVertex(lv);
263  auto it = sizemap.find(v);
264  values[lv] = it->second;
265  ;
266  }
267  double a = std::abs(t->getVolume());
268  integral +=
269  a * 1. / std::pow(1. / 3. * (values[0] + values[1] + values[2]), 2);
270  }
271  Msg::Info("Size map statistics: min=%.3f, max=%.3f, target #elements: %.3f",
272  vmin, vmax, integral);
273 }
274 
275 int fillSizemapFromTriangleSizes(const std::vector<MTriangle *> &triangles,
276  std::unordered_map<MVertex *, double> &sizeMap)
277 {
278  const double tol = CTX::instance()->geom.tolerance;
279  std::unordered_map<MVertex *, std::vector<MVertex *> > v2v;
280  buildVertexToVertexMap(triangles, v2v);
281  for(auto &kv : v2v) {
282  MVertex *v = kv.first;
283  double sum = 0.;
284  double avg = 0.;
285  for(MVertex *v2 : kv.second) {
286  double dist = v->distance(v2);
287  if(dist > tol) {
288  avg += dist;
289  sum += 1.;
290  }
291  }
292  if(sum == 0.) continue;
293  sizeMap[v] = avg / sum;
294  }
295 
296  /* Laplacian smoothing of the size map */
297  size_t iterMax = 3;
298  for(size_t iter = 0; iter < iterMax; ++iter) {
299  for(auto &kv : v2v) {
300  MVertex *v = kv.first;
301  // if (v->onWhat()->dim() < 2) continue;
302  double sum = 0.;
303  double avg = 0.;
304  for(MVertex *v2 : kv.second) {
305  avg += sizeMap[v2];
306  sum += 1.;
307  }
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;
312  }
313  }
314 
315  return 0;
316 }
317 
318 int fillSizemapFromScalarBackgroundField(
319  GModel *gm, const std::vector<MTriangle *> &triangles,
320  std::unordered_map<MVertex *, double> &sizeMap)
321 {
322  Field *field = nullptr;
323  FieldManager *fields = gm->getFields();
324  if(fields->getBackgroundField() > 0) {
325  field = fields->get(fields->getBackgroundField());
326  if(field && field->numComponents() != 1) { field = nullptr; }
327  }
328  if(field == nullptr) {
329  Msg::Error("Scalar background field not found");
330  return -1;
331  }
332  for(MTriangle *t : triangles)
333  for(size_t lv = 0; lv < 3; ++lv) {
334  MVertex *v = t->getVertex(lv);
335  auto it = sizeMap.find(v);
336  if(it == sizeMap.end()) {
337  double value = (*field)(v->point().x(), v->point().y(), v->point().z());
338  if(std::isnan(value) || value == -DBL_MAX || value == DBL_MAX) continue;
339  sizeMap[v] = value;
340  }
341  }
342  return 0;
343 }
344 
345 std::string nameOfSizeMapMethod(int method)
346 {
347  if(method == 0) { return "default (" + nameOfSizeMapMethod(4) + ")"; }
348  else if(method == 1) {
349  return "cross-field conformal scaling";
350  }
351  else if(method == 2) {
352  return "cross-field conformal scaling and CAD adaptation";
353  }
354  else if(method == 3) {
355  return "background mesh sizes";
356  }
357  else if(method == 4) {
358  return "cross-field conformal scaling and CAD adaptation (clamped by "
359  "background mesh)";
360  }
361  return "unknown";
362 }
363 
364 bool generateMeshWithSpecialParameters(GModel *gm,
365  double scalingOnTriangulation)
366 {
367  Msg::Debug("build background triangulation ...");
368 
369  /* Unlock if called from GenerateMesh() */
370  bool shouldLock = false;
371  if(CTX::instance()->lock) {
372  CTX::instance()->lock = 0;
373  shouldLock = true;
374  }
375 
376  /* Change meshing parameters to build a good triangulation
377  * for cross field */
378  /* - the triangulation must be a bit more refined than the quad mesh */
379  int minCurveNodes = CTX::instance()->mesh.minCurveNodes;
380  int minCircleNodes = CTX::instance()->mesh.minCircleNodes;
381  double lcFactor = CTX::instance()->mesh.lcFactor;
382  int recombineAll = CTX::instance()->mesh.recombineAll;
383  int algoRecombine = CTX::instance()->mesh.algoRecombine;
384  int algo = CTX::instance()->mesh.algo2d;
385  CTX::instance()->mesh.minCurveNodes = std::min(minCurveNodes, 5);
386  CTX::instance()->mesh.minCircleNodes = std::min(minCircleNodes, 30);
387  CTX::instance()->mesh.lcFactor = lcFactor * scalingOnTriangulation;
391  // ALGO_2D_MESHADAPT; /* slow but frontal does not always work */
392 
393  /* Generate the triangulation with standard gmsh pipeline */
394  GenerateMesh(gm, 2);
395 
396  /* Restore parameters */
397  CTX::instance()->mesh.minCurveNodes = minCurveNodes;
398  CTX::instance()->mesh.minCircleNodes = minCircleNodes;
399  CTX::instance()->mesh.lcFactor = lcFactor;
400  CTX::instance()->mesh.recombineAll = recombineAll;
401  CTX::instance()->mesh.algoRecombine = algoRecombine;
402  CTX::instance()->mesh.algo2d = algo;
403 
404  /* Lock again before going back to GenerateMesh() */
405  if(shouldLock) CTX::instance()->lock = 1;
406 
407  return true;
408 }
409 
410 bool getGFaceBackgroundMeshLinesAndTriangles(
411  GlobalBackgroundMesh &bmesh, GFace *gf, std::vector<MLine *> &lines,
412  std::vector<MTriangle *> &triangles)
413 {
414  lines.clear();
415  triangles.clear();
416 
417  /* Collect pointers to background mesh elements */
418  std::vector<GEdge *> edges = face_edges(gf);
419  for(GEdge *ge : edges) {
420  auto it = bmesh.edgeBackgroundMeshes.find(ge);
421  if(it == bmesh.edgeBackgroundMeshes.end()) {
422  Msg::Warning(
423  "getGFaceBackgroundMeshLinesAndTriangles: GEdge %i not found "
424  "in background mesh",
425  ge->tag());
426  continue;
427  }
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]));
431  }
432  }
433  auto it = bmesh.faceBackgroundMeshes.find(gf);
434  if(it == bmesh.faceBackgroundMeshes.end()) {
435  Msg::Warning("getGFaceBackgroundMeshLinesAndTriangles: GFace %i not found "
436  "in background mesh",
437  gf->tag());
438  return false;
439  }
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]));
443  }
444 
445  return true;
446 }
447 
448 int BuildBackgroundMeshAndGuidingField(GModel *gm, bool overwriteGModelMesh,
449  bool deleteGModelMeshAfter,
450  bool overwriteField, int N)
451 {
452  if(CTX::instance()->abortOnError && Msg::GetErrorCount()) return 0;
453  if(CTX::instance()->mesh.algo2d != ALGO_2D_PACK_PRLGRMS &&
455  return 0;
456  if(N != 4 && N != 6) {
457  Msg::Error("guiding field: %i-symmetry field not supported", N);
458  return -1;
459  }
460 
461  const int qqsSizemapMethod = CTX::instance()->mesh.quadqsSizemapMethod;
462  if(qqsSizemapMethod == 5) {
463  Msg::Warning("Quadqs method: no background mesh");
464  return 0;
465  }
466 
467  bool midpointSubdivisionAfter = true;
468  if(!CTX::instance()->mesh.recombineAll ||
469  CTX::instance()->mesh.algoRecombine == 4) {
470  midpointSubdivisionAfter = false;
471  }
472 
473  const bool SHOW_INTERMEDIATE_VIEWS = (Msg::GetVerbosity() >= 99);
474 
475  Msg::Info("Build background mesh and guiding field ...");
476  bool externalSizemap = false;
477  {
478  FieldManager *fields = gm->getFields();
479  if(fields->getBackgroundField() > 0) {
480  Field *field = fields->get(fields->getBackgroundField());
481  if(field && field->numComponents() == 3) {
482  if(!overwriteField) {
483  Msg::Info(
484  "vector background field exists, using it as a guiding field");
485  return 0;
486  }
487  else {
488  Msg::Info(
489  "disabled current vector background field, building a new one");
490  fields->setBackgroundFieldId(0);
491  }
492  }
493  else if(field && field->numComponents() == 1) {
494  if(qqsSizemapMethod == SizeMapDefault) {
495  Msg::Info("scalar background field exists, using it as size map");
496  externalSizemap = true;
497  }
498  else {
499  Msg::Warning("scalar background field exists, but ignored because "
500  "QuadqsSizemapMethod is %i",
501  CTX::instance()->mesh.quadqsSizemapMethod);
502  }
503  }
504  }
505  }
506 
507  if(overwriteGModelMesh) {
508  Msg::Debug("delete current GModel mesh");
509  gm->deleteMesh();
510  }
511 
512  /* Check if triangulation available */
513  bool surfaceMeshed = true;
514  {
515  bool onlyVisible = CTX::instance()->mesh.meshOnlyVisible;
516  for(GFace *gf : gm->getFaces())
517  if(gf->getNumMeshElements() == 0) {
518  if(onlyVisible && !gf->getVisibility()) continue;
519  surfaceMeshed = false;
520  }
521  }
522 
523  /* Generate triangulation */
524  /* - scalingOnTriangulation: this factor is used to get a triangulation a bit
525  * more finer than the target quadrangulation, to get a more accurate cross
526  * field */
527  double edgeScaling = CTX::instance()->mesh.quadqsScalingOnTriangulation;
528  if(!surfaceMeshed) { generateMeshWithSpecialParameters(gm, edgeScaling); }
529 
530  GlobalBackgroundMesh &bmesh = getBackgroundMesh(BMESH_NAME);
531  bool overwrite = true;
532  int status = bmesh.importGModelMeshes(gm, overwrite);
533  if(status != 0) {
534  Msg::Error("failed to import model mesh in background mesh");
535  return -1;
536  }
537 
538  if(deleteGModelMeshAfter) {
539  Msg::Debug("delete GModel mesh");
540  gm->deleteMesh();
541  }
542 
543  /* Build guiding field on background mesh:
544  * - per GFace:
545  * - cross field (unit vectors, one value per edge)
546  * - conformal scaling (scalar, one value per vertex)
547  * - offset conformal scaling by target number of quads
548  * -> one vector (scaled direction) per triangle corner
549  * - global:
550  * - compute/extract size map
551  * - apply size map to triangle vectors (take minimum)
552  * - one-way smoothing with Dijkstra + gradient max
553  */
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; /* format: gf->tag(), index, x, y, z */
559  /* Per GFace computations, in parallel */
560  {
561  Msg::Info(
562  "- quadqs sizemap method: %s (%i), expect midpoint subdivision: %i, "
563  "scaling on edge length: %f",
564  nameOfSizeMapMethod(CTX::instance()->mesh.quadqsSizemapMethod).c_str(),
565  CTX::instance()->mesh.quadqsSizemapMethod, midpointSubdivisionAfter,
566  edgeScaling);
567 
568  std::vector<GFace *> faces = model_faces(gm);
569  size_t ntris = 0;
570  for(size_t f = 0; f < faces.size(); ++f) {
571  GFace *gf = faces[f];
572  auto it = bmesh.faceBackgroundMeshes.find(gf);
573  if(it != bmesh.faceBackgroundMeshes.end()) {
574  ntris += it->second.triangles.size();
575  }
576  }
577  global_triangles.reserve(ntris);
578  global_size_map.reserve(3 * ntris);
579 
580  int nthreads = getNumThreads();
581 #pragma omp parallel for schedule(dynamic) num_threads(nthreads)
582  for(size_t f = 0; f < faces.size(); ++f) {
583  GFace *gf = faces[f];
584 
585  if(CTX::instance()->mesh.meshOnlyVisible && !gf->getVisibility())
586  continue;
587  if(CTX::instance()->debugSurface > 0 &&
588  gf->tag() != CTX::instance()->debugSurface)
589  continue;
590 
591  /* Compute a cross field on each face */
592 
593  /* Get mesh elements for solver */
594  std::vector<MLine *> lines;
595  std::vector<MTriangle *> triangles;
596  bool oklt =
597  getGFaceBackgroundMeshLinesAndTriangles(bmesh, gf, lines, triangles);
598  if(!oklt && triangles.size() == 0) {
599  Msg::Error("- Face %i: failed to get triangles from background mesh",
600  gf->tag());
601  continue;
602  }
603 
604  /* Cross field */
605  std::vector<std::array<double, 3> > triEdgeTheta;
606  int nbDiffusionLevels = 4;
607  double thresholdNormConvergence = 1.e-2;
608  int nbBoundaryExtensionLayer = 1;
609  int verbosity = 0;
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(),
613  nbDiffusionLevels);
614  int scf = computeCrossFieldWithHeatEquation(
615  N, triangles, lines, triEdgeTheta, nbDiffusionLevels,
616  thresholdNormConvergence, nbBoundaryExtensionLayer, verbosity);
617  if(scf != 0) {
618  Msg::Warning("- Face %i: failed to compute cross field", gf->tag());
619  }
620 
621  /* Cross field singularities */
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);
628  if(scsi != 0) {
629  Msg::Warning("- Face %i: failed to compute cross field singularities",
630  gf->tag());
631  }
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()};
639  }
640 
641  if(SHOW_INTERMEDIATE_VIEWS) {
642  for(auto &kv : singularities) {
643  GeoLog::add(kv.first, double(kv.second), "singularities");
644  }
645  }
646 
647  std::vector<std::array<double, 9> > triangleDirections;
648  int sc = convertToPerTriangleCrossFieldDirections(
649  N, triangles, triEdgeTheta, triangleDirections);
650  if(sc != 0) {
651  Msg::Warning(
652  "- Face %i: failed to resample cross field at triangle corners",
653  gf->tag());
654  }
655 
656  /* Build the size map of the guiding field */
657  std::unordered_map<MVertex *, double> localSizemap;
658  if(externalSizemap) { /* Size map from background field */
659  int sts =
660  fillSizemapFromScalarBackgroundField(gm, triangles, localSizemap);
661  if(sts != 0) {
662  Msg::Warning(
663  "- Face %i: failed to fill size map from scalar background field",
664  gf->tag());
665  }
666  }
667  else if(qqsSizemapMethod ==
668  SizeMapBackgroundMesh) { /* Size map from background triangulation
669  */
670  int sts = fillSizemapFromTriangleSizes(triangles, localSizemap);
671  if(sts != 0) {
672  Msg::Warning("- Face %i: failed to fill size map from triangle sizes",
673  gf->tag());
674  }
675  else if(sts == 0 && edgeScaling > 0.) {
676  /* re-adjust the target edge size as if the triangulation was
677  * not finer for more accurate cross field */
678  for(auto &kv : localSizemap) {
679  kv.second /= edgeScaling;
680  if(midpointSubdivisionAfter) { kv.second *= 2; }
681  }
682  }
683  }
684  else {
685  /* Conformal scaling associated to cross field */
686  Msg::Info("- Face %i/%li: compute cross field conformal scaling ...",
687  gf->tag(), faces.size());
688  int scs = computeCrossFieldConformalScaling(N, triangles, triEdgeTheta,
689  localSizemap);
690  if(scs != 0) {
691  Msg::Warning(
692  "- Face %i: failed to compute conformal scaling, use uniform size",
693  gf->tag());
694  for(MTriangle *t : triangles)
695  for(size_t lv = 0; lv < 3; ++lv) {
696  localSizemap[t->getVertex(lv)] = 1.;
697  }
698  }
699 
700  /* Quantile filtering on the conformal scaling histogram */
701  Msg::Debug("- Face %i/%li: conformal scaling quantile filtering ...",
702  gf->tag(), faces.size());
703  double filteringRange = 0.05;
704  quantileFiltering(localSizemap, filteringRange);
705 
706  size_t targetNumberOfQuads = gf->triangles.size() / 2.;
707  if(targetNumberOfQuads == 0) { /* Check in background mesh */
708  auto it = bmesh.faceBackgroundMeshes.find(gf);
709  if(it == bmesh.faceBackgroundMeshes.end()) {
710  Msg::Error("- Face %i: background mesh not found", gf->tag());
711  targetNumberOfQuads = 1000;
712  }
713  else {
714  targetNumberOfQuads = 0.5 * it->second.triangles.size();
715  targetNumberOfQuads *= std::pow(edgeScaling, 2);
716  }
717  }
718 
719  /* Midpoint subdivision => 4 times more quads */
720  if(midpointSubdivisionAfter) { targetNumberOfQuads /= 4; }
721 
722  if(targetNumberOfQuads == 0) targetNumberOfQuads = 1;
723 
724  int scso = computeQuadSizeMapFromCrossFieldConformalFactor(
725  triangles, targetNumberOfQuads, localSizemap);
726  if(scso != 0) {
727  Msg::Warning(
728  "- Face %i: failed to compute size map from conformal scaling",
729  gf->tag());
730  }
731  }
732 
733 #pragma omp critical
734  {
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});
740  }
741  // GeoLog::add(dynamic_cast_vector<MTriangle*,MElement*>(triangles),
742  // localSizemap, "sizemap_f"+std::to_string(gf->tag()));
743  }
744  }
745  }
746 
747  sort_unique(global_size_map);
748 
749  /* Warning: from now on, code is not optimized in terms of data structures
750  * (slow unordered_map instead of contiguous vectors, etc)
751  * should be improved if this step is time-consuming in the
752  * global pipeline. */
753 
754  if(SHOW_INTERMEDIATE_VIEWS) {
755  showCrossFieldInView(global_triangles, global_triangle_directions,
756  "cross_field");
757  }
758 
759  /* Global operations */
760 
761  /* Compute minimum / maximum of "natural" size map.
762  * Use this to avoid cases when minimal size on curves
763  * tends to 0 */
764  double smMin = DBL_MAX;
765  for(auto &kv : global_size_map) { smMin = std::min(smMin, kv.second); }
766  double factor = 0.1; /* size map can be reduced up to factor */
767  double minSizeClampMin = factor * smMin;
768 
769  /* Minimal size on curves */
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;
779  }
780 
781  int scad = computeMinimalSizeOnCurves(bmesh, clampMinWithTriEdges,
782  cadMinimalSizeOnCurves);
783  if(scad != 0) {
784  Msg::Warning("failed to compute minimal size on CAD curves");
785  }
786  }
787  }
788 
789  /* Initialize global size map defined on the background mesh */
790  std::unordered_map<MVertex *, double> sizeMap = cadMinimalSizeOnCurves;
791  for(auto &kv : global_size_map) {
792  MVertex *v = kv.first;
793  auto it = sizeMap.find(v);
794  if(it == sizeMap.end()) {
795  sizeMap[kv.first] = kv.second; /* "natural" size */
796  }
797  else {
798  double sizeFromAdaptation = std::max(it->second, minSizeClampMin);
799  it->second = std::min(sizeFromAdaptation, kv.second);
800  }
801  }
802 
803  /* One-way propagation of values */
804  if(qqsSizemapMethod != SizeMapBackgroundMesh) {
805  const double gradientMax =
806  1.2; /* this param should be a global gmsh option */
807  Msg::Info("Sizemap smoothing (progression ratio: %.2f)", gradientMax);
808  int sop = sizeMapOneWaySmoothing(global_triangles, sizeMap, gradientMax);
809  if(sop != 0) {
810  Msg::Warning("failed to compute one-way size map smoothing");
811  }
812  }
813 
814  /* Clamp with global minimum/maximum mesh size */
815  {
816  // TODO: should be multiplied by lcFactor or not ?
817  double fs = midpointSubdivisionAfter ? 2. : 1.;
818  double sizeMin =
820  double sizeMax =
822  for(auto &kv : sizeMap) {
823  if(kv.second < sizeMin) { kv.second = sizeMin; }
824  else if(kv.second > sizeMax) {
825  kv.second = sizeMax;
826  }
827  }
828  }
829 
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");
834  GeoLog::flush();
835  // showUVParametrization(bmesh);
836  std::unordered_map<MVertex *, double> sizemap_init;
837  for(auto &kv : global_size_map) {
838  MVertex *v = kv.first;
839  auto it = sizemap_init.find(v);
840  if(it == sizemap_init.end()) {
841  sizemap_init[kv.first] = kv.second; /* "natural" size */
842  }
843  else {
844  it->second = std::min(it->second, kv.second);
845  }
846  }
847  GeoLog::add(elements, sizemap_init, "size_map_init");
848  GeoLog::flush();
849  }
850 
851  printSizeMapStats(global_triangles, sizeMap);
852 
853  /* Build the background field */
854  int sbf =
855  buildBackgroundField(gm, global_triangles, global_triangle_directions,
856  sizeMap, global_singularity_list, "guiding_field");
857  if(sbf != 0) {
858  Msg::Warning("failed to build background guiding field");
859  return -1;
860  }
861 
862  return 0;
863 }
864 
866 {
867  bool bgmOk = backgroudMeshExists(BMESH_NAME);
868  bool bfOk = false;
869  FieldManager *fields = gm->getFields();
870  if(fields->getBackgroundField() > 0) {
871  Field *guiding_field = fields->get(fields->getBackgroundField());
872  if(guiding_field && guiding_field->numComponents() == 3) { bfOk = true; }
873  }
874  return bgmOk && bfOk;
875 }
876 
877 bool getSingularitiesFromBackgroundField(
878  GFace *gf, std::vector<std::pair<SPoint3, int> > &singularities)
879 {
880  singularities.clear();
881 
882  Field *field = nullptr;
883  FieldManager *fields = gf->model()->getFields();
884  if(fields->getBackgroundField() > 0) {
885  Field *guiding_field = fields->get(fields->getBackgroundField());
886  if(guiding_field && guiding_field->numComponents() == 3) {
887  field = guiding_field;
888  }
889  }
890  if(field == nullptr) {
891  Msg::Debug("get singularities: face %i, failed to get background field",
892  gf->tag());
893  return false;
894  }
895 
896  int viewIndex = int(field->options["IView"]->numericalValue());
897  PView *view = nullptr;
898  if(viewIndex >= 0 && viewIndex < (int)PView::list.size()) {
899  view = PView::list[viewIndex];
900  }
901  else {
902  Msg::Error("failed to get view for index = %i", viewIndex);
903  return false;
904  }
905  PViewDataList *d = dynamic_cast<PViewDataList *>(view->getData());
906  if(d == nullptr) {
907  Msg::Error("view type is wrong");
908  return false;
909  }
910 
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));
920  }
921 
922  return true;
923 }
924 
925 bool getSingularitiesFromNewCrossFieldComputation(
926  GlobalBackgroundMesh &bmesh, GFace *gf,
927  std::vector<std::pair<SPoint3, int> > &singularities)
928 {
929  const int N = 4;
930 
931  std::vector<MLine *> lines;
932  std::vector<MTriangle *> triangles;
933  bool oklt =
934  getGFaceBackgroundMeshLinesAndTriangles(bmesh, gf, lines, triangles);
935  if(!oklt && triangles.size() == 0) {
936  Msg::Error("- Face %i: failed to get triangles from background mesh",
937  gf->tag());
938  return false;
939  }
940 
941  /* Cross field */
942  std::vector<std::array<double, 3> > triEdgeTheta;
943  int nbDiffusionLevels = 4;
944  double thresholdNormConvergence = 1.e-2;
945  int nbBoundaryExtensionLayer = 1;
946  int verbosity = 0;
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);
953  if(scf != 0) {
954  Msg::Warning("- Face %i: failed to compute cross field", gf->tag());
955  }
956 
957  /* Cross field singularities */
958  bool addSingularitiesAtAcuteCorners = true;
959  double thresholdInDeg = 30.;
960  int scsi = detectCrossFieldSingularities(N, triangles, triEdgeTheta,
961  addSingularitiesAtAcuteCorners,
962  thresholdInDeg, singularities);
963  if(scsi != 0) {
964  Msg::Warning("- Face %i: failed to compute cross field singularities",
965  gf->tag());
966  }
967 
968  return true;
969 }
970 
971 void computeBdrVertexIdealValence(const std::vector<MQuadrangle *> &quads,
972  unordered_map<MVertex *, double> &qValIdeal)
973 {
974  qValIdeal.clear();
975  for(MQuadrangle *f : quads) {
976  for(size_t lv = 0; lv < 4; ++lv) {
977  MVertex *v = f->getVertex(lv);
978  GFace *gf = dynamic_cast<GFace *>(v->onWhat());
979  if(gf == nullptr) { /* On boundary */
980  MVertex *vPrev = f->getVertex((4 + lv - 1) % 4);
981  MVertex *vNext = f->getVertex((lv + 1) % 4);
982  SVector3 pNext = vNext->point();
983  SVector3 pPrev = vPrev->point();
984  SVector3 pCurr = v->point();
985  double agl = angleVectors(pNext - pCurr, pPrev - pCurr);
986  qValIdeal[v] += agl * 2. / M_PI;
987  }
988  }
989  }
990 }
991 
992 inline int clamp(int val, int lower, int upper)
993 {
994  return std::min(upper, std::max(val, lower));
995 }
996 
997 size_t idealBoundaryValence(const MVertex *v, double ideal)
998 {
999  if(v->onWhat() && v->onWhat()->dim() == 1) {
1000  /* Regular on curves */
1001  return 2;
1002  }
1003  if(ideal <= 1.25) { return 1; }
1004  else if(ideal <= 2.75) {
1005  return 2;
1006  }
1007  else if(ideal <= 3.5) {
1008  return 3;
1009  }
1010  return 4;
1011 }
1012 
1013 bool getBoundaryIdealAndAllowedValences(
1014  int fixingDim, /* 0 when fixing corners, 1 when fixing curves, 2 when fixing
1015  surfaces */
1016  GFaceMeshPatch &patch,
1017  const unordered_map<MVertex *, std::vector<MElement *> > &adj,
1018  const unordered_map<MVertex *, double> &qValIdeal,
1019  std::vector<int> &bndIdealValence,
1020  std::vector<std::pair<int, int> > &bndAllowedValenceRange)
1021 {
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];
1028  GVertex *gv = dynamic_cast<GVertex *>(bv->onWhat());
1029  GEdge *ge = dynamic_cast<GEdge *>(bv->onWhat());
1030  GFace *gf = dynamic_cast<GFace *>(bv->onWhat());
1031  int idealTot = 4;
1032  if(gf == nullptr) {
1033  auto it = qValIdeal.find(bv);
1034  if(it == qValIdeal.end()) {
1035  Msg::Error("getBoundaryIdealAndAllowedValences: bdr vertex %i not "
1036  "found in qValIdeal",
1037  bv->getNum());
1038  return false;
1039  }
1040  idealTot = idealBoundaryValence(it->first, it->second);
1041  }
1042  auto it = adj.find(bv);
1043  if(it == adj.end()) {
1044  Msg::Error(
1045  "getBoundaryIdealAndAllowedValences: bdr vertex not found in adj");
1046  return false;
1047  }
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;
1052  if(idealIn <= 0) {
1053  idealIn = 1;
1054  // DBG(bv->getNum(), idealIn, idealTot, valExterior);
1055  // Msg::Error("getBoundaryIdealAndAllowedValences: ideal valence inside is
1056  // <= 0, weird"); return false;
1057  }
1058  bndIdealValence[i] = idealIn;
1059  if(exterior.size() == 0) { /* boundary vertex "inside" the cavity, probably
1060  the one to remesh */
1061  bndAllowedValenceRange[i] = {idealIn, idealIn};
1062  }
1063  else if(gv != nullptr) { /* Current bdr vertex is corner */
1064  bndAllowedValenceRange[i] = {idealIn, idealIn};
1065  }
1066  else if(ge != nullptr) { /* Current bdr vertex is on curve */
1067  if(fixingDim == 0) {
1068  /* When fixing corners, can degrade curves */
1069  // bndAllowedValenceRange[i] = {1, 2};
1070 
1071  // No: do not degrade curves
1072  bndAllowedValenceRange[i] = {idealIn, idealIn};
1073  }
1074  else {
1075  bndAllowedValenceRange[i] = {idealIn, idealIn};
1076  }
1077  }
1078  else if(gf != nullptr) {
1079  if(valExterior <= 0) {
1080  Msg::Error("getBoundaryIdealAndAllowedValences: surface bdr vertex but "
1081  "exterior valence is: %i",
1082  valExterior);
1083  return false;
1084  }
1085  if(fixingDim == 0 && idealTot >= 3 && it->second.size() == 4) {
1086  /* When fixing concave corner, do not degrade close regular interior
1087  * vertices */
1088  bndAllowedValenceRange[i] = {valInterior, valInterior};
1089  }
1090  else if(fixingDim == 0 ||
1091  fixingDim ==
1092  1) { /* When fixing corner/curve, can degrade surface */
1093  /* Allowed total range: [3,6], minimize if valExterior higher to this */
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};
1099  }
1100  else {
1101  /* Allowed total range: [3,5], minimize if valExterior higher to this */
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};
1107  }
1108  }
1109  else {
1110  Msg::Error("getBoundaryIdealAndAllowedValences: vertex on no CAD entity, "
1111  "should not happen");
1112  return false;
1113  }
1114 
1115  // if (fixingDim == 2) {
1116  // std::string name = "[" +
1117  // std::to_string(bndAllowedValenceRange[i].first)
1118  // + "," + std::to_string(int(bndAllowedValenceRange[i].second)) + "]";
1119  // GeoLog::add({bv->point()},double(bndIdealValence[i]), name);
1120  // }
1121  }
1122 
1123  return true;
1124 }
1125 
1126 bool meshOrientationIsOppositeOfCadOrientation(GFace *gf)
1127 {
1128  size_t nOk = 0;
1129  size_t nInv = 0;
1130  for(std::size_t iEl = 0; iEl < gf->getNumMeshElements(); iEl++) {
1131  MElement *e = gf->getMeshElement(iEl);
1132  SVector3 ne = e->getFace(0).normal();
1133  MVertex *v = e->getVertex(0);
1134  SPoint2 param;
1135  if(v->onWhat() == gf && v->getParameter(0, param[0]) &&
1136  v->getParameter(1, param[1])) {
1137  SVector3 nf = gf->normal(param);
1138  if(dot(nf, ne) < 0.) { nInv += 1; }
1139  else {
1140  nOk += 1;
1141  }
1142  }
1143  }
1144  return (nInv > nOk);
1145 }
1146 
1147 void updateAdjacencies(const GFaceMeshPatch &before,
1148  const GFaceMeshPatch &after,
1149  unordered_map<MVertex *, std::vector<MElement *> > &adj)
1150 {
1151  /* Remove old vertices from adjacency keys */
1152  for(MVertex *v : before.intVertices) {
1153  auto it = adj.find(v);
1154  if(it != adj.end()) { adj.erase(it); }
1155  }
1156  /* Remove old elements from adjacency values */
1157  for(auto &loop : before.bdrVertices) {
1158  for(MVertex *bv : loop) {
1159  auto it = adj.find(bv);
1160  if(it != adj.end()) {
1161  it->second = difference(it->second, before.elements);
1162  }
1163  }
1164  }
1165  /* Add new keys and values */
1166  for(MElement *e : after.elements) {
1167  for(size_t lv = 0; lv < e->getNumVertices(); ++lv) {
1168  MVertex *v = e->getVertex(lv);
1169  adj[v].push_back(e);
1170  }
1171  }
1172 }
1173 
1174 std::vector<MElement *>
1175 getNeighbors(const std::vector<MElement *> &elements,
1176  const unordered_map<MVertex *, std::vector<MElement *> > &adj)
1177 {
1178  std::vector<MElement *> neighbors;
1179  for(MElement *e : elements) {
1180  for(size_t lv = 0; lv < e->getNumVertices(); ++lv) {
1181  MVertex *v = e->getVertex(lv);
1182  auto it = adj.find(v);
1183  if(it != adj.end()) {
1184  for(MElement *e2 : it->second) { neighbors.push_back(e2); }
1185  }
1186  }
1187  }
1188  neighbors = difference(neighbors, elements);
1189  return neighbors;
1190 }
1191 
1192 bool smoothThePool(
1193  GFace *gf, const std::vector<MVertex *> &smoothingPool,
1194  const unordered_map<MVertex *, std::vector<MElement *> > &adj,
1195  bool invertNormalsForQuality, SurfaceProjector *sp = nullptr)
1196 {
1197  /* Get all quads used for smoothing */
1198  std::vector<MElement *> elements;
1199  {
1200  std::vector<MVertex *> unq = smoothingPool;
1201  sort_unique(unq);
1202  elements.reserve(unq.size());
1203  for(MVertex *v : unq) {
1204  auto it = adj.find(v);
1205  if(it != adj.end()) {
1206  auto it2 =
1207  std::find(gf->mesh_vertices.begin(), gf->mesh_vertices.end(), v);
1208  if(it2 != gf->mesh_vertices.end()) { append(elements, it->second); }
1209  }
1210  }
1211  }
1212  sort_unique(elements);
1213 
1214  // ensures all the elements are in the GFace. Should not be different,
1215  // but there are some bugs in the smoothing pool
1216  std::vector<MElement *> faceElements =
1217  dynamic_cast_vector<MQuadrangle *, MElement *>(gf->quadrangles);
1218  elements = intersection(elements, faceElements);
1219 
1220  /* Build the disconnected patches and smooth them independently */
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);
1229  if(!okp) continue;
1230 
1231  PatchGeometryBackup backup(patch);
1232 
1233  GeomOptimStats stats;
1234  double sicnBefore = 0.;
1235  if(haveNiceParametrization(gf)) {
1236  if(patch.bdrVertices.size() == 1) { /* disk topology */
1237  /* Global UV smoothing */
1238  patchOptimizeGeometryGlobal(patch, stats);
1239  sicnBefore = stats.sicnMinBefore;
1240 
1241  if(stats.sicnMinAfter < 0.5) {
1242  /* Kernel smoothing */
1243  GeomOptimOptions opt;
1244  opt.invertCADNormals = invertNormalsForQuality;
1245  patchOptimizeGeometryWithKernel(patch, opt, stats);
1246  }
1247  }
1248  else {
1249  double sicnAvg;
1250  computeSICN(patch.elements, sicnBefore, sicnAvg);
1251  if(sicnBefore < 0.5) {
1252  /* Kernel smoothing */
1253  GeomOptimOptions opt;
1254  opt.invertCADNormals = invertNormalsForQuality;
1255  patchOptimizeGeometryWithKernel(patch, opt, stats);
1256  }
1257  }
1258  }
1259  else {
1260  GeomOptimOptions opt;
1261  opt.sp = sp;
1262  opt.outerLoopIterMax = 20.;
1263  opt.timeMax = 10.;
1264  opt.withBackup = true;
1265  opt.invertCADNormals = invertNormalsForQuality;
1266  // opt.smartMinThreshold = 0.1;
1267  // opt.localLocking = true;
1268  opt.dxLocalMax = 1.e-4;
1269  patchOptimizeGeometryWithKernel(patch, opt, stats);
1270  }
1271 
1272  /* Check after smoothing */
1273  if(stats.sicnMinAfter < sicnBefore) {
1274  Msg::Debug(
1275  "quality (SICN) decreased (%.3f -> %.3f), restore previous geometry",
1276  sicnBefore, stats.sicnMinAfter);
1277  backup.restore();
1278  }
1279  }
1280 
1281  return true;
1282 }
1283 
1284 struct DQOptions {
1285  bool invertNormalsForQuality = false;
1286  SurfaceProjector *sp = nullptr;
1287 };
1288 
1289 struct DQStats {
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;
1296 };
1297 
1298 int improveCornerValences(
1299  GFace *gf,
1300  const unordered_map<MVertex *, double>
1301  &qValIdeal, /* valence on bdr vertices */
1302  unordered_map<MVertex *, std::vector<MElement *> > &adj, DQOptions &opt,
1303  DQStats &stats)
1304 {
1305  Msg::Debug("- Face %i: improve corner valences", gf->tag());
1306  std::vector<MVertex *>
1307  smoothingPool; /* for smoothing after topological changes */
1308  smoothingPool.reserve(gf->mesh_vertices.size());
1309 
1310  /* qValIdeal is unordered_map and its ordering is random, we replace
1311  * it with a deterministic ordering, containing only the corners */
1312  std::vector<std::pair<MVertex *, double> > cornerAndIdeal;
1313  for(auto &kv : qValIdeal) {
1314  GVertex *gv = dynamic_cast<GVertex *>(kv.first->onWhat());
1315  if(gv != nullptr) { cornerAndIdeal.push_back({kv.first, kv.second}); }
1316  }
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());
1323  });
1324 
1325  int SMALL_PATCH = 0; /* just the quads adjacent to corner */
1326  int LARGER_PATCH = 1; /* add the neighbors */
1327  int CORNER_PATCH =
1328  2; /* add the neighbors of neighbors of the extruded vertex if valence 2 */
1329  int CORNER_PATCH_2 = 3; /* CORNER_PATCH + one layer */
1330  /* We try larger cavities first because the result is usually better
1331  * when it works (less new defects on adjacent curves). But it does
1332  * not work when the larger cavity is too constrained by CAD curves,
1333  * so we try the small cavity after */
1334  for(int pass : {CORNER_PATCH, CORNER_PATCH_2, LARGER_PATCH, SMALL_PATCH}) {
1335  for(const auto &kv : cornerAndIdeal) {
1336  MVertex *v = kv.first;
1337  GVertex *gv = dynamic_cast<GVertex *>(v->onWhat());
1338  if(gv == nullptr) continue;
1339 
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();
1347 
1348  /* From here, we try to change the local configuration */
1349  Msg::Debug(
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);
1352 
1353  /* Init patch with quads adjacent to corner */
1354  GFaceMeshPatch patch;
1355  if((pass == CORNER_PATCH || pass == CORNER_PATCH_2) &&
1356  quads.size() == 2) {
1357  /* The corner is valence and has been extruded
1358  * take all the quads around the extruded vertex */
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;
1367  MVertex *ext = nullptr;
1368  if(shared[0] == v) { ext = shared[1]; }
1369  else if(shared[1] == v) {
1370  ext = shared[0];
1371  }
1372  else {
1373  continue;
1374  }
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));
1381  }
1382  bool okp = patchFromElements(gf, quadsPlus, patch);
1383  if(!okp) continue;
1384  }
1385  else if(pass == LARGER_PATCH) {
1386  /* and the neighbors */
1387  std::vector<MElement *> quadsPlus = quads;
1388  append(quadsPlus, getNeighbors(quads, adj));
1389  bool okp = patchFromElements(gf, quadsPlus, patch);
1390  if(!okp) continue;
1391  }
1392  else if(pass == SMALL_PATCH) {
1393  bool okp = patchFromElements(gf, quads, patch);
1394  if(!okp) continue;
1395  }
1396  if(patch.bdrVertices.size() != 1) {
1397  Msg::Debug("patch has %li bdr loops, weird", patch.bdrVertices.size());
1398  continue;
1399  }
1400  if(patch.embVertices.size() > 0) {
1401  Msg::Debug("patch has %li embedded vertices loops, avoid",
1402  patch.embVertices.size());
1403  continue;
1404  }
1405 
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);
1412  if(!okb) continue;
1413 
1414  GFaceMeshDiff diff;
1415  std::vector<MElement *> neighborsForGeometry =
1416  getNeighbors(patch.elements, adj);
1417  double minSICNafer = 0.1;
1418  if(ival_float <= 0.5) minSICNafer = 0.001; /* acute corner */
1419  int sdq = remeshLocalWithDiskQuadrangulation(
1420  gf, patch.elements, patch.intVertices, patch.bdrVertices.front(),
1421  bndIdealValence, bndAllowedValenceRange, neighborsForGeometry,
1422  minSICNafer, opt.invertNormalsForQuality, opt.sp, diff);
1423  if(sdq == 0) {
1424  /* Copy the pointers to update the adjacencies in case success */
1425  GFaceMeshPatch patchBefore = diff.before;
1426  GFaceMeshPatch patchAfter = diff.after;
1427 
1428  if(SHOW_DQR) {
1429  // FIXME using the public API inside Gmsh is not a good idea
1430  if(!gmsh::isInitialized()) gmsh::initialize();
1431  GeoLog::add(patchBefore.elements, "fixCornerB");
1432  GeoLog::add(patchAfter.elements, "fixCornerA");
1433  gmsh::view::add("---");
1434  GeoLog::flush();
1435  }
1436 
1437  /* Execute the diff on the mesh */
1438  bool ok = diff.execute(true); /* warning: GFace mesh changes here */
1439  if(PARANO_VALIDITY) {
1440  errorAndAbortIfInvalidSurfaceMesh(
1441  gf, "improveCornerValences | after execute");
1442  }
1443  if(ok) {
1444  updateAdjacencies(patchBefore, patchAfter, adj);
1445  append(smoothingPool, patchAfter.intVertices);
1446  append(smoothingPool, patchAfter.bdrVertices.front());
1447  stats.nCornerValFixed += 1;
1448  }
1449  else {
1450  Msg::Error("failed to apply diff, abort");
1451  abort();
1452  }
1453  Msg::Debug("-- corner %li fixed", num);
1454  }
1455  else {
1456  Msg::Debug("-- failed to fix corner %li", num);
1457  if(SHOW_DQR) {
1458  // FIXME using the public API inside Gmsh is not a good idea
1459  if(!gmsh::isInitialized()) gmsh::initialize();
1460  GeoLog::add(patch.elements, "fixCornerFAILED");
1461  gmsh::view::add("---");
1462  GeoLog::flush();
1463  }
1464  }
1465  }
1466  }
1467 
1468  if(smoothingPool.size() > 0)
1469  smoothThePool(gf, smoothingPool, adj, opt.invertNormalsForQuality, opt.sp);
1470 
1471  /* Remaining cases, just for stats */
1472  for(const auto &kv : cornerAndIdeal) {
1473  MVertex *v = kv.first;
1474  GVertex *gv = dynamic_cast<GVertex *>(v->onWhat());
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;
1482  }
1483 
1484  return 0;
1485 }
1486 
1487 int improveCurveValences(
1488  GFace *gf,
1489  const unordered_map<MVertex *, double>
1490  &qValIdeal, /* valence on bdr vertices */
1491  unordered_map<MVertex *, std::vector<MElement *> > &adj, DQOptions &opt,
1492  DQStats &stats)
1493 {
1494  Msg::Debug("- Face %i: improve curve valences", gf->tag());
1495  std::vector<MVertex *>
1496  smoothingPool; /* for smoothing after topological changes */
1497  smoothingPool.reserve(gf->mesh_vertices.size());
1498 
1499  /* qValIdeal is unordered_map and its ordering is random, we replace
1500  * it with a deterministic ordering, containing only the corners */
1501  std::vector<std::pair<MVertex *, double> > curveVertexAndIdeal;
1502  curveVertexAndIdeal.reserve(qValIdeal.size());
1503  for(auto &kv : qValIdeal) {
1504  MVertex *v = kv.first;
1505  GEdge *ge = dynamic_cast<GEdge *>(v->onWhat());
1506  if(ge != nullptr) { curveVertexAndIdeal.push_back({kv.first, kv.second}); }
1507  }
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());
1514  });
1515 
1516  for(const auto &kv : curveVertexAndIdeal) {
1517  MVertex *v = kv.first;
1518  GEdge *ge = dynamic_cast<GEdge *>(v->onWhat());
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();
1526 
1527  /* From here, we try to change the local configuration */
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);
1530 
1531  /* Init patch with quads adjacent to corner */
1532  GFaceMeshPatch patch;
1533  bool okp = patchFromElements(gf, quads, patch);
1534  if(!okp) continue;
1535  if(patch.bdrVertices.size() != 1) {
1536  Msg::Debug("patch has %li bdr loops, weird", patch.bdrVertices.size());
1537  continue;
1538  }
1539  if(patch.embVertices.size() > 0) {
1540  Msg::Debug("patch has %li embedded vertices loops, avoid",
1541  patch.embVertices.size());
1542  continue;
1543  }
1544 
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);
1550  if(!okb) continue;
1551 
1552  GFaceMeshDiff diff;
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);
1560  if(sdq == 0) {
1561  /* Copy the pointers to update the adjacencies in case success */
1562  GFaceMeshPatch patchBefore = diff.before;
1563  GFaceMeshPatch patchAfter = diff.after;
1564 
1565  if(SHOW_DQR) {
1566  // FIXME using the public API inside Gmsh is not a good idea
1567  if(!gmsh::isInitialized()) gmsh::initialize();
1568  GeoLog::add(patchBefore.elements, "fixCurveB");
1569  GeoLog::add(patchAfter.elements, "fixCurveA");
1570  gmsh::view::add("---");
1571  GeoLog::flush();
1572  }
1573 
1574  /* Execute the diff on the mesh */
1575  bool ok = diff.execute(true); /* warning: GFace mesh changes here */
1576  if(PARANO_VALIDITY) {
1577  errorAndAbortIfInvalidSurfaceMesh(
1578  gf, "improveCurveValences | after execute");
1579  }
1580  if(ok) {
1581  updateAdjacencies(patchBefore, patchAfter, adj);
1582  append(smoothingPool, patchAfter.intVertices);
1583  append(smoothingPool, patchAfter.bdrVertices.front());
1584  stats.nCurveValFixed += 1;
1585 
1586  if(PARANO_QUALITY) {
1587  errorAndAbortIfNegativeElement(
1588  gf, dynamic_cast_vector<MQuadrangle *, MElement *>(gf->quadrangles),
1589  "after execute");
1590  }
1591  }
1592  else {
1593  Msg::Error("failed to apply diff, abort");
1594  abort();
1595  }
1596  Msg::Debug("-- curve vertex %li fixed", num);
1597  }
1598  else {
1599  Msg::Debug("-- failed to fix curve vertex %li", num);
1600 
1601  if(PARANO_QUALITY) {
1602  errorAndAbortIfNegativeElement(
1603  gf, dynamic_cast_vector<MQuadrangle *, MElement *>(gf->quadrangles),
1604  "after failed to fix, baaad");
1605  }
1606  }
1607  }
1608 
1609  if(PARANO_QUALITY) {
1610  errorAndAbortIfNegativeElement(
1611  gf, dynamic_cast_vector<MQuadrangle *, MElement *>(gf->quadrangles),
1612  "before pool");
1613  }
1614 
1615  if(smoothingPool.size() > 0)
1616  smoothThePool(gf, smoothingPool, adj, opt.invertNormalsForQuality, opt.sp);
1617 
1618  if(PARANO_QUALITY) {
1619  errorAndAbortIfNegativeElement(
1620  gf, dynamic_cast_vector<MQuadrangle *, MElement *>(gf->quadrangles),
1621  "after pool");
1622  }
1623 
1624  /* Remaining cases, just for stats */
1625  for(const auto &kv : curveVertexAndIdeal) {
1626  MVertex *v = kv.first;
1627  GEdge *ge = dynamic_cast<GEdge *>(v->onWhat());
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;
1635  }
1636 
1637  return 0;
1638 }
1639 
1640 double irregularityEnergy(
1641  const GFaceMeshPatch &patch,
1642  const unordered_map<MVertex *, double> &qValIdeal,
1643  const unordered_map<MVertex *, std::vector<MElement *> > &adj)
1644 {
1645  double Ir = 0.;
1646  /* Boundary vertices */
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.);
1654 
1655  double val = (double)it2->second.size();
1656  Ir += std::pow(valIdeal - val, 2);
1657  }
1658  /* Interior vertices */
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);
1666  }
1667  return Ir;
1668 }
1669 
1670 double irregularityEnergy(
1671  GFace *gf, const std::vector<MElement *> &quads,
1672  const unordered_map<MVertex *, double> &qValIdeal,
1673  const unordered_map<MVertex *, std::vector<MElement *> > &adj)
1674 {
1675  GFaceMeshPatch patch;
1676  bool okp = patchFromElements(gf, quads, patch);
1677  if(!okp) return 0.;
1678  return irregularityEnergy(patch, qValIdeal, adj);
1679 }
1680 
1681 int improveInteriorValences(
1682  GFace *gf,
1683  const unordered_map<MVertex *, double>
1684  &qValIdeal, /* valence on bdr vertices */
1685  unordered_map<MVertex *, std::vector<MElement *> > &adj, DQOptions &opt,
1686  DQStats &stats)
1687 {
1688  Msg::Debug("- Face %i: improve interior valences", gf->tag());
1689 
1690  std::vector<MVertex *>
1691  smoothingPool; /* for smoothing after topological changes */
1692  smoothingPool.reserve(gf->mesh_vertices.size());
1693 
1694  /* Priority queue */
1695  std::priority_queue<std::pair<double, MVertex *>,
1696  std::vector<std::pair<double, MVertex *> > >
1697  Q;
1698 
1699  /* Initialize with all very irregular vertices of the face */
1700  for(const auto &kv : adj) {
1701  MVertex *v = kv.first;
1702  GFace *gf = dynamic_cast<GFace *>(v->onWhat());
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;
1709 
1710  /* Build patch with quads adjacent to very irregular vertex */
1711  GFaceMeshPatch patch;
1712  bool okp = patchFromElements(gf, quads, patch);
1713  if(!okp || patch.bdrVertices.size() != 1) continue;
1714 
1715  if(patch.embVertices.size() > 0) {
1716  Msg::Debug("patch has %li embedded vertices loops, avoid",
1717  patch.embVertices.size());
1718  continue;
1719  }
1720 
1721  double Ir = irregularityEnergy(gf, quads, qValIdeal, adj);
1722  if(Ir > 0.) Q.push({Ir, v});
1723  }
1724 
1725  /* Topological replacement loop */
1726  while(Q.size() > 0) {
1727  MVertex *v = Q.top().second;
1728  Q.pop();
1729 
1730  auto it = adj.find(v);
1731  /* Check if vertex still exists, may have been removed */
1732  if(it == adj.end()) continue;
1733  const std::vector<MElement *> &quads = it->second;
1734  size_t num = v->getNum();
1735 
1736  /* Build patch with quads adjacent to very irregular vertex */
1737  GFaceMeshPatch patch;
1738  bool okp = patchFromElements(gf, quads, patch);
1739  if(!okp || patch.bdrVertices.size() != 1) continue;
1740 
1741  /* Get ideal and allowed ranges on the patch boundary */
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);
1747  if(!okb) continue;
1748 
1749  /* Check if there is a valid disk remeshing */
1750  GFaceMeshDiff diff;
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);
1758  if(sdq == 0) {
1759  /* Copy the pointers to update the adjacencies in case success */
1760  GFaceMeshPatch patchBefore = diff.before;
1761  GFaceMeshPatch patchAfter = diff.after;
1762 
1763  if(SHOW_DQR) {
1764  // FIXME using the public API inside Gmsh is not a good idea
1765  if(!gmsh::isInitialized()) gmsh::initialize();
1766  GeoLog::add(patchBefore.elements, "fixIntB");
1767  GeoLog::add(patchAfter.elements, "fixIntA");
1768  gmsh::view::add("---");
1769  GeoLog::flush();
1770  }
1771 
1772  /* Execute the diff on the mesh */
1773  bool ok = diff.execute(true); /* warning: GFace mesh changes here */
1774  if(PARANO_VALIDITY) {
1775  errorAndAbortIfInvalidSurfaceMesh(
1776  gf, "improveInteriorValences | after execute");
1777  }
1778  if(ok) {
1779  updateAdjacencies(patchBefore, patchAfter, adj);
1780  append(smoothingPool, patchAfter.intVertices);
1781  append(smoothingPool, patchAfter.bdrVertices.front());
1782  stats.nSurfaceValFixed += 1;
1783 
1784  /* If new very irregular vertices have been created,
1785  * add them to the queue */
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});
1793  }
1794  }
1795  }
1796  else {
1797  Msg::Error("failed to apply diff, abort");
1798  abort();
1799  }
1800  Msg::Debug("-- interior vertex %li fixed", num);
1801  }
1802  else {
1803  Msg::Debug("-- failed to fix interior vertex %li", num);
1804  // GeoLog::add(quads, "!I_v"+std::to_string(v->getNum()));
1805  }
1806  }
1807  // GeoLog::flush();
1808 
1809  if(smoothingPool.size() > 0)
1810  smoothThePool(gf, smoothingPool, adj, opt.invertNormalsForQuality, opt.sp);
1811 
1812  /* Remaining cases, just for stats */
1813  for(const auto &kv : adj) {
1814  MVertex *v = kv.first;
1815  GFace *gf = dynamic_cast<GFace *>(v->onWhat());
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;
1823  }
1824 
1825  return 0;
1826 }
1827 
1828 bool patchIsValidForDiskRequadrangulation(
1829  const GFaceMeshPatch &patch,
1830  const unordered_map<MVertex *, std::vector<MElement *> > &adj)
1831 {
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;
1836  }
1837  for(MVertex *v : patch.bdrVertices[0]) {
1838  auto it = adj.find(v);
1839  if(it == adj.end()) return false;
1840  }
1841  for(MElement *e : patch.elements) {
1842  auto it =
1843  std::find(patch.gf->quadrangles.begin(), patch.gf->quadrangles.end(), e);
1844  if(it == patch.gf->quadrangles.end()) return false;
1845  }
1846  std::vector<MVertex *> bndt;
1847  bool okbb = buildBoundary(patch.elements, bndt);
1848  if(!okbb) return false;
1849 
1850  return true;
1851 }
1852 
1853 bool getIrregularPatchesAroundVertices(
1854  const std::vector<MVertex *> &vert,
1855  const unordered_map<MVertex *, std::vector<MElement *> > &adj,
1856  const unordered_map<MVertex *, double> &qValIdeal,
1857  std::vector<std::pair<double, GFaceMeshPatch> > &patches)
1858 {
1859  for(MVertex *v : vert) {
1860  GFace *gf = dynamic_cast<GFace *>(v->onWhat());
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;
1867  if(val > 5) {
1868  /* Take adjacent quads around very irregular vertex */
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});
1875  }
1876  else {
1877  /* Loop on edges around v */
1878  for(MElement *q : quads)
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;
1883  if(v1->getNum() > v2->getNum()) continue;
1884  auto it2 = (v1 == v) ? adj.find(v2) : adj.find(v1);
1885  if(it2 == adj.end()) continue;
1886  if(it2->second.size() != 4) {
1887  /* v1 and v2 are irregular, build cavity around them */
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});
1896  }
1897  }
1898  /* Check for irreg quads */
1899  for(MElement *q : quads) {
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() ||
1905  it3 == adj.end())
1906  continue;
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) {
1911  /* Opposite vertices are irregular */
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});
1920  break;
1921  }
1922  }
1923  }
1924  }
1925  }
1926  return true;
1927 }
1928 
1929 int improveInteriorValencesV2(
1930  GFace *gf,
1931  const unordered_map<MVertex *, double>
1932  &qValIdeal, /* valence on bdr vertices */
1933  unordered_map<MVertex *, std::vector<MElement *> > &adj, DQOptions &opt,
1934  DQStats &stats)
1935 {
1936  Msg::Debug("- Face %i: improve interior valences", gf->tag());
1937 
1938  std::vector<MVertex *>
1939  smoothingPool; /* for smoothing after topological changes */
1940  smoothingPool.reserve(gf->mesh_vertices.size());
1941 
1942  /* Priority queue */
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());
1948  };
1949  std::priority_queue<Ir_and_Patch, std::vector<Ir_and_Patch>, decltype(comp)>
1950  Q(comp);
1951 
1952  /* Initialize with patches around irregular vertices of the face */
1953  for(const auto &kv : adj) {
1954  MVertex *v = kv.first;
1955  GFace *gf = dynamic_cast<GFace *>(v->onWhat());
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;
1962 
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]); }
1966  }
1967 
1968  /* Topological replacement loop */
1969  while(Q.size() > 0) {
1970  GFaceMeshPatch patch = Q.top().second;
1971  Q.pop();
1972 
1973  if(!patchIsValidForDiskRequadrangulation(patch, adj)) continue;
1974 
1975  /* Get ideal and allowed ranges on the patch boundary */
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);
1981  if(!okb) continue;
1982 
1983  /* Check if there is a valid disk remeshing */
1984  GFaceMeshDiff diff;
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);
1992  if(sdq == 0) {
1993  /* Copy the pointers to update the adjacencies in case success */
1994  GFaceMeshPatch patchBefore = diff.before;
1995  GFaceMeshPatch patchAfter = diff.after;
1996 
1997  if(SHOW_DQR) {
1998  // FIXME using the public API inside Gmsh is not a good idea
1999  if(!gmsh::isInitialized()) gmsh::initialize();
2000  GeoLog::add(patchBefore.elements, "fixIntB");
2001  GeoLog::add(patchAfter.elements, "fixIntA");
2002  gmsh::view::add("---");
2003  GeoLog::flush();
2004  }
2005 
2006  /* Execute the diff on the mesh */
2007  bool ok = diff.execute(true); /* warning: GFace mesh changes here */
2008  if(PARANO_VALIDITY) {
2009  errorAndAbortIfInvalidSurfaceMesh(
2010  gf, "improveInteriorValencesV2 | after execute");
2011  }
2012  if(ok) {
2013  updateAdjacencies(patchBefore, patchAfter, adj);
2014  append(smoothingPool, patchAfter.intVertices);
2015  append(smoothingPool, patchAfter.bdrVertices.front());
2016  stats.nSurfaceValFixed += 1;
2017 
2018  /* If new irregular patches have been created,
2019  * add them to the queue */
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]); }
2024  }
2025  else {
2026  Msg::Error("failed to apply diff, abort");
2027  abort();
2028  }
2029  Msg::Debug("-- interior patch fixed");
2030  }
2031  else {
2032  Msg::Debug("-- failed to fix interior patch");
2033  // GeoLog::add(quads, "!I_v"+std::to_string(v->getNum()));
2034  }
2035  }
2036  // GeoLog::flush();
2037 
2038  if(smoothingPool.size() > 0)
2039  smoothThePool(gf, smoothingPool, adj, opt.invertNormalsForQuality, opt.sp);
2040 
2041  /* Remaining cases, just for stats */
2042  for(const auto &kv : adj) {
2043  MVertex *v = kv.first;
2044  GFace *gf = dynamic_cast<GFace *>(v->onWhat());
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;
2052  }
2053 
2054  return 0;
2055 }
2056 
2057 int RefineMeshWithBackgroundMeshProjectionSimple(GModel *gm)
2058 {
2059  Msg::Info(
2060  "Refine mesh (midpoint subdivision, with background projection) ...");
2061 
2062  bool linear = true;
2063  RefineMesh(gm, linear, true, false);
2064 
2065  /* Convert vertex types:
2066  * - MVertex on curve -> MEdgeVertex
2067  * - MVertex on face -> MFaceVertex */
2068  std::vector<GEdge *> edges = model_edges(gm);
2069  std::vector<GFace *> faces = model_faces(gm);
2070 
2071  /* old2new use to update mesh elements after */
2072  unordered_map<MVertex *, MVertex *> old2new;
2073 
2074  // FIXME: crash #1799
2075  int nthreads = 1; //getNumThreads();
2076 
2077 #pragma omp parallel for schedule(dynamic) num_threads(nthreads)
2078  for(size_t e = 0; e < edges.size(); ++e) {
2079  GEdge *ge = edges[e];
2080  if(CTX::instance()->mesh.meshOnlyVisible && !ge->getVisibility()) continue;
2081  if(ge->mesh_vertices.size() == 0) continue;
2082  std::vector<MVertex *> toProj;
2083  toProj.reserve(ge->mesh_vertices.size() / 2);
2084 
2085  /* Replace MVertex by MEdgeVertex */
2086  for(size_t i = 0; i < ge->mesh_vertices.size(); ++i) {
2087  MVertex *v = ge->mesh_vertices[i];
2088  if(v->onWhat() != ge) {
2089  Msg::Error("- Edge %i: vertex %li is associated to entity (%i,%i)",
2090  ge->tag(), v->getNum(), v->onWhat()->dim(),
2091  v->onWhat()->tag());
2092  continue;
2093  }
2094  MEdgeVertex *mev = dynamic_cast<MEdgeVertex *>(v);
2095  if(mev == nullptr) {
2096  MVertex *v2 = new MEdgeVertex(v->point().x(), v->point().y(),
2097  v->point().z(), ge, 0.);
2098  ge->mesh_vertices[i] = v2;
2099  old2new[v] = v2;
2100  toProj.push_back(v2);
2101  delete v;
2102  }
2103  }
2104  /* Projections */
2105  double tMin = ge->parBounds(0).low();
2106  double tMax = ge->parBounds(0).high();
2107  for(size_t i = 0; i < toProj.size(); ++i) {
2108  MVertex *v = toProj[i];
2109  double tGuess =
2110  tMin + (tMax - tMin) * double(i + 1) / double(toProj.size());
2111  GPoint proj = ge->closestPoint(v->point(), tGuess);
2112  if(proj.succeeded()) {
2113  v->setXYZ(proj.x(), proj.y(), proj.z());
2114  v->setParameter(0, proj.u());
2115  }
2116  else {
2117  Msg::Warning("- Edge %i, vertex %li: curve projection failed",
2118  ge->tag(), v->getNum());
2119  }
2120  }
2121  }
2122 
2123  GlobalBackgroundMesh *bmesh = nullptr;
2124  if(backgroudMeshExists(BMESH_NAME)) {
2125  bmesh = &(getBackgroundMesh(BMESH_NAME));
2126  }
2127  else {
2128  Msg::Warning("refine mesh with background projection: no background mesh, "
2129  "using CAD projection (slow)");
2130  }
2131 
2132 #pragma omp parallel for schedule(dynamic) num_threads(nthreads)
2133  for(size_t f = 0; f < faces.size(); ++f) {
2134  GFace *gf = faces[f];
2135  if(CTX::instance()->mesh.meshOnlyVisible && !gf->getVisibility()) continue;
2136  if(CTX::instance()->debugSurface > 0 &&
2137  gf->tag() != CTX::instance()->debugSurface)
2138  continue;
2139  if(gf->triangles.size() == 0 && gf->quadrangles.size() == 0) continue;
2140  std::vector<MVertex *> toProj;
2141  toProj.reserve(gf->mesh_vertices.size() / 2);
2142  for(size_t i = 0; i < gf->mesh_vertices.size(); ++i) {
2143  MVertex *v = gf->mesh_vertices[i];
2144  if(v->onWhat() != gf) {
2145  Msg::Error("- Face %i: vertex %li is associated to entity (%i,%i)",
2146  gf->tag(), v->getNum(), v->onWhat()->dim(),
2147  v->onWhat()->tag());
2148  continue;
2149  }
2150 
2151  /* Replace MVertex by MFaceVertex */
2152  MFaceVertex *mfv = dynamic_cast<MFaceVertex *>(v);
2153  if(mfv == nullptr) {
2154  MVertex *v2 = new MFaceVertex(v->point().x(), v->point().y(),
2155  v->point().z(), gf, 0., 0.);
2156  gf->mesh_vertices[i] = v2;
2157  old2new[v] = v2;
2158  toProj.push_back(v2);
2159  delete v;
2160  }
2161  }
2162 
2163  /* Projections */
2164  SurfaceProjector *sp = nullptr;
2165  sp = new SurfaceProjector();
2166  bool oki = sp->initialize(gf, {}, true);
2167  if(!oki && bmesh) {
2168  auto it2 = bmesh->faceBackgroundMeshes.find(gf);
2169  if(it2 == bmesh->faceBackgroundMeshes.end()) {
2170  Msg::Error("background mesh not found for face %i", gf->tag());
2171  }
2172  else {
2173  /* Get pointers to triangles in the background mesh */
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]);
2177  }
2178  oki = sp->initialize(gf, triangles);
2179  if(!oki) {
2180  Msg::Warning("failed to initialize surface projector");
2181  delete sp;
2182  sp = nullptr;
2183  }
2184  }
2185  }
2186  if(!oki && sp) {
2187  delete sp;
2188  sp = nullptr;
2189  }
2190 
2191  bool evalOnCAD = false;
2192  bool projOnCad = false;
2193  // if (gf->haveParametrization() && !gf->periodic(0) && !gf->periodic(1))
2194  // evalOnCAD = true;
2195 
2196  for(size_t i = 0; i < toProj.size(); ++i) {
2197  MVertex *v = toProj[i];
2198  GPoint proj;
2199  if(sp != nullptr) {
2200  proj = sp->closestPoint(v->point().data(), evalOnCAD, projOnCad);
2201  if(!proj.succeeded() && gf->haveParametrization()) {
2202  double uvg[2] = {0., 0.};
2203  proj = gf->closestPoint(v->point(), uvg);
2204  }
2205  }
2206  else {
2207  double uvg[2] = {0., 0.};
2208  proj = gf->closestPoint(v->point(), uvg);
2209  }
2210  if(proj.succeeded()) {
2211  v->setXYZ(proj.x(), proj.y(), proj.z());
2212  v->setParameter(0, proj.u());
2213  v->setParameter(1, proj.v());
2214  }
2215  else {
2216  Msg::Warning("- Face %i, vertex %li: surface projection failed",
2217  gf->tag(), v->getNum());
2218  }
2219  }
2220 
2221  /* Delete the surface */
2222  if(sp) delete sp;
2223  }
2224 
2225 /* Update elements */
2226 #pragma omp parallel for schedule(dynamic) num_threads(nthreads)
2227  for(size_t e = 0; e < edges.size(); ++e) {
2228  GEdge *ge = edges[e];
2229  for(MLine *l : ge->lines) {
2230  for(size_t lv = 0; lv < 2; ++lv) {
2231  MVertex *v = l->getVertex(lv);
2232  auto it = old2new.find(v);
2233  if(it != old2new.end()) { l->setVertex(lv, it->second); }
2234  }
2235  }
2236  }
2237 
2238 #pragma omp parallel for schedule(dynamic) num_threads(nthreads)
2239  for(size_t f = 0; f < faces.size(); ++f) {
2240  GFace *gf = faces[f];
2241  for(size_t i = 0; i < gf->getNumMeshElements(); ++i) {
2242  MElement *e = gf->getMeshElement(i);
2243  for(size_t lv = 0; lv < e->getNumVertices(); ++lv) {
2244  MVertex *v = e->getVertex(lv);
2245  auto it2 = old2new.find(v);
2246  if(it2 != old2new.end()) { e->setVertex(lv, it2->second); }
2247  }
2248  }
2249  }
2250 
2251  if(PARANO_VALIDITY) {
2252  errorAndAbortIfInvalidVertexInModel(gm, "after refine + proj");
2253  }
2254 
2255  if(DBG_EXPORT) { gm->writeMSH("qqs_subdiv.msh", 4.1); }
2256 
2257  optimizeGeometryQuadqs(gm);
2258 
2259  if(true || Msg::GetVerbosity() >= 99) {
2260  std::unordered_map<std::string, double> stats;
2261  appendQuadMeshStatistics(gm, stats, "Mesh_");
2262  printStatistics(
2263  stats, "Quad mesh after subdivision, projection and small smoothing:");
2264  }
2265 
2266  return 0;
2267 }
2268 
2270 {
2271  return RefineMeshWithBackgroundMeshProjectionSimple(gm);
2272 
2273  const bool USE_CAD_PROJECTION = false;
2274  if(USE_CAD_PROJECTION) {
2275  // Problem: it is slow and buggy on periodic CAD
2276  Msg::Info("Refine mesh (midpoint subdivision, with CAD projection) ...");
2277  bool linear = false;
2278  RefineMesh(gm, linear, true, false);
2279 
2280  std::unordered_map<std::string, double> stats;
2281  appendQuadMeshStatistics(gm, stats, "Mesh_");
2282  printStatistics(stats, "Quad mesh after subdivision with CAD projection:");
2283 
2284  return 0;
2285  }
2286 
2287  const bool SHOW_INTERMEDIATE_VIEWS = (Msg::GetVerbosity() >= 99);
2288  if(SHOW_INTERMEDIATE_VIEWS) {
2289  std::vector<MElement *> elements;
2290  for(GFace *gf : model_faces(gm)) {
2291  append(elements,
2292  dynamic_cast_vector<MTriangle *, MElement *>(gf->triangles));
2293  append(elements,
2294  dynamic_cast_vector<MQuadrangle *, MElement *>(gf->quadrangles));
2295  }
2296  GeoLog::add(elements, "qqs_quadtri");
2297  GeoLog::flush();
2298  }
2299  if(DBG_EXPORT) { gm->writeMSH("qqs_init.msh", 4.1); }
2300 
2301  Msg::Info(
2302  "Refine mesh (midpoint subdivision, with background projection) ...");
2303 
2304  bool linear = true;
2305  RefineMesh(gm, linear, true, false);
2306 
2307  if(Msg::GetVerbosity() >= 99) {
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); }
2312  }
2313 
2314  /* Convert vertex types:
2315  * - MVertex on curve -> MEdgeVertex
2316  * - MVertex on face -> MFaceVertex */
2317  std::vector<GEdge *> edges = model_edges(gm);
2318  std::vector<GFace *> faces = model_faces(gm);
2319  /* Build custom edgeToFaces because ge->faces() does not work
2320  * for embedded edges */
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);
2327  }
2328 
2329  int nthreads = getNumThreads();
2330 #pragma omp parallel for schedule(dynamic) num_threads(nthreads)
2331  for(size_t e = 0; e < edges.size(); ++e) {
2332  GEdge *ge = edges[e];
2333  if(CTX::instance()->mesh.meshOnlyVisible && !ge->getVisibility()) continue;
2334  if(ge->lines.size() == 0 || ge->mesh_vertices.size() == 0) continue;
2335  unordered_map<MVertex *, MVertex *> old2new_ge;
2336  std::vector<MVertex *> &projList = toProjectOnCurve[ge];
2337  projList.reserve(ge->mesh_vertices.size());
2338  for(size_t i = 0; i < ge->mesh_vertices.size(); ++i) {
2339  MVertex *v = ge->mesh_vertices[i];
2340  MEdgeVertex *mev = dynamic_cast<MEdgeVertex *>(v);
2341  if(mev == nullptr) {
2342  MVertex *v2 = new MEdgeVertex(v->point().x(), v->point().y(),
2343  v->point().z(), ge, 0.);
2344  ge->mesh_vertices[i] = v2;
2345  old2new_ge[v] = v2;
2346  projList.push_back(v2);
2347  delete v;
2348  }
2349  }
2350  /* Update lines */
2351  for(MLine *l : ge->lines) {
2352  for(size_t lv = 0; lv < 2; ++lv) {
2353  MVertex *v = l->getVertex(lv);
2354  auto it = old2new_ge.find(v);
2355  if(it != old2new_ge.end()) { l->setVertex(lv, it->second); }
2356  }
2357  }
2358  /* Update elements in adjacent faces */
2359  for(GFace *gf : edgeToFaces[ge]) {
2360  for(size_t i = 0; i < gf->getNumMeshElements(); ++i) {
2361  MElement *e = gf->getMeshElement(i);
2362  for(size_t lv = 0; lv < e->getNumVertices(); ++lv) {
2363  MVertex *v = e->getVertex(lv);
2364  auto it = old2new_ge.find(v);
2365  if(it != old2new_ge.end()) { e->setVertex(lv, it->second); }
2366  }
2367  }
2368  }
2369  }
2370 
2371 #pragma omp parallel for schedule(dynamic) num_threads(nthreads)
2372  for(size_t f = 0; f < faces.size(); ++f) {
2373  GFace *gf = faces[f];
2374  if(CTX::instance()->mesh.meshOnlyVisible && !gf->getVisibility()) continue;
2375  if(CTX::instance()->debugSurface > 0 &&
2376  gf->tag() != CTX::instance()->debugSurface)
2377  continue;
2378  if(gf->triangles.size() == 0 && gf->quadrangles.size() == 0) continue;
2379  unordered_map<MVertex *, MVertex *> old2new_gf;
2380  std::vector<MVertex *> &projList = toProjectOnFace[gf];
2381  projList.reserve(gf->mesh_vertices.size());
2382  for(size_t i = 0; i < gf->mesh_vertices.size(); ++i) {
2383  MVertex *v = gf->mesh_vertices[i];
2384  if(v->onWhat() != gf) {
2385  Msg::Error("- Face %i: vertex %li is associated to entity (%i,%i)",
2386  gf->tag(), v->getNum(), v->onWhat()->dim(),
2387  v->onWhat()->tag());
2388  continue;
2389  }
2390  MFaceVertex *mfv = dynamic_cast<MFaceVertex *>(v);
2391  if(mfv == nullptr) {
2392  MVertex *v2 = new MFaceVertex(v->point().x(), v->point().y(),
2393  v->point().z(), gf, 0., 0.);
2394  gf->mesh_vertices[i] = v2;
2395  old2new_gf[v] = v2;
2396  projList.push_back(v2);
2397  delete v;
2398  }
2399  }
2400  /* Update elements */
2401  for(size_t i = 0; i < gf->getNumMeshElements(); ++i) {
2402  MElement *e = gf->getMeshElement(i);
2403  for(size_t lv = 0; lv < e->getNumVertices(); ++lv) {
2404  MVertex *v = e->getVertex(lv);
2405  auto it2 = old2new_gf.find(v);
2406  if(it2 != old2new_gf.end()) { e->setVertex(lv, it2->second); }
2407  }
2408  }
2409  }
2410 
2411 /* Geometric projection on model */
2412 
2413 /* - projections on curves */
2414 #pragma omp parallel for schedule(dynamic) num_threads(nthreads)
2415  for(size_t e = 0; e < edges.size(); ++e) {
2416  GEdge *ge = edges[e];
2417  auto it = toProjectOnCurve.find(ge);
2418  if(it == toProjectOnCurve.end()) continue;
2419 
2420  double tMin = ge->parBounds(0).low();
2421  double tMax = ge->parBounds(0).high();
2422 
2423  for(size_t j = 0; j < it->second.size(); ++j) {
2424  MVertex *v = it->second[j];
2425  double tGuess =
2426  tMin + (tMax - tMin) * double(j + 1) / double(it->second.size() + 1);
2427  GPoint proj = ge->closestPoint(v->point(), tGuess);
2428  if(proj.succeeded()) {
2429  v->setXYZ(proj.x(), proj.y(), proj.z());
2430  v->setParameter(0, proj.u());
2431  }
2432  else {
2433  Msg::Warning("- Edge %i, vertex %li: curve projection failed",
2434  ge->tag(), v->getNum());
2435  }
2436  }
2437  }
2438 
2439  GlobalBackgroundMesh *bmesh = nullptr;
2440  if(backgroudMeshExists(BMESH_NAME)) {
2441  bmesh = &(getBackgroundMesh(BMESH_NAME));
2442  }
2443  else {
2444  Msg::Warning("refine mesh with background projection: no background mesh, "
2445  "using CAD projection (slow)");
2446  }
2447 
2448 /* - projections on faces */
2449 #pragma omp parallel for schedule(dynamic) num_threads(nthreads)
2450  for(size_t f = 0; f < faces.size(); ++f) {
2451  GFace *gf = faces[f];
2452 
2453  auto it = toProjectOnFace.find(gf);
2454  if(it == toProjectOnFace.end()) continue;
2455 
2456  Msg::Info("- Face %i: project %li vertices and smooth the quad mesh ...",
2457  gf->tag(), it->second.size());
2458 
2459  SurfaceProjector *sp = nullptr;
2460  if(bmesh) {
2461  auto it2 = bmesh->faceBackgroundMeshes.find(gf);
2462  if(it2 == bmesh->faceBackgroundMeshes.end()) {
2463  Msg::Error("background mesh not found for face %i", gf->tag());
2464  }
2465  else {
2466  /* Get pointers to triangles in the background mesh */
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]);
2470  }
2471 
2472  sp = new SurfaceProjector();
2473  bool oki = sp->initialize(gf, triangles);
2474  if(!oki) {
2475  Msg::Warning("failed to initialize surface projector");
2476  delete sp;
2477  sp = nullptr;
2478  }
2479  }
2480  }
2481 
2482  /* Project the vertices */
2483  bool evalOnCAD = false;
2484  bool projOnCad = false;
2485  if(gf->haveParametrization() && haveNiceParametrization(gf))
2486  evalOnCAD = true;
2487  for(size_t j = 0; j < it->second.size(); ++j) {
2488  MVertex *v = it->second[j];
2489  GPoint proj;
2490  if(sp != nullptr) {
2491  proj = sp->closestPoint(v->point().data(), evalOnCAD, projOnCad);
2492  if(!proj.succeeded() && gf->haveParametrization()) {
2493  double uvg[2] = {0., 0.};
2494  proj = gf->closestPoint(v->point(), uvg);
2495  }
2496  }
2497  else {
2498  double uvg[2] = {0., 0.};
2499  proj = gf->closestPoint(v->point(), uvg);
2500  }
2501  if(proj.succeeded()) {
2502  v->setXYZ(proj.x(), proj.y(), proj.z());
2503  v->setParameter(0, proj.u());
2504  v->setParameter(1, proj.v());
2505  }
2506  else {
2507  Msg::Warning("- Face %i, vertex %li: surface projection failed",
2508  gf->tag(), v->getNum());
2509  }
2510  }
2511 
2512  /* Quad mesh smoothing */
2513  double timeMax = 0.3;
2514  optimizeGeometryQuadMesh(gf, sp, timeMax);
2515  double sicnMin, sicnAvg;
2516  computeSICN(dynamic_cast_vector<MQuadrangle *, MElement *>(gf->quadrangles),
2517  sicnMin, sicnAvg);
2518  if(sicnMin < 0.) {
2519  double timeMax = 2.;
2520  optimizeGeometryQuadMesh(gf, sp, timeMax);
2521  }
2522 
2523  if(sp) delete sp;
2524  }
2525 
2526  if(SHOW_INTERMEDIATE_VIEWS) {
2527  std::vector<MElement *> elements;
2528  for(GFace *gf : model_faces(gm)) {
2529  append(elements,
2530  dynamic_cast_vector<MQuadrangle *, MElement *>(gf->quadrangles));
2531  // showUVParametrization(gf,dynamic_cast_vector<MQuadrangle*,MElement*>(gf->quadrangles),"quad_uvs");
2532  }
2533  GeoLog::add(elements, "qqs_quadinit");
2534  GeoLog::flush();
2535  }
2536 
2537  if(true || Msg::GetVerbosity() >= 99) {
2538  std::unordered_map<std::string, double> stats;
2539  appendQuadMeshStatistics(gm, stats, "Mesh_");
2540  printStatistics(
2541  stats, "Quad mesh after subdivision, projection and small smoothing:");
2542  }
2543 
2544  if(PARANO_VALIDITY) {
2545  errorAndAbortIfInvalidVertexInModel(gm, "after refine + proj");
2546  }
2547 
2548  if(DBG_EXPORT) { gm->writeMSH("qqs_subdiv.msh", 4.1); }
2549 
2550  return 0;
2551 }
2552 
2553 int checkAndReplaceQuadDominantMesh(GFace *gf, int valenceMaxBdr = 6,
2554  int valenceMaxIn = 8)
2555 {
2556  /* Check valence */
2557  unordered_map<MVertex *, int> valence;
2558  for(MQuadrangle *q : gf->quadrangles)
2559  for(size_t lv = 0; lv < 4; ++lv) { valence[q->getVertex(lv)] += 1; }
2560  for(MTriangle *t : gf->triangles)
2561  for(size_t lv = 0; lv < 3; ++lv) { valence[t->getVertex(lv)] += 1; }
2562  int vMaxInt = 0;
2563  int vMaxBdr = 0;
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);
2568  }
2569  }
2570  if(vMaxInt <= valenceMaxIn && vMaxBdr <= valenceMaxBdr) return 0;
2571 
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);
2575 
2576  int algo2d = gf->getMeshingAlgo();
2578  gf->mesh(false);
2579  gf->setMeshingAlgo(algo2d);
2580 
2581  if(gf->meshStatistics.status != GFace::DONE) {
2582  Msg::Warning("- Face %i: failed to build triangulation", gf->tag());
2583  return -1;
2584  }
2585 
2586  /* Recombine triangles in quads */
2587  recombineIntoQuads(gf, false, 0, false, .1);
2588 
2589  return 0;
2590 }
2591 
2593 {
2594  std::vector<GFace *> faces = model_faces(gm);
2595 
2596  int nthreads = getNumThreads();
2597 #pragma omp parallel for schedule(dynamic) num_threads(nthreads)
2598  for(size_t f = 0; f < faces.size(); ++f) {
2599  GFace *gf = faces[f];
2600  if(CTX::instance()->mesh.meshOnlyVisible && !gf->getVisibility()) continue;
2601  if(CTX::instance()->debugSurface > 0 &&
2602  gf->tag() != CTX::instance()->debugSurface)
2603  continue;
2604  if(gf->getNumMeshElements() == 0) continue;
2605 
2606  int st = checkAndReplaceQuadDominantMesh(gf);
2607  if(st != 0) {
2608  Msg::Warning("- Face %i: failed to replace bad quad-tri mesh", gf->tag());
2609  }
2610  }
2611 
2612  return 0;
2613 }
2614 
2615 int optimizeQuadMeshWithDiskQuadrangulationRemeshing(GFace *gf)
2616 {
2617  if(gf->triangles.size() > 0 || gf->quadrangles.size() == 0) return -1;
2618 
2619  // Disk quadrangulation remeshing use the CAD normals to compute the signed
2620  // quality, so the orientation is important.
2621  bool invertNormals = meshOrientationIsOppositeOfCadOrientation(gf);
2622 
2623  /* For each bdr vertex, compute the ideal valence (based on angle viewed from
2624  * the face) */
2625  unordered_map<MVertex *, double> qValIdeal;
2626  computeBdrVertexIdealValence(gf->quadrangles, qValIdeal);
2627 
2628  /* Vertex to quads */
2629  unordered_map<MVertex *, std::vector<MElement *> > adj;
2630  for(MQuadrangle *f : gf->quadrangles)
2631  for(size_t lv = 0; lv < 4; ++lv) {
2632  MVertex *v = f->getVertex(lv);
2633  adj[v].push_back(f);
2634  }
2635 
2636  DQStats stats;
2637  DQOptions opt;
2638  opt.invertNormalsForQuality = invertNormals;
2639  bool alwaysBuildSurfaceProjector = true;
2640  if(alwaysBuildSurfaceProjector || !haveNiceParametrization(gf) ||
2641  (gf->periodic(0) || gf->periodic(1))) {
2642  opt.sp = new SurfaceProjector();
2643  bool okf = fillSurfaceProjector(gf, opt.sp);
2644  if(!okf) {
2645  Msg::Error("- Face %i: failed to get a surface projector", gf->tag());
2646  return false;
2647  }
2648  }
2649 
2650  if(SHOW_DQR) {
2651  for(auto &kv : qValIdeal) {
2652  GeoLog::add(kv.first->point(), std::round(kv.second),
2653  "f_" + std::to_string(gf->tag()) + "_val_ideal");
2654  }
2655  GeoLog::flush();
2656  }
2657 
2658  double t1 = Cpu();
2659 
2660  const bool ENABLE_CORNER = true;
2661  const bool ENABLE_CURVE = true;
2662 
2663  if(ENABLE_CORNER) {
2664  int sc = improveCornerValences(gf, qValIdeal, adj, opt, stats);
2665  if(sc != 0) {
2666  Msg::Warning("optimize quad topology: failed to improve corner valences");
2667  }
2668  if(PARANO_QUALITY) {
2669  errorAndAbortIfNegativeElement(
2670  gf, dynamic_cast_vector<MQuadrangle *, MElement *>(gf->quadrangles),
2671  "after corner");
2672  }
2673  }
2674 
2675  if(ENABLE_CURVE) {
2676  int scu = improveCurveValences(gf, qValIdeal, adj, opt, stats);
2677  if(scu != 0) {
2678  Msg::Warning("optimize quad topology: failed to improve curve valences");
2679  }
2680  if(PARANO_QUALITY) {
2681  errorAndAbortIfNegativeElement(
2682  gf, dynamic_cast_vector<MQuadrangle *, MElement *>(gf->quadrangles),
2683  "after curve");
2684  }
2685  }
2686 
2687  int sci = improveInteriorValencesV2(gf, qValIdeal, adj, opt, stats);
2688  if(sci != 0) {
2689  Msg::Warning("optimize quad topology: failed to improve interior valences");
2690  }
2691  if(PARANO_QUALITY) {
2692  errorAndAbortIfNegativeElement(
2693  gf, dynamic_cast_vector<MQuadrangle *, MElement *>(gf->quadrangles),
2694  "after interior");
2695  }
2696 
2697  /* Smooth geometry (quick) */
2698  double timeMax = 0.3;
2699  optimizeGeometryQuadMesh(gf, opt.sp, timeMax);
2700 
2701  if(opt.sp) {
2702  delete opt.sp;
2703  opt.sp = nullptr;
2704  }
2705 
2706  double t2 = Cpu();
2707 
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);
2714 
2715  if(PARANO_QUALITY) {
2716  errorAndAbortIfNegativeElement(
2717  gf, dynamic_cast_vector<MQuadrangle *, MElement *>(gf->quadrangles),
2718  "after geom optim");
2719  }
2720 
2721  return 0;
2722 }
2723 
2724 int insertExtrudedBoundaryLayer(
2725  GFace *gf, const std::vector<MVertex *> &loop,
2726  const unordered_map<MVertex *, std::vector<MElement *> > &adj)
2727 {
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;
2732  MVertex *v = loop[i];
2733  auto it = adj.find(v);
2734  if(it == adj.end()) {
2735  Msg::Error("insert extruded layer: vertex not found in adj");
2736  return -1;
2737  }
2738 
2739  /* Create extruded vertex */
2740  SPoint3 pos = v->point();
2741  double sum = 1.;
2742  SPoint2 uv(0., 0.);
2743  double sumuv = 0.;
2744  for(auto &q : it->second) {
2745  for(size_t lv = 0; lv < 4; ++lv) {
2746  MVertex *qv = q->getVertex(lv);
2747  MFaceVertex *mfv = dynamic_cast<MFaceVertex *>(qv);
2748  if(mfv) {
2749  double u, v;
2750  mfv->getParameter(0, u);
2751  mfv->getParameter(1, v);
2752  uv = uv + SPoint2(u, v);
2753  sumuv += 1.;
2754  }
2755  }
2756  pos = pos + q->barycenter();
2757  sum += 1.;
2758  }
2759  if(sum > 0.) pos = pos * double(1. / sum);
2760  if(sumuv > 0.) uv = uv * double(1. / sumuv);
2761 
2762  MVertex *v2 =
2763  new MFaceVertex(pos.x(), pos.y(), pos.z(), gf, uv.x(), uv.y());
2764  gf->mesh_vertices.push_back(v2);
2765  gf->model()->addMVertexToVertexCache(v2);
2766  extrusion[v] = v2;
2767 
2768  /* Update adjacent quads */
2769  for(auto &q : it->second) {
2770  for(size_t lv = 0; lv < 4; ++lv) {
2771  MVertex *qv = q->getVertex(lv);
2772  if(qv == v) { q->setVertex(lv, v2); }
2773  }
2774  }
2775  }
2776 
2777  /* Create new quads */
2778  for(size_t i = 0; i < loop.size(); ++i) {
2779  MVertex *v0 = loop[i];
2780  MVertex *v1 = loop[(i + 1) % loop.size()];
2781  if(v0 == v1) continue;
2782  MVertex *v2 = extrusion[v1];
2783  MVertex *v3 = extrusion[v0];
2784  MQuadrangle *nq = new MQuadrangle(v0, v1, v2, v3);
2785  gf->quadrangles.push_back(nq);
2786  }
2787 
2788  return 0;
2789 }
2790 
2791 int optimizeFaceQuadMeshBoundaries(GFace *gf, bool ignoreAcuteCorners = false)
2792 {
2793  if(gf->triangles.size() > 0 || gf->quadrangles.size() == 0) return -1;
2794 
2795  size_t nv = gf->mesh_vertices.size();
2796  size_t nq = gf->quadrangles.size();
2797 
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);
2807  }
2808  std::vector<SPoint3> positionsInit(gf->mesh_vertices.size());
2809  for(size_t i = 0; i < gf->mesh_vertices.size(); ++i)
2810  positionsInit[i] = gf->mesh_vertices[i]->point();
2811 
2812  /* For each bdr vertex, compute the ideal valence (based on angle viewed from
2813  * the face) */
2814  unordered_map<MVertex *, double> qValIdeal;
2815  computeBdrVertexIdealValence(gf->quadrangles, qValIdeal);
2816 
2817  /* Face boundary loops */
2818  std::vector<std::vector<MVertex *> > loops;
2819  bool okb = buildBoundaries(elts, loops);
2820  if(!okb) {
2821  Msg::Warning("failed to build boundary loops on face %i", gf->tag());
2822  return -1;
2823  }
2824 
2825  /* Vertex to quads */
2826  unordered_map<MVertex *, std::vector<MElement *> > adj;
2827  for(MQuadrangle *f : gf->quadrangles)
2828  for(size_t lv = 0; lv < 4; ++lv) {
2829  MVertex *v = f->getVertex(lv);
2830  adj[v].push_back(f);
2831  }
2832 
2833  /* Check if valences are not right on a loop */
2834  size_t nlayer = 0;
2835  for(size_t i = 0; i < loops.size(); ++i) {
2836  bool goodAcute = false;
2837  bool loopIsGood = true;
2838  for(MVertex *v : loops[i]) {
2839  auto it = adj.find(v);
2840  if(it == adj.end()) continue;
2841  size_t val = it->second.size();
2842 
2843  auto iti = qValIdeal.find(v);
2844  if(iti == qValIdeal.end()) continue;
2845  size_t ival = idealBoundaryValence(iti->first, iti->second);
2846 
2847  if(val != ival) { loopIsGood = false; }
2848 
2849  if(val == ival && iti->second < 0.5) { goodAcute = true; }
2850  }
2851 
2852  if(ignoreAcuteCorners && goodAcute) continue;
2853 
2854  /* Add a layer */
2855  if(!loopIsGood) {
2856  insertExtrudedBoundaryLayer(gf, loops[i], adj);
2857  nlayer += 1;
2858  }
2859  }
2860 
2861  if(nlayer > 0) {
2862  double timeMax = 1;
2863  SurfaceProjector sp;
2864  fillSurfaceProjector(gf, &sp);
2865  optimizeGeometryQuadMesh(gf, &sp, timeMax);
2866 
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);
2872 
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);
2877  /* Delete added vertices */
2878  for(size_t v = nv; v < gf->mesh_vertices.size(); ++v) {
2879  delete gf->mesh_vertices[v];
2880  }
2881  gf->mesh_vertices.resize(nv);
2882  /* Restore positions */
2883  for(size_t i = 0; i < gf->mesh_vertices.size(); ++i) {
2884  gf->mesh_vertices[i]->setXYZ(positionsInit[i]);
2885  }
2886  /* Delete added quads */
2887  for(size_t f = nq; f < gf->quadrangles.size(); ++f) {
2888  delete gf->quadrangles[f];
2889  }
2890  gf->quadrangles.resize(nq);
2891  /* Restore quad vertices */
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]);
2895  }
2896  return 1;
2897  }
2898  Msg::Info("- Face %i: added %li extruded boundary layers", gf->tag(),
2899  nlayer);
2900  }
2901 
2902  return 0;
2903 }
2904 
2905 int ensureEvenNumberOfEdgesOnCurvesAfterInitialSurfaceMesh(GModel *gm)
2906 {
2907  Msg::Info("Generating empty surface meshes ...");
2908 
2909  int algo2d = CTX::instance()->mesh.algo2d;
2911 
2912  std::vector<GFace *> faces = model_faces(gm);
2913  int nIter = 0;
2914  while(1) {
2915  int nPending = 0;
2916  for(size_t f = 0; f < faces.size(); ++f) {
2917  GFace *gf = faces[f];
2918  if(gf->meshStatistics.status != GFace::PENDING) continue;
2919  if(CTX::instance()->mesh.meshOnlyVisible && !gf->getVisibility())
2920  continue;
2921  if(CTX::instance()->debugSurface > 0 &&
2922  gf->tag() != CTX::instance()->debugSurface)
2923  continue;
2924  nPending += 1;
2926 
2927  /* Initial mesh only */
2928  char method = gf->meshAttributes.method;
2929  int algo = gf->meshAttributes.algorithm;
2930  if(method == 1) { /* remove transfinite property */
2932  }
2934  gf->mesh(true);
2935  gf->meshAttributes.algorithm = algo;
2936  gf->meshAttributes.method = method;
2937  }
2938  if(!nPending) break;
2939  if(nIter++ > CTX::instance()->mesh.maxRetries) break;
2940  }
2941  CTX::instance()->mesh.algo2d = algo2d;
2942 
2943  /* Remove the meshes */
2944  std::vector<GEdge *> edges;
2945 
2946  Msg::Info("Deleting empty surface meshes ...");
2947  for(size_t f = 0; f < faces.size(); ++f) {
2948  GFace *gf = faces[f];
2949  if(CTX::instance()->mesh.meshOnlyVisible && !gf->getVisibility()) continue;
2950  if(CTX::instance()->debugSurface > 0 &&
2951  gf->tag() != CTX::instance()->debugSurface)
2952  continue;
2953  if(gf->meshStatistics.status == GFace::PENDING) {
2954  Msg::Warning("- Face %i not meshed !", gf->tag());
2955  }
2956  deMeshGFace killer;
2957  killer(gf);
2958 
2959  append(edges, gf->edges()); /* face edges saved to verify nb of edges */
2960  }
2961  Msg::Info("done.");
2962 
2963  /* Verify the curves */
2964  sort_unique(edges);
2965  for(size_t e = 0; e < edges.size(); ++e) {
2966  GEdge *ge = edges[e];
2967  if(ge->lines.size() % 2 != 0) {
2968  Msg::Warning(
2969  "- Curve %i has %li lines (due to boundary recovery), splitting one",
2970  ge->tag(), ge->lines.size());
2971  size_t i = ge->lines.size() / 2;
2972  MLine *l = ge->lines[i];
2973  MVertex *v1 = l->getVertex(0);
2974  MVertex *v2 = l->getVertex(1);
2975  double t1, t2;
2976  v1->getParameter(0, t1);
2977  v2->getParameter(0, t2);
2978  double t = 0.5 * (t1 + t2);
2979  SPoint3 mid = (v1->point() + v2->point()) * 0.5;
2980  GPoint proj = ge->closestPoint(mid, t);
2981  if(!proj.succeeded()) { /* force values */
2982  proj.x() = mid.x();
2983  proj.y() = mid.y();
2984  proj.z() = mid.z();
2985  t = 0.5 * (t1 + t2);
2986  }
2987  MEdgeVertex *mev1 = dynamic_cast<MEdgeVertex *>(v1);
2988  MEdgeVertex *mev2 = dynamic_cast<MEdgeVertex *>(v2);
2989  double lc = -1.;
2990  if(mev1 && mev2) { lc = 0.5 * (mev1->getLc() + mev2->getLc()); }
2991  else if(mev1) {
2992  lc = mev1->getLc();
2993  }
2994  else if(mev2) {
2995  lc = mev2->getLc();
2996  }
2997  MEdgeVertex *newv =
2998  new MEdgeVertex(proj.x(), proj.y(), proj.z(), ge, t, 0, lc);
2999  ge->mesh_vertices.push_back(newv);
3000  ge->lines[i] = new MLine(v1, newv);
3001  ge->lines.push_back(new MLine(newv, v2));
3002  delete l;
3003  }
3004  }
3005 
3006  /* Undo transfinite which are no longer possibles */
3007  for(size_t f = 0; f < faces.size(); ++f) {
3008  GFace *gf = faces[f];
3009  if(CTX::instance()->mesh.meshOnlyVisible && !gf->getVisibility()) continue;
3010  if(CTX::instance()->debugSurface > 0 &&
3011  gf->tag() != CTX::instance()->debugSurface)
3012  continue;
3014  gf->edges().size() == 4) {
3015  std::vector<size_t> sizes;
3016  for(GEdge *ge : gf->edges()) { sizes.push_back(ge->lines.size()); }
3017  std::sort(sizes.begin(), sizes.end());
3018  if(sizes.size() != 4 || sizes[0] != sizes[1] || sizes[2] != sizes[3]) {
3019  Msg::Warning(
3020  "- Face %i: transfinite no longer possible, remove the attribute",
3021  gf->tag());
3023  }
3024  }
3025  }
3026 
3027  return 0;
3028 }
3029 
3031  double minimumQualityRequired)
3032 {
3033  if(CTX::instance()->mesh.quadqsTopoOptimMethods != 0 &&
3038  Msg::Debug("optimize topology of simple CAD faces with patterns: avoided "
3039  "because quadqsTopoOptimMethods = %i",
3040  CTX::instance()->mesh.quadqsTopoOptimMethods);
3041  return 0;
3042  }
3043 
3044  std::vector<GFace *> faces = model_faces(gm);
3045  Msg::Info("Pattern-based quad meshing of simple CAD faces ...", faces.size());
3046 
3047  initQuadPatterns();
3048 
3049  int nthreads = getNumThreads();
3050 #pragma omp parallel for schedule(dynamic) num_threads(nthreads)
3051  for(size_t f = 0; f < faces.size(); ++f) {
3052  GFace *gf = faces[f];
3053  if(gf->meshStatistics.status != GFace::PENDING) continue;
3054  if(CTX::instance()->mesh.meshOnlyVisible && !gf->getVisibility()) continue;
3055  if(CTX::instance()->debugSurface > 0 &&
3056  gf->tag() != CTX::instance()->debugSurface)
3057  continue;
3058  if(gf->triangles.size() > 0 || gf->quadrangles.size() == 0) continue;
3059  if(gf->embeddedEdges().size() > 0 || gf->embeddedVertices().size() > 0)
3060  continue;
3061 
3062  bool invertNormals = meshOrientationIsOppositeOfCadOrientation(gf);
3063  meshFaceWithGlobalPattern(gf, invertNormals, minimumQualityRequired);
3064 
3065  if(PARANO_VALIDITY) {
3066  errorAndAbortIfInvalidSurfaceMesh(gf, "after mesh with global pattern");
3067  }
3068  }
3069 
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:");
3074 
3075  if(PARANO_VALIDITY) {
3076  errorAndAbortIfInvalidVertexInModel(
3077  gm, "global check after face pattern meshing");
3078  }
3079 
3080  if(DBG_EXPORT) { gm->writeMSH("qqs_simplefaces.msh", 4.1); }
3081 
3082  return 0;
3083 }
3084 
3086 {
3087  if(CTX::instance()->mesh.quadqsTopoOptimMethods != 0 &&
3092  Msg::Debug("optimize topology with disk quadrangulation remeshing: avoided "
3093  "because quadqsTopoOptimMethods = %i",
3094  CTX::instance()->mesh.quadqsTopoOptimMethods);
3095  return 0;
3096  }
3097 
3098  Msg::Info(
3099  "Optimize topology of quad meshes with disk quadrangulation remeshing ...");
3100 
3101  initDiskQuadrangulations();
3102 
3103  std::vector<GFace *> faces = model_faces(gm);
3104 
3105  int nthreads = getNumThreads();
3106 #pragma omp parallel for schedule(dynamic) num_threads(nthreads)
3107  for(size_t f = 0; f < faces.size(); ++f) {
3108  GFace *gf = faces[f];
3109  if(gf->meshStatistics.status != GFace::PENDING) continue;
3110  if(CTX::instance()->mesh.meshOnlyVisible && !gf->getVisibility()) continue;
3111  if(CTX::instance()->debugSurface > 0 &&
3112  gf->tag() != CTX::instance()->debugSurface)
3113  continue;
3114  if(gf->triangles.size() > 0 || gf->quadrangles.size() == 0) continue;
3115 
3116  optimizeQuadMeshWithDiskQuadrangulationRemeshing(gf);
3117 
3118  if(PARANO_VALIDITY) {
3119  errorAndAbortIfInvalidSurfaceMesh(gf,
3120  "after disk quadrangulation remeshing");
3121  }
3122  }
3123 
3124  std::unordered_map<std::string, double> stats;
3125  appendQuadMeshStatistics(gm, stats, "Mesh_");
3126  printStatistics(stats, "Quad mesh after disk quadrangulation remeshing:");
3127 
3128  if(stats["Mesh_SICN_min"] < 0.) {
3129  Msg::Warning("negative quality on some quads");
3130  }
3131 
3132  if(PARANO_VALIDITY) {
3133  errorAndAbortIfInvalidVertexInModel(
3134  gm, "global check after disk quadrangulation remeshing");
3135  }
3136 
3137  if(DBG_EXPORT) { gm->writeMSH("qqs_diskrmsh.msh", 4.1); }
3138 
3139  return 0;
3140 }
3141 
3143 {
3144  if(CTX::instance()->mesh.quadqsTopoOptimMethods != 0 &&
3149  Msg::Debug("optimize topology with cavity remeshing: avoided because "
3150  "quadqsTopoOptimMethods = %i",
3151  CTX::instance()->mesh.quadqsTopoOptimMethods);
3152  return 0;
3153  }
3154 
3155  std::vector<GFace *> faces = model_faces(gm);
3156  Msg::Info(
3157  "Optimize topology of quad meshes with cavity remeshing (%li faces) ...",
3158  faces.size());
3159 
3160  initQuadPatterns();
3161 
3162  if(!backgroudMeshExists(BMESH_NAME)) {
3163  Msg::Info("no background mesh, creating one with the current quad mesh");
3164  GlobalBackgroundMesh &bmesh = getBackgroundMesh(BMESH_NAME);
3165  int status = bmesh.importGModelMeshes(gm, true);
3166  if(status != 0) {
3167  Msg::Error("failed to import model mesh in background mesh");
3168  return -1;
3169  }
3170  }
3171 
3172  GlobalBackgroundMesh &bmesh = getBackgroundMesh(BMESH_NAME);
3173 
3174  int nthreads = getNumThreads();
3175 #pragma omp parallel for schedule(dynamic) num_threads(nthreads)
3176  for(size_t f = 0; f < faces.size(); ++f) {
3177  GFace *gf = faces[f];
3178  if(gf->meshStatistics.status != GFace::PENDING) continue;
3179  if(CTX::instance()->mesh.meshOnlyVisible && !gf->getVisibility()) continue;
3180  if(CTX::instance()->debugSurface > 0 &&
3181  gf->tag() != CTX::instance()->debugSurface)
3182  continue;
3183  if(gf->triangles.size() > 0 || gf->quadrangles.size() == 0) continue;
3185 
3186  /* Get singularities from global storage */
3187  std::vector<std::pair<SPoint3, int> > singularities;
3188  bool okg = getSingularitiesFromBackgroundField(gf, singularities);
3189  if(!okg) {
3190  okg = getSingularitiesFromNewCrossFieldComputation(bmesh, gf,
3191  singularities);
3192  if(!okg) {
3193  Msg::Warning("- Face %i: failed to get singularities", gf->tag());
3194  }
3195  }
3196 
3197  bool invertNormals = meshOrientationIsOppositeOfCadOrientation(gf);
3198  improveQuadMeshTopologyWithCavityRemeshing(gf, singularities,
3199  invertNormals);
3200 
3201  if(PARANO_VALIDITY) {
3202  errorAndAbortIfInvalidSurfaceMesh(gf, "after cavity remeshing");
3203  }
3204  }
3205 
3206  std::unordered_map<std::string, double> stats;
3207  appendQuadMeshStatistics(gm, stats, "Mesh_");
3208  printStatistics(stats, "Quad mesh after cavity remeshing:");
3209 
3210  writeStatistics(stats, "quadqs_statistics.json");
3211 
3212  if(PARANO_VALIDITY) {
3213  errorAndAbortIfInvalidVertexInModel(gm,
3214  "global check after cavity remeshing");
3215  }
3216 
3217  GeoLog::flush();
3218 
3219  if(DBG_EXPORT) { gm->writeMSH("qqs_cavrmsh.msh", 4.1); }
3220 
3221  return 0;
3222 }
3223 
3225 {
3226  Msg::Info("Optimize topology of quad mesh boundaries with extrusion and "
3227  "remeshing ...");
3228 
3229  std::vector<GFace *> faces = model_faces(gm);
3230 
3231  int nthreads = getNumThreads();
3232 #pragma omp parallel for schedule(dynamic) num_threads(nthreads)
3233  for(size_t f = 0; f < faces.size(); ++f) {
3234  GFace *gf = faces[f];
3235  if(gf->meshStatistics.status != GFace::PENDING) continue;
3236  if(CTX::instance()->mesh.meshOnlyVisible && !gf->getVisibility()) continue;
3237  if(CTX::instance()->debugSurface > 0 &&
3238  gf->tag() != CTX::instance()->debugSurface)
3239  continue;
3240  if(gf->triangles.size() > 0 || gf->quadrangles.size() == 0) continue;
3241 
3242  optimizeFaceQuadMeshBoundaries(gf, true);
3243  }
3244 
3245  return 0;
3246 }
3247 
3248 #else
3249 /* else: without QUADMESHINGTOOLS module*/
3250 
3251 int BuildBackgroundMeshAndGuidingField(GModel *gm, bool overwriteGModelMesh,
3252  bool deleteGModelMeshAfter,
3253  bool overwriteField, int N)
3254 {
3255  Msg::Error("Module QUADMESHINGTOOLS required for function "
3256  "BuildBackgroundMeshAndGuidingField");
3257  return -10;
3258 }
3260 {
3261  Msg::Error("Module QUADMESHINGTOOLS required for function "
3262  "backgroundMeshAndGuidingFieldExists");
3263  return -10;
3264 }
3266 {
3267  Msg::Error("Module QUADMESHINGTOOLS required for function "
3268  "optimizeTopologyWithCavityRemeshing");
3269  return -10;
3270 }
3272 {
3273  Msg::Error("Module QUADMESHINGTOOLS required for function "
3274  "optimizeTopologyWithCavityRemeshing");
3275  return -10;
3276 }
3278  double minimumQualityRequired)
3279 {
3280  Msg::Error("Module QUADMESHINGTOOLS required for function "
3281  "quadMeshingOfSimpleFacesWithPatterns");
3282  return -10;
3283 }
3285 {
3286  Msg::Error("Module QUADMESHINGTOOLS required for function "
3287  "RefineMeshWithBackgroundMeshProjection");
3288  return -10;
3289 }
3291 {
3292  Msg::Error("Module QUADMESHINGTOOLS required for function "
3293  "replaceBadQuadDominantMeshes");
3294  return -10;
3295 }
3297 {
3298  Msg::Error("Module QUADMESHINGTOOLS required for function "
3299  "optimizeQuadMeshBoundaries");
3300  return -10;
3301 }
3302 
3303 #endif
3304 /* endif: QUADMESHINGTOOLS*/
3305 
3307 {
3308  for(GFace *gf : gm->getFaces()) {
3309  /* Transfer the vertices from seam GEdge to associated GFace */
3310  std::unordered_map<MVertex *, MVertex *> old2new;
3311  for(GEdge *ge : gf->edges()) {
3312  if(ge->isSeam(gf) && ge->faces().size() == 1 && ge->faces()[0] == gf) {
3313  /* GEdge interior vertices */
3314  for(MVertex *ov : ge->mesh_vertices) {
3315  auto it = old2new.find(ov);
3316  if(it != old2new.end()) continue; /* already changed */
3317  SPoint3 p = ov->point();
3318  double t;
3319  ov->getParameter(0, t);
3320  SPoint2 uv = ge->reparamOnFace(gf, t, -1);
3321  MVertex *nv = new MFaceVertex(p.x(), p.y(), p.z(), gf, uv[0], uv[1]);
3322  nv->setParameter(0, uv[0]);
3323  nv->setParameter(1, uv[1]);
3324  gf->mesh_vertices.push_back(nv);
3325  old2new[ov] = nv;
3326  delete ov;
3327  }
3328  ge->mesh_vertices.clear();
3329  for(size_t i = 0; i < ge->lines.size(); ++i) { delete ge->lines[i]; }
3330  ge->lines.clear();
3331 
3332  /* GEdge boundary vertices */
3333  for(GVertex *gv : ge->vertices())
3334  if(gv->mesh_vertices.size() == 1) {
3335  std::vector<GEdge *> otherCurves;
3336  for(GEdge *ge2 : gv->edges())
3337  if(ge2 != ge) {
3338  if(ge2->vertices().front() == ge2->vertices().back() &&
3339  ge2->length() <
3340  CTX::instance()->geom.tolerance) { /* Empty curve */
3341  continue;
3342  }
3343  otherCurves.push_back(ge2);
3344  }
3345  std::sort(otherCurves.begin(), otherCurves.end());
3346  otherCurves.erase(
3347  std::unique(otherCurves.begin(), otherCurves.end()),
3348  otherCurves.end());
3349 
3350  if(otherCurves.size() > 0) continue;
3351  MVertex *ov = gv->mesh_vertices[0];
3352  auto it = old2new.find(ov);
3353  if(it != old2new.end()) continue; /* already changed */
3354  Msg::Debug(
3355  "transfer: mesh vertex at CAD corner %i moved to face %i",
3356  gv->tag(), gf->tag());
3357  SPoint3 p = ov->point();
3358  SPoint2 uv = gv->reparamOnFace(gf, 0);
3359  MVertex *nv =
3360  new MFaceVertex(p.x(), p.y(), p.z(), gf, uv[0], uv[1]);
3361  nv->setParameter(0, uv[0]);
3362  nv->setParameter(1, uv[1]);
3363  gf->mesh_vertices.push_back(nv);
3364  old2new[ov] = nv;
3365 
3366  /* Note/warning: let the MVertex on the GVertex live. If the MVertex
3367  * is deleted, I/O is broken.
3368  * FIXME: support for GVertex without MVertex in mesh I/O */
3369  // delete ov;
3370  // gv->mesh_vertices.clear();
3371  }
3372  }
3373  }
3374  if(old2new.size() > 0) {
3375  for(MElement *f : gf->triangles)
3376  for(size_t lv = 0; lv < 3; ++lv) {
3377  MVertex *v = f->getVertex(lv);
3378  auto it = old2new.find(v);
3379  if(it != old2new.end()) { f->setVertex(lv, it->second); }
3380  }
3381  for(MElement *f : gf->quadrangles)
3382  for(size_t lv = 0; lv < 4; ++lv) {
3383  MVertex *v = f->getVertex(lv);
3384  auto it = old2new.find(v);
3385  if(it != old2new.end()) { f->setVertex(lv, it->second); }
3386  }
3387  }
3388  }
3389  return 0;
3390 }
3391 
3393 {
3394 #if defined(HAVE_QUADMESHINGTOOLS)
3395  backups_int.push_back(
3396  new RestoreValueAtEndOfLife<int>(&CTX::instance()->mesh.algo2d));
3397  backups_int.push_back(
3398  new RestoreValueAtEndOfLife<int>(&CTX::instance()->mesh.recombineAll));
3399  backups_int.push_back(
3400  new RestoreValueAtEndOfLife<int>(&CTX::instance()->mesh.algoRecombine));
3401  backups_int.push_back(new RestoreValueAtEndOfLife<int>(
3402  &CTX::instance()->mesh.recombineOptimizeTopology));
3403  backups_double.push_back(
3404  new RestoreValueAtEndOfLife<double>(&CTX::instance()->mesh.lcFactor));
3405  backups_double.push_back(
3406  new RestoreValueAtEndOfLife<double>(&CTX::instance()->mesh.lcMin));
3407  backups_double.push_back(
3408  new RestoreValueAtEndOfLife<double>(&CTX::instance()->mesh.lcMax));
3409  backups_int.push_back(
3410  new RestoreValueAtEndOfLife<int>(&CTX::instance()->mesh.lcFromPoints));
3411  backups_int.push_back(
3412  new RestoreValueAtEndOfLife<int>(&CTX::instance()->mesh.minCurveNodes));
3413  backups_int.push_back(
3414  new RestoreValueAtEndOfLife<int>(&CTX::instance()->mesh.minCircleNodes));
3415 
3416  // TODOMX: should restore abortOnError, but testing stuff
3417  backups_int.push_back(
3418  new RestoreValueAtEndOfLife<int>(&CTX::instance()->abortOnError));
3419 
3420  // Backup GEdge meshing attributes
3421  for(GEdge *ge : model_edges(GModel::current())) {
3422  backups_char.push_back(
3424  backups_double.push_back(new RestoreValueAtEndOfLife<double>(
3426  backups_double.push_back(
3428  backups_double.push_back(
3430  backups_int.push_back(new RestoreValueAtEndOfLife<int>(
3432  backups_int.push_back(
3434  backups_int.push_back(new RestoreValueAtEndOfLife<int>(
3436  backups_bool.push_back(
3438  }
3439 
3440  // Backup GFace meshing attributes
3441  for(GFace *gf : model_faces(GModel::current())) {
3442  backups_int.push_back(
3444  backups_double.push_back(
3446  backups_char.push_back(
3448  backups_int.push_back(new RestoreValueAtEndOfLife<int>(
3450  backups_int.push_back(new RestoreValueAtEndOfLife<int>(
3452  backups_bool.push_back(
3454  backups_double.push_back(
3456  backups_double.push_back(
3458  backups_int.push_back(
3460  backups_int.push_back(new RestoreValueAtEndOfLife<int>(
3462  }
3463 #else
3464  Msg::Error("Module QUADMESHINGTOOLS required to use QuadqsContextUpdater");
3465 #endif
3466 
3467  setQuadqsOptions();
3468 }
3469 
3470 QuadqsContextUpdater::~QuadqsContextUpdater() { restoreInitialOption(); }
3471 
3473 {
3474  Msg::Debug("set special quadqs options in the global context");
3475 
3476  // CTX::instance()->mesh.algo2d = ALGO_2D_QUAD_QUASI_STRUCT;
3478  // CTX::instance()->mesh.algoRecombine = 4; /* bipartite labelling */
3479  if(CTX::instance()->mesh.algoRecombine != 4) {
3480  CTX::instance()->mesh.algoRecombine = 0; /* midpoint subdivision */
3481  }
3483  CTX::instance()->mesh.lcFactor = 1.;
3484  CTX::instance()->mesh.lcMin = 0.;
3485  CTX::instance()->mesh.lcMax = 1.e22;
3487  CTX::instance()->abortOnError = 0;
3490 }
3491 
3493 {
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];
3501  }
3502 #else
3503  Msg::Error("Module QUADMESHINGTOOLS required to use QuadqsContextUpdater");
3504 #endif
3505 }
3506 
3508 {
3509  Msg::Info("Cleaning quadqs background mesh and field");
3510  global_bmeshes.clear(); /* background meshes used in quadqs */
3511  if(gm->getFields()->getBackgroundField() > 0) { /* background field */
3512  gm->getFields()->reset();
3513  // Field *field =
3514  // gm->getFields()->get(gm->getFields()->getBackgroundField()); if(field &&
3515  // field->numComponents() == 3) {
3516  // gm->getFields()->deleteField(field->id);
3517  // gm->getFields()->setBackgroundMesh(0);
3518  // }
3519  }
3520 #if defined(HAVE_POST)
3521  PView *view = PView::getViewByName("guiding_field");
3522  delete view;
3523 #if defined(HAVE_FLTK)
3524  if(FlGui::available()) FlGui::instance()->updateViews(true, true);
3525 #endif
3526 #endif
3527  return 0;
3528 }
3529 
3531  GFace *gf,
3532  std::unordered_map<MVertex *, std::vector<MVertex *> > &acuteCorners,
3533  double angle_threshold_rad)
3534 {
3535  std::unordered_map<MVertex *, std::vector<MVertex *> > corner2vertices;
3536  for(GEdge *ge : gf->edges()) {
3537  for(MLine *line : ge->lines) {
3538  MVertex *v1 = line->getVertex(0);
3539  MVertex *v2 = line->getVertex(1);
3540  if(v1->onWhat() && v1->onWhat()->dim() == 0) {
3541  corner2vertices[v1].push_back(v2);
3542  }
3543  if(v2->onWhat() && v2->onWhat()->dim() == 0) {
3544  corner2vertices[v2].push_back(v1);
3545  }
3546  }
3547  }
3548  for(auto &kv : corner2vertices) {
3549  if(kv.second.size() == 2) {
3550  MVertex *v = kv.first;
3551  MVertex *v2 = kv.second[0];
3552  MVertex *v3 = kv.second[1];
3553  double agl = angle3Vertices(v2, v, v3);
3554  if(agl < angle_threshold_rad) {
3555  acuteCorners[v].push_back(v2);
3556  acuteCorners[v].push_back(v3);
3557  }
3558  }
3559  }
3560 }
3561 
3563 {
3564  /* Collect acute corners */
3565  std::unordered_map<MVertex *, std::vector<MVertex *> > acuteCorners;
3566  double threshold = 30. * M_PI / 180.;
3567  for(GFace *gf : gm->getFaces()) {
3568  for(GEdge *ge : gf->edges())
3569  if(ge->lines.size() == 0) {
3570  Msg::Warning("Optimize 1D mesh at acute corners: no lines in curve %i",
3571  ge->tag());
3572  }
3573  if(gf->triangles.size() > 0 || gf->quadrangles.size() > 0) {
3574  Msg::Warning("Optimize 1D mesh at acute corners: elements in face %i",
3575  gf->tag());
3576  }
3577  getAcuteCorners(gf, acuteCorners, threshold);
3578  }
3579 
3580  /* Move vertices */
3581  size_t n = 0;
3582  for(auto &kv : acuteCorners) {
3583  MVertex *v = kv.first;
3584  /* Compute averaged distance */
3585  double avgLen = 0.;
3586  double avgN = 0.;
3587  double forcedLen = 0.;
3588  double forcedLenN = 0.;
3589  for(MVertex *v2 : kv.second) {
3590  double len = v->distance(v2);
3591  avgLen += len;
3592  avgN += 1.;
3593  if(v2->onWhat() && v2->onWhat()->dim() == 0) {
3594  forcedLen += len;
3595  forcedLenN += 1.;
3596  }
3597  }
3598  if(avgN > 0.) avgLen /= avgN;
3599  if(forcedLenN > 0.) forcedLen /= forcedLenN;
3600 
3601  /* Move vertices */
3602  for(MVertex *v2 : kv.second)
3603  if(v2->onWhat() && v2->onWhat()->dim() == 1) {
3604  SVector3 dir = v2->point() - v->point();
3605  if(dir.normSq() > 0.) { dir.normalize(); }
3606  double len = (forcedLen > 0.) ? forcedLen : avgLen;
3607  SPoint3 newPos = v->point() + len * dir;
3608  GEdge *ge = dynamic_cast<GEdge *>(v2->onWhat());
3609  double t = 0.;
3610  v2->getParameter(0, t);
3611  GPoint proj = ge->closestPoint(newPos, t);
3612  if(proj.succeeded()) {
3613  v2->setXYZ(proj.x(), proj.y(), proj.z());
3614  v2->setParameter(0, proj.u());
3615  n += 1;
3616  }
3617  }
3618  }
3619  if(n > 0) {
3620  Msg::Debug("optimize mesh 1D at acute corners: moved %li curve vertices",
3621  n);
3622  }
3623  return 0;
3624 }
PViewDataList::importList
void importList(int index, int n, const std::vector< double > &v, bool finalize)
Definition: PViewDataListIO.cpp:812
GPoint::succeeded
bool succeeded() const
Definition: GPoint.h:63
contextMeshOptions::recombineAll
int recombineAll
Definition: Context.h:30
MTriangle.h
robin_hood.h
replaceBadQuadDominantMeshes
int replaceBadQuadDominantMeshes(GModel *gm)
The initial unstructured quad-tri mesh may contain very bad configurations (e.g. valence 50+) due to ...
Definition: meshQuadQuasiStructured.cpp:3290
contextGeometryOptions::tolerance
double tolerance
Definition: Context.h:99
GFace::edges
virtual std::vector< GEdge * > const & edges() const
Definition: GFace.h:121
PView
Definition: PView.h:27
GFace::status
GEntity::MeshGenerationStatus status
Definition: GFace.h:395
Field.h
RefineMeshWithBackgroundMeshProjection
int RefineMeshWithBackgroundMeshProjection(GModel *gm)
Midpoint subdivision of the surface mesh with projections on the CAD surfaces, using the background m...
Definition: meshQuadQuasiStructured.cpp:3284
GFace::getMeshingAlgo
int getMeshingAlgo() const
Definition: GFace.cpp:63
FieldManager::reset
void reset()
Definition: Field.cpp:68
contextMeshOptions::quadqsTopoOptimMethods
int quadqsTopoOptimMethods
Definition: Context.h:53
GEdge::reparamOnFace
virtual SPoint2 reparamOnFace(const GFace *face, double epar, int dir) const
Definition: GEdge.cpp:482
dot
double dot(const SVector3 &a, const SMetric3 &m, const SVector3 &b)
Definition: STensor3.h:71
CppUtils
Definition: meshQuadQuasiStructured.h:15
Msg::GetVerbosity
static int GetVerbosity()
Definition: GmshMessage.cpp:254
GPoint::y
double y() const
Definition: GPoint.h:22
GFace::recombine
int recombine
Definition: GFace.h:344
SVector3::normSq
double normSq() const
Definition: SVector3.h:34
GenerateMesh
void GenerateMesh(GModel *m, int ask)
Definition: Generator.cpp:1649
PView::getViewByName
static PView * getViewByName(const std::string &name, int timeStep=-1, int partition=-1, const std::string &fileName="")
Definition: PView.cpp:347
MLine::getVertex
virtual MVertex * getVertex(int num)
Definition: MLine.h:45
isInitialized
static bool isInitialized
Definition: GmshGlobal.cpp:63
GModel::getFaces
std::set< GFace *, GEntityPtrLessThan > getFaces() const
Definition: GModel.h:376
GFace
Definition: GFace.h:33
GFace::triangles
std::vector< MTriangle * > triangles
Definition: GFace.h:428
MQuadrangle::getVertex
virtual MVertex * getVertex(int num)
Definition: MQuadrangle.h:64
Msg::Info
static void Info(const char *fmt,...)
Definition: GmshMessage.cpp:587
GEdge::lines
std::vector< MLine * > lines
Definition: GEdge.h:42
GEntity::model
GModel * model() const
Definition: GEntity.h:277
SPoint2
Definition: SPoint2.h:12
OS.h
Msg::Debug
static void Debug(const char *fmt,...)
Definition: GmshMessage.cpp:752
GEdge::meshSize
double meshSize
Definition: GEdge.h:252
GFace::algorithm
int algorithm
Definition: GFace.h:363
MESH_TRANSFINITE
#define MESH_TRANSFINITE
Definition: GmshDefines.h:259
PViewDataList
Definition: PViewDataList.h:17
MVertex
Definition: MVertex.h:24
CTX::numThreads
int numThreads
Definition: Context.h:169
GModel::getName
std::string getName() const
Definition: GModel.h:329
Msg::Warning
static void Warning(const char *fmt,...)
Definition: GmshMessage.cpp:543
Msg::Error
static void Error(const char *fmt,...)
Definition: GmshMessage.cpp:482
BackgroundMesh.h
GFace::transfiniteArrangement
int transfiniteArrangement
Definition: GFace.h:353
GFace::method
char method
Definition: GFace.h:348
GEdge::reverseMesh
bool reverseMesh
Definition: GEdge.h:259
backgroudMeshExists
bool backgroudMeshExists(const std::string &name)
Definition: BackgroundMesh.cpp:733
SPoint3
Definition: SPoint3.h:14
LegendrePolynomials::f
void f(int n, double u, double *val)
Definition: orthogonalBasis.cpp:77
contextMeshOptions::recombineOptimizeTopology
int recombineOptimizeTopology
Definition: Context.h:30
MVertex::getNum
std::size_t getNum() const
Definition: MVertex.h:86
ALGO_2D_PACK_PRLGRMS
#define ALGO_2D_PACK_PRLGRMS
Definition: GmshDefines.h:245
MFace::normal
SVector3 normal() const
Definition: MFace.cpp:204
GFace::meshAttributes
struct GFace::@18 meshAttributes
contextMeshOptions::lcMin
double lcMin
Definition: Context.h:25
SVector3
Definition: SVector3.h:16
intersection
SMetric3 intersection(const SMetric3 &m1, const SMetric3 &m2)
Definition: STensor3.cpp:119
PView::getIndex
int getIndex()
Definition: PView.h:92
meshGFaceOptimize.h
PView.h
GVertex::edges
virtual std::vector< GEdge * > const & edges() const
Definition: GVertex.h:54
GlobalBackgroundMesh::edgeBackgroundMeshes
std::unordered_map< GEdge *, BackgroundMeshGEdge > edgeBackgroundMeshes
Definition: BackgroundMesh.h:148
optimizeQuadMeshBoundaries
int optimizeQuadMeshBoundaries(GModel *gm)
Add one extruded quad layer on curves where the boundary quad valences are not ideal.
Definition: meshQuadQuasiStructured.cpp:3296
meshGEdge.h
contextMeshOptions::meshOnlyVisible
int meshOnlyVisible
Definition: Context.h:36
MTriangle::getVertex
virtual MVertex * getVertex(int num)
Definition: MTriangle.h:62
MVertex::onWhat
GEntity * onWhat() const
Definition: MVertex.h:82
Field::numComponents
virtual int numComponents() const
Definition: Field.h:114
SurfaceProjector::initialize
bool initialize(GFace *gf, const std::vector< MTriangle * > &triangles, bool useCADStl=false)
Fill the triangles and uvs from the triangles, then build the octree. Overwrite existing triangulatio...
Definition: meshOctreeLibOL.cpp:364
MVertex::point
SPoint3 point() const
Definition: MVertex.h:67
GmshMessage.h
MLine.h
edges
static int edges[6][2]
Definition: meshGRegionLocalMeshMod.cpp:23
GFace::meshSizeFactor
double meshSizeFactor
Definition: GFace.h:361
PViewData.h
SurfaceProjector
Class to project 3D points on a triangulated surface. If a parametrization is available,...
Definition: meshOctreeLibOL.h:24
global_bmeshes
std::vector< std::unique_ptr< GlobalBackgroundMesh > > global_bmeshes
Definition: BackgroundMesh.cpp:731
GPoint
Definition: GPoint.h:13
GFace::closestPoint
virtual GPoint closestPoint(const SPoint3 &queryPoint, const double initialGuess[2]) const
Definition: GFace.cpp:1323
GModel::addMVertexToVertexCache
void addMVertexToVertexCache(MVertex *v)
Definition: GModel.cpp:1970
MLine::setVertex
virtual void setVertex(int num, MVertex *v)
Definition: MLine.h:47
debugSurface
int debugSurface
Definition: meshGFace.cpp:3150
PViewData::setFileName
virtual void setFileName(const std::string &val)
Definition: PViewData.h:75
MElement::getNumVertices
virtual std::size_t getNumVertices() const =0
MElement::setVertex
virtual void setVertex(int num, MVertex *v)
Definition: MElement.h:109
GFace::quadrangles
std::vector< MQuadrangle * > quadrangles
Definition: GFace.h:429
contextMeshOptions::lcMax
double lcMax
Definition: Context.h:25
PView::setData
void setData(PViewData *val)
Definition: PView.h:86
transferSeamGEdgesVerticesToGFace
int transferSeamGEdgesVerticesToGFace(GModel *gm)
Mesh vertices on seam curves (and isolated corners) are reparametrized on the associated GFace and tr...
Definition: meshQuadQuasiStructured.cpp:3306
MLine
Definition: MLine.h:21
CppUtils::RestoreValueAtEndOfLife
Definition: meshQuadQuasiStructured.h:16
MFaceVertex::getParameter
virtual bool getParameter(int i, double &par) const
Definition: MVertex.h:184
GPoint::z
double z() const
Definition: GPoint.h:23
getAcuteCorners
void getAcuteCorners(GFace *gf, std::unordered_map< MVertex *, std::vector< MVertex * > > &acuteCorners, double angle_threshold_rad)
Definition: meshQuadQuasiStructured.cpp:3530
GFace::meshSizeFromBoundary
int meshSizeFromBoundary
Definition: GFace.h:365
MEdgeVertex::getLc
double getLc() const
Definition: MVertex.h:165
CTX::instance
static CTX * instance()
Definition: Context.cpp:122
MElement::getVertex
virtual const MVertex * getVertex(int num) const =0
SPoint3::x
double x(void) const
Definition: SPoint3.h:125
GEntity::periodic
virtual bool periodic(int dim) const
Definition: GEntity.h:244
optimize1DMeshAtAcuteCorners
int optimize1DMeshAtAcuteCorners(GModel *gm)
Identify face acute corners and set the first curve mesh vertices at same length from corner.
Definition: meshQuadQuasiStructured.cpp:3562
robin_hood::unordered_map
detail::Table< sizeof(robin_hood::pair< Key, T >)<=sizeof(size_t) *6 &&std::is_nothrow_move_constructible< robin_hood::pair< Key, T > >::value &&std::is_nothrow_move_assignable< robin_hood::pair< Key, T > >::value, MaxLoadFactor100, Key, T, Hash, KeyEqual > unordered_map
Definition: robin_hood.h:2509
MVertex::setParameter
virtual bool setParameter(int i, double par)
Definition: MVertex.h:102
SPoint3::data
const double * data() const
Definition: SPoint3.h:76
GFace::meshStatistics
struct GFace::@19 meshStatistics
GPoint::u
double u() const
Definition: GPoint.h:27
MQuadrangle::setVertex
virtual void setVertex(int num, MVertex *v)
Definition: MQuadrangle.h:66
GEntity::mesh_vertices
std::vector< MVertex * > mesh_vertices
Definition: GEntity.h:56
FieldManager::setBackgroundFieldId
void setBackgroundFieldId(int id)
Definition: Field.h:165
backgroundMeshAndGuidingFieldExists
bool backgroundMeshAndGuidingFieldExists(GModel *gm)
To check if a compatible background mesh and guiding field already exists.
Definition: meshQuadQuasiStructured.cpp:3259
PViewDataList::VP
std::vector< double > VP
Definition: PViewDataList.h:27
MEdgeVertex
Definition: MVertex.h:137
SurfaceProjector::closestPoint
GPoint closestPoint(const double query[3], bool evalOnCAD=false, bool projectOnCAD=false) const
Get the query closest point on the triangulated surface.
Definition: meshOctreeLibOL.cpp:567
GFace::recombineAngle
double recombineAngle
Definition: GFace.h:346
GVertex
Definition: GVertex.h:23
QuadqsContextUpdater::~QuadqsContextUpdater
~QuadqsContextUpdater()
Definition: meshQuadQuasiStructured.cpp:3470
ALGO_2D_FRONTAL
#define ALGO_2D_FRONTAL
Definition: GmshDefines.h:242
meshRefine.h
MESH_UNSTRUCTURED
#define MESH_UNSTRUCTURED
Definition: GmshDefines.h:260
GFace::embeddedVertices
std::set< GVertex *, GEntityPtrLessThan > & embeddedVertices()
Definition: GFace.h:160
MTriangle::getVolume
virtual double getVolume()
Definition: MTriangle.cpp:59
Numeric.h
FieldManager::get
Field * get(int id)
Definition: Field.cpp:74
contextMeshOptions::maxNumThreads2D
int maxNumThreads2D
Definition: Context.h:46
MElement::getFace
virtual MFace getFace(int num) const =0
GModel
Definition: GModel.h:44
Cpu
double Cpu()
Definition: OS.cpp:366
PView::getData
PViewData * getData(bool useAdaptiveIfAvailable=false)
Definition: PView.cpp:233
Msg::GetMaxThreads
static int GetMaxThreads()
Definition: GmshMessage.cpp:1637
GFace::meshSize
double meshSize
Definition: GFace.h:361
GEntity::DONE
@ DONE
Definition: GEntity.h:130
PViewOptions.h
PViewDataList.h
MVertex::setXYZ
void setXYZ(double x, double y, double z)
Definition: MVertex.h:68
QuadqsContextUpdater::setQuadqsOptions
void setQuadqsOptions()
Definition: meshQuadQuasiStructured.cpp:3472
FieldManager
Definition: Field.h:145
GEdge::meshAttributes
struct GEdge::@16 meshAttributes
SPoint3::y
double y(void) const
Definition: SPoint3.h:127
optimizeTopologyWithCavityRemeshing
int optimizeTopologyWithCavityRemeshing(GModel *gm)
Look for patches of quads with >=3 irregular vertices which can be remeshed with more regular quad me...
Definition: meshQuadQuasiStructured.cpp:3271
contextMeshOptions::minCurveNodes
int minCurveNodes
Definition: Context.h:37
PViewDataList::finalize
bool finalize(bool computeMinMax=true, const std::string &interpolationScheme="")
Definition: PViewDataList.cpp:81
PViewData::setName
virtual void setName(const std::string &val)
Definition: PViewData.h:71
CTX::mesh
contextMeshOptions mesh
Definition: Context.h:313
GlobalBackgroundMesh
Definition: BackgroundMesh.h:144
GEdge::isSeam
virtual bool isSeam(const GFace *face) const
Definition: GEdge.h:101
ALGO_2D_MESHADAPT
#define ALGO_2D_MESHADAPT
Definition: GmshDefines.h:238
deMeshGFace
Definition: meshGFace.h:30
GModel::getFields
FieldManager * getFields()
Definition: GModel.h:325
MElement
Definition: MElement.h:30
GFace::mesh
virtual void mesh(bool verbose)
Definition: GFace.cpp:2142
PViewDataGModel.h
angle3Vertices
double angle3Vertices(const MVertex *p1, const MVertex *p2, const MVertex *p3)
Definition: MVertex.cpp:16
GEntity::tag
int tag() const
Definition: GEntity.h:280
MFaceVertex
Definition: MVertex.h:168
GEdge::coeffTransfinite
double coeffTransfinite
Definition: GEdge.h:251
GModel::deleteMesh
void deleteMesh()
Definition: GModel.cpp:236
Field
Definition: Field.h:103
BuildBackgroundMeshAndGuidingField
int BuildBackgroundMeshAndGuidingField(GModel *gm, bool overwriteGModelMesh, bool deleteGModelMeshAfter, bool overwriteField, int N)
Definition: meshQuadQuasiStructured.cpp:3251
tMin
#define tMin
Definition: Gmsh.tab.cpp:358
FieldManager::setBackgroundMesh
void setBackgroundMesh(int iView)
Definition: Field.cpp:3280
CTX::geom
contextGeometryOptions geom
Definition: Context.h:311
SurfaceProjectorUtils::vec3
std::array< double, 3 > vec3
Definition: meshOctreeLibOL.cpp:28
GEdge::vertices
virtual std::vector< GVertex * > vertices() const
Definition: GEdge.cpp:971
GEdge::faces
virtual std::vector< GFace * > faces() const
Definition: GEdge.h:113
GFace::getMeshElement
MElement * getMeshElement(std::size_t index) const
Definition: GFace.cpp:234
MTriangle
Definition: MTriangle.h:26
GlobalBackgroundMesh::importGModelMeshes
int importGModelMeshes(GModel *gm, bool overwriteExisting=true)
Fill the entityMesh map by copying the meshes in the GModel. New MVertex, MLine and MTriangle instanc...
Definition: BackgroundMesh.cpp:764
GEdge::typeTransfinite
int typeTransfinite
Definition: GEdge.h:254
contextMeshOptions::minCircleNodes
int minCircleNodes
Definition: Context.h:37
GModel::writeMSH
int writeMSH(const std::string &name, double version=2.2, bool binary=false, bool saveAll=false, bool saveParametric=false, double scalingFactor=1.0, int elementStartNum=0, int saveSinglePartition=0, bool append=false)
Definition: GModelIO_MSH.cpp:76
FieldManager::getBackgroundField
int getBackgroundField()
Definition: Field.h:178
optimizeTopologyWithDiskQuadrangulationRemeshing
int optimizeTopologyWithDiskQuadrangulationRemeshing(GModel *gm)
Look for non-ideal vertex valences in quad mesh and find a better local remeshing by looking into all...
Definition: meshQuadQuasiStructured.cpp:3265
GFace::setMeshingAlgo
void setMeshingAlgo(int val)
Definition: GFace.h:371
recombineIntoQuads
void recombineIntoQuads(GFace *gf, bool blossom, int topologicalOptiPasses, bool nodeRepositioning, double minqual)
Definition: meshGFaceOptimize.cpp:1311
contextMeshOptions::quadqsScalingOnTriangulation
double quadqsScalingOnTriangulation
Definition: Context.h:54
MVertex::getParameter
virtual bool getParameter(int i, double &par) const
Definition: MVertex.h:97
meshGFace.h
PView::write
bool write(const std::string &fileName, int format, bool append=false)
Definition: PViewIO.cpp:378
GEdge::closestPoint
virtual GPoint closestPoint(const SPoint3 &queryPoint, double &param) const
Definition: GEdge.cpp:552
CTX::debugSurface
int debugSurface
Definition: Context.h:138
Context.h
GFace::transfiniteSmoothing
int transfiniteSmoothing
Definition: GFace.h:355
Field::options
std::map< std::string, FieldOption * > options
Definition: Field.h:112
QuadqsContextUpdater::restoreInitialOption
void restoreInitialOption()
Definition: meshQuadQuasiStructured.cpp:3492
GEdge::parBounds
virtual Range< double > parBounds(int i) const =0
backgroundMesh::unset
static void unset()
Definition: BackgroundMesh.cpp:60
GEdge::meshSizeFactor
double meshSizeFactor
Definition: GEdge.h:252
tMax
#define tMax
Definition: Gmsh.tab.cpp:359
QuadqsContextUpdater::QuadqsContextUpdater
QuadqsContextUpdater()
Definition: meshQuadQuasiStructured.cpp:3392
GFace::embeddedEdges
std::vector< GEdge * > & embeddedEdges()
Definition: GFace.h:156
z
const double z
Definition: GaussQuadratureQuad.cpp:56
MQuadrangle.h
contextMeshOptions::lcFactor
double lcFactor
Definition: Context.h:21
contextMeshOptions::algo2d
int algo2d
Definition: Context.h:29
ALGO_2D_INITIAL_ONLY
#define ALGO_2D_INITIAL_ONLY
Definition: GmshDefines.h:240
contextMeshOptions::quadqsSizemapMethod
int quadqsSizemapMethod
Definition: Context.h:53
MElement.h
GEdge
Definition: GEdge.h:26
PView::getOptions
PViewOptions * getOptions()
Definition: PView.h:81
robin_hood::unordered_set
detail::Table< sizeof(Key)<=sizeof(size_t) *6 &&std::is_nothrow_move_constructible< Key >::value &&std::is_nothrow_move_assignable< Key >::value, MaxLoadFactor100, Key, void, Hash, KeyEqual > unordered_set
Definition: robin_hood.h:2526
quadqsCleanup
int quadqsCleanup(GModel *gm)
Delete background meshes and fields that have been used by quadqs meshing/remeshing.
Definition: meshQuadQuasiStructured.cpp:3507
MElement::barycenter
virtual SPoint3 barycenter(bool primary=false) const
Definition: MElement.cpp:520
backgroundMesh::current
static backgroundMesh * current()
Definition: BackgroundMesh.cpp:68
SPoint3::z
double z(void) const
Definition: SPoint3.h:129
meshGRegion.h
GPoint::v
double v() const
Definition: GPoint.h:28
GVertex::reparamOnFace
virtual SPoint2 reparamOnFace(const GFace *gf, int) const
Definition: GVertex.cpp:49
RefineMesh
void RefineMesh(GModel *m, bool linear, bool splitIntoQuads, bool splitIntoHexas)
Definition: meshRefine.cpp:463
GModel.h
PViewData::getName
virtual std::string getName()
Definition: PViewData.h:70
GFace::normal
virtual SVector3 normal(const SPoint2 &param) const
Definition: GFace.cpp:1416
contextMeshOptions::algoRecombine
int algoRecombine
Definition: Context.h:30
meshQuadQuasiStructured.h
GEdge::nbPointsTransfinite
int nbPointsTransfinite
Definition: GEdge.h:253
line
Definition: shapeFunctions.h:342
GFace::getNumMeshElements
std::size_t getNumMeshElements() const
Definition: GFace.cpp:181
GlobalBackgroundMesh::faceBackgroundMeshes
std::unordered_map< GFace *, BackgroundMeshGFace > faceBackgroundMeshes
Definition: BackgroundMesh.h:149
PView::list
static std::vector< PView * > list
Definition: PView.h:112
MVertex::distance
double distance(MVertex *const v)
Definition: MVertex.h:105
GEntity::dim
virtual int dim() const
Definition: GEntity.h:196
Generator.h
GPoint::x
double x() const
Definition: GPoint.h:21
GEntity::getVisibility
virtual char getVisibility()
Definition: GEntity.cpp:35
GEntity::PENDING
@ PENDING
Definition: GEntity.h:130
CTX::lock
int lock
Definition: Context.h:252
MQuadrangle
Definition: MQuadrangle.h:26
ALGO_2D_QUAD_QUASI_STRUCT
#define ALGO_2D_QUAD_QUASI_STRUCT
Definition: GmshDefines.h:247
GEdge::minimumMeshSegments
int minimumMeshSegments
Definition: GEdge.h:255
robin_hood::detail::Table
Definition: robin_hood.h:916
contextMeshOptions::lcFromPoints
int lcFromPoints
Definition: Context.h:27
meshOctreeLibOL.h
Msg::GetErrorCount
static int GetErrorCount()
Definition: GmshMessage.cpp:347
GEdge::method
char method
Definition: GEdge.h:250
GFace::reverseMesh
bool reverseMesh
Definition: GFace.h:359
GModel::current
static GModel * current(int index=-1)
Definition: GModel.cpp:136
PViewOptions::visible
int visible
Definition: PViewOptions.h:54
GEntity::haveParametrization
virtual bool haveParametrization()
Definition: GEntity.h:251
getBackgroundMesh
GlobalBackgroundMesh & getBackgroundMesh(const std::string &name)
Definition: BackgroundMesh.cpp:741
quadMeshingOfSimpleFacesWithPatterns
int quadMeshingOfSimpleFacesWithPatterns(GModel *gm, double minimumQualityRequired)
Look for simple CAD faces (topological disk, a few corners) which can be remeshed with simple quad pa...
Definition: meshQuadQuasiStructured.cpp:3277
SVector3::normalize
double normalize()
Definition: SVector3.h:38
faces
static int faces[4][3]
Definition: meshGRegionDelaunayInsertion.cpp:165
CTX::abortOnError
int abortOnError
Definition: Context.h:153