diff --git a/src/common/libmmgtypes.h b/src/common/libmmgtypes.h index fe75d6193..591d5d721 100644 --- a/src/common/libmmgtypes.h +++ b/src/common/libmmgtypes.h @@ -40,6 +40,50 @@ #ifndef _LIBMMGTYPES_H #define _LIBMMGTYPES_H +/** + * \enum MMG5_progressPhase + * \brief Phases reported by the progress callback. + */ +enum MMG5_progressPhase { + MMG5_PHASE_GEOMETRIC_MESH, /*!< Geometric mesh analysis and splitting */ + MMG5_PHASE_COMPUTATIONAL_MESH, /*!< Metric definition and gradation */ + MMG5_PHASE_ADAPTATION, /*!< Main adaptation loop + (split/collapse/swap/move) */ + MMG5_PHASE_OPTIMIZATION /*!< Final quality optimization (swap/move) */ +}; + +/** + * \typedef MMG5_progressCallback + * \brief Callback function type for progress reporting. + * + * Called at each iteration within a phase to report progress. + * + * \param mesh pointer to the mesh structure (opaque to caller). + * \param phase current phase (\ref MMG5_progressPhase). + * \param iteration current iteration within the phase (0-based). + * \param max_iterations maximum iterations for this phase. + * \param n_split number of edge splits performed in this iteration. + * \param n_collapse number of edge collapses performed in this iteration. + * \param n_swap number of edge swaps performed in this iteration. + * \param n_move number of vertex moves performed in this iteration. + * \param user_data opaque pointer passed by the user. + * + * \return 1 to continue, 0 to request cancellation. + * + * \remark The callback is only invoked when set (non-NULL). When not set, + * there is zero overhead — no function pointer check occurs in the hot + * loops. + */ +typedef int (*MMG5_progressCallback)(void *mesh, + int phase, + int iteration, + int max_iterations, + int64_t n_split, + int64_t n_collapse, + int64_t n_swap, + int64_t n_move, + void *user_data); + /** * \def MMG5_SUCCESS * @@ -561,6 +605,12 @@ typedef struct { MMG5_pMat mat; MMG5_InvMat invmat; + + /* Progress callback (optional, NULL by default) */ + MMG5_progressCallback progressCb; /**< Progress callback (NULL = + disabled, zero overhead) */ + void *progressData; /**< Opaque user data forwarded + to progressCb */ } MMG5_Info; /** diff --git a/src/mmg2d/API_functions_2d.c b/src/mmg2d/API_functions_2d.c index 3205094b3..503dd48fe 100644 --- a/src/mmg2d/API_functions_2d.c +++ b/src/mmg2d/API_functions_2d.c @@ -1950,3 +1950,11 @@ int MMG2D_Free_names(const int starter,...) return ier; } + +int MMG2D_Set_progressCallback(MMG5_pMesh mesh, + MMG5_progressCallback callback, + void *user_data) { + mesh->info.progressCb = callback; + mesh->info.progressData = user_data; + return 1; +} diff --git a/src/mmg2d/libmmg2d.h b/src/mmg2d/libmmg2d.h index f0097d566..501d27a62 100644 --- a/src/mmg2d/libmmg2d.h +++ b/src/mmg2d/libmmg2d.h @@ -2769,6 +2769,21 @@ LIBMMG2D_EXPORT int MMG2D_Free_all(const int starter,...); */ LIBMMG2D_EXPORT int MMG2D_scaleMesh(MMG5_pMesh mesh,MMG5_pSol met,MMG5_pSol ls); +/** + * \brief Set a progress callback for the remeshing process. + * + * \param mesh pointer to the mesh structure. + * \param callback progress callback function (NULL to disable). + * \param user_data opaque pointer forwarded to every callback invocation. + * + * \return 1 on success. + * + * \sa MMG5_progressCallback, MMG3D_Set_progressCallback. + */ + LIBMMG2D_EXPORT int MMG2D_Set_progressCallback(MMG5_pMesh mesh, + MMG5_progressCallback callback, + void *user_data); + #ifdef __cplusplus } #endif diff --git a/src/mmg2d/mmg2d1.c b/src/mmg2d/mmg2d1.c index 4b4f7704e..247afff32 100644 --- a/src/mmg2d/mmg2d1.c +++ b/src/mmg2d/mmg2d1.c @@ -96,6 +96,25 @@ int MMG2D_anatri(MMG5_pMesh mesh,MMG5_pSol met,int8_t typchk) { if ( (abs(mesh->info.imprim) > 4 || mesh->info.ddebug) && ns+nc > 0 ) fprintf(stdout," %8" MMG5_PRId " splitted, %8" MMG5_PRId " collapsed, %8" MMG5_PRId " swapped\n",ns,nc,nsw); + + /* Progress callback */ + if ( mesh->info.progressCb ) { + int phase = (typchk == 1) ? + MMG5_PHASE_GEOMETRIC_MESH : + MMG5_PHASE_COMPUTATIONAL_MESH; + if ( !mesh->info.progressCb( + mesh, phase, it, maxit, + (int64_t)ns, (int64_t)nc, + (int64_t)nsw, 0, + mesh->info.progressData) ) { + fprintf(stderr, + "\n ## %s cancelled by user callback.\n", + (typchk == 1) ? + "Geometric mesh" : "Computational mesh"); + return 0; + } + } + if ( it > 3 && MMG5_abs(nc-ns) < 0.1 * MG_MAX(nc,ns) ) break; } while ( ++it < maxit && ns+nc+nsw >0 ); @@ -606,6 +625,17 @@ int MMG2D_adptri(MMG5_pMesh mesh,MMG5_pSol met) { if ( (abs(mesh->info.imprim) > 4 || mesh->info.ddebug) && ns+nc+nsw+nm > 0 ) fprintf(stdout," %8" MMG5_PRId " splitted, %8" MMG5_PRId " collapsed, %8" MMG5_PRId " swapped, %8" MMG5_PRId " moved\n",ns,nc,nsw,nm); + + /* Progress callback */ + if ( mesh->info.progressCb ) { + if ( !mesh->info.progressCb(mesh, MMG5_PHASE_ADAPTATION, it, maxit, + (int64_t)ns, (int64_t)nc, (int64_t)nsw, + (int64_t)nm, mesh->info.progressData) ) { + fprintf(stderr,"\n ## Remeshing cancelled by user callback.\n"); + return 0; + } + } + if ( ns < 10 && MMG5_abs(nc-ns) < 3 ) break; else if ( it > 3 && MMG5_abs(nc-ns) < 0.3 * MG_MAX(nc,ns) ) break; } diff --git a/src/mmg3d/API_functions_3d.c b/src/mmg3d/API_functions_3d.c index d86290777..9d2b4c8fc 100644 --- a/src/mmg3d/API_functions_3d.c +++ b/src/mmg3d/API_functions_3d.c @@ -2585,3 +2585,11 @@ int MMG3D_Free_names(const int starter,...) return ier; } + +int MMG3D_Set_progressCallback(MMG5_pMesh mesh, + MMG5_progressCallback callback, + void *user_data) { + mesh->info.progressCb = callback; + mesh->info.progressData = user_data; + return 1; +} diff --git a/src/mmg3d/libmmg3d.h b/src/mmg3d/libmmg3d.h index 26d3c265c..e0d5054dc 100644 --- a/src/mmg3d/libmmg3d.h +++ b/src/mmg3d/libmmg3d.h @@ -3458,6 +3458,31 @@ LIBMMG3D_EXPORT int MMG3D_loadVtuMesh_and_allData(MMG5_pMesh mesh,MMG5_pSol *sol * functions. */ LIBMMG3D_EXPORT void MMG3D_Set_commonFunc(void); + +/** + * \brief Set a progress callback for the remeshing process. + * + * \param mesh pointer to the mesh structure. + * \param callback progress callback function (NULL to disable). + * \param user_data opaque pointer forwarded to every callback invocation. + * + * \return 1 on success. + * + * The callback is invoked at each iteration of every remeshing phase: + * geometric mesh, computational mesh, adaptation loop, and optimization. + * Returning 0 from the callback requests graceful cancellation. + * + * When \a callback is NULL (the default), no function-pointer check is + * performed in the hot loops, so there is zero overhead. + * + * \remark No Fortran interface. + * + * \sa MMG5_progressCallback for the callback signature. + */ + LIBMMG3D_EXPORT int MMG3D_Set_progressCallback(MMG5_pMesh mesh, + MMG5_progressCallback callback, + void *user_data); + #ifdef __cplusplus } #endif diff --git a/src/mmg3d/mmg3d1.c b/src/mmg3d/mmg3d1.c index dc005e3c7..58f4d790a 100644 --- a/src/mmg3d/mmg3d1.c +++ b/src/mmg3d/mmg3d1.c @@ -3263,6 +3263,24 @@ int MMG5_anatet(MMG5_pMesh mesh,MMG5_pSol met,int8_t typchk, int patternMode) { fprintf(stdout," %8" MMG5_PRId " splitted, %8" MMG5_PRId " collapsed, %8" MMG5_PRId " swapped\n",ns,nc,nf); } + /* Progress callback */ + if ( mesh->info.progressCb ) { + int phase = (typchk == 1) ? + MMG5_PHASE_GEOMETRIC_MESH : + MMG5_PHASE_COMPUTATIONAL_MESH; + if ( !mesh->info.progressCb( + mesh, phase, it, maxit, + (int64_t)ns, (int64_t)nc, + (int64_t)nf, 0, + mesh->info.progressData) ) { + fprintf(stderr, + "\n ## %s cancelled by user callback.\n", + (typchk == 1) ? + "Geometric mesh" : "Computational mesh"); + return 0; + } + } + if ( it > minit-1 && ( !(ns+nc) || (MMG5_abs(nc-ns) < 0.1 * MG_MAX(nc,ns)) ) ) { ++lastit; if ( it > minit && lastit>2 ) break; diff --git a/src/mmg3d/mmg3d1_delone.c b/src/mmg3d/mmg3d1_delone.c index 62911114b..f1d39c7d9 100644 --- a/src/mmg3d/mmg3d1_delone.c +++ b/src/mmg3d/mmg3d1_delone.c @@ -614,6 +614,16 @@ int MMG5_optbad(MMG5_pMesh mesh, MMG5_pSol met,MMG3D_pPROctree PROctree) { fprintf(stdout," "); fprintf(stdout," %8" MMG5_PRId " improved, %8" MMG5_PRId " swapped, %8" MMG5_PRId " moved\n",nw,nf,nm); } + + /* Progress callback */ + if ( mesh->info.progressCb ) { + if ( !mesh->info.progressCb(mesh, MMG5_PHASE_OPTIMIZATION, it, maxit, + 0, 0, (int64_t)nf, (int64_t)nm, + mesh->info.progressData) ) { + fprintf(stderr,"\n ## Optimization cancelled by user callback.\n"); + return 0; + } + } } while( ++it < maxit && nw+nm+nf > 0 ); @@ -716,10 +726,22 @@ int MMG5_adpdel(MMG5_pMesh mesh,MMG5_pSol met,MMG3D_pPROctree *PROctree, int* wa fprintf(stdout," %8"MMG5_PRId" filtered, %8" MMG5_PRId " splitted, %8" MMG5_PRId " collapsed," " %8" MMG5_PRId " swapped, %8" MMG5_PRId " moved\n",ifilt,ns,nc,nf,nm); + /* Progress callback */ + if ( mesh->info.progressCb ) { + if ( !mesh->info.progressCb(mesh, MMG5_PHASE_ADAPTATION, it, maxit, + (int64_t)ns, (int64_t)nc, (int64_t)nf, + (int64_t)nm, mesh->info.progressData) ) { + fprintf(stderr,"\n ## Remeshing cancelled by user callback.\n"); + return 0; + } + } + /*optimization*/ dd = MMG5_abs(nc-ns); if ( !noptim && (it==5 || ((dd < 5) || (dd < 0.05*MG_MAX(nc,ns)) || !(ns+nc))) ) { - MMG5_optbad(mesh,met,*PROctree); + if ( !MMG5_optbad(mesh,met,*PROctree) ) { + return 0; + } noptim = 1; } @@ -806,6 +828,16 @@ int MMG5_optetLES(MMG5_pMesh mesh, MMG5_pSol met,MMG3D_pPROctree PROctree) { fprintf(stdout," "); fprintf(stdout," %8" MMG5_PRId " improved, %8" MMG5_PRId " swapped, %8" MMG5_PRId " moved\n",nw,nf,nm); } + + /* Progress callback */ + if ( mesh->info.progressCb ) { + if ( !mesh->info.progressCb(mesh, MMG5_PHASE_OPTIMIZATION, it, maxit, + 0, 0, (int64_t)nf, (int64_t)nm, + mesh->info.progressData) ) { + fprintf(stderr,"\n ## Optimization cancelled by user callback.\n"); + return 0; + } + } } while( ++it < maxit && nw+nm+nf > 0 ); @@ -911,6 +943,16 @@ int MMG5_optet(MMG5_pMesh mesh, MMG5_pSol met,MMG3D_pPROctree PROctree) { fprintf(stdout," %8" MMG5_PRId " improved, %8" MMG5_PRId " swapped, %8" MMG5_PRId " moved\n",nw,nf,nm); } + /* Progress callback */ + if ( mesh->info.progressCb ) { + if ( !mesh->info.progressCb(mesh, MMG5_PHASE_OPTIMIZATION, it, maxit, + 0, 0, (int64_t)nf, (int64_t)nm, + mesh->info.progressData) ) { + fprintf(stderr,"\n ## Optimization cancelled by user callback.\n"); + return 0; + } + } + if ( it > 3 ) { if ( !nw && (!nm || !nf) ) break; } diff --git a/src/mmg3d/mmg3d1_pattern.c b/src/mmg3d/mmg3d1_pattern.c index 643271929..3e66d7fe9 100644 --- a/src/mmg3d/mmg3d1_pattern.c +++ b/src/mmg3d/mmg3d1_pattern.c @@ -323,6 +323,17 @@ static int MMG5_adptet(MMG5_pMesh mesh,MMG5_pSol met,MMG5_int *permNodGlob) { if ( (abs(mesh->info.imprim) > 4 || mesh->info.ddebug) && ns+nc > 0 ) fprintf(stdout," %8" MMG5_PRId " splitted, %8" MMG5_PRId " collapsed, %8" MMG5_PRId " swapped, %8" MMG5_PRId " moved\n",ns,nc,nf,nm); + + /* Progress callback */ + if ( mesh->info.progressCb ) { + if ( !mesh->info.progressCb(mesh, MMG5_PHASE_ADAPTATION, it, maxit, + (int64_t)ns, (int64_t)nc, (int64_t)nf, + (int64_t)nm, mesh->info.progressData) ) { + fprintf(stderr,"\n ## Remeshing cancelled by user callback.\n"); + return 0; + } + } + if ( ns < 10 && MMG5_abs(nc-ns) < 3 ) break; else if ( it > 3 && MMG5_abs(nc-ns) < 0.3 * MG_MAX(nc,ns) ) break; } @@ -387,6 +398,16 @@ static int MMG5_adptet(MMG5_pMesh mesh,MMG5_pSol met,MMG5_int *permNodGlob) { fprintf(stdout," "); fprintf(stdout,"%8" MMG5_PRId " swapped, %8" MMG5_PRId " moved\n",nf,nm); } + + /* Progress callback */ + if ( mesh->info.progressCb ) { + if ( !mesh->info.progressCb(mesh, MMG5_PHASE_OPTIMIZATION, it, maxit, + 0, 0, (int64_t)nf, (int64_t)nm, + mesh->info.progressData) ) { + fprintf(stderr,"\n ## Optimization cancelled by user callback.\n"); + return 0; + } + } } while( ++it < maxit && /*nw+*/nm+nf > 0 ); diff --git a/src/mmgs/API_functions_s.c b/src/mmgs/API_functions_s.c index 71cefa643..c99873cb3 100644 --- a/src/mmgs/API_functions_s.c +++ b/src/mmgs/API_functions_s.c @@ -1728,3 +1728,11 @@ int MMGS_Free_names(const int starter,...) return ier; } + +int MMGS_Set_progressCallback(MMG5_pMesh mesh, + MMG5_progressCallback callback, + void *user_data) { + mesh->info.progressCb = callback; + mesh->info.progressData = user_data; + return 1; +} diff --git a/src/mmgs/libmmgs.h b/src/mmgs/libmmgs.h index c9de5efd5..4c0361617 100644 --- a/src/mmgs/libmmgs.h +++ b/src/mmgs/libmmgs.h @@ -2576,6 +2576,21 @@ LIBMMGS_EXPORT void MMGS_Free_solutions(MMG5_pMesh mesh,MMG5_pSol sol); */ LIBMMGS_EXPORT void MMGS_Set_commonFunc(void); +/** + * \brief Set a progress callback for the remeshing process. + * + * \param mesh pointer to the mesh structure. + * \param callback progress callback function (NULL to disable). + * \param user_data opaque pointer forwarded to every callback invocation. + * + * \return 1 on success. + * + * \sa MMG5_progressCallback, MMG3D_Set_progressCallback. + */ +LIBMMGS_EXPORT int MMGS_Set_progressCallback(MMG5_pMesh mesh, + MMG5_progressCallback callback, + void *user_data); + #ifdef __cplusplus } #endif diff --git a/src/mmgs/mmgs1.c b/src/mmgs/mmgs1.c index e619e8527..499e5a3fa 100644 --- a/src/mmgs/mmgs1.c +++ b/src/mmgs/mmgs1.c @@ -1324,6 +1324,17 @@ static int adptri(MMG5_pMesh mesh,MMG5_pSol met,MMG5_int* permNodGlob) { nnm += nm; if ( (abs(mesh->info.imprim) > 4 || mesh->info.ddebug) && ns+nc+nf+nm > 0 ) fprintf(stdout," %8" MMG5_PRId " splitted, %8" MMG5_PRId " collapsed, %8" MMG5_PRId " swapped, %8" MMG5_PRId " moved\n",ns,nc,nf,nm); + + /* Progress callback */ + if ( mesh->info.progressCb ) { + if ( !mesh->info.progressCb(mesh, MMG5_PHASE_ADAPTATION, it, maxit, + (int64_t)ns, (int64_t)nc, (int64_t)nf, + (int64_t)nm, mesh->info.progressData) ) { + fprintf(stderr,"\n ## Remeshing cancelled by user callback.\n"); + return 0; + } + } + if ( ns < 10 && MMG5_abs(nc-ns) < 3 ) break; else if ( it > 3 && MMG5_abs(nc-ns) < 0.3 * MG_MAX(nc,ns) ) break; } @@ -1362,6 +1373,16 @@ static int adptri(MMG5_pMesh mesh,MMG5_pSol met,MMG5_int* permNodGlob) { fprintf(stdout," "); fprintf(stdout,"%8" MMG5_PRId " swapped, %8" MMG5_PRId " moved\n",nf,nm); } + + /* Progress callback */ + if ( mesh->info.progressCb ) { + if ( !mesh->info.progressCb(mesh, MMG5_PHASE_OPTIMIZATION, it, maxit, + 0, 0, (int64_t)nf, (int64_t)nm, + mesh->info.progressData) ) { + fprintf(stderr,"\n ## Optimization cancelled by user callback.\n"); + return 0; + } + } } while( ++it < maxit && nm+nf > 0 ); @@ -1438,6 +1459,25 @@ static int anatri(MMG5_pMesh mesh,MMG5_pSol met,int8_t typchk) { nnf += nf; if ( (abs(mesh->info.imprim) > 4 || mesh->info.ddebug) && ns+nc > 0 ) fprintf(stdout," %8" MMG5_PRId " splitted, %8" MMG5_PRId " collapsed, %8" MMG5_PRId " swapped\n",ns,nc,nf); + + /* Progress callback */ + if ( mesh->info.progressCb ) { + int phase = (typchk == 1) ? + MMG5_PHASE_GEOMETRIC_MESH : + MMG5_PHASE_COMPUTATIONAL_MESH; + if ( !mesh->info.progressCb( + mesh, phase, it, maxit, + (int64_t)ns, (int64_t)nc, + (int64_t)nf, 0, + mesh->info.progressData) ) { + fprintf(stderr, + "\n ## %s cancelled by user callback.\n", + (typchk == 1) ? + "Geometric mesh" : "Computational mesh"); + return 0; + } + } + if ( it > 3 && MMG5_abs(nc-ns) < 0.1 * MG_MAX(nc,ns) ) break; } while ( ++it < maxit && ns+nc+nf > 0 );