diff --git a/scripts/Linux/0_setup_env.sh b/scripts/Linux/0_setup_env.sh index 9df9aef78..2462b807c 100644 --- a/scripts/Linux/0_setup_env.sh +++ b/scripts/Linux/0_setup_env.sh @@ -5,8 +5,8 @@ # Run this script if you never installed any of the MeshLab dependencies. DONT_INSTALL_QT=false -DONT_INSTALL_CGAL_BOOST=false -DONT_INSTALL_EMBREE=false +DONT_INSTALL_CGAL_BOOST=true +DONT_INSTALL_EMBREE=true #checking for parameters for i in "$@" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f8e80e533..b7c6a5867 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -161,6 +161,7 @@ if(NOT DEFINED MESHLAB_PLUGINS) # it may be already defined in parent directory meshlabplugins/filter_layer meshlabplugins/filter_measure meshlabplugins/filter_mesh_booleans + meshlabplugins/filter_mesh_alpha_wrap meshlabplugins/filter_meshing meshlabplugins/filter_mls meshlabplugins/filter_mutualglobal diff --git a/src/external/cgal.cmake b/src/external/cgal.cmake index 4317336c4..22fdb6802 100644 --- a/src/external/cgal.cmake +++ b/src/external/cgal.cmake @@ -13,7 +13,7 @@ if(MESHLAB_ALLOW_SYSTEM_CGAL AND TARGET CGAL::CGAL) add_library(external-cgal INTERFACE) target_link_libraries(external-cgal INTERFACE CGAL::CGAL Threads::Threads) elseif(MESHLAB_ALLOW_DOWNLOAD_SOURCE_CGAL) - set(CGAL_DIR "${MESHLAB_EXTERNAL_DOWNLOAD_DIR}/CGAL-5.2.1") + set(CGAL_DIR "${MESHLAB_EXTERNAL_DOWNLOAD_DIR}/CGAL-5.6") set(CGAL_CHECK "${CGAL_DIR}/include/CGAL/version.h") set(CGAL_WIN_CHECK "${CGAL_DIR}/auxiliary/gmp/lib/libmpfr-4.lib") @@ -21,9 +21,10 @@ elseif(MESHLAB_ALLOW_DOWNLOAD_SOURCE_CGAL) if (NOT EXISTS ${CGAL_CHECK}) set(CGAL_LINK - https://github.com/CGAL/cgal/releases/download/v5.2.1/CGAL-5.2.1.zip - https://www.meshlab.net/data/libs/CGAL-5.2.1.zip) - set(CGAL_MD5 1a999ab90ea4cf28f6aa17ea2af24876) + https://github.com/CGAL/cgal/releases/download/v5.6/CGAL-5.6.zip + # https://www.meshlab.net/data/libs/CGAL-5.6.zip # Note by LvdS: please host this file on the MeshLab site as well, then remove this comment + ) + set(CGAL_MD5 6d1d067b88e20f7080d07d5108b4c772) download_and_unzip( NAME "CGAL" LINK ${CGAL_LINK} @@ -37,8 +38,9 @@ elseif(MESHLAB_ALLOW_DOWNLOAD_SOURCE_CGAL) if (WIN32 AND NOT EXISTS ${CGAL_WIN_CHECK}) set(CGAL_AUX_LINK - https://github.com/CGAL/cgal/releases/download/v5.2.1/CGAL-5.2.1-win64-auxiliary-libraries-gmp-mpfr.zip - https://www.meshlab.net/data/libs/CGAL-5.2.1-win64-auxiliary-libraries-gmp-mpfr.zip) + https://github.com/CGAL/cgal/releases/download/v5.6/CGAL-5.6-win64-auxiliary-libraries-gmp-mpfr.zip + # https://www.meshlab.net/data/libs/CGAL-5.6-win64-auxiliary-libraries-gmp-mpfr.zip # Note by LvdS: please host this file on the MeshLab site as well, then remove this comment + ) set(CGAL_AUX_MD5 247f4dca741c6b9a9be76286414070fa) download_and_unzip( NAME "CGAL auxiliary libraries" diff --git a/src/meshlabplugins/filter_mesh_alpha_wrap/CMakeLists.txt b/src/meshlabplugins/filter_mesh_alpha_wrap/CMakeLists.txt new file mode 100644 index 000000000..e528ddf9d --- /dev/null +++ b/src/meshlabplugins/filter_mesh_alpha_wrap/CMakeLists.txt @@ -0,0 +1,14 @@ +# Copyright 2023, PTC + +if (TARGET external-boost AND TARGET external-cgal AND TARGET external-libigl) + set(SOURCES filter_mesh_alpha_wrap.cpp) + + set(HEADERS filter_mesh_alpha_wrap.h) + + add_meshlab_plugin(filter_mesh_alpha_wrap ${SOURCES} ${HEADERS}) + + target_link_libraries(filter_mesh_alpha_wrap PRIVATE external-boost external-cgal external-libigl) +else() + message( + STATUS "Skipping filter_mesh_alpha_wrap - don't know about boost, cgal or libigl on this system.") +endif() diff --git a/src/meshlabplugins/filter_mesh_alpha_wrap/filter_mesh_alpha_wrap.cpp b/src/meshlabplugins/filter_mesh_alpha_wrap/filter_mesh_alpha_wrap.cpp new file mode 100644 index 000000000..d8b14291f --- /dev/null +++ b/src/meshlabplugins/filter_mesh_alpha_wrap/filter_mesh_alpha_wrap.cpp @@ -0,0 +1,319 @@ +/***************************************************************************** + * MeshLab - Alpha Wrap plugin o o * + * by PTC, based on CGAL o o * + * _ O _ * + * Copyright(C) 2023 \/)\/ * + * PTC /\/| * + * Lex van der Sluijs | * + * \ * + * All rights reserved. * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License (http://www.gnu.org/licenses/gpl.txt) * + * for more details. * + * * + ****************************************************************************/ + +#include "filter_mesh_alpha_wrap.h" + +#include + +#include +#include +#include +#include "CGAL/alpha_wrap_3.h" +#include "qdebug.h" + + +namespace PMP = CGAL::Polygon_mesh_processing; +using K = CGAL::Exact_predicates_inexact_constructions_kernel; +using Point_3 = K::Point_3; +using CgalMesh = CGAL::Surface_mesh; + +typedef CgalMesh::Vertex_index vertex_descriptor; +typedef CgalMesh::Face_index face_descriptor; + +typedef boost::graph_traits::vertex_descriptor boost_vertex_descriptor; + +/** + * @brief + * Constructor usually performs only two simple tasks of filling the two lists + * - typeList: with all the possible id of the filtering actions + * - actionList with the corresponding actions. + * If you want to add icons to your filtering actions you can do here by construction the QActions + * accordingly + */ +FilterMeshAlphaWrap::FilterMeshAlphaWrap() +{ + typeList = {MESH_ALPHA_WRAP}; + + for (const ActionIDType& tt : typeList) + actionList.push_back(new QAction(filterName(tt), this)); +} + +QString FilterMeshAlphaWrap::pluginName() const +{ + return "FilterMeshAlphaWrap"; +} + +QString FilterMeshAlphaWrap::vendor() const +{ + return "PTC"; +} + +/** + * @brief ST() must return the very short string describing each filtering action + * (this string is used also to define the menu entry) + * @param filterId: the id of the filter + * @return the name of the filter + */ +QString FilterMeshAlphaWrap::filterName(ActionIDType filterId) const +{ + switch (filterId) { + case MESH_ALPHA_WRAP: return "Alpha Wrap"; + default: assert(0); return QString(); + } +} + +QString FilterMeshAlphaWrap::pythonFilterName(ActionIDType f) const +{ + switch (f) { + case MESH_ALPHA_WRAP: return "generate_alpha_wrap"; + default: assert(0); return QString(); + } +} + +/** + * @brief // Info() must return the longer string describing each filtering action + * (this string is used in the About plugin dialog) + * @param filterId: the id of the filter + * @return an info string of the filter + */ +QString FilterMeshAlphaWrap::filterInfo(ActionIDType filterId) const +{ + QString description = + "This filter extecutes an Alpha Wrap based on the input mesh.
" + "The filter uses the original code provided in CGAL " + "3D Alpha Wrapping.
" + "
" + "Alpha: this is the size of the 'ball', specified as the fraction of the length of the largest diagonal of the bounding box. So, if this value is 0.02 then the size of the ball is 2% of the largest diagonal. Note that the run-time and memory consumption will increase with a smaller ball size.

" + "Offset: the offset distance that is added to the surface, always larger than 0, as a fraction of the length of the largest diagonal. A value of 0.001 means that the surface will be offset by a thousandth of this length.

" + "The implementation is based on the following paper:
" + "Cédric Portaneri, Mael Rouxel-Labbé, Michael Hemmer, David Cohen-Steiner, Pierre Alliez, " + "\"Alpha Wrapping with an Offset\" (2022)

This plugin is contributed by Lex van der Sluijs at PTC.
"; + switch (filterId) { + case MESH_ALPHA_WRAP: return description; + default: assert(0); return "Unknown Filter"; + } +} + +/** + * @brief The FilterClass describes in which generic class of filters it fits. + * This choice affect the submenu in which each filter will be placed + * More than a single class can be chosen. + * @param a: the action of the filter + * @return the class od the filter + */ +FilterMeshAlphaWrap::FilterClass FilterMeshAlphaWrap::getClass(const QAction* a) const +{ + switch (ID(a)) { + case MESH_ALPHA_WRAP: + return FilterPlugin::FilterClass( + FilterPlugin::FilterClass(FilterPlugin::Layer + FilterPlugin::Remeshing)); + default: assert(0); return FilterPlugin::Generic; + } +} + +/** + * @brief FilterSamplePlugin::filterArity + * @return + */ +FilterPlugin::FilterArity FilterMeshAlphaWrap::filterArity(const QAction*) const +{ + return FIXED; +} + +/** + * @brief FilterSamplePlugin::getPreConditions + * @return + */ +// int FilterMeshBooleans::getPreConditions(const QAction*) const +//{ +// return MeshModel::MM_NONE; +//} + +/** + * @brief FilterSamplePlugin::postCondition + * @return + */ +// int FilterMeshBooleans::postCondition(const QAction*) const +//{ +// return MeshModel::MM_VERTCOORD | MeshModel::MM_FACENORMAL | MeshModel::MM_VERTNORMAL; +//} + +/** + * @brief This function define the needed parameters for each filter. Return true if the filter has + * some parameters it is called every time, so you can set the default value of parameters according + * to the mesh For each parameter you need to define, + * - the name of the parameter, + * - the default value + * - the string shown in the dialog + * - a possibly long string describing the meaning of that parameter (shown as a popup help in the + * dialog) + * @param action + * @param m + * @param parlst + */ +RichParameterList +FilterMeshAlphaWrap::initParameterList(const QAction* action, const MeshDocument& md) +{ + RichParameterList parlst; + switch (ID(action)) { + case MESH_ALPHA_WRAP: + { + parlst.addParam(RichFloat( + "Alpha fraction", + 0.02, + tr("Alpha: the size of the ball (fraction)"), + tr(""))); + parlst.addParam(RichFloat( + "Offset fraction", + 0.001, + tr("Offset added to the surface (fraction)"), + tr(""))); + } break; + default: assert(0); + } + return parlst; +} + + + +/** + * @brief The Real Core Function doing the actual mesh processing. + * @param action + * @param md: an object containing all the meshes and rasters of MeshLab + * @param par: the set of parameters of each filter + * @param cb: callback object to tell MeshLab the percentage of execution of the filter + * @return true if the filter has been applied correctly, false otherwise + */ +std::map FilterMeshAlphaWrap::applyFilter( + const QAction* action, + const RichParameterList& par, + MeshDocument& md, + unsigned int& /*postConditionMask*/, + vcg::CallBackPos*) +{ + float alpha_fraction = par.getFloat("Alpha fraction"); + float offset_fraction = par.getFloat("Offset fraction"); + + CgalMesh inputMesh; + CgalMesh wrapResult; + + // ------- initialize the CGAL input mesh based on the contents of the MeshDocument ---------- + // 'cm' is 'current mesh' or 'compute mesh' ? + CMeshO& m = md.mm()->cm; + + // note: coding guided from these pages: + // http://vcglib.net/platonic_8h_source.html to read from VCGlib mesh + // https://doc.cgal.org/latest/Surface_mesh/index.html#Chapter_3D_Surface_mesh to write to CGAL mesh + + for (auto vi = m.vert.begin(); vi != m.vert.end(); ++vi){ + vertex_descriptor vertex = inputMesh.add_vertex(K::Point_3((*vi).P().X(), (*vi).P().Y(), (*vi).P().Z())); + } + + bool meshContainsQuads = false; + for (auto fi = m.face.begin(); fi != m.face.end(); ++fi) + { + vertex_descriptor u( (*fi).V(0)->Index() ); + vertex_descriptor v( (*fi).V(1)->Index() ); + vertex_descriptor w( (*fi).V(2)->Index() ); + + //qDebug() << "m.face (*fi).VN() " << (*fi).VN(); + if ( (*fi).VN() == 4) + { + meshContainsQuads = true; + vertex_descriptor x( (*fi).V(3)->Index() ); + face_descriptor face = inputMesh.add_face(u, v, w, x); + } + else + face_descriptor face = inputMesh.add_face(u, v, w); + } + + CGAL::Bbox_3 bbox = CGAL::Polygon_mesh_processing::bbox(inputMesh); + const double diag_length = std::sqrt(CGAL::square(bbox.xmax() - bbox.xmin()) + + CGAL::square(bbox.ymax() - bbox.ymin()) + + CGAL::square(bbox.zmax() - bbox.zmin())); + + switch (ID(action)) { + case MESH_ALPHA_WRAP: + + { + if(meshContainsQuads) + { + qDebug() << "Input mesh contains quads, triangulating first"; + CGAL::Polygon_mesh_processing::triangulate_faces(inputMesh); + } + + CGAL::alpha_wrap_3(inputMesh, diag_length * alpha_fraction, diag_length * offset_fraction, wrapResult); + + // making a nice label that has the used parameter doesn't work, since it will be interpreted + // as a file name and the UI will manipulate the string based on where the dots are. + // QString newName = QString::asprintf("Alpha Wrap a=%.4f, o=%.4f").arg(alpha_fraction, offset_fraction); + QString newName = "Alpha wrap"; + MeshModel* mesh = md.addNewMesh("", newName, true); + + EigenMatrixX3m VR; + Eigen::MatrixX3i FR; + + + VR.resize(wrapResult.num_vertices(), Eigen::NoChange); + FR.resize(wrapResult.num_faces(), Eigen::NoChange); + + // --------- copy results from wrap into Eigen matrices -------------- + // 'rvi' = result vertex iterator, vi = vertex index + int vi = 0; + for(auto rvi = wrapResult.vertices().begin(); rvi != wrapResult.vertices().end(); ++rvi) + { + VR(vi, 0) = wrapResult.point(*rvi).x(); + VR(vi, 1) = wrapResult.point(*rvi).y(); + VR(vi, 2) = wrapResult.point(*rvi).z(); + ++vi; + } + + int faceIndex = 0; + for(auto rfi = wrapResult.faces().begin(); rfi != wrapResult.faces().end(); ++rfi) + { + // qDebug() << "vertices around face " << *rfi; + CGAL::Vertex_around_face_iterator vbegin, vend; + int i = 0; + for(boost::tie(vbegin, vend) = vertices_around_face(wrapResult.halfedge( *rfi), wrapResult); vbegin != vend; ++vbegin) + { + // qDebug() << *vbegin << " vbegin->idx() " << vbegin->idx(); + FR(faceIndex, i) = vbegin->idx(); + ++i; + } + + ++faceIndex; + } + + + // ------------- then initialize the new mesh using these Eigen matrices ---------- + mesh->cm = meshlab::meshFromMatrices(VR, FR); + } + break; + + default: wrongActionCalled(action); + } + return std::map(); +} + + +MESHLAB_PLUGIN_NAME_EXPORTER(FilterMeshAlphaWrap) diff --git a/src/meshlabplugins/filter_mesh_alpha_wrap/filter_mesh_alpha_wrap.h b/src/meshlabplugins/filter_mesh_alpha_wrap/filter_mesh_alpha_wrap.h new file mode 100644 index 000000000..363bf981a --- /dev/null +++ b/src/meshlabplugins/filter_mesh_alpha_wrap/filter_mesh_alpha_wrap.h @@ -0,0 +1,73 @@ +/***************************************************************************** + * MeshLab - Alpha Wrap plugin o o * + * by PTC, based on CGAL o o * + * _ O _ * + * Copyright(C) 2023 \/)\/ * + * PTC /\/| * + * Lex van der Sluijs | * + * \ * + * All rights reserved. * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License (http://www.gnu.org/licenses/gpl.txt) * + * for more details. * + * * + ****************************************************************************/ + +#ifndef MESHLAB_FILTER_MESH_ALPHAWRAP_H +#define MESHLAB_FILTER_MESH_ALPHAWRAP_H + +// from meshlab common, include the abstract class file of filter plugins +#include + +/** + * @brief The FilterMeshAlphaWrap class + * This Meshlab plugin implements the Alpha Wrap algorithm from CGAL. + * + * It is very useful for making scaled-down CAD models 3D printable. + * It is also able to robustly make a watertight mesh based on a mesh with degenerate geometries. + * + */ +class FilterMeshAlphaWrap : public QObject, public FilterPlugin +{ + Q_OBJECT + MESHLAB_PLUGIN_IID_EXPORTER(FILTER_PLUGIN_IID) + Q_INTERFACES(FilterPlugin) + +public: + // enum used to give an ID to every filter implemented in the plugin + enum FilterIds { MESH_ALPHA_WRAP }; + + FilterMeshAlphaWrap(); + + QString pluginName() const; + QString vendor() const; + + QString filterName(ActionIDType filter) const; + QString pythonFilterName(ActionIDType f) const; + + QString filterInfo(ActionIDType filter) const; + FilterClass getClass(const QAction* a) const; + FilterArity filterArity(const QAction*) const; + // int getPreConditions(const QAction *) const; + // int postCondition(const QAction* ) const; + RichParameterList initParameterList(const QAction*, const MeshDocument& /*m*/); + std::map applyFilter( + const QAction* action, + const RichParameterList& params, + MeshDocument& md, + unsigned int& postConditionMask, + vcg::CallBackPos* cb); + +private: + +}; + +#endif // MESHLAB_FILTER_MESH_ALPHAWRAP_H