From b9a91b7d43d6d45e87f16b8459350adfc5da6271 Mon Sep 17 00:00:00 2001 From: Steve Demlow Date: Mon, 8 Oct 2018 17:26:14 -0500 Subject: [PATCH 01/54] Add support for loading multiple mesh files from the command line --- src/meshlab/main.cpp | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/meshlab/main.cpp b/src/meshlab/main.cpp index 1bcad16ca..be2b03226 100644 --- a/src/meshlab/main.cpp +++ b/src/meshlab/main.cpp @@ -55,17 +55,20 @@ int main(int argc, char *argv[]) QString helpOpt2="--help"; if( (helpOpt1==argv[1]) || (helpOpt2==argv[1]) ) printf( - "usage:\n" - "meshlab \n" - "Look at http://www.meshlab.net\n" - "for a longer documentation\n" - ); + "usage:\n" + "meshlab \n" + "See http://www.meshlab.net for more documentation\n" + ); - if(QString::fromLocal8Bit(argv[1]).endsWith("mlp",Qt::CaseInsensitive) || QString::fromLocal8Bit(argv[1]).endsWith("mlb", Qt::CaseInsensitive) || QString::fromLocal8Bit(argv[1]).endsWith("aln",Qt::CaseInsensitive) || QString::fromLocal8Bit(argv[1]).endsWith("out",Qt::CaseInsensitive) || QString::fromLocal8Bit(argv[1]).endsWith("nvm",Qt::CaseInsensitive)) - window.openProject(QString::fromLocal8Bit(argv[1])); - else - window.importMeshWithLayerManagement(QString::fromLocal8Bit(argv[1])); + for (int i = 1; i < argc; ++i) + { + QString arg = QString::fromLocal8Bit(argv[i]); + if(arg.endsWith("mlp",Qt::CaseInsensitive) || arg.endsWith("mlb",Qt::CaseInsensitive) || arg.endsWith("aln",Qt::CaseInsensitive) || arg.endsWith("out",Qt::CaseInsensitive) || arg.endsWith("nvm",Qt::CaseInsensitive)) + window.openProject(arg); + else + window.importMeshWithLayerManagement(arg); + } } - //else if(filterObj->noEvent) window.open(); + //else if (filterObj->noEvent) window.open(); return app.exec(); } From 7b0fc5c1907e31039022fd4bcec8b34adf9925f1 Mon Sep 17 00:00:00 2001 From: Steve Demlow Date: Tue, 9 Oct 2018 09:32:37 -0500 Subject: [PATCH 02/54] Reverted command line change --- src/meshlab/main.cpp | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/meshlab/main.cpp b/src/meshlab/main.cpp index be2b03226..1bcad16ca 100644 --- a/src/meshlab/main.cpp +++ b/src/meshlab/main.cpp @@ -55,20 +55,17 @@ int main(int argc, char *argv[]) QString helpOpt2="--help"; if( (helpOpt1==argv[1]) || (helpOpt2==argv[1]) ) printf( - "usage:\n" - "meshlab \n" - "See http://www.meshlab.net for more documentation\n" - ); + "usage:\n" + "meshlab \n" + "Look at http://www.meshlab.net\n" + "for a longer documentation\n" + ); - for (int i = 1; i < argc; ++i) - { - QString arg = QString::fromLocal8Bit(argv[i]); - if(arg.endsWith("mlp",Qt::CaseInsensitive) || arg.endsWith("mlb",Qt::CaseInsensitive) || arg.endsWith("aln",Qt::CaseInsensitive) || arg.endsWith("out",Qt::CaseInsensitive) || arg.endsWith("nvm",Qt::CaseInsensitive)) - window.openProject(arg); - else - window.importMeshWithLayerManagement(arg); - } + if(QString::fromLocal8Bit(argv[1]).endsWith("mlp",Qt::CaseInsensitive) || QString::fromLocal8Bit(argv[1]).endsWith("mlb", Qt::CaseInsensitive) || QString::fromLocal8Bit(argv[1]).endsWith("aln",Qt::CaseInsensitive) || QString::fromLocal8Bit(argv[1]).endsWith("out",Qt::CaseInsensitive) || QString::fromLocal8Bit(argv[1]).endsWith("nvm",Qt::CaseInsensitive)) + window.openProject(QString::fromLocal8Bit(argv[1])); + else + window.importMeshWithLayerManagement(QString::fromLocal8Bit(argv[1])); } - //else if (filterObj->noEvent) window.open(); + //else if(filterObj->noEvent) window.open(); return app.exec(); } From 958a0c0e1cede66a50d73ef525ccc103021888c5 Mon Sep 17 00:00:00 2001 From: Steve Demlow Date: Mon, 18 Jan 2021 00:17:22 -0600 Subject: [PATCH 03/54] Added Layer dialog GUI to animate a subset of meshes --- src/meshlab/layerDialog.cpp | 207 +++++++++++++++++++++++++++++++++- src/meshlab/layerDialog.h | 17 +++ src/meshlab/ui/layerDialog.ui | 100 +++++++++++++++- 3 files changed, 322 insertions(+), 2 deletions(-) diff --git a/src/meshlab/layerDialog.cpp b/src/meshlab/layerDialog.cpp index 1cc3b7931..e7d9b045b 100644 --- a/src/meshlab/layerDialog.cpp +++ b/src/meshlab/layerDialog.cpp @@ -80,18 +80,31 @@ LayerDialog::LayerDialog(QWidget *parent ) // The following connection is used to associate the click with the switch between raster and mesh view. connect(ui->rasterTreeWidget, SIGNAL(itemClicked(QTreeWidgetItem * , int )) , this, SLOT(rasterItemClicked(QTreeWidgetItem * , int ) ) ); - // state buttons + // state and animation buttons isRecording = false; viewState[0] = viewState[1] = viewState[2] = viewState[3] = ""; connect(ui->bW1, SIGNAL(clicked()), this, SLOT(clickW1())); connect(ui->bW2, SIGNAL(clicked()), this, SLOT(clickW2())); connect(ui->bW3, SIGNAL(clicked()), this, SLOT(clickW3())); connect(ui->bW4, SIGNAL(clicked()), this, SLOT(clickW4())); + connect(ui->animSlower, SIGNAL(clicked()), this, SLOT(clickAnimSlower())); + connect(ui->animStepBackward, SIGNAL(clicked()), this, SLOT(clickAnimStepBackward())); + connect(ui->animPlay, SIGNAL(clicked()), this, SLOT(clickAnimPlay())); + connect(ui->animStepForward, SIGNAL(clicked()), this, SLOT(clickAnimStepForward())); + connect(ui->animFaster, SIGNAL(clicked()), this, SLOT(clickAnimFaster())); connect(ui->bV1, SIGNAL(clicked()), this, SLOT(clickV1())); connect(ui->bV2, SIGNAL(clicked()), this, SLOT(clickV2())); connect(ui->bV3, SIGNAL(clicked()), this, SLOT(clickV3())); connect(ui->bV4, SIGNAL(clicked()), this, SLOT(clickV4())); + animIndex = -1; + animMsecDelay = 500; + animTimer = new QTimer(this); + resetAnim(); + connect(animTimer, SIGNAL(timeout()), this, SLOT(updateAnim())); + // Make wide enough to accommodate alternate text ("||") from clickAnimPlay() + ui->animPlay->setMinimumSize(ui->animPlay->size().width() + 6, ui->animPlay->size().height()); + this->setContextMenuPolicy(Qt::CustomContextMenu); ui->meshTreeWidget->setContextMenuPolicy(Qt::CustomContextMenu); ui->rasterTreeWidget->setContextMenuPolicy(Qt::CustomContextMenu); @@ -217,6 +230,61 @@ void LayerDialog::clickW4() mw->meshDoc()->Log.Log(0, "No View to Restore"); } +void LayerDialog::clickAnimSlower() +{ + if (animMsecDelay < 3000) + { + animMsecDelay = int(animMsecDelay * 1.2); + } +} + +void LayerDialog::clickAnimStepBackward() +{ + pauseAnim(); + stepAnim(-1); +} + +void LayerDialog::clickAnimPlay() +{ + if (!animTimer->isActive() && startAnim()) + { + ui->animPlay->setText(tr("||")); + ui->animPlay->setToolTip(tr("Pause animation")); + stepAnim(1); + animTimer->start(animMsecDelay); + } + else + { + pauseAnim(); + } +} + +void LayerDialog::clickAnimStepForward() +{ + pauseAnim(); + stepAnim(1); +} + +void LayerDialog::clickAnimFaster() +{ + if (animMsecDelay > 10) + { + animMsecDelay = int(animMsecDelay / 1.2f); + } +} + +void LayerDialog::updateAnim() +{ + if (stepAnim(1) > 1) + { + animTimer->start(animMsecDelay); // Restart in case animMsecDelay changed + } + else + { + pauseAnim(); + } +} + void LayerDialog::clickV1() { MeshDocument *md = mw->meshDoc(); @@ -406,6 +474,13 @@ void LayerDialog::meshItemClicked (QTreeWidgetItem * item , int col) if (mItem != NULL) mw->meshDoc()->setCurrentMesh(clickedId); updatePerMeshItemVisibility(); + + // If not animating, or a mesh in the animation set was clicked, reset the animation + if (!ui->animPlay->isChecked() || + std::find(animMeshIDs.begin(), animMeshIDs.end(), clickedId) != animMeshIDs.end()) + { + resetAnim(); + } } break; case 1 : @@ -691,6 +766,28 @@ void LayerDialog::updateTable(const MLSceneGLSharedDataContext::PerMeshRendering } _docitem->addChildren(itms); + // Delete any animation IDs no longer in the layer dialog + for (auto animIter = animMeshIDs.begin(); animIter != animMeshIDs.end(); ) + { + bool foundInMeshList = false; + foreach(MeshModel *mp, md->meshList) + { + if (mp->id() == *animIter) + { + foundInMeshList = true; + break; + } + } + if (foundInMeshList) + { + ++animIter; + } + else + { + animIter = animMeshIDs.erase(animIter); + } + } + int wid = 0; for(int i=0; i< ui->meshTreeWidget->columnCount(); i++) { @@ -866,6 +963,114 @@ void LayerDialog::actionActivated(MLRenderingAction* ract) _tabw->switchTab(ract->meshId(),ract->text()); } +bool LayerDialog::startAnim() +{ + if (animMeshIDs.size() > 1) + { + return true; + } + + MeshDocument *md = mw->meshDoc(); + bool canAnimate = md->meshList.size() > 1; + + if (canAnimate) + { + int visibleCount = 0; + foreach(MeshModel *mp, md->meshList) + { + if (mp->isVisible()) + { + ++visibleCount; + } + } + // If fewer than two meshes were visible select all meshes, else select only the visible ones + animIndex = -1; + animMeshIDs.clear(); + foreach(MeshModel *mp, md->meshList) + { + if (mp->isVisible() && animIndex < 0) + { + animIndex = animMeshIDs.size(); // Remember first visible mesh + } + if (mp->isVisible() || visibleCount < 2) + { + animMeshIDs.push_back(mp->id()); + } + } + if (animIndex >= 0) + { + --animIndex; + } + ui->animStepBackward->setEnabled(true); + ui->animStepForward->setEnabled(true); + } + return canAnimate; +} + +// Advance the animation by the specified offset (1 = forward, -1 = backward) +int LayerDialog::stepAnim(int offset) +{ + bool foundVisible = false; + int animatingCount = 0; + MeshDocument *md = mw->meshDoc(); + + if (md->meshList.size() > 1) + { + MeshModel* lastMP = NULL; + + while (!foundVisible && animMeshIDs.size() > 1) + { + animIndex = (animMeshIDs.size() + animIndex + offset) % animMeshIDs.size(); + animatingCount = 0; + for (auto& id : animMeshIDs) + { + foreach(MeshModel *mp, md->meshList) + { + if (mp->id() == id) + { + bool makeVisible = mp->id() == animMeshIDs[animIndex]; + mw->GLA()->meshSetVisibility(mp, makeVisible); + foundVisible |= makeVisible; + ++animatingCount; + lastMP = mp; + } + } + } + if (!foundVisible) + { + // The mesh being animated to was deleted; remove it from the list and try again + animMeshIDs.erase(animMeshIDs.begin() + animIndex); + animIndex -= offset; + } + } + if (animMeshIDs.size() == 1 && lastMP != NULL) + { + mw->GLA()->meshSetVisibility(lastMP, true); + } + updatePerMeshItemVisibility(); + updatePerMeshItemSelectionStatus(); + mw->GLA()->update(); + } + return animatingCount; +} + +void LayerDialog::pauseAnim() +{ + animTimer->stop(); + ui->animPlay->setChecked(false); + ui->animPlay->setText(tr(">")); + ui->animPlay->setToolTip(tr("Resume animation")); +} + +void LayerDialog::resetAnim() +{ + pauseAnim(); + animMeshIDs.clear(); + ui->animStepBackward->setEnabled(false); + ui->animStepForward->setEnabled(false); + ui->animPlay->setToolTip(tr("Animate visible meshes, or all if < 2 are visible")); +} + LayerDialog::~LayerDialog() { delete ui; diff --git a/src/meshlab/layerDialog.h b/src/meshlab/layerDialog.h index 917cf9187..264314be6 100644 --- a/src/meshlab/layerDialog.h +++ b/src/meshlab/layerDialog.h @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include "ml_render_gui.h" @@ -139,6 +140,12 @@ public slots: void clickW2(); void clickW3(); void clickW4(); + void clickAnimSlower(); + void clickAnimStepBackward(); + void clickAnimPlay(); + void clickAnimStepForward(); + void clickAnimFaster(); + void updateAnim(); void clickV1(); void clickV2(); void clickV3(); @@ -174,6 +181,16 @@ private: QString viewState[4]; QMap visibilityState[4]; + int animIndex; + std::vector animMeshIDs; + int animMsecDelay; + QTimer* animTimer; + + bool startAnim(); + int stepAnim(int offset); + void pauseAnim(); + void resetAnim(); + QTreeWidgetItem* _docitem; int _previd; QGroupBox* _renderingtabcontainer; diff --git a/src/meshlab/ui/layerDialog.ui b/src/meshlab/ui/layerDialog.ui index 0acfcf385..8dc1c32a6 100644 --- a/src/meshlab/ui/layerDialog.ui +++ b/src/meshlab/ui/layerDialog.ui @@ -125,7 +125,105 @@ - 40 + 1 + 20 + + + + + + + + + 9 + 75 + true + + + + Decrease animation speed + + + - + + + + + + + + 9 + 75 + true + + + + Animate one step backward + + + -1 + + + + + + + + 9 + 75 + true + + + + > + + + true + + + + + + + + 9 + 75 + true + + + + Animate one step forward + + + +1 + + + + + + + + 9 + 75 + true + + + + Increase animation speed + + + + + + + + + + + Qt::Horizontal + + + + 1 20 From 3c4270bc77378cc2642a0b15bcdb152177f0a3da Mon Sep 17 00:00:00 2001 From: alemuntoni Date: Wed, 7 Jun 2023 14:33:44 +0200 Subject: [PATCH 04/54] fix minimum osx release --- src/cmake/meshlab_global_settings.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmake/meshlab_global_settings.cmake b/src/cmake/meshlab_global_settings.cmake index 096c2450d..20aa94f1e 100644 --- a/src/cmake/meshlab_global_settings.cmake +++ b/src/cmake/meshlab_global_settings.cmake @@ -18,7 +18,7 @@ set(CMAKE_C_STANDARD 99) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) -set(CMAKE_OSX_DEPLOYMENT_TARGET "10.11" CACHE STRING "Minimum OS X deployment version" FORCE) +set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13" CACHE STRING "Minimum OS X deployment version" FORCE) ### Settings needed for both "external" and internal code set(CMAKE_POSITION_INDEPENDENT_CODE ON) From a596a1ddb67d37baa22f979f15fd75f48130f9b2 Mon Sep 17 00:00:00 2001 From: Alessandro Muntoni Date: Wed, 7 Jun 2023 15:03:09 +0200 Subject: [PATCH 05/54] not using archived actions anymore on CreateRelease workflow --- .github/workflows/CreateRelease.yml | 22 +---------- scripts/macOS/2_deploy.sh | 22 ++++++++++- .../macOS/internal/2c_notarize_appbundle.sh | 39 +++++++++++++++++++ .../macOS/internal/{2c_dmg.sh => 2d_dmg.sh} | 0 4 files changed, 61 insertions(+), 22 deletions(-) create mode 100644 scripts/macOS/internal/2c_notarize_appbundle.sh rename scripts/macOS/internal/{2c_dmg.sh => 2d_dmg.sh} (100%) diff --git a/.github/workflows/CreateRelease.yml b/.github/workflows/CreateRelease.yml index 63bc27f0d..8b63f8910 100644 --- a/.github/workflows/CreateRelease.yml +++ b/.github/workflows/CreateRelease.yml @@ -90,27 +90,7 @@ jobs: - name: Deploy shell: bash run: | - bash scripts/${{ runner.os }}/2_deploy.sh --cert_pssw='${{ secrets.WIN_CERTIFICATE_PSSW }}' --cert_id=${{ secrets.MACOS_CERT_ID }} - - name: Get AppBundle Name - if: runner.os == 'macOS' - id: abn - shell: bash - run: | - cd install - NAME=$(ls -d MeshLab*) - echo "app_bundle_name=$NAME" >> $GITHUB_OUTPUT - - name: Notarize macOS - if: runner.os == 'macOS' - uses: devbotsxyz/xcode-notarize@v1 - with: - product-path: "install/${{steps.abn.outputs.app_bundle_name}}" - appstore-connect-username: ${{ secrets.MACOS_NOTARIZATION_USER }} - appstore-connect-password: ${{ secrets.MACOS_NOTARIZATION_PSSW }} - - name: Staple Release macOS - if: runner.os == 'macOS' - uses: devbotsxyz/xcode-staple@v1 - with: - product-path: "install/${{steps.abn.outputs.app_bundle_name}}" + bash scripts/${{ runner.os }}/2_deploy.sh --cert_pssw='${{ secrets.WIN_CERTIFICATE_PSSW }}' --cert_id=${{ secrets.MACOS_CERT_ID }} --notarization_user=${{ secrets.MACOS_NOTARIZATION_USER }} --notarization_pssw='${{ secrets.MACOS_NOTARIZATION_PSSW }}' - name: Upload MeshLab Portable uses: actions/upload-artifact@v3 with: diff --git a/scripts/macOS/2_deploy.sh b/scripts/macOS/2_deploy.sh index b3a3a791a..16007fef3 100755 --- a/scripts/macOS/2_deploy.sh +++ b/scripts/macOS/2_deploy.sh @@ -6,7 +6,10 @@ INSTALL_PATH=$SCRIPTS_PATH/../../install QT_DIR_OPTION="" PACKAGES_PATH=$SCRIPTS_PATH/../../packages SIGN=false +NOTARIZE=false CERT_ID="" +NOT_USER="" +NOT_PASSWORD="" #checking for parameters for i in "$@" @@ -31,6 +34,17 @@ case $i in fi shift # past argument=value ;; + -nu=*|--notarization_user=*) + if [ -z "${i#*=}" ]; then + NOTARIZE=true + NOT_USER="${i#*=}" + fi + shift # past argument=value + ;; + -np=*|--notarization_password=*) + NOT_PASSWORD="${i#*=}" + shift # past argument=value + ;; *) # unknown option ;; @@ -47,6 +61,12 @@ if [ "$SIGN" = true ] ; then echo "======= AppBundle Signed =======" fi -bash $SCRIPTS_PATH/internal/2c_dmg.sh -i=$INSTALL_PATH -p=$PACKAGES_PATH +if [ "$NOTARIZE" = true ] ; then + bash $SCRIPTS_PATH/internal/2c_notarize_appbundle.sh -i=$INSTALL_PATH -nu=$NOT_USER -np=$NOT_PASSWORD + + echo "======= AppBundle Notarized =======" +fi + +bash $SCRIPTS_PATH/internal/2d_dmg.sh -i=$INSTALL_PATH -p=$PACKAGES_PATH echo "======= DMG Created =======" \ No newline at end of file diff --git a/scripts/macOS/internal/2c_notarize_appbundle.sh b/scripts/macOS/internal/2c_notarize_appbundle.sh new file mode 100644 index 000000000..902122bab --- /dev/null +++ b/scripts/macOS/internal/2c_notarize_appbundle.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +SCRIPTS_PATH="$(dirname "$(realpath "$0")")"/.. + +INSTALL_PATH=$SCRIPTS_PATH/../../install +NOT_USER="" +NOT_PASSWORD="" + +#checking for parameters +for i in "$@" +do +case $i in + -i=*|--install_path=*) + INSTALL_PATH="${i#*=}" + shift # past argument=value + ;; + -nu=*|--notarization_user=*) + NOT_USER="${i#*=}" + shift # past argument=value + ;; + -np=*|--notarization_password=*) + NOT_PASSWORD="${i#*=}" + shift # past argument=value + ;; + *) + # unknown option + ;; +esac +done + +xcrun notarytool store-credentials "notarytool-profile" --apple-id "$NOT_USER" --password "$NOT_PASSWORD" + +ditto -c -k --keepParent "$INSTALL_PATH/meshlab.app" "$INSTALL_PATH/notarization.zip" + +xcrun notarytool submit "install/notarization.zip" --keychain-profile "notarytool-profile" --wait + +xcrun stapler staple "$INSTALL_PATH/meshlab.app" + +rm -rf $INSTALL_PATH/notarization.zip \ No newline at end of file diff --git a/scripts/macOS/internal/2c_dmg.sh b/scripts/macOS/internal/2d_dmg.sh similarity index 100% rename from scripts/macOS/internal/2c_dmg.sh rename to scripts/macOS/internal/2d_dmg.sh From d87e091195e07e160473ac7948f71e52962a4edd Mon Sep 17 00:00:00 2001 From: Alessandro Muntoni Date: Wed, 7 Jun 2023 15:40:38 +0200 Subject: [PATCH 06/54] fix macos deploy script --- scripts/macOS/2_deploy.sh | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/scripts/macOS/2_deploy.sh b/scripts/macOS/2_deploy.sh index 16007fef3..0ad081a2a 100755 --- a/scripts/macOS/2_deploy.sh +++ b/scripts/macOS/2_deploy.sh @@ -28,17 +28,13 @@ case $i in shift # past argument=value ;; -ci=*|--cert_id=*) - if [ -z "${i#*=}" ]; then - SIGN=true - CERT_ID="${i#*=}" - fi + SIGN=true + CERT_ID="${i#*=}" shift # past argument=value ;; -nu=*|--notarization_user=*) - if [ -z "${i#*=}" ]; then - NOTARIZE=true - NOT_USER="${i#*=}" - fi + NOTARIZE=true + NOT_USER="${i#*=}" shift # past argument=value ;; -np=*|--notarization_password=*) From 8ff46533341c24a6a0859b15a3e7c11419767ad5 Mon Sep 17 00:00:00 2001 From: Alessandro Muntoni Date: Wed, 7 Jun 2023 15:57:48 +0200 Subject: [PATCH 07/54] sign and notarize only when options available --- scripts/macOS/2_deploy.sh | 16 ++++++++++------ scripts/macOS/internal/2c_notarize_appbundle.sh | 10 +++++----- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/scripts/macOS/2_deploy.sh b/scripts/macOS/2_deploy.sh index 0ad081a2a..1426d0258 100755 --- a/scripts/macOS/2_deploy.sh +++ b/scripts/macOS/2_deploy.sh @@ -8,8 +8,8 @@ PACKAGES_PATH=$SCRIPTS_PATH/../../packages SIGN=false NOTARIZE=false CERT_ID="" -NOT_USER="" -NOT_PASSWORD="" +NOTAR_USER="" +NOTAR_PASSWORD="" #checking for parameters for i in "$@" @@ -28,17 +28,21 @@ case $i in shift # past argument=value ;; -ci=*|--cert_id=*) - SIGN=true CERT_ID="${i#*=}" + if [ -n "$CERT_ID" ]; then + SIGN=true + fi shift # past argument=value ;; -nu=*|--notarization_user=*) - NOTARIZE=true NOT_USER="${i#*=}" + if [ -n "$NOT_USER" ]; then + NOTARIZE=true + fi shift # past argument=value ;; -np=*|--notarization_password=*) - NOT_PASSWORD="${i#*=}" + NOTAR_PASSWORD="${i#*=}" shift # past argument=value ;; *) @@ -58,7 +62,7 @@ if [ "$SIGN" = true ] ; then fi if [ "$NOTARIZE" = true ] ; then - bash $SCRIPTS_PATH/internal/2c_notarize_appbundle.sh -i=$INSTALL_PATH -nu=$NOT_USER -np=$NOT_PASSWORD + bash $SCRIPTS_PATH/internal/2c_notarize_appbundle.sh -i=$INSTALL_PATH -nu=$NOT_USER -np=$NOTAR_PASSWORD echo "======= AppBundle Notarized =======" fi diff --git a/scripts/macOS/internal/2c_notarize_appbundle.sh b/scripts/macOS/internal/2c_notarize_appbundle.sh index 902122bab..9b2188eb4 100644 --- a/scripts/macOS/internal/2c_notarize_appbundle.sh +++ b/scripts/macOS/internal/2c_notarize_appbundle.sh @@ -3,8 +3,8 @@ SCRIPTS_PATH="$(dirname "$(realpath "$0")")"/.. INSTALL_PATH=$SCRIPTS_PATH/../../install -NOT_USER="" -NOT_PASSWORD="" +NOTAR_USER="" +NOTAR_PASSWORD="" #checking for parameters for i in "$@" @@ -15,11 +15,11 @@ case $i in shift # past argument=value ;; -nu=*|--notarization_user=*) - NOT_USER="${i#*=}" + NOTAR_USER="${i#*=}" shift # past argument=value ;; -np=*|--notarization_password=*) - NOT_PASSWORD="${i#*=}" + NOTAR_PASSWORD="${i#*=}" shift # past argument=value ;; *) @@ -28,7 +28,7 @@ case $i in esac done -xcrun notarytool store-credentials "notarytool-profile" --apple-id "$NOT_USER" --password "$NOT_PASSWORD" +xcrun notarytool store-credentials "notarytool-profile" --apple-id "$NOTAR_USER" --password "$NOTAR_PASSWORD" ditto -c -k --keepParent "$INSTALL_PATH/meshlab.app" "$INSTALL_PATH/notarization.zip" From 7872cc26ab7ab6fd7bf02af4fb459b5acdb18a21 Mon Sep 17 00:00:00 2001 From: alemuntoni Date: Wed, 7 Jun 2023 16:36:54 +0200 Subject: [PATCH 08/54] fix macos deploy script --- scripts/macOS/2_deploy.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/macOS/2_deploy.sh b/scripts/macOS/2_deploy.sh index 1426d0258..6a938ee9d 100755 --- a/scripts/macOS/2_deploy.sh +++ b/scripts/macOS/2_deploy.sh @@ -62,7 +62,7 @@ if [ "$SIGN" = true ] ; then fi if [ "$NOTARIZE" = true ] ; then - bash $SCRIPTS_PATH/internal/2c_notarize_appbundle.sh -i=$INSTALL_PATH -nu=$NOT_USER -np=$NOTAR_PASSWORD + bash $SCRIPTS_PATH/internal/2c_notarize_appbundle.sh -i=$INSTALL_PATH -nu=$NOTAR_USER -np=$NOTAR_PASSWORD echo "======= AppBundle Notarized =======" fi From ebfb70655fedea4dec78adb12f6817c9a9c87d1f Mon Sep 17 00:00:00 2001 From: Alessandro Muntoni Date: Thu, 8 Jun 2023 09:31:31 +0200 Subject: [PATCH 09/54] fix macos notarization script --- scripts/macOS/internal/2c_notarize_appbundle.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/macOS/internal/2c_notarize_appbundle.sh b/scripts/macOS/internal/2c_notarize_appbundle.sh index 9b2188eb4..b394892bb 100644 --- a/scripts/macOS/internal/2c_notarize_appbundle.sh +++ b/scripts/macOS/internal/2c_notarize_appbundle.sh @@ -28,7 +28,7 @@ case $i in esac done -xcrun notarytool store-credentials "notarytool-profile" --apple-id "$NOTAR_USER" --password "$NOTAR_PASSWORD" +xcrun notarytool store-credentials "notarytool-profile" --apple-id $NOTAR_USER --password $NOTAR_PASSWORD ditto -c -k --keepParent "$INSTALL_PATH/meshlab.app" "$INSTALL_PATH/notarization.zip" From 511e6902631d9c6355ffa5d4af732978b7e8d5fe Mon Sep 17 00:00:00 2001 From: Alessandro Muntoni Date: Thu, 8 Jun 2023 09:45:40 +0200 Subject: [PATCH 10/54] fix macos deploy script --- scripts/macOS/2_deploy.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/macOS/2_deploy.sh b/scripts/macOS/2_deploy.sh index 6a938ee9d..e1e4c0572 100755 --- a/scripts/macOS/2_deploy.sh +++ b/scripts/macOS/2_deploy.sh @@ -35,8 +35,8 @@ case $i in shift # past argument=value ;; -nu=*|--notarization_user=*) - NOT_USER="${i#*=}" - if [ -n "$NOT_USER" ]; then + NOTAR_USER="${i#*=}" + if [ -n "$NOTAR_USER" ]; then NOTARIZE=true fi shift # past argument=value From ebaf1a2249d8aafd94f89ba48b19f73f549d2476 Mon Sep 17 00:00:00 2001 From: Alessandro Muntoni Date: Thu, 8 Jun 2023 09:55:43 +0200 Subject: [PATCH 11/54] fix CreateRelease workflow --- .github/workflows/CreateRelease.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CreateRelease.yml b/.github/workflows/CreateRelease.yml index 8b63f8910..b32b799db 100644 --- a/.github/workflows/CreateRelease.yml +++ b/.github/workflows/CreateRelease.yml @@ -90,7 +90,7 @@ jobs: - name: Deploy shell: bash run: | - bash scripts/${{ runner.os }}/2_deploy.sh --cert_pssw='${{ secrets.WIN_CERTIFICATE_PSSW }}' --cert_id=${{ secrets.MACOS_CERT_ID }} --notarization_user=${{ secrets.MACOS_NOTARIZATION_USER }} --notarization_pssw='${{ secrets.MACOS_NOTARIZATION_PSSW }}' + bash scripts/${{ runner.os }}/2_deploy.sh --cert_pssw='${{ secrets.WIN_CERTIFICATE_PSSW }}' --cert_id=${{ secrets.MACOS_CERT_ID }} --notarization_user=${{ secrets.MACOS_NOTARIZATION_USER }} --notarization_pssw=${{ secrets.MACOS_NOTARIZATION_PSSW }} - name: Upload MeshLab Portable uses: actions/upload-artifact@v3 with: From bcd070823f4a3cbf75da1c8ecbe6f12d9358ba43 Mon Sep 17 00:00:00 2001 From: Alessandro Muntoni Date: Thu, 8 Jun 2023 10:08:34 +0200 Subject: [PATCH 12/54] fix macos notarization scripts --- scripts/macOS/2_deploy.sh | 2 +- scripts/macOS/internal/2c_notarize_appbundle.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/macOS/2_deploy.sh b/scripts/macOS/2_deploy.sh index e1e4c0572..7f5f32a6d 100755 --- a/scripts/macOS/2_deploy.sh +++ b/scripts/macOS/2_deploy.sh @@ -41,7 +41,7 @@ case $i in fi shift # past argument=value ;; - -np=*|--notarization_password=*) + -np=*|--notarization_pssw=*) NOTAR_PASSWORD="${i#*=}" shift # past argument=value ;; diff --git a/scripts/macOS/internal/2c_notarize_appbundle.sh b/scripts/macOS/internal/2c_notarize_appbundle.sh index b394892bb..2a1be5b88 100644 --- a/scripts/macOS/internal/2c_notarize_appbundle.sh +++ b/scripts/macOS/internal/2c_notarize_appbundle.sh @@ -18,7 +18,7 @@ case $i in NOTAR_USER="${i#*=}" shift # past argument=value ;; - -np=*|--notarization_password=*) + -np=*|--notarization_pssw=*) NOTAR_PASSWORD="${i#*=}" shift # past argument=value ;; From 424fcbbda4e4f0b9b7312da963d4cc3c8cd21ed3 Mon Sep 17 00:00:00 2001 From: Alessandro Muntoni Date: Thu, 8 Jun 2023 10:21:10 +0200 Subject: [PATCH 13/54] fix macos notarization scripts --- scripts/macOS/internal/2c_notarize_appbundle.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/macOS/internal/2c_notarize_appbundle.sh b/scripts/macOS/internal/2c_notarize_appbundle.sh index 2a1be5b88..29025b79c 100644 --- a/scripts/macOS/internal/2c_notarize_appbundle.sh +++ b/scripts/macOS/internal/2c_notarize_appbundle.sh @@ -28,7 +28,7 @@ case $i in esac done -xcrun notarytool store-credentials "notarytool-profile" --apple-id $NOTAR_USER --password $NOTAR_PASSWORD +xcrun notarytool store-credentials "notarytool-profile" --apple-id "$NOTAR_USER" --password "$NOTAR_PASSWORD" ditto -c -k --keepParent "$INSTALL_PATH/meshlab.app" "$INSTALL_PATH/notarization.zip" From e46cf95350c5aec9e4a1d51d31ca0b33108c2434 Mon Sep 17 00:00:00 2001 From: Alessandro Muntoni Date: Thu, 8 Jun 2023 10:49:07 +0200 Subject: [PATCH 14/54] fix CreateRelease workflow --- .github/workflows/CreateRelease.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CreateRelease.yml b/.github/workflows/CreateRelease.yml index b32b799db..eed8a321d 100644 --- a/.github/workflows/CreateRelease.yml +++ b/.github/workflows/CreateRelease.yml @@ -90,7 +90,7 @@ jobs: - name: Deploy shell: bash run: | - bash scripts/${{ runner.os }}/2_deploy.sh --cert_pssw='${{ secrets.WIN_CERTIFICATE_PSSW }}' --cert_id=${{ secrets.MACOS_CERT_ID }} --notarization_user=${{ secrets.MACOS_NOTARIZATION_USER }} --notarization_pssw=${{ secrets.MACOS_NOTARIZATION_PSSW }} + bash scripts/${{ runner.os }}/2_deploy.sh --cert_pssw='${{ secrets.WIN_CERTIFICATE_PSSW }}' --cert_id='${{ secrets.MACOS_CERT_ID }}' --notarization_user='${{ secrets.MACOS_NOTARIZATION_USER }}' --notarization_pssw='${{ secrets.MACOS_NOTARIZATION_PSSW }}' - name: Upload MeshLab Portable uses: actions/upload-artifact@v3 with: From 5bd08a343ca9b4d9088972146a00bcbaa3747e82 Mon Sep 17 00:00:00 2001 From: Alessandro Muntoni Date: Thu, 8 Jun 2023 11:22:58 +0200 Subject: [PATCH 15/54] add notarization team --- .github/workflows/CreateRelease.yml | 2 +- scripts/macOS/2_deploy.sh | 7 ++++++- scripts/macOS/internal/2c_notarize_appbundle.sh | 7 ++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/.github/workflows/CreateRelease.yml b/.github/workflows/CreateRelease.yml index eed8a321d..ec0770101 100644 --- a/.github/workflows/CreateRelease.yml +++ b/.github/workflows/CreateRelease.yml @@ -90,7 +90,7 @@ jobs: - name: Deploy shell: bash run: | - bash scripts/${{ runner.os }}/2_deploy.sh --cert_pssw='${{ secrets.WIN_CERTIFICATE_PSSW }}' --cert_id='${{ secrets.MACOS_CERT_ID }}' --notarization_user='${{ secrets.MACOS_NOTARIZATION_USER }}' --notarization_pssw='${{ secrets.MACOS_NOTARIZATION_PSSW }}' + bash scripts/${{ runner.os }}/2_deploy.sh --cert_pssw='${{ secrets.WIN_CERTIFICATE_PSSW }}' --cert_id='${{ secrets.MACOS_CERT_ID }}' --notarization_user='${{ secrets.MACOS_NOTARIZATION_USER }}' --notarization_team='${{ secrets.MACOS_NOTARIZATION_TEAM_ID }}' --notarization_pssw='${{ secrets.MACOS_NOTARIZATION_PSSW }}' - name: Upload MeshLab Portable uses: actions/upload-artifact@v3 with: diff --git a/scripts/macOS/2_deploy.sh b/scripts/macOS/2_deploy.sh index 7f5f32a6d..f359e86e6 100755 --- a/scripts/macOS/2_deploy.sh +++ b/scripts/macOS/2_deploy.sh @@ -9,6 +9,7 @@ SIGN=false NOTARIZE=false CERT_ID="" NOTAR_USER="" +NOTAR_TEAM_ID="" NOTAR_PASSWORD="" #checking for parameters @@ -45,6 +46,10 @@ case $i in NOTAR_PASSWORD="${i#*=}" shift # past argument=value ;; + -nt=*|--notarization_team=*) + NOTAR_TEAM_ID="${i#*=}" + shift # past argument=value + ;; *) # unknown option ;; @@ -62,7 +67,7 @@ if [ "$SIGN" = true ] ; then fi if [ "$NOTARIZE" = true ] ; then - bash $SCRIPTS_PATH/internal/2c_notarize_appbundle.sh -i=$INSTALL_PATH -nu=$NOTAR_USER -np=$NOTAR_PASSWORD + bash $SCRIPTS_PATH/internal/2c_notarize_appbundle.sh -i=$INSTALL_PATH -nu=$NOTAR_USER -nt=$NOTAR_TEAM_ID -np=$NOTAR_PASSWORD echo "======= AppBundle Notarized =======" fi diff --git a/scripts/macOS/internal/2c_notarize_appbundle.sh b/scripts/macOS/internal/2c_notarize_appbundle.sh index 29025b79c..3eb6346ca 100644 --- a/scripts/macOS/internal/2c_notarize_appbundle.sh +++ b/scripts/macOS/internal/2c_notarize_appbundle.sh @@ -5,6 +5,7 @@ SCRIPTS_PATH="$(dirname "$(realpath "$0")")"/.. INSTALL_PATH=$SCRIPTS_PATH/../../install NOTAR_USER="" NOTAR_PASSWORD="" +NOTAR_TEAM_ID="" #checking for parameters for i in "$@" @@ -18,6 +19,10 @@ case $i in NOTAR_USER="${i#*=}" shift # past argument=value ;; + -nt=*|--notarization_team=*) + NOTAR_TEAM_ID="${i#*=}" + shift # past argument=value + ;; -np=*|--notarization_pssw=*) NOTAR_PASSWORD="${i#*=}" shift # past argument=value @@ -28,7 +33,7 @@ case $i in esac done -xcrun notarytool store-credentials "notarytool-profile" --apple-id "$NOTAR_USER" --password "$NOTAR_PASSWORD" +xcrun notarytool store-credentials "notarytool-profile" --apple-id $NOTAR_USER --team-id $NOTAR_TEAM_ID --password $NOTAR_PASSWORD ditto -c -k --keepParent "$INSTALL_PATH/meshlab.app" "$INSTALL_PATH/notarization.zip" From 75a9b8cdd9ff9cf14e431c5d7c26407c18e3caad Mon Sep 17 00:00:00 2001 From: Alessandro Muntoni Date: Thu, 8 Jun 2023 12:14:53 +0200 Subject: [PATCH 16/54] fix sign --- scripts/macOS/internal/2b_sign_appbundle.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/macOS/internal/2b_sign_appbundle.sh b/scripts/macOS/internal/2b_sign_appbundle.sh index 9837efb8a..268d6c456 100644 --- a/scripts/macOS/internal/2b_sign_appbundle.sh +++ b/scripts/macOS/internal/2b_sign_appbundle.sh @@ -23,4 +23,6 @@ case $i in esac done -codesign --options "runtime" --timestamp --force --deep --sign $CERT_ID $INSTALL_PATH/meshlab.app \ No newline at end of file +codesign --options "runtime" --timestamp --force --deep --sign $CERT_ID $INSTALL_PATH/meshlab.app + +spctl -a -vvv $INSTALL_PATH/meshlab.app \ No newline at end of file From 481a66dbb6ac816318d5c253e372439cd18ef9b8 Mon Sep 17 00:00:00 2001 From: Alessandro Muntoni Date: Thu, 8 Jun 2023 12:36:29 +0200 Subject: [PATCH 17/54] using fixed update release action --- .github/workflows/CreateRelease.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CreateRelease.yml b/.github/workflows/CreateRelease.yml index ec0770101..d4b541f3d 100644 --- a/.github/workflows/CreateRelease.yml +++ b/.github/workflows/CreateRelease.yml @@ -222,7 +222,7 @@ jobs: cd .. #Create release and upload - - uses: "marvinpinto/action-automatic-releases@latest" + - uses: "marvinpinto/action-automatic-releases@6273874b61ebc8c71f1a61b2d98e234cf389b303" with: repo_token: "${{ secrets.GITHUB_TOKEN }}" automatic_release_tag: "MeshLab-${{steps.envs.outputs.ml_version}}" From 266a16f485272ab439d08a2f4f0080e30a3aa8c2 Mon Sep 17 00:00:00 2001 From: Alessandro Muntoni Date: Fri, 9 Jun 2023 17:19:47 +0200 Subject: [PATCH 18/54] fix deploy call in BuildMeshLab workflow --- .github/workflows/BuildMeshLab.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/BuildMeshLab.yml b/.github/workflows/BuildMeshLab.yml index 870f2a1c7..6f9327af4 100644 --- a/.github/workflows/BuildMeshLab.yml +++ b/.github/workflows/BuildMeshLab.yml @@ -70,7 +70,7 @@ jobs: - name: Deploy shell: bash run: | - bash scripts/${{ runner.os }}/2_deploy.sh --cert_pssw='${{ secrets.WIN_CERTIFICATE_PSSW }}' --cert_id=${{ secrets.MACOS_CERT_ID }} + bash scripts/${{ runner.os }}/2_deploy.sh --cert_pssw='${{ secrets.WIN_CERTIFICATE_PSSW }}' --cert_id='${{ secrets.MACOS_CERT_ID }}' - name: Upload MeshLab Portable uses: actions/upload-artifact@v3 with: From 8ef1e76548e7e5cb7b869239b4e5e7f85ab4ded0 Mon Sep 17 00:00:00 2001 From: davidboening Date: Sat, 17 Jun 2023 22:27:09 +0200 Subject: [PATCH 19/54] added heat geodesic (not tested) --- .../filter_heatgeodesic/CMakeLists.txt | 5 + .../filter_heatgeodesic.cpp | 247 +++++++++++++++++ .../filter_heatgeodesic/filter_heatgeodesic.h | 39 +++ .../filter_heatgeodesic/trimesh_heatmethod.h | 250 ++++++++++++++++++ 4 files changed, 541 insertions(+) create mode 100644 src/meshlabplugins/filter_heatgeodesic/CMakeLists.txt create mode 100644 src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.cpp create mode 100644 src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.h create mode 100644 src/meshlabplugins/filter_heatgeodesic/trimesh_heatmethod.h diff --git a/src/meshlabplugins/filter_heatgeodesic/CMakeLists.txt b/src/meshlabplugins/filter_heatgeodesic/CMakeLists.txt new file mode 100644 index 000000000..732bcaacc --- /dev/null +++ b/src/meshlabplugins/filter_heatgeodesic/CMakeLists.txt @@ -0,0 +1,5 @@ +set(SOURCES filter_heatgeodesic.cpp) + +set(HEADERS filter_heatgeodesic.h trimesh_heatmethod.h) + +add_meshlab_plugin(filter_heatgeodesic ${SOURCES} ${HEADERS}) diff --git a/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.cpp b/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.cpp new file mode 100644 index 000000000..ececbd4b9 --- /dev/null +++ b/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.cpp @@ -0,0 +1,247 @@ +#include "trimesh_heatmethod.h" + +#include "filter_heatgeodesic.h" + +#include + +/** + * @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 + */ +FilterHeatGeodesicPlugin::FilterHeatGeodesicPlugin() +{ + typeList = {FP_COMPUTE_HEATGEODESIC_FROM_SELECTION}; + + for(ActionIDType tt : types()) + actionList.push_back(new QAction(filterName(tt), this)); +} + +FilterHeatGeodesicPlugin::~FilterHeatGeodesicPlugin() +{ +} + +QString FilterHeatGeodesicPlugin::pluginName() const +{ + return "FilterHeatGeodesic"; +} + +/** + * @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 FilterHeatGeodesicPlugin::filterName(ActionIDType filterId) const +{ + switch(filterId) { + case FP_COMPUTE_HEATGEODESIC_FROM_SELECTION : + return "Compute Geodesic From Selection (Heat)"; + default : + assert(0); + return QString(); + } +} + +/** + * @brief FilterHeatGeodesicPlugin::pythonFilterName if you want that your filter should have a different + * name on pymeshlab, use this function to return its python name. + * @param f + * @return + */ +QString FilterHeatGeodesicPlugin::pythonFilterName(ActionIDType f) const +{ + switch(f) { + case FP_COMPUTE_HEATGEODESIC_FROM_SELECTION : + return "compute_heat_geodesic_from_selection"; + 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 FilterHeatGeodesicPlugin::filterInfo(ActionIDType filterId) const +{ + switch(filterId) { + case FP_COMPUTE_HEATGEODESIC_FROM_SELECTION : + return "Computes an approximated geodesic distance from the selected vertices to all others. This algorithm implements the heat method."; + 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 + */ +FilterHeatGeodesicPlugin::FilterClass FilterHeatGeodesicPlugin::getClass(const QAction *a) const +{ + switch(ID(a)) { + case FP_COMPUTE_HEATGEODESIC_FROM_SELECTION : + return FilterPlugin::Measure; + default : + assert(0); + return FilterPlugin::Generic; + } +} + +/** + * @brief FilterSamplePlugin::filterArity + * @return + */ +FilterPlugin::FilterArity FilterHeatGeodesicPlugin::filterArity(const QAction*) const +{ + return SINGLE_MESH; +} + +/** + * @brief FilterSamplePlugin::getPreConditions + * @return + */ +int FilterHeatGeodesicPlugin::getPreConditions(const QAction*) const +{ + // NOTE: note sure about these + return MeshModel::MM_VERTCOORD | MeshModel::MM_VERTQUALITY | + MeshModel::MM_FACEFACETOPO | MeshModel::MM_VERTFACETOPO | + MeshModel::MM_FACEQUALITY | MeshModel::MM_FACENORMAL | + MeshModel::MM_VERTFLAGSELECT; +} + +/** + * @brief FilterSamplePlugin::postCondition + * @return + */ +int FilterHeatGeodesicPlugin::postCondition(const QAction*) const +{ + // NOTE: note sure about these + return MeshModel::MM_VERTQUALITY | MeshModel::MM_FACEQUALITY; +} + +/** + * @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 FilterHeatGeodesicPlugin::initParameterList(const QAction *action,const MeshModel &m) +{ + RichParameterList parlst; + switch(ID(action)) { + case FP_COMPUTE_HEATGEODESIC_FROM_SELECTION : + parlst.addParam(RichFloat("m", 1, "m", "Multiplier used in backward Euler timestep.")); + 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 FilterHeatGeodesicPlugin::applyFilter(const QAction * action, const RichParameterList & parameters, MeshDocument &md, unsigned int& /*postConditionMask*/, vcg::CallBackPos *cb) +{ + switch(ID(action)) { + case FP_COMPUTE_HEATGEODESIC_FROM_SELECTION : + { + CMeshO &mesh = md.mm()->cm; + cb(100*0/10, "Computing Boundary Conditions..."); + Eigen::VectorXd initialConditions(mesh.VN()); + for(int i=0; i < mesh.vert.size(); i++){ + initialConditions[i] = mesh.vert[i].IsS() ? 1 : 0; + } + cb(100*1/10, "Updating Topology and Computing Face Normals..."); + vcg::tri::UpdateTopology::VertexFace(mesh); + vcg::tri::UpdateTopology::FaceFace(mesh); + vcg::tri::UpdateNormal::PerFaceNormalized(mesh); + + cb(100*2/10, "Building Linear System (Heatflow)..."); + Eigen::SparseMatrix mass(mesh.VN(), mesh.VN()); + buildMassMatrix(mesh, mass); + + Eigen::SparseMatrix cotanOperator(mesh.VN(), mesh.VN()); + buildCotanMatrix(mesh, cotanOperator); + + double avg_edge_len = computeAverageEdgeLength(mesh); + double timestep = parameters.getFloat("m") * avg_edge_len * avg_edge_len; + Eigen::SparseMatrix system1(mesh.VN(), mesh.VN()); + system1 = mass - timestep * cotanOperator; + + Eigen::SimplicialLDLT> solver; + + cb(100*3/10, "Computing Matrix Factorization (Heatflow)..."); + solver.compute(system1); + if(solver.info() != Eigen::Success) { + log("Error: Factorization Failed (Heatflow)."); + } + cb(100*4/10, "Solving Linear System (Heatflow)..."); + Eigen::VectorXd heatflow = solver.solve(initialConditions); // (VN) + if(solver.info() != Eigen::Success) { + log("Error: Solver Failed (Heatflow)."); + } + cb(100*5/10, "Computing Heat Gradient..."); + Eigen::MatrixX3d heatGradient = computeVertexGradient(mesh, heatflow); // (FN, 3) + + Eigen::MatrixX3d normalizedVectorField = normalizeVectorField(-heatGradient); // (FN, 3) + cb(100*6/10, "Computing Divergence..."); + Eigen::VectorXd divergence = computeVertexDivergence(mesh, normalizedVectorField); // (VN) + + cb(100*7/10, "Building Linear System (Geodesic)..."); + Eigen::SparseMatrix system2(mesh.VN(), mesh.VN()); + system2 = cotanOperator; //+ 1e-6 * Eigen::Matrix::Identity(mesh.VN(), mesh.VN()); + + cb(100*8/10, "Computing Matrix Factorization (Geodesic)..."); + solver.compute(system2); + if(solver.info() != Eigen::Success) { + log("Error: Factorization Failed (Geodesic)."); + } + cb(100*9/10, "Solving Linear System (Geodesic)..."); + Eigen::VectorXd geodesicDistance = solver.solve(divergence); + if(solver.info() != Eigen::Success) { + log("Error: Solver Failed (Geodesic)."); + } + cb(100*10/10, "Saving Geodesic Distance..."); + // invert and shift + geodesicDistance.array() *= -1; // no clue as to why this needs to be here + geodesicDistance.array() -= geodesicDistance.minCoeff(); + + // set geodesic distance as quality + for(int i=0; i < mesh.vert.size(); i++){ + mesh.vert[i].Q() = geodesicDistance(0); + } + } + break; + default : + wrongActionCalled(action); + } + return std::map(); +} + +void computeHeatGeodesicFromSelection(MeshDocument &md, vcg::CallBackPos *cb, float m){ + +} + +MESHLAB_PLUGIN_NAME_EXPORTER(FilterSamplePlugin) diff --git a/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.h b/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.h new file mode 100644 index 000000000..2fb67ad6d --- /dev/null +++ b/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.h @@ -0,0 +1,39 @@ +#ifndef FILTERSAMPLE_PLUGIN_H +#define FILTERSAMPLE_PLUGIN_H + +#include + +class FilterHeatGeodesicPlugin : public QObject, public FilterPlugin +{ + Q_OBJECT + MESHLAB_PLUGIN_IID_EXPORTER(FILTER_PLUGIN_IID) + Q_INTERFACES(FilterPlugin) + +public: + enum { FP_COMPUTE_HEATGEODESIC_FROM_SELECTION } ; + + FilterHeatGeodesicPlugin(); + virtual ~FilterHeatGeodesicPlugin(); + + QString pluginName() 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 MeshModel &/*m*/); + std::map applyFilter( + const QAction* action, + const RichParameterList & parameters, + MeshDocument &md, + unsigned int& postConditionMask, + vcg::CallBackPos * cb); + +private: + void computeHeatGeodesicFromSelection(MeshDocument& mesh, vcg::CallBackPos* cb, float m); +}; + +#endif diff --git a/src/meshlabplugins/filter_heatgeodesic/trimesh_heatmethod.h b/src/meshlabplugins/filter_heatgeodesic/trimesh_heatmethod.h new file mode 100644 index 000000000..3f3eab0e0 --- /dev/null +++ b/src/meshlabplugins/filter_heatgeodesic/trimesh_heatmethod.h @@ -0,0 +1,250 @@ +#ifndef TRIMESH_HEAT_METHOD +#define TRIMESH_HEAT_METHOD + +#include +#include + +#include + +#include + + +// foward declarations +inline Eigen::Vector3d toEigen(const vcg::Point3f& p); +inline double cotan(const Eigen::Vector3d& v0, const Eigen::Vector3d& v1); +inline void buildMassMatrix(CMeshO &mesh, Eigen::SparseMatrix &mass); +inline void buildCotanMatrix(CMeshO &mesh, Eigen::SparseMatrix &cotanOperator); +inline double computeAverageEdgeLength(CMeshO &mesh); +inline Eigen::MatrixX3d computeVertexGradient(CMeshO &mesh, const Eigen::VectorXd &heat); +inline Eigen::VectorXd computeVertexDivergence(CMeshO &mesh, const Eigen::MatrixX3d &field); +inline Eigen::MatrixX3d normalizeVectorField(const Eigen::MatrixX3d &field); + +inline Eigen::Vector3d toEigen(const vcg::Point3f& p) +{ + return Eigen::Vector3d(p.X(), p.Y(), p.Z()); +}; + + +inline double cotan(const Eigen::Vector3d& v0, const Eigen::Vector3d& v1) +{ + // cos(theta) / sin(theta) + return v0.dot(v1) / v0.cross(v1).norm(); +}; + + +inline void buildMassMatrix(CMeshO &mesh, Eigen::SparseMatrix &mass){ + // compute area of all faces + for (CMeshO::FaceIterator fi = mesh.face.begin(); fi != mesh.face.end(); ++fi) + { + vcg::Point3f p0 = fi->V(0)->P(); + vcg::Point3f p1 = fi->V(1)->P(); + vcg::Point3f p2 = fi->V(2)->P(); + double e0 = toEigen(p1 - p0).norm(); + double e1 = toEigen(p2 - p0).norm(); + double e2 = toEigen(p2 - p1).norm(); + double s = (e0 + e1 + e2) / 2; + double area = std::sqrt(s * (s - e0) * (s - e1) * (s - e2)); + // we store face areas in quality field to avoid a hash table + // this will also be useful for the gradient computation + fi->Q() = area; + } + // compute area of the dual cell for each vertex + for (int i = 0; i < mesh.VN(); ++i){ + CMeshO::VertexType *vp = &mesh.vert[i]; + + std::vector faces; + std::vector indices; + vcg::face::VFStarVF(vp, faces, indices); + + double area = 0; + for (int j = 0; j < faces.size(); ++j) + { + area += faces[j]->Q(); + } + area /= 3; + mass.coeffRef(i, i) = area; + } +} + + +inline void buildCotanMatrix(CMeshO &mesh, Eigen::SparseMatrix &cotanOperator){ + // initialize a hashtable from vertex pointers to ids + std::unordered_map vertex_ids; + for (int i = 0; i < mesh.VN(); ++i){ + vertex_ids[&mesh.vert[i]] = i; + } + + // iterate over all vertices to fill cotan matrix + for (int i = 0; i < mesh.VN(); ++i){ + CMeshO::VertexType *vp = &mesh.vert[i]; + CMeshO::FaceType *fp = vp->VFp(); + vcg::face::Pos pos(fp, vp); + vcg::face::Pos start(fp, vp); + // iterate over all incident edges of vp + do { + // get the vertex opposite to vp + pos.FlipV(); + CMeshO::VertexType *vo = pos.V(); + // move to left vertex + pos.FlipE();pos.FlipV(); + CMeshO::VertexType *vl = pos.V(); + // move back then to right vertex + pos.FlipV();pos.FlipE(); // back to vo + pos.FlipF();pos.FlipE();pos.FlipV(); + CMeshO::VertexType *vr = pos.V(); + pos.FlipV();pos.FlipE();pos.FlipF();pos.FlipV(); // back to vp + + // compute cotan of left edges and right edges + Eigen::Vector3d elf = toEigen(vo->P() - vl->P()); // far left edge + Eigen::Vector3d eln = toEigen(vp->P() - vl->P()); // near left edge + Eigen::Vector3d erf = toEigen(vp->P() - vr->P()); // far right edge + Eigen::Vector3d ern = toEigen(vo->P() - vr->P()); // near right edge + + double cotan_l = cotan(elf, eln); + double cotan_r = cotan(ern, erf); + + // add to the matrix + cotanOperator.coeffRef(vertex_ids[vp], vertex_ids[vo]) = (cotan_l + cotan_r)/2; + + // move to the next edge + pos.FlipF();pos.FlipE(); + } while (pos != start); + } + + // compute diagonal entries + for (int i = 0; i < mesh.VN(); ++i){ + cotanOperator.coeffRef(i, i) = -cotanOperator.row(i).sum(); + } + +} + + +inline double computeAverageEdgeLength(CMeshO &mesh){ + // compute total length of all edges + double total_length = 0; + for (CMeshO::FaceIterator fi = mesh.face.begin(); fi != mesh.face.end(); ++fi) + { + vcg::Point3f p0 = fi->V(0)->P(); + vcg::Point3f p1 = fi->V(1)->P(); + vcg::Point3f p2 = fi->V(2)->P(); + double e2 = toEigen(p1 - p0).norm(); + double e1 = toEigen(p2 - p0).norm(); + double e0 = toEigen(p2 - p1).norm(); + total_length += (e0 + e1 + e2) / 2; + } + return total_length / (3./2. * mesh.FN()); +} + + +inline Eigen::MatrixX3d computeVertexGradient(CMeshO &mesh, const Eigen::VectorXd &heat){ + Eigen::MatrixX3d heatGradientField(mesh.FN(), 3); + // initialize a hashtable from vertex pointers to ids + std::unordered_map vertex_ids; + for (int i = 0; i < mesh.VN(); ++i){ + vertex_ids[&mesh.vert[i]] = i; + } + // compute gradient of heat function at each vertex + for (int i = 0; i < mesh.FN(); ++i){ + CMeshO::FaceType *fp = &mesh.face[i]; + + vcg::Point3f p0 = fp->V(0)->P(); + vcg::Point3f p1 = fp->V(1)->P(); + vcg::Point3f p2 = fp->V(2)->P(); + + // normal unit vector + Eigen::Vector3d n = toEigen(fp->N()); + n /= n.norm(); + // face area + double faceArea = fp->Q(); + // (ORDERING): edge unit vectors (assuming counter-clockwise ordering) + // note if the ordering is clockwise, the gradient will point in the opposite direction + Eigen::Vector3d e0 = toEigen(p2 - p1); + e0 /= e0.norm(); + Eigen::Vector3d e1 = toEigen(p0 - p2); + e1 /= e1.norm(); + Eigen::Vector3d e2 = toEigen(p1 - p0); + e2 /= e2.norm(); + // gradient unit vectors + Eigen::Vector3d g0 = n.cross(e0); //v0 grad + Eigen::Vector3d g1 = n.cross(e1); //v1 grad + Eigen::Vector3d g2 = n.cross(e2); //v2 grad + + // add vertex gradient contributions + Eigen::Vector3d total_grad = ( + g0 * heat(vertex_ids[fp->V(0)]) + + g1 * heat(vertex_ids[fp->V(1)]) + + g2 * heat(vertex_ids[fp->V(2)]) + ) / (2 * faceArea); + + heatGradientField.row(i) = total_grad; + } + return heatGradientField; +} + + +inline Eigen::MatrixX3d normalizeVectorField(const Eigen::MatrixX3d &field){ + Eigen::MatrixX3d normalizedField(field.rows(), 3); + normalizedField.setZero(); + // normalize vector field at each vertex + for (int i = 0; i < field.rows(); ++i){ + Eigen::Vector3d v = field.row(i); + normalizedField.row(i) = v / v.norm(); + } + return normalizedField; +} + + +inline Eigen::VectorXd computeVertexDivergence(CMeshO &mesh, const Eigen::MatrixX3d &field){ + Eigen::VectorXd divergence(mesh.VN()); + divergence.setZero(); + // initialize a hashtable from face pointers to ids + std::unordered_map face_ids; + for (int i = 0; i < mesh.FN(); ++i){ + face_ids[&mesh.face[i]] = i; + } + + // compute divergence of vector field at each vertex + for (int i = 0; i < mesh.VN(); ++i){ + CMeshO::VertexType *vp = &mesh.vert[i]; + + std::vector faces; + std::vector indices; + vcg::face::VFStarVF(vp, faces, indices); + for (int j = 0; j < faces.size(); ++j) + { + CMeshO::FaceType *fp = faces[j]; + int index = indices[j]; + vcg::Point3f p0 = fp->V(0)->P(); + vcg::Point3f p1 = fp->V(1)->P(); + vcg::Point3f p2 = fp->V(2)->P(); + // (ORDERING) edge vectors + Eigen::Vector3d el, er, eo; //left, right, opposite to vp + if (index == 0){ + el = toEigen(p2 - p0); //-e1 + er = toEigen(p1 - p0); //e2 + eo = toEigen(p2 - p1); //e0 + } else if (index == 1){ + el = toEigen(p0 - p1); //-e2 + er = toEigen(p2 - p1); //e0 + eo = toEigen(p0 - p2); //e1 + } else if (index == 2){ + el = toEigen(p1 - p2); //-e0 + er = toEigen(p0 - p2); //e1 + eo = toEigen(p1 - p0); //e2 + } + // compute left and right cotangents + double cotl = cotan(-el, eo); // -el -> angle between el and eo + double cotr = cotan(er, eo); + // normalize edge vectors after cotangent computation + el /= el.norm(); + er /= er.norm(); + // add divergence contribution of given face + Eigen::Vector3d x = field.row(face_ids[fp]); + divergence(i) += (cotl * er.dot(x) + cotr * el.dot(x)) / 2; + } + } + return divergence; +} + + +#endif From dc9385c2461fcc711a5d1e7ee89e8e31ebb7999e Mon Sep 17 00:00:00 2001 From: davidboening Date: Sat, 17 Jun 2023 22:27:51 +0200 Subject: [PATCH 20/54] added heatgeodesic to cmake --- src/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 11600523e..f13281016 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -153,6 +153,7 @@ if(NOT DEFINED MESHLAB_PLUGINS) # it may be already defined in parent directory meshlabplugins/filter_dirt meshlabplugins/filter_fractal meshlabplugins/filter_func + meshlabplugins/filter_heatgeodesic # added meshlabplugins/filter_img_patch_param meshlabplugins/filter_icp meshlabplugins/filter_io_nxs From 66a60362778de1f11acf44f0e41dacc2ce61e0b4 Mon Sep 17 00:00:00 2001 From: davidboening Date: Mon, 19 Jun 2023 11:09:10 +0200 Subject: [PATCH 21/54] version 1.0 fixed requirements, tested on various meshes. --- .../filter_heatgeodesic.cpp | 60 ++++++++++++------- 1 file changed, 38 insertions(+), 22 deletions(-) diff --git a/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.cpp b/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.cpp index ececbd4b9..406f235bd 100644 --- a/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.cpp +++ b/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.cpp @@ -37,7 +37,7 @@ QString FilterHeatGeodesicPlugin::filterName(ActionIDType filterId) const { switch(filterId) { case FP_COMPUTE_HEATGEODESIC_FROM_SELECTION : - return "Compute Geodesic From Selection (Heat)"; + return QString("Compute Geodesic From Selection (Heat)"); default : assert(0); return QString(); @@ -54,7 +54,7 @@ QString FilterHeatGeodesicPlugin::pythonFilterName(ActionIDType f) const { switch(f) { case FP_COMPUTE_HEATGEODESIC_FROM_SELECTION : - return "compute_heat_geodesic_from_selection"; + return "compute_approximate_geodesic_from_selection"; default : assert(0); return QString(); @@ -72,10 +72,10 @@ QString FilterHeatGeodesicPlugin::pythonFilterName(ActionIDType f) const { switch(filterId) { case FP_COMPUTE_HEATGEODESIC_FROM_SELECTION : - return "Computes an approximated geodesic distance from the selected vertices to all others. This algorithm implements the heat method."; + return QString("Computes an approximated geodesic distance from the selected vertices to all others. This algorithm implements the heat method."); default : assert(0); - return "Unknown Filter"; + return QString("Unknown Filter"); } } @@ -90,7 +90,8 @@ FilterHeatGeodesicPlugin::FilterClass FilterHeatGeodesicPlugin::getClass(const Q { switch(ID(a)) { case FP_COMPUTE_HEATGEODESIC_FROM_SELECTION : - return FilterPlugin::Measure; + return FilterPlugin::Measure; // Note: not sure + // return FilterGeodesic::FilterClass(FilterPlugin::VertexColoring + FilterPlugin::Quality); default : assert(0); return FilterPlugin::Generic; @@ -113,10 +114,7 @@ FilterPlugin::FilterArity FilterHeatGeodesicPlugin::filterArity(const QAction*) int FilterHeatGeodesicPlugin::getPreConditions(const QAction*) const { // NOTE: note sure about these - return MeshModel::MM_VERTCOORD | MeshModel::MM_VERTQUALITY | - MeshModel::MM_FACEFACETOPO | MeshModel::MM_VERTFACETOPO | - MeshModel::MM_FACEQUALITY | MeshModel::MM_FACENORMAL | - MeshModel::MM_VERTFLAGSELECT; + return MeshModel::MM_FACEFACETOPO | MeshModel::MM_VERTFACETOPO; } /** @@ -126,7 +124,7 @@ int FilterHeatGeodesicPlugin::getPreConditions(const QAction*) const int FilterHeatGeodesicPlugin::postCondition(const QAction*) const { // NOTE: note sure about these - return MeshModel::MM_VERTQUALITY | MeshModel::MM_FACEQUALITY; + return MeshModel::MM_VERTQUALITY | MeshModel::MM_VERTCOLOR; } /** @@ -164,20 +162,40 @@ RichParameterList FilterHeatGeodesicPlugin::initParameterList(const QAction *act */ std::map FilterHeatGeodesicPlugin::applyFilter(const QAction * action, const RichParameterList & parameters, MeshDocument &md, unsigned int& /*postConditionMask*/, vcg::CallBackPos *cb) { + MeshModel &mm=*(md.mm()); switch(ID(action)) { case FP_COMPUTE_HEATGEODESIC_FROM_SELECTION : { - CMeshO &mesh = md.mm()->cm; - cb(100*0/10, "Computing Boundary Conditions..."); - Eigen::VectorXd initialConditions(mesh.VN()); - for(int i=0; i < mesh.vert.size(); i++){ - initialConditions[i] = mesh.vert[i].IsS() ? 1 : 0; - } - cb(100*1/10, "Updating Topology and Computing Face Normals..."); + mm.updateDataMask(MeshModel::MM_FACEFACETOPO); + mm.updateDataMask(MeshModel::MM_VERTFACETOPO); + mm.updateDataMask(MeshModel::MM_FACEQUALITY); + mm.updateDataMask(MeshModel::MM_FACENORMAL); + mm.updateDataMask(MeshModel::MM_VERTQUALITY); + mm.updateDataMask(MeshModel::MM_VERTCOLOR); + CMeshO &mesh = mm.cm; + + cb(100*0/10, "Updating Topology and Computing Face Normals..."); vcg::tri::UpdateTopology::VertexFace(mesh); vcg::tri::UpdateTopology::FaceFace(mesh); vcg::tri::UpdateNormal::PerFaceNormalized(mesh); + cb(100*1/10, "Computing Boundary Conditions..."); + Eigen::VectorXd initialConditions(mesh.VN()); + int selection_count = 0; + for(int i=0; i < mesh.vert.size(); i++){ + if (mesh.vert[i].IsS()){ + initialConditions[i] = 1; + ++selection_count; + } + else { + initialConditions[i] = 0; + } + } + if (selection_count < 1){ + log("Warning: no vertices are selected! aborting computation."); + break; + } + cb(100*2/10, "Building Linear System (Heatflow)..."); Eigen::SparseMatrix mass(mesh.VN(), mesh.VN()); buildMassMatrix(mesh, mass); @@ -230,8 +248,10 @@ std::map FilterHeatGeodesicPlugin::applyFilter(const QAct // set geodesic distance as quality for(int i=0; i < mesh.vert.size(); i++){ - mesh.vert[i].Q() = geodesicDistance(0); + mesh.vert[i].Q() = geodesicDistance(i); } + + vcg::tri::UpdateColor::PerVertexQualityRamp(mesh); } break; default : @@ -240,8 +260,4 @@ std::map FilterHeatGeodesicPlugin::applyFilter(const QAct return std::map(); } -void computeHeatGeodesicFromSelection(MeshDocument &md, vcg::CallBackPos *cb, float m){ - -} - MESHLAB_PLUGIN_NAME_EXPORTER(FilterSamplePlugin) From a3879881cc36713ea572e03bf9a5eec9f90e5996 Mon Sep 17 00:00:00 2001 From: davidboening Date: Mon, 19 Jun 2023 11:39:10 +0200 Subject: [PATCH 22/54] enabled actions to github --- src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.cpp b/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.cpp index 406f235bd..9151247a8 100644 --- a/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.cpp +++ b/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.cpp @@ -196,6 +196,7 @@ std::map FilterHeatGeodesicPlugin::applyFilter(const QAct break; } + cb(100*2/10, "Building Linear System (Heatflow)..."); Eigen::SparseMatrix mass(mesh.VN(), mesh.VN()); buildMassMatrix(mesh, mass); From adc07be2cea32d508fe0a7186cab5c2c45feba04 Mon Sep 17 00:00:00 2001 From: Alessandro Muntoni Date: Tue, 20 Jun 2023 14:29:01 +0200 Subject: [PATCH 23/54] fix windows sign --- scripts/Windows/2_deploy.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/Windows/2_deploy.sh b/scripts/Windows/2_deploy.sh index 515a70098..54e6371d2 100644 --- a/scripts/Windows/2_deploy.sh +++ b/scripts/Windows/2_deploy.sh @@ -30,9 +30,9 @@ case $i in shift # past argument=value ;; -cp=*|--cert_pssw=*) - if [ -z "${i#*=}" ]; then + CERT_PSSW="${i#*=}" + if [ -n "$CERT_PSSW" ]; then SIGN=true - CERT_PSSW="${i#*=}" fi shift # past argument=value ;; @@ -60,4 +60,4 @@ if [ "$SIGN" = true ] ; then bash $SCRIPTS_PATH/internal/2b_sign_dlls.sh -i=$PACKAGES_PATH $CERT_FILE_OPTION -cp=$CERT_PSSW echo "======= Installer Signed =======" -fi \ No newline at end of file +fi From f8f213ea4e8a90fd80c5759cc6cd51a14a3d2746 Mon Sep 17 00:00:00 2001 From: davidboening Date: Wed, 21 Jun 2023 18:35:03 +0200 Subject: [PATCH 24/54] version 1.1 Added matrix caching, major speedup in cotan weights computation --- .../filter_heatgeodesic.cpp | 188 ++++++++++-------- .../filter_heatgeodesic/filter_heatgeodesic.h | 2 +- .../filter_heatgeodesic/trimesh_heatmethod.h | 139 ++++++------- 3 files changed, 181 insertions(+), 148 deletions(-) diff --git a/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.cpp b/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.cpp index 9151247a8..c780e7d07 100644 --- a/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.cpp +++ b/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.cpp @@ -173,86 +173,8 @@ std::map FilterHeatGeodesicPlugin::applyFilter(const QAct mm.updateDataMask(MeshModel::MM_VERTQUALITY); mm.updateDataMask(MeshModel::MM_VERTCOLOR); CMeshO &mesh = mm.cm; - - cb(100*0/10, "Updating Topology and Computing Face Normals..."); - vcg::tri::UpdateTopology::VertexFace(mesh); - vcg::tri::UpdateTopology::FaceFace(mesh); - vcg::tri::UpdateNormal::PerFaceNormalized(mesh); - - cb(100*1/10, "Computing Boundary Conditions..."); - Eigen::VectorXd initialConditions(mesh.VN()); - int selection_count = 0; - for(int i=0; i < mesh.vert.size(); i++){ - if (mesh.vert[i].IsS()){ - initialConditions[i] = 1; - ++selection_count; - } - else { - initialConditions[i] = 0; - } - } - if (selection_count < 1){ - log("Warning: no vertices are selected! aborting computation."); - break; - } - - - cb(100*2/10, "Building Linear System (Heatflow)..."); - Eigen::SparseMatrix mass(mesh.VN(), mesh.VN()); - buildMassMatrix(mesh, mass); - - Eigen::SparseMatrix cotanOperator(mesh.VN(), mesh.VN()); - buildCotanMatrix(mesh, cotanOperator); - - double avg_edge_len = computeAverageEdgeLength(mesh); - double timestep = parameters.getFloat("m") * avg_edge_len * avg_edge_len; - Eigen::SparseMatrix system1(mesh.VN(), mesh.VN()); - system1 = mass - timestep * cotanOperator; - - Eigen::SimplicialLDLT> solver; - - cb(100*3/10, "Computing Matrix Factorization (Heatflow)..."); - solver.compute(system1); - if(solver.info() != Eigen::Success) { - log("Error: Factorization Failed (Heatflow)."); - } - cb(100*4/10, "Solving Linear System (Heatflow)..."); - Eigen::VectorXd heatflow = solver.solve(initialConditions); // (VN) - if(solver.info() != Eigen::Success) { - log("Error: Solver Failed (Heatflow)."); - } - cb(100*5/10, "Computing Heat Gradient..."); - Eigen::MatrixX3d heatGradient = computeVertexGradient(mesh, heatflow); // (FN, 3) - - Eigen::MatrixX3d normalizedVectorField = normalizeVectorField(-heatGradient); // (FN, 3) - cb(100*6/10, "Computing Divergence..."); - Eigen::VectorXd divergence = computeVertexDivergence(mesh, normalizedVectorField); // (VN) - - cb(100*7/10, "Building Linear System (Geodesic)..."); - Eigen::SparseMatrix system2(mesh.VN(), mesh.VN()); - system2 = cotanOperator; //+ 1e-6 * Eigen::Matrix::Identity(mesh.VN(), mesh.VN()); - - cb(100*8/10, "Computing Matrix Factorization (Geodesic)..."); - solver.compute(system2); - if(solver.info() != Eigen::Success) { - log("Error: Factorization Failed (Geodesic)."); - } - cb(100*9/10, "Solving Linear System (Geodesic)..."); - Eigen::VectorXd geodesicDistance = solver.solve(divergence); - if(solver.info() != Eigen::Success) { - log("Error: Solver Failed (Geodesic)."); - } - cb(100*10/10, "Saving Geodesic Distance..."); - // invert and shift - geodesicDistance.array() *= -1; // no clue as to why this needs to be here - geodesicDistance.array() -= geodesicDistance.minCoeff(); - - // set geodesic distance as quality - for(int i=0; i < mesh.vert.size(); i++){ - mesh.vert[i].Q() = geodesicDistance(i); - } - - vcg::tri::UpdateColor::PerVertexQualityRamp(mesh); + // TODO: compact all vectors + computeHeatGeodesicFromSelection(mesh, cb, parameters.getFloat("m")); } break; default : @@ -261,4 +183,110 @@ std::map FilterHeatGeodesicPlugin::applyFilter(const QAct return std::map(); } +inline void FilterHeatGeodesicPlugin::computeHeatGeodesicFromSelection(CMeshO& mesh, vcg::CallBackPos* cb, float m){ + // build boundary conditions + Eigen::VectorXd boundaryConditions(mesh.VN()); + int selection_count = 0; + for(int i=0; i < mesh.VN(); i++){ + boundaryConditions(i) = mesh.vert[i].IsS() ? (++selection_count, 1) : 0; + } + if (selection_count < 1){ + log("Warning: no vertices are selected! aborting computation."); + return; + } + + // update topology and face normals + vcg::tri::UpdateTopology::VertexFace(mesh); + vcg::tri::UpdateTopology::FaceFace(mesh); + vcg::tri::UpdateNormal::PerFaceNormalized(mesh); + + // state variables + Eigen::SparseMatrix massMatrix; + Eigen::SparseMatrix cotanMatrix; + double avg_edge_len; + + typedef std::tuple< + Eigen::SparseMatrix, // system1 fact + Eigen::SparseMatrix, // system2 fact + double // avg edge len + > HeatMethodData; + + // recover state if it exists + if (!vcg::tri::HasPerMeshAttribute(mesh, "HeatMethodData")){ + // current solution saves matrices not factorizations + // if we save factorization it is best to at least also save cotanMatrix + // to avoid an expensive reconstruction when parameter("m") changes + buildMassMatrix(mesh, massMatrix); + // this step is very slow (95% of total time) hence we pass the callback + // to update progress and avoid freezes + buildCotanLowerTriMatrix(mesh, cotanMatrix, cb); + avg_edge_len = computeAverageEdgeLength(mesh); + + HeatMethodData &HMData = vcg::tri::Allocator::GetPerMeshAttribute(mesh, std::string("HeatMethodData"))(); + HMData = HeatMethodData(massMatrix, cotanMatrix, avg_edge_len); + } + else { + HeatMethodData &HMData = vcg::tri::Allocator::GetPerMeshAttribute(mesh, std::string("HeatMethodData"))(); + std::tie (massMatrix, cotanMatrix, avg_edge_len) = HMData; + } + + // cholesky type solver + Eigen::SimplicialLDLT> solver; + + // build system 1 + double timestep = m * avg_edge_len * avg_edge_len; + Eigen::SparseMatrix system1(mesh.VN(), mesh.VN()); + system1 = massMatrix - timestep * cotanMatrix; + + cb(91, "Computing Factorization 1..."); + + // factorize system 1 + solver.compute(system1); + if(solver.info() != Eigen::Success) { + log("Error: Factorization Failed (Heatflow)."); + } + + cb(93, "Solving System 1..."); + // solve system 1 + Eigen::VectorXd heatflow = solver.solve(boundaryConditions); // (VN) + if(solver.info() != Eigen::Success) { + log("Error: Solver Failed (Heatflow)."); + } + + cb(95, "Computing Gradient, VectorField, Divergence..."); + // intermediate steps + Eigen::MatrixX3d heatGradient = computeVertexGradient(mesh, heatflow); // (FN, 3) + Eigen::MatrixX3d normalizedVectorField = normalizeVectorField(-heatGradient); // (FN, 3) + Eigen::VectorXd divergence = computeVertexDivergence(mesh, normalizedVectorField); // (VN) + + // build system 2 + Eigen::SparseMatrix system2(mesh.VN(), mesh.VN()); + system2 = cotanMatrix; //+ 1e-6 * Eigen::Matrix::Identity(mesh.VN(), mesh.VN()); + + cb(96, "Computing Factorization 2..."); + // factorize system 2 + solver.compute(system2); + if(solver.info() != Eigen::Success) { + log("Error: Factorization Failed (Geodesic)."); + } + + cb(97, "Solving System 2..."); + // solve system 2 + Eigen::VectorXd geodesicDistance = solver.solve(divergence); + + if(solver.info() != Eigen::Success) { + log("Error: Solver Failed (Geodesic)."); + } + + // shift to impose boundary conditions (dist(d) = 0 \forall d \in init_cond) + geodesicDistance.array() -= geodesicDistance.minCoeff(); + + cb(99, "Updating Quality and Color..."); + // set geodesic distance as quality and color + for(int i=0; i < mesh.vert.size(); i++){ + mesh.vert[i].Q() = geodesicDistance(i); + } + vcg::tri::UpdateColor::PerVertexQualityRamp(mesh); +} + MESHLAB_PLUGIN_NAME_EXPORTER(FilterSamplePlugin) diff --git a/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.h b/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.h index 2fb67ad6d..b421550ab 100644 --- a/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.h +++ b/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.h @@ -33,7 +33,7 @@ public: vcg::CallBackPos * cb); private: - void computeHeatGeodesicFromSelection(MeshDocument& mesh, vcg::CallBackPos* cb, float m); + void computeHeatGeodesicFromSelection(CMeshO& mesh, vcg::CallBackPos* cb, float m); }; #endif diff --git a/src/meshlabplugins/filter_heatgeodesic/trimesh_heatmethod.h b/src/meshlabplugins/filter_heatgeodesic/trimesh_heatmethod.h index 3f3eab0e0..37ebe5d2e 100644 --- a/src/meshlabplugins/filter_heatgeodesic/trimesh_heatmethod.h +++ b/src/meshlabplugins/filter_heatgeodesic/trimesh_heatmethod.h @@ -9,30 +9,26 @@ #include -// foward declarations -inline Eigen::Vector3d toEigen(const vcg::Point3f& p); -inline double cotan(const Eigen::Vector3d& v0, const Eigen::Vector3d& v1); -inline void buildMassMatrix(CMeshO &mesh, Eigen::SparseMatrix &mass); -inline void buildCotanMatrix(CMeshO &mesh, Eigen::SparseMatrix &cotanOperator); -inline double computeAverageEdgeLength(CMeshO &mesh); -inline Eigen::MatrixX3d computeVertexGradient(CMeshO &mesh, const Eigen::VectorXd &heat); -inline Eigen::VectorXd computeVertexDivergence(CMeshO &mesh, const Eigen::MatrixX3d &field); -inline Eigen::MatrixX3d normalizeVectorField(const Eigen::MatrixX3d &field); +// LOCAL FUNCTIONS -inline Eigen::Vector3d toEigen(const vcg::Point3f& p) -{ - return Eigen::Vector3d(p.X(), p.Y(), p.Z()); -}; +namespace { + inline Eigen::Vector3d toEigen(const vcg::Point3f& p) + { + return Eigen::Vector3d(p.X(), p.Y(), p.Z()); + }; -inline double cotan(const Eigen::Vector3d& v0, const Eigen::Vector3d& v1) -{ - // cos(theta) / sin(theta) - return v0.dot(v1) / v0.cross(v1).norm(); -}; + inline double cotan(const Eigen::Vector3d& v0, const Eigen::Vector3d& v1) + { + // cos(theta) / sin(theta) + return v0.dot(v1) / v0.cross(v1).norm(); + }; +} +// GLOBAL FUNCTIONS inline void buildMassMatrix(CMeshO &mesh, Eigen::SparseMatrix &mass){ + mass.resize(mesh.VN(), mesh.VN()); // compute area of all faces for (CMeshO::FaceIterator fi = mesh.face.begin(); fi != mesh.face.end(); ++fi) { @@ -55,7 +51,7 @@ inline void buildMassMatrix(CMeshO &mesh, Eigen::SparseMatrix &mass){ std::vector faces; std::vector indices; vcg::face::VFStarVF(vp, faces, indices); - + double area = 0; for (int j = 0; j < faces.size(); ++j) { @@ -67,55 +63,65 @@ inline void buildMassMatrix(CMeshO &mesh, Eigen::SparseMatrix &mass){ } -inline void buildCotanMatrix(CMeshO &mesh, Eigen::SparseMatrix &cotanOperator){ +inline void buildCotanLowerTriMatrix(CMeshO &mesh, Eigen::SparseMatrix &cotanOperator, vcg::CallBackPos* cb = NULL){ + cotanOperator.resize(mesh.VN(), mesh.VN()); // initialize a hashtable from vertex pointers to ids std::unordered_map vertex_ids; for (int i = 0; i < mesh.VN(); ++i){ vertex_ids[&mesh.vert[i]] = i; } - // iterate over all vertices to fill cotan matrix - for (int i = 0; i < mesh.VN(); ++i){ - CMeshO::VertexType *vp = &mesh.vert[i]; - CMeshO::FaceType *fp = vp->VFp(); - vcg::face::Pos pos(fp, vp); - vcg::face::Pos start(fp, vp); - // iterate over all incident edges of vp - do { - // get the vertex opposite to vp - pos.FlipV(); - CMeshO::VertexType *vo = pos.V(); - // move to left vertex - pos.FlipE();pos.FlipV(); - CMeshO::VertexType *vl = pos.V(); - // move back then to right vertex - pos.FlipV();pos.FlipE(); // back to vo - pos.FlipF();pos.FlipE();pos.FlipV(); - CMeshO::VertexType *vr = pos.V(); - pos.FlipV();pos.FlipE();pos.FlipF();pos.FlipV(); // back to vp + // progress bar variables + int update_size = mesh.FN() / 90; // 90 updates (90% weight of the progress bar) + int progress = 0; - // compute cotan of left edges and right edges - Eigen::Vector3d elf = toEigen(vo->P() - vl->P()); // far left edge - Eigen::Vector3d eln = toEigen(vp->P() - vl->P()); // near left edge - Eigen::Vector3d erf = toEigen(vp->P() - vr->P()); // far right edge - Eigen::Vector3d ern = toEigen(vo->P() - vr->P()); // near right edge + // compute cotan weights + for (int i = 0; i < mesh.FN(); ++i){ + // progress bar update + if (cb != NULL && i % update_size == 89){ + cb(++progress, "Computing Cotan Weights..."); + } + CMeshO::FaceType* fi = &mesh.face[i]; - double cotan_l = cotan(elf, eln); - double cotan_r = cotan(ern, erf); + CMeshO::VertexType* v0 = fi->V(0); + CMeshO::VertexType* v1 = fi->V(1); + CMeshO::VertexType* v2 = fi->V(2); - // add to the matrix - cotanOperator.coeffRef(vertex_ids[vp], vertex_ids[vo]) = (cotan_l + cotan_r)/2; + vcg::Point3f p0 = v0->P(); + vcg::Point3f p1 = v1->P(); + vcg::Point3f p2 = v2->P(); - // move to the next edge - pos.FlipF();pos.FlipE(); - } while (pos != start); + Eigen::Vector3d e0 = toEigen(p2 - p1); + Eigen::Vector3d e1 = toEigen(p0 - p2); + Eigen::Vector3d e2 = toEigen(p1 - p0); + + // first edge is inverted to get correct orientation + double alpha0 = cotan(-e1, e2) / 2; + double alpha1 = cotan(-e2, e0) / 2; + double alpha2 = cotan(-e0, e1) / 2; + + int i0 = vertex_ids[v0]; + int i1 = vertex_ids[v1]; + int i2 = vertex_ids[v2]; + + // save only lower triangular part + if (i0 > i1) + cotanOperator.coeffRef(i0, i1) += alpha2; + else + cotanOperator.coeffRef(i1, i0) += alpha2; + if (i0 > i2) + cotanOperator.coeffRef(i0, i2) += alpha1; + else + cotanOperator.coeffRef(i2, i0) += alpha1; + if (i1 > i2) + cotanOperator.coeffRef(i1, i2) += alpha0; + else + cotanOperator.coeffRef(i2, i1) += alpha0; + + cotanOperator.coeffRef(i0, i0) -= (alpha1 + alpha2); + cotanOperator.coeffRef(i1, i1) -= (alpha0 + alpha2); + cotanOperator.coeffRef(i2, i2) -= (alpha0 + alpha1); } - - // compute diagonal entries - for (int i = 0; i < mesh.VN(); ++i){ - cotanOperator.coeffRef(i, i) = -cotanOperator.row(i).sum(); - } - } @@ -156,8 +162,7 @@ inline Eigen::MatrixX3d computeVertexGradient(CMeshO &mesh, const Eigen::VectorX n /= n.norm(); // face area double faceArea = fp->Q(); - // (ORDERING): edge unit vectors (assuming counter-clockwise ordering) - // note if the ordering is clockwise, the gradient will point in the opposite direction + // edge unit vectors (counter-clockwise) Eigen::Vector3d e0 = toEigen(p2 - p1); e0 /= e0.norm(); Eigen::Vector3d e1 = toEigen(p0 - p2); @@ -170,13 +175,13 @@ inline Eigen::MatrixX3d computeVertexGradient(CMeshO &mesh, const Eigen::VectorX Eigen::Vector3d g2 = n.cross(e2); //v2 grad // add vertex gradient contributions - Eigen::Vector3d total_grad = ( - g0 * heat(vertex_ids[fp->V(0)]) + - g1 * heat(vertex_ids[fp->V(1)]) + - g2 * heat(vertex_ids[fp->V(2)]) + Eigen::Vector3d tri_grad = ( + g0 * heat(vertex_ids[fp->V(0)]) + + g1 * heat(vertex_ids[fp->V(1)]) + + g2 * heat(vertex_ids[fp->V(2)]) ) / (2 * faceArea); - heatGradientField.row(i) = total_grad; + heatGradientField.row(i) = tri_grad; } return heatGradientField; } @@ -227,14 +232,14 @@ inline Eigen::VectorXd computeVertexDivergence(CMeshO &mesh, const Eigen::Matrix el = toEigen(p0 - p1); //-e2 er = toEigen(p2 - p1); //e0 eo = toEigen(p0 - p2); //e1 - } else if (index == 2){ + } else if (index == 2){ el = toEigen(p1 - p2); //-e0 er = toEigen(p0 - p2); //e1 eo = toEigen(p1 - p0); //e2 } // compute left and right cotangents - double cotl = cotan(-el, eo); // -el -> angle between el and eo - double cotr = cotan(er, eo); + double cotl = cotan(-el, -eo); + double cotr = cotan(-er, eo); // normalize edge vectors after cotangent computation el /= el.norm(); er /= er.norm(); From 9d7664aa6ed10becae96b1dc7ee456ce98794d14 Mon Sep 17 00:00:00 2001 From: davidboening Date: Thu, 22 Jun 2023 12:28:53 +0200 Subject: [PATCH 25/54] version 1.2 two levels of caching (factorization, matrices), better encapsulation --- .../filter_heatgeodesic.cpp | 120 +++++++----------- .../filter_heatgeodesic/trimesh_heatmethod.h | 62 +++++++++ 2 files changed, 105 insertions(+), 77 deletions(-) diff --git a/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.cpp b/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.cpp index c780e7d07..1d6544786 100644 --- a/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.cpp +++ b/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.cpp @@ -184,106 +184,72 @@ std::map FilterHeatGeodesicPlugin::applyFilter(const QAct } inline void FilterHeatGeodesicPlugin::computeHeatGeodesicFromSelection(CMeshO& mesh, vcg::CallBackPos* cb, float m){ + vcg::tri::Allocator::CompactEveryVector(mesh); + // build boundary conditions - Eigen::VectorXd boundaryConditions(mesh.VN()); + Eigen::VectorXd sourcePoints(mesh.VN()); int selection_count = 0; for(int i=0; i < mesh.VN(); i++){ - boundaryConditions(i) = mesh.vert[i].IsS() ? (++selection_count, 1) : 0; + sourcePoints(i) = mesh.vert[i].IsS() ? (++selection_count, 1) : 0; } if (selection_count < 1){ log("Warning: no vertices are selected! aborting computation."); return; } - // update topology and face normals - vcg::tri::UpdateTopology::VertexFace(mesh); - vcg::tri::UpdateTopology::FaceFace(mesh); - vcg::tri::UpdateNormal::PerFaceNormalized(mesh); + std::shared_ptr HMSolver; - // state variables - Eigen::SparseMatrix massMatrix; - Eigen::SparseMatrix cotanMatrix; - double avg_edge_len; - - typedef std::tuple< - Eigen::SparseMatrix, // system1 fact - Eigen::SparseMatrix, // system2 fact - double // avg edge len - > HeatMethodData; + // delete state if the mesh has changed + bool meshHasChanged = false; // Update with appropriate function + if (meshHasChanged) { + auto handle = vcg::tri::Allocator::GetPerMeshAttribute>(mesh, std::string("HeatMethodSolver")); + vcg::tri::Allocator::DeletePerMeshAttribute>(mesh, handle); + } // recover state if it exists - if (!vcg::tri::HasPerMeshAttribute(mesh, "HeatMethodData")){ - // current solution saves matrices not factorizations - // if we save factorization it is best to at least also save cotanMatrix - // to avoid an expensive reconstruction when parameter("m") changes + if (!vcg::tri::HasPerMeshAttribute(mesh, "HeatMethodSolver")){ + + // update topology and face normals + vcg::tri::UpdateTopology::VertexFace(mesh); + vcg::tri::UpdateTopology::FaceFace(mesh); + vcg::tri::UpdateNormal::PerFaceNormalized(mesh); + + // state variables + Eigen::SparseMatrix massMatrix; + Eigen::SparseMatrix cotanMatrix; + double averageEdgeLength; + buildMassMatrix(mesh, massMatrix); // this step is very slow (95% of total time) hence we pass the callback // to update progress and avoid freezes buildCotanLowerTriMatrix(mesh, cotanMatrix, cb); - avg_edge_len = computeAverageEdgeLength(mesh); + averageEdgeLength = computeAverageEdgeLength(mesh); - HeatMethodData &HMData = vcg::tri::Allocator::GetPerMeshAttribute(mesh, std::string("HeatMethodData"))(); - HMData = HeatMethodData(massMatrix, cotanMatrix, avg_edge_len); + cb(91, "Matrix Factorization..."); + HMSolver = std::make_shared(HeatMethodSolver(std::move(massMatrix), std::move(cotanMatrix), averageEdgeLength, m)); + if (HMSolver->factorization_failed()) + log("Warning: factorization has failed. The mesh is non-manifold or badly conditioned (angles ~ 0deg)"); + auto HMSolverHandle = vcg::tri::Allocator::GetPerMeshAttribute>(mesh, std::string("HeatMethodSolver")); + HMSolverHandle() = HMSolver; } else { - HeatMethodData &HMData = vcg::tri::Allocator::GetPerMeshAttribute(mesh, std::string("HeatMethodData"))(); - std::tie (massMatrix, cotanMatrix, avg_edge_len) = HMData; + // recover solver + HMSolver = vcg::tri::Allocator::GetPerMeshAttribute>(mesh, std::string("HeatMethodSolver"))(); + // if m has changed rebuild factorizations from existing matrices + if (HMSolver->get_m() != m){ + cb(91, "Rebuilding Factorization..."); + HMSolver->rebuildFactorization(m); + } } - // cholesky type solver - Eigen::SimplicialLDLT> solver; + cb(95, "Computing Geodesic Distance..."); + Eigen::VectorXd geodesicDistance = HMSolver->solve(mesh, sourcePoints); + if (HMSolver->solver_failed()) + log("Warning: solver has failed."); - // build system 1 - double timestep = m * avg_edge_len * avg_edge_len; - Eigen::SparseMatrix system1(mesh.VN(), mesh.VN()); - system1 = massMatrix - timestep * cotanMatrix; - - cb(91, "Computing Factorization 1..."); - - // factorize system 1 - solver.compute(system1); - if(solver.info() != Eigen::Success) { - log("Error: Factorization Failed (Heatflow)."); - } - - cb(93, "Solving System 1..."); - // solve system 1 - Eigen::VectorXd heatflow = solver.solve(boundaryConditions); // (VN) - if(solver.info() != Eigen::Success) { - log("Error: Solver Failed (Heatflow)."); - } - - cb(95, "Computing Gradient, VectorField, Divergence..."); - // intermediate steps - Eigen::MatrixX3d heatGradient = computeVertexGradient(mesh, heatflow); // (FN, 3) - Eigen::MatrixX3d normalizedVectorField = normalizeVectorField(-heatGradient); // (FN, 3) - Eigen::VectorXd divergence = computeVertexDivergence(mesh, normalizedVectorField); // (VN) - - // build system 2 - Eigen::SparseMatrix system2(mesh.VN(), mesh.VN()); - system2 = cotanMatrix; //+ 1e-6 * Eigen::Matrix::Identity(mesh.VN(), mesh.VN()); - - cb(96, "Computing Factorization 2..."); - // factorize system 2 - solver.compute(system2); - if(solver.info() != Eigen::Success) { - log("Error: Factorization Failed (Geodesic)."); - } - - cb(97, "Solving System 2..."); - // solve system 2 - Eigen::VectorXd geodesicDistance = solver.solve(divergence); - - if(solver.info() != Eigen::Success) { - log("Error: Solver Failed (Geodesic)."); - } - - // shift to impose boundary conditions (dist(d) = 0 \forall d \in init_cond) - geodesicDistance.array() -= geodesicDistance.minCoeff(); - - cb(99, "Updating Quality and Color..."); + cb(99, "Updating Mesh..."); // set geodesic distance as quality and color - for(int i=0; i < mesh.vert.size(); i++){ + for(int i=0; i < mesh.VN(); i++){ mesh.vert[i].Q() = geodesicDistance(i); } vcg::tri::UpdateColor::PerVertexQualityRamp(mesh); diff --git a/src/meshlabplugins/filter_heatgeodesic/trimesh_heatmethod.h b/src/meshlabplugins/filter_heatgeodesic/trimesh_heatmethod.h index 37ebe5d2e..4de5d1901 100644 --- a/src/meshlabplugins/filter_heatgeodesic/trimesh_heatmethod.h +++ b/src/meshlabplugins/filter_heatgeodesic/trimesh_heatmethod.h @@ -8,6 +8,8 @@ #include +#include + // LOCAL FUNCTIONS @@ -252,4 +254,64 @@ inline Eigen::VectorXd computeVertexDivergence(CMeshO &mesh, const Eigen::Matrix } +class HeatMethodSolver { +public: + HeatMethodSolver(Eigen::SparseMatrix &&massMatrix, Eigen::SparseMatrix &&cotanMatrix, double averageEdgeLength, double m) + : massMatrix(std::move(massMatrix)), cotanMatrix(std::move(cotanMatrix)), averageEdgeLength(averageEdgeLength), m(m) + { + // initialize pointers + solver1 = std::shared_ptr>>(new Eigen::SimplicialLDLT>); + solver2 = std::shared_ptr>>(new Eigen::SimplicialLDLT>); + + // build factorization + double timestep = m * averageEdgeLength * averageEdgeLength; + solver1->compute(massMatrix - timestep * cotanMatrix); + solver2->compute(cotanMatrix); + if((solver1->info() != Eigen::Success) || (solver2->info() != Eigen::Success)) + factorizationFailed = true; + else + factorizationFailed = false; + } + HeatMethodSolver() = delete; + HeatMethodSolver& operator=(const HeatMethodSolver&) = delete; + void rebuildFactorization(double new_m){ + m = new_m; + double timestep = m * averageEdgeLength * averageEdgeLength; + solver1->compute(massMatrix - timestep * cotanMatrix); + solver2->compute(cotanMatrix); + if((solver1->info() != Eigen::Success) || (solver2->info() != Eigen::Success)) + factorizationFailed = true; + else + factorizationFailed = false; + } + Eigen::VectorXd solve(CMeshO &mesh, Eigen::VectorXd &sourcePoints){ + Eigen::VectorXd heatflow = solver1->solve(sourcePoints); // (VN) + Eigen::MatrixX3d heatGradient = computeVertexGradient(mesh, heatflow); // (FN, 3) + Eigen::MatrixX3d unitVectorField = normalizeVectorField(-heatGradient); // (FN, 3) + Eigen::VectorXd divergence = computeVertexDivergence(mesh, unitVectorField); // (VN) + Eigen::VectorXd geodesicDistance = solver2->solve(divergence); // (VN) + if((solver1->info() != Eigen::Success) || (solver2->info() != Eigen::Success)) + solverFailed = true; + else + solverFailed = false; + // shift to impose dist(d) = 0 \forall d \in sourcePoints + geodesicDistance.array() -= geodesicDistance.minCoeff(); + return geodesicDistance; + } + double get_m(){return m;} + bool factorization_failed(){return factorizationFailed;} + bool solver_failed(){return solverFailed;} +private: + // Eigen::SimplicialLDLT has no copy/move constructor + std::shared_ptr>> solver1; + std::shared_ptr>> solver2; + Eigen::SparseMatrix massMatrix; + Eigen::SparseMatrix cotanMatrix; + double averageEdgeLength; + double m; + + bool factorizationFailed; + bool solverFailed; +}; + #endif From e53c72026e3151ce31b9c50ab76ab66382b011d2 Mon Sep 17 00:00:00 2001 From: Alessandro Muntoni Date: Fri, 23 Jun 2023 11:36:16 +0200 Subject: [PATCH 26/54] try composite action --- .github/actions/0_setup/action.yml | 22 ++++++++++++++++++++++ .github/workflows/BuildMeshLab.yml | 12 +++++------- 2 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 .github/actions/0_setup/action.yml diff --git a/.github/actions/0_setup/action.yml b/.github/actions/0_setup/action.yml new file mode 100644 index 000000000..9dcd139fb --- /dev/null +++ b/.github/actions/0_setup/action.yml @@ -0,0 +1,22 @@ +name: 'Setup Environment' +description: 'Setup Environment' + +inputs: + mac-certificate: + description: 'MacOS Certificate' + required: false + mac-certificate-pssw: + description: 'MacOS Certificate Password' + required: false + +runs: + using: "composite" + steps: + - name: Setup MSVC + uses: ilammy/msvc-dev-cmd@v1 + - name: Set CodeSign Certificate macOS + if: runner.os == 'macOS' && ${{ inputs.mac-certificate }} != null + uses: apple-actions/import-codesign-certs@v2 + with: + p12-file-base64: ${{ inputs.mac-certificate }} + p12-password: ${{ inputs.mac-certificate-pssw }} diff --git a/.github/workflows/BuildMeshLab.yml b/.github/workflows/BuildMeshLab.yml index 6f9327af4..e84670c65 100644 --- a/.github/workflows/BuildMeshLab.yml +++ b/.github/workflows/BuildMeshLab.yml @@ -6,6 +6,7 @@ on: env: QT_VERSION: 5.15.2 MAC_CERT: ${{secrets.MACOS_CERT_ID}} + MAC_CERT_PSSW: ${{secrets.MACOS_CERTIFICATE_PSSW}} WIN_CERT: ${{secrets.WIN_CERTIFICATE}} jobs: @@ -21,14 +22,11 @@ jobs: - uses: actions/checkout@v3 with: submodules: recursive - - name: Setup MSVC - uses: ilammy/msvc-dev-cmd@v1 - - name: Set CodeSign Certificate macOS - if: runner.os == 'macOS' && env.MAC_CERT != null - uses: apple-actions/import-codesign-certs@v2 + - name: Setup Environment + uses: ./.github/actions/0_setup with: - p12-file-base64: ${{ secrets.MACOS_CERTIFICATE }} - p12-password: ${{ secrets.MACOS_CERTIFICATE_PSSW }} + mac-certificate: $MAC_CERT + mac-certificate-pssw: $MAC_CERT_PSSW - name: Set CodeSign Certificate Windows if: runner.os == 'Windows' && env.WIN_CERT != null run: | From 16d8084ad3a8d14e1cbcf10d6348f9aa1a82dd64 Mon Sep 17 00:00:00 2001 From: Alessandro Muntoni Date: Fri, 23 Jun 2023 11:52:28 +0200 Subject: [PATCH 27/54] try using composite with if --- .github/actions/0_setup/action.yml | 11 +++++------ .github/workflows/BuildMeshLab.yml | 6 ++++++ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/actions/0_setup/action.yml b/.github/actions/0_setup/action.yml index 9dcd139fb..589e6d977 100644 --- a/.github/actions/0_setup/action.yml +++ b/.github/actions/0_setup/action.yml @@ -14,9 +14,8 @@ runs: steps: - name: Setup MSVC uses: ilammy/msvc-dev-cmd@v1 - - name: Set CodeSign Certificate macOS - if: runner.os == 'macOS' && ${{ inputs.mac-certificate }} != null - uses: apple-actions/import-codesign-certs@v2 - with: - p12-file-base64: ${{ inputs.mac-certificate }} - p12-password: ${{ inputs.mac-certificate-pssw }} + - name: Try + if: ${{ runner.os }} == 'macOS' + shell: bash + run: | + echo "Hello World ${{ runner.os }}" diff --git a/.github/workflows/BuildMeshLab.yml b/.github/workflows/BuildMeshLab.yml index e84670c65..162e77066 100644 --- a/.github/workflows/BuildMeshLab.yml +++ b/.github/workflows/BuildMeshLab.yml @@ -27,6 +27,12 @@ jobs: with: mac-certificate: $MAC_CERT mac-certificate-pssw: $MAC_CERT_PSSW + - name: Set CodeSign Certificate macOS + if: runner.os == 'macOS' && env.MAC_CERT != null + uses: apple-actions/import-codesign-certs@v2 + with: + p12-file-base64: ${{ secrets.MACOS_CERTIFICATE }} + p12-password: ${{ secrets.MACOS_CERTIFICATE_PSSW }} - name: Set CodeSign Certificate Windows if: runner.os == 'Windows' && env.WIN_CERT != null run: | From 2cbe1da0ba5c89b37b6273caef22dff856d176d3 Mon Sep 17 00:00:00 2001 From: Alessandro Muntoni Date: Fri, 23 Jun 2023 11:55:16 +0200 Subject: [PATCH 28/54] test always false condition --- .github/actions/0_setup/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/0_setup/action.yml b/.github/actions/0_setup/action.yml index 589e6d977..cf793e73a 100644 --- a/.github/actions/0_setup/action.yml +++ b/.github/actions/0_setup/action.yml @@ -15,7 +15,7 @@ runs: - name: Setup MSVC uses: ilammy/msvc-dev-cmd@v1 - name: Try - if: ${{ runner.os }} == 'macOS' + if: 'Windows' == 'macOS' shell: bash run: | echo "Hello World ${{ runner.os }}" From e22e5180be599a72b24146c115b73a4101396f1d Mon Sep 17 00:00:00 2001 From: Alessandro Muntoni Date: Fri, 23 Jun 2023 12:02:17 +0200 Subject: [PATCH 29/54] pass runner as input --- .github/actions/0_setup/action.yml | 7 +++++-- .github/workflows/BuildMeshLab.yml | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/actions/0_setup/action.yml b/.github/actions/0_setup/action.yml index cf793e73a..fe530f367 100644 --- a/.github/actions/0_setup/action.yml +++ b/.github/actions/0_setup/action.yml @@ -2,6 +2,9 @@ name: 'Setup Environment' description: 'Setup Environment' inputs: + runner-os: + description: 'Runner OS' + required: true mac-certificate: description: 'MacOS Certificate' required: false @@ -15,7 +18,7 @@ runs: - name: Setup MSVC uses: ilammy/msvc-dev-cmd@v1 - name: Try - if: 'Windows' == 'macOS' + if: ${{ inputs.runner-os }} == 'macOS' shell: bash run: | - echo "Hello World ${{ runner.os }}" + echo "Hello World ${{ inputs.runner-os }}" diff --git a/.github/workflows/BuildMeshLab.yml b/.github/workflows/BuildMeshLab.yml index 162e77066..8a6280ff0 100644 --- a/.github/workflows/BuildMeshLab.yml +++ b/.github/workflows/BuildMeshLab.yml @@ -25,6 +25,7 @@ jobs: - name: Setup Environment uses: ./.github/actions/0_setup with: + runner-os: ${{ runner.os }} mac-certificate: $MAC_CERT mac-certificate-pssw: $MAC_CERT_PSSW - name: Set CodeSign Certificate macOS From f56fb04d427e26665bd54e7350c85d89bf28ceba Mon Sep 17 00:00:00 2001 From: Alessandro Muntoni Date: Fri, 23 Jun 2023 12:11:43 +0200 Subject: [PATCH 30/54] try different check --- .github/actions/0_setup/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/0_setup/action.yml b/.github/actions/0_setup/action.yml index fe530f367..5a9f6be81 100644 --- a/.github/actions/0_setup/action.yml +++ b/.github/actions/0_setup/action.yml @@ -18,7 +18,7 @@ runs: - name: Setup MSVC uses: ilammy/msvc-dev-cmd@v1 - name: Try - if: ${{ inputs.runner-os }} == 'macOS' + if: ${{ inputs.runner-os == 'macOS' }} shell: bash run: | echo "Hello World ${{ inputs.runner-os }}" From 96c778446e88aa0c0e0c658771c1ebd80b71488a Mon Sep 17 00:00:00 2001 From: Alessandro Muntoni Date: Fri, 23 Jun 2023 12:13:30 +0200 Subject: [PATCH 31/54] not passing runner os as input --- .github/actions/0_setup/action.yml | 5 +---- .github/workflows/BuildMeshLab.yml | 1 - 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/actions/0_setup/action.yml b/.github/actions/0_setup/action.yml index 5a9f6be81..c999b7d76 100644 --- a/.github/actions/0_setup/action.yml +++ b/.github/actions/0_setup/action.yml @@ -2,9 +2,6 @@ name: 'Setup Environment' description: 'Setup Environment' inputs: - runner-os: - description: 'Runner OS' - required: true mac-certificate: description: 'MacOS Certificate' required: false @@ -18,7 +15,7 @@ runs: - name: Setup MSVC uses: ilammy/msvc-dev-cmd@v1 - name: Try - if: ${{ inputs.runner-os == 'macOS' }} + if: ${{ runner.os == 'macOS' }} shell: bash run: | echo "Hello World ${{ inputs.runner-os }}" diff --git a/.github/workflows/BuildMeshLab.yml b/.github/workflows/BuildMeshLab.yml index 8a6280ff0..162e77066 100644 --- a/.github/workflows/BuildMeshLab.yml +++ b/.github/workflows/BuildMeshLab.yml @@ -25,7 +25,6 @@ jobs: - name: Setup Environment uses: ./.github/actions/0_setup with: - runner-os: ${{ runner.os }} mac-certificate: $MAC_CERT mac-certificate-pssw: $MAC_CERT_PSSW - name: Set CodeSign Certificate macOS From 723e39cdf353bd1f067a998293eda13bc9fbdb87 Mon Sep 17 00:00:00 2001 From: Alessandro Muntoni Date: Fri, 23 Jun 2023 12:17:24 +0200 Subject: [PATCH 32/54] sign on composite action --- .github/actions/0_setup/action.yml | 12 +++++++----- .github/workflows/BuildMeshLab.yml | 6 ------ 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/.github/actions/0_setup/action.yml b/.github/actions/0_setup/action.yml index c999b7d76..a0e8c13fa 100644 --- a/.github/actions/0_setup/action.yml +++ b/.github/actions/0_setup/action.yml @@ -5,6 +5,7 @@ inputs: mac-certificate: description: 'MacOS Certificate' required: false + default: '' mac-certificate-pssw: description: 'MacOS Certificate Password' required: false @@ -14,8 +15,9 @@ runs: steps: - name: Setup MSVC uses: ilammy/msvc-dev-cmd@v1 - - name: Try - if: ${{ runner.os == 'macOS' }} - shell: bash - run: | - echo "Hello World ${{ inputs.runner-os }}" + - name: Set CodeSign Certificate macOS + if: ${{ runner.os == 'macOS' && inputs.mac-certificate != ''}} + uses: apple-actions/import-codesign-certs@v2 + with: + p12-file-base64: ${{ inputs.mac-certificate }} + p12-password: ${{ inputs.mac-certificate-pssw }} diff --git a/.github/workflows/BuildMeshLab.yml b/.github/workflows/BuildMeshLab.yml index 162e77066..e84670c65 100644 --- a/.github/workflows/BuildMeshLab.yml +++ b/.github/workflows/BuildMeshLab.yml @@ -27,12 +27,6 @@ jobs: with: mac-certificate: $MAC_CERT mac-certificate-pssw: $MAC_CERT_PSSW - - name: Set CodeSign Certificate macOS - if: runner.os == 'macOS' && env.MAC_CERT != null - uses: apple-actions/import-codesign-certs@v2 - with: - p12-file-base64: ${{ secrets.MACOS_CERTIFICATE }} - p12-password: ${{ secrets.MACOS_CERTIFICATE_PSSW }} - name: Set CodeSign Certificate Windows if: runner.os == 'Windows' && env.WIN_CERT != null run: | From 42cafd353c7c55c82409ceb178f4dbb2df298616 Mon Sep 17 00:00:00 2001 From: Alessandro Muntoni Date: Fri, 23 Jun 2023 12:21:38 +0200 Subject: [PATCH 33/54] fix mac certificate --- .github/workflows/BuildMeshLab.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/BuildMeshLab.yml b/.github/workflows/BuildMeshLab.yml index e84670c65..e25291df9 100644 --- a/.github/workflows/BuildMeshLab.yml +++ b/.github/workflows/BuildMeshLab.yml @@ -25,8 +25,8 @@ jobs: - name: Setup Environment uses: ./.github/actions/0_setup with: - mac-certificate: $MAC_CERT - mac-certificate-pssw: $MAC_CERT_PSSW + mac-certificate: ${{ secrets.MACOS_CERTIFICATE }} + mac-certificate-pssw: ${{ secrets.MACOS_CERTIFICATE_PSSW }} - name: Set CodeSign Certificate Windows if: runner.os == 'Windows' && env.WIN_CERT != null run: | From 9cb8fa34b1dd303f928bf6448b37662ec1f74173 Mon Sep 17 00:00:00 2001 From: Alessandro Muntoni Date: Fri, 23 Jun 2023 12:29:16 +0200 Subject: [PATCH 34/54] move win certificate on composite action --- .github/actions/0_setup/action.yml | 19 +++++++++++++++++++ .github/workflows/BuildMeshLab.yml | 12 +----------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/.github/actions/0_setup/action.yml b/.github/actions/0_setup/action.yml index a0e8c13fa..fcef22fdc 100644 --- a/.github/actions/0_setup/action.yml +++ b/.github/actions/0_setup/action.yml @@ -9,6 +9,14 @@ inputs: mac-certificate-pssw: description: 'MacOS Certificate Password' required: false + win-certificate: + description: 'Windows Certificate' + required: false + default: '' + qt-version: + description: 'Qt Version' + required: false + default: '5.15.2' runs: using: "composite" @@ -21,3 +29,14 @@ runs: with: p12-file-base64: ${{ inputs.mac-certificate }} p12-password: ${{ inputs.mac-certificate-pssw }} + - name: Set CodeSign Certificate Windows + if: ${{ runner.os == 'Windows' && inputs.win-certificate != '' }} + run: | + New-Item -ItemType directory -Path certificate + Set-Content -Path certificate\certificate.txt -Value '${{ inputs.win-certificate }}' + certutil -decode certificate\certificate.txt certificate\certificate.pfx + - name: Install Qt + uses: jurplel/install-qt-action@v3 + with: + cache: true + version: ${{ inputs.qt-version }} diff --git a/.github/workflows/BuildMeshLab.yml b/.github/workflows/BuildMeshLab.yml index e25291df9..17ee9f5ee 100644 --- a/.github/workflows/BuildMeshLab.yml +++ b/.github/workflows/BuildMeshLab.yml @@ -27,17 +27,7 @@ jobs: with: mac-certificate: ${{ secrets.MACOS_CERTIFICATE }} mac-certificate-pssw: ${{ secrets.MACOS_CERTIFICATE_PSSW }} - - name: Set CodeSign Certificate Windows - if: runner.os == 'Windows' && env.WIN_CERT != null - run: | - New-Item -ItemType directory -Path certificate - Set-Content -Path certificate\certificate.txt -Value '${{ secrets.WIN_CERTIFICATE }}' - certutil -decode certificate\certificate.txt certificate\certificate.pfx - - name: Install Qt - uses: jurplel/install-qt-action@v3 - with: - cache: true - version: ${{ env.QT_VERSION }} + win-certificate: ${{ secrets.WIN_CERTIFICATE }} - name: Install dependencies shell: bash run: | From 65262978ac001a268136ad8cb4fb39e111559551 Mon Sep 17 00:00:00 2001 From: Alessandro Muntoni Date: Fri, 23 Jun 2023 12:31:13 +0200 Subject: [PATCH 35/54] missing powershell --- .github/actions/0_setup/action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/0_setup/action.yml b/.github/actions/0_setup/action.yml index fcef22fdc..a010b8719 100644 --- a/.github/actions/0_setup/action.yml +++ b/.github/actions/0_setup/action.yml @@ -30,6 +30,7 @@ runs: p12-file-base64: ${{ inputs.mac-certificate }} p12-password: ${{ inputs.mac-certificate-pssw }} - name: Set CodeSign Certificate Windows + shell: powershell if: ${{ runner.os == 'Windows' && inputs.win-certificate != '' }} run: | New-Item -ItemType directory -Path certificate From f33415303d4c0e04a5a1d877751a88c61d88ca3f Mon Sep 17 00:00:00 2001 From: Alessandro Muntoni Date: Fri, 23 Jun 2023 14:02:08 +0200 Subject: [PATCH 36/54] build in a composite action --- .github/actions/0_setup/action.yml | 4 +++ .github/actions/1_build/action.yml | 56 ++++++++++++++++++++++++++++++ .github/workflows/BuildMeshLab.yml | 23 ++++-------- 3 files changed, 66 insertions(+), 17 deletions(-) create mode 100644 .github/actions/1_build/action.yml diff --git a/.github/actions/0_setup/action.yml b/.github/actions/0_setup/action.yml index a010b8719..d305c6940 100644 --- a/.github/actions/0_setup/action.yml +++ b/.github/actions/0_setup/action.yml @@ -41,3 +41,7 @@ runs: with: cache: true version: ${{ inputs.qt-version }} + - name: Install dependencies + shell: bash + run: | + bash scripts/${{ runner.os }}/0_setup_env.sh --dont_install_qt \ No newline at end of file diff --git a/.github/actions/1_build/action.yml b/.github/actions/1_build/action.yml new file mode 100644 index 000000000..e0d0f7b0a --- /dev/null +++ b/.github/actions/1_build/action.yml @@ -0,0 +1,56 @@ +name: 'Build' +description: 'Build' + +inputs: + cache-path: + description: 'Directory to cache after build' + required: false + default: '' + cache-key: + description: 'Cache key' + required: false + default: '' + build-option: + description: 'Build option' + required: false + default: '' + nightly: + description: 'Nightly build' + required: false + type: boolean + default: false + +runs: + using: "composite" + steps: + - name: Setup env variables + id: envs + shell: bash + run: | + if [ "${{ inputs.build-option }}" != "" ]; then + echo "build_option=--${{ inputs.build-option }}" >> $GITHUB_OUTPUT + echo "artifact_suffix=_${{ inputs.build-option }}" >> $GITHUB_OUTPUT + else + echo "build_option=" >> $GITHUB_OUTPUT + echo "artifact_suffix=" >> $GITHUB_OUTPUT + fi + if [ "${{ inputs.nightly }}" = "true" ]; then + echo "nightly=--nightly" >> $GITHUB_OUTPUT + else + echo "nightly=" >> $GITHUB_OUTPUT + fi + - name: Cache external libraries sources + id: cache-ext-libs + if: ${{ inputs.cache-path != '' }} + uses: actions/cache@v3 + with: + path: ${{ inputs.cache-path }} + key: ${{ runner.os }}-${{ inputs.cache-key }} + - name: Ccache + uses: hendrikmuhs/ccache-action@v1.2 + with: + key: ${{ runner.os }}-${{ github.ref }}-${{ inputs.build-option }} + - name: Configure and Build + shell: bash + run: | + bash scripts/${{ runner.os }}/1_build.sh ${{ steps.envs.outputs.build_option }} ${{ steps.envs.outputs.nightly }} --ccache \ No newline at end of file diff --git a/.github/workflows/BuildMeshLab.yml b/.github/workflows/BuildMeshLab.yml index 17ee9f5ee..d64ec602d 100644 --- a/.github/workflows/BuildMeshLab.yml +++ b/.github/workflows/BuildMeshLab.yml @@ -28,10 +28,6 @@ jobs: mac-certificate: ${{ secrets.MACOS_CERTIFICATE }} mac-certificate-pssw: ${{ secrets.MACOS_CERTIFICATE_PSSW }} win-certificate: ${{ secrets.WIN_CERTIFICATE }} - - name: Install dependencies - shell: bash - run: | - bash scripts/${{ runner.os }}/0_setup_env.sh --dont_install_qt - name: Setup env variables id: envs shell: bash @@ -41,20 +37,13 @@ jobs: else echo "artifact_suffix=" >> $GITHUB_OUTPUT fi - - name: Cache external libraries sources - id: cache-ext-libs - uses: actions/cache@v3 + - name: Build + uses: ./.github/actions/1_build with: - path: src/external/downloads/* - key: ${{ runner.os }}-external-libraries - - name: Ccache - uses: hendrikmuhs/ccache-action@v1.2 - with: - key: ${{ runner.os }}-${{ github.ref }}-${{ matrix.precision }} - - name: Configure and Build - shell: bash - run: | - bash scripts/${{ runner.os }}/1_build.sh --${{ matrix.precision }} --nightly --ccache + cache-path: src/external/downloads/* + cache-key: external-libraries + build-option: ${{matrix.precision}} + nightly: true - name: Deploy shell: bash run: | From ab419fa6c2e9b0c1eeb051eda7590084189e186f Mon Sep 17 00:00:00 2001 From: Alessandro Muntoni Date: Fri, 23 Jun 2023 14:44:26 +0200 Subject: [PATCH 37/54] deploy in a composite action --- .github/actions/0_setup/action.yml | 24 ---------------- .github/actions/2_deploy/action.yml | 44 +++++++++++++++++++++++++++++ .github/workflows/BuildMeshLab.yml | 14 ++++----- 3 files changed, 51 insertions(+), 31 deletions(-) create mode 100644 .github/actions/2_deploy/action.yml diff --git a/.github/actions/0_setup/action.yml b/.github/actions/0_setup/action.yml index d305c6940..24bcf9db8 100644 --- a/.github/actions/0_setup/action.yml +++ b/.github/actions/0_setup/action.yml @@ -2,17 +2,6 @@ name: 'Setup Environment' description: 'Setup Environment' inputs: - mac-certificate: - description: 'MacOS Certificate' - required: false - default: '' - mac-certificate-pssw: - description: 'MacOS Certificate Password' - required: false - win-certificate: - description: 'Windows Certificate' - required: false - default: '' qt-version: description: 'Qt Version' required: false @@ -23,19 +12,6 @@ runs: steps: - name: Setup MSVC uses: ilammy/msvc-dev-cmd@v1 - - name: Set CodeSign Certificate macOS - if: ${{ runner.os == 'macOS' && inputs.mac-certificate != ''}} - uses: apple-actions/import-codesign-certs@v2 - with: - p12-file-base64: ${{ inputs.mac-certificate }} - p12-password: ${{ inputs.mac-certificate-pssw }} - - name: Set CodeSign Certificate Windows - shell: powershell - if: ${{ runner.os == 'Windows' && inputs.win-certificate != '' }} - run: | - New-Item -ItemType directory -Path certificate - Set-Content -Path certificate\certificate.txt -Value '${{ inputs.win-certificate }}' - certutil -decode certificate\certificate.txt certificate\certificate.pfx - name: Install Qt uses: jurplel/install-qt-action@v3 with: diff --git a/.github/actions/2_deploy/action.yml b/.github/actions/2_deploy/action.yml new file mode 100644 index 000000000..cf207d465 --- /dev/null +++ b/.github/actions/2_deploy/action.yml @@ -0,0 +1,44 @@ +name: 'Deploy' +description: 'Deploy' + +inputs: + mac-certificate: + description: 'MacOS Certificate' + required: false + default: '' + mac-certificate-id: + description: 'MacOS Certificate ID' + required: false + default: '' + mac-certificate-pssw: + description: 'MacOS Certificate Password' + required: false + win-certificate: + description: 'Windows Certificate' + required: false + default: '' + win-certificate-pssw: + description: 'Windows Certificate Password' + required: false + default: '' + +runs: + using: "composite" + steps: + - name: Set CodeSign Certificate macOS + if: ${{ runner.os == 'macOS' && inputs.mac-certificate != ''}} + uses: apple-actions/import-codesign-certs@v2 + with: + p12-file-base64: ${{ inputs.mac-certificate }} + p12-password: ${{ inputs.mac-certificate-pssw }} + - name: Set CodeSign Certificate Windows + shell: powershell + if: ${{ runner.os == 'Windows' && inputs.win-certificate != '' }} + run: | + New-Item -ItemType directory -Path certificate + Set-Content -Path certificate\certificate.txt -Value '${{ inputs.win-certificate }}' + certutil -decode certificate\certificate.txt certificate\certificate.pfx + - name: Deploy + shell: bash + run: | + bash scripts/${{ runner.os }}/2_deploy.sh --cert_pssw='${{ inputs.win-certificate-pssw }}' --cert_id='${{ inputs.mac-certificate-id }}' \ No newline at end of file diff --git a/.github/workflows/BuildMeshLab.yml b/.github/workflows/BuildMeshLab.yml index d64ec602d..97333163c 100644 --- a/.github/workflows/BuildMeshLab.yml +++ b/.github/workflows/BuildMeshLab.yml @@ -24,10 +24,6 @@ jobs: submodules: recursive - name: Setup Environment uses: ./.github/actions/0_setup - with: - mac-certificate: ${{ secrets.MACOS_CERTIFICATE }} - mac-certificate-pssw: ${{ secrets.MACOS_CERTIFICATE_PSSW }} - win-certificate: ${{ secrets.WIN_CERTIFICATE }} - name: Setup env variables id: envs shell: bash @@ -45,9 +41,13 @@ jobs: build-option: ${{matrix.precision}} nightly: true - name: Deploy - shell: bash - run: | - bash scripts/${{ runner.os }}/2_deploy.sh --cert_pssw='${{ secrets.WIN_CERTIFICATE_PSSW }}' --cert_id='${{ secrets.MACOS_CERT_ID }}' + uses: ./.github/actions/2_deploy + with: + mac-certificate: ${{ secrets.MACOS_CERTIFICATE }} + mac-certificate-id: ${{ secrets.MACOS_CERT_ID }} + mac-certificate-pssw: ${{ secrets.MACOS_CERTIFICATE_PSSW }} + win-certificate: ${{ secrets.WIN_CERTIFICATE }} + win-certificate-pssw: ${{ secrets.WIN_CERTIFICATE_PSSW }} - name: Upload MeshLab Portable uses: actions/upload-artifact@v3 with: From 9fda93bb109535a284b33992f18ff6285905fec1 Mon Sep 17 00:00:00 2001 From: Alessandro Muntoni Date: Fri, 23 Jun 2023 15:24:51 +0200 Subject: [PATCH 38/54] using composites on CreateRelease --- .github/actions/2_deploy/action.yml | 23 +++++++++++- .github/workflows/CreateRelease.yml | 58 +++++++++-------------------- 2 files changed, 40 insertions(+), 41 deletions(-) diff --git a/.github/actions/2_deploy/action.yml b/.github/actions/2_deploy/action.yml index cf207d465..69b582081 100644 --- a/.github/actions/2_deploy/action.yml +++ b/.github/actions/2_deploy/action.yml @@ -13,6 +13,18 @@ inputs: mac-certificate-pssw: description: 'MacOS Certificate Password' required: false + mac-notarization-user: + description: 'MacOS Notarization User' + required: false + default: '' + mac-notarization-team: + description: 'MacOS Notarization Team' + required: false + default: '' + mac-notarization-pssw: + description: 'MacOS Notarization Password' + required: false + default: '' win-certificate: description: 'Windows Certificate' required: false @@ -25,6 +37,15 @@ inputs: runs: using: "composite" steps: + - name: Set Notarization settings + id: notarization + shell: bash + run: | + if [ "${{ inputs.mac-notarization-user }}" != "" ]; then + echo "not_setting=--notarization_user='${{ inputs.mac-notarization-user }}' --notarization_team='${{ inputs.mac-notarization-team }}' --notarization_pssw='${{ inputs.mac-notarization-pssw }}'" >> $GITHUB_OUTPUT + else + echo "not_setting=" >> $GITHUB_OUTPUT + fi - name: Set CodeSign Certificate macOS if: ${{ runner.os == 'macOS' && inputs.mac-certificate != ''}} uses: apple-actions/import-codesign-certs@v2 @@ -41,4 +62,4 @@ runs: - name: Deploy shell: bash run: | - bash scripts/${{ runner.os }}/2_deploy.sh --cert_pssw='${{ inputs.win-certificate-pssw }}' --cert_id='${{ inputs.mac-certificate-id }}' \ No newline at end of file + bash scripts/${{ runner.os }}/2_deploy.sh --cert_pssw='${{ inputs.win-certificate-pssw }}' --cert_id='${{ inputs.mac-certificate-id }}' ${{ steps.notarization.outputs.not_setting }} \ No newline at end of file diff --git a/.github/workflows/CreateRelease.yml b/.github/workflows/CreateRelease.yml index d4b541f3d..129273263 100644 --- a/.github/workflows/CreateRelease.yml +++ b/.github/workflows/CreateRelease.yml @@ -41,29 +41,8 @@ jobs: with: submodules: recursive ref: main - - name: Setup MSVC - uses: ilammy/msvc-dev-cmd@v1 - - name: Set CodeSign Certificate macOS - if: runner.os == 'macOS' - uses: apple-actions/import-codesign-certs@v2 - with: - p12-file-base64: ${{ secrets.MACOS_CERTIFICATE }} - p12-password: ${{ secrets.MACOS_CERTIFICATE_PSSW }} - - name: Set CodeSign Certificate Windows - if: runner.os == 'Windows' - run: | - New-Item -ItemType directory -Path certificate - Set-Content -Path certificate\certificate.txt -Value '${{ secrets.WIN_CERTIFICATE }}' - certutil -decode certificate\certificate.txt certificate\certificate.pfx - - name: Install Qt - uses: jurplel/install-qt-action@v3 - with: - cache: true - version: ${{ env.QT_VERSION }} - - name: Install dependencies - shell: bash - run: | - bash scripts/${{ runner.os }}/0_setup_env.sh --dont_install_qt + - name: Setup Environment + uses: ./.github/actions/0_setup - name: Setup env variables id: envs shell: bash @@ -73,24 +52,23 @@ jobs: else echo "artifact_suffix=" >> $GITHUB_OUTPUT fi - - name: Cache external libraries sources - id: cache-ext-libs - uses: actions/cache@v3 - with: - path: src/external/downloads/* - key: ${{ runner.os }}-external-libraries - - name: Ccache - uses: hendrikmuhs/ccache-action@v1.2 - with: - key: ${{ runner.os }}-${{ github.ref }}-${{ matrix.precision }} - - name: Configure and Build - shell: bash - run: | - bash scripts/${{ runner.os }}/1_build.sh --${{ matrix.precision }} --ccache + - name: Build + uses: ./.github/actions/1_build + with: + cache-path: src/external/downloads/* + cache-key: external-libraries + build-option: ${{matrix.precision}} - name: Deploy - shell: bash - run: | - bash scripts/${{ runner.os }}/2_deploy.sh --cert_pssw='${{ secrets.WIN_CERTIFICATE_PSSW }}' --cert_id='${{ secrets.MACOS_CERT_ID }}' --notarization_user='${{ secrets.MACOS_NOTARIZATION_USER }}' --notarization_team='${{ secrets.MACOS_NOTARIZATION_TEAM_ID }}' --notarization_pssw='${{ secrets.MACOS_NOTARIZATION_PSSW }}' + uses: ./.github/actions/2_deploy + with: + mac-certificate: ${{ secrets.MACOS_CERTIFICATE }} + mac-certificate-id: ${{ secrets.MACOS_CERT_ID }} + mac-certificate-pssw: ${{ secrets.MACOS_CERTIFICATE_PSSW }} + mac-notarization-user: ${{ secrets.MACOS_NOTARIZATION_USER }} + mac-notarization-team: ${{ secrets.MACOS_NOTARIZATION_TEAM_ID }} + mac-notarization-pssw: ${{ secrets.MACOS_NOTARIZATION_PSSW }} + win-certificate: ${{ secrets.WIN_CERTIFICATE }} + win-certificate-pssw: ${{ secrets.WIN_CERTIFICATE_PSSW }} - name: Upload MeshLab Portable uses: actions/upload-artifact@v3 with: From ac0fa3f42d8b5cd9bd7d3f143317c0c7e7a53a5f Mon Sep 17 00:00:00 2001 From: Alessandro Muntoni Date: Fri, 23 Jun 2023 15:39:16 +0200 Subject: [PATCH 39/54] fix CreateRelease workflow --- .github/workflows/CreateRelease.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/CreateRelease.yml b/.github/workflows/CreateRelease.yml index 129273263..ea091dc19 100644 --- a/.github/workflows/CreateRelease.yml +++ b/.github/workflows/CreateRelease.yml @@ -52,12 +52,12 @@ jobs: else echo "artifact_suffix=" >> $GITHUB_OUTPUT fi - - name: Build - uses: ./.github/actions/1_build - with: - cache-path: src/external/downloads/* - cache-key: external-libraries - build-option: ${{matrix.precision}} + - name: Build + uses: ./.github/actions/1_build + with: + cache-path: src/external/downloads/* + cache-key: external-libraries + build-option: ${{matrix.precision}} - name: Deploy uses: ./.github/actions/2_deploy with: From 4e4e950cd79731a28b5859b5679938e24a41be68 Mon Sep 17 00:00:00 2001 From: davidboening Date: Fri, 23 Jun 2023 18:53:40 +0200 Subject: [PATCH 40/54] version 1.3 fixed bottleneck (cotan weights) using Eigen::Triplet changed progress bar changed hashmap to vcg::tri::Index other minor changes --- .../filter_heatgeodesic.cpp | 17 ++-- .../filter_heatgeodesic/trimesh_heatmethod.h | 92 +++++++++---------- 2 files changed, 56 insertions(+), 53 deletions(-) diff --git a/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.cpp b/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.cpp index 1d6544786..a2da10cf0 100644 --- a/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.cpp +++ b/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.cpp @@ -186,7 +186,8 @@ std::map FilterHeatGeodesicPlugin::applyFilter(const QAct inline void FilterHeatGeodesicPlugin::computeHeatGeodesicFromSelection(CMeshO& mesh, vcg::CallBackPos* cb, float m){ vcg::tri::Allocator::CompactEveryVector(mesh); - // build boundary conditions + cb(1, "Checking Selection..."); + // build and check source vertices Eigen::VectorXd sourcePoints(mesh.VN()); int selection_count = 0; for(int i=0; i < mesh.VN(); i++){ @@ -208,7 +209,7 @@ inline void FilterHeatGeodesicPlugin::computeHeatGeodesicFromSelection(CMeshO& m // recover state if it exists if (!vcg::tri::HasPerMeshAttribute(mesh, "HeatMethodSolver")){ - + cb(10, "Updating Topology..."); // update topology and face normals vcg::tri::UpdateTopology::VertexFace(mesh); vcg::tri::UpdateTopology::FaceFace(mesh); @@ -222,27 +223,29 @@ inline void FilterHeatGeodesicPlugin::computeHeatGeodesicFromSelection(CMeshO& m buildMassMatrix(mesh, massMatrix); // this step is very slow (95% of total time) hence we pass the callback // to update progress and avoid freezes - buildCotanLowerTriMatrix(mesh, cotanMatrix, cb); + cb(20, "Building Cotan Matrix..."); + buildCotanLowerTriMatrix(mesh, cotanMatrix); averageEdgeLength = computeAverageEdgeLength(mesh); - cb(91, "Matrix Factorization..."); + cb(70, "Matrix Factorization..."); HMSolver = std::make_shared(HeatMethodSolver(std::move(massMatrix), std::move(cotanMatrix), averageEdgeLength, m)); if (HMSolver->factorization_failed()) - log("Warning: factorization has failed. The mesh is non-manifold or badly conditioned (angles ~ 0deg)"); + log("Warning: factorization has failed. The mesh is non-manifold or badly conditioned (e.g. angles ~ 0deg) or has disconnected components"); auto HMSolverHandle = vcg::tri::Allocator::GetPerMeshAttribute>(mesh, std::string("HeatMethodSolver")); HMSolverHandle() = HMSolver; } else { + cb(10, "Recovering State..."); // recover solver HMSolver = vcg::tri::Allocator::GetPerMeshAttribute>(mesh, std::string("HeatMethodSolver"))(); // if m has changed rebuild factorizations from existing matrices if (HMSolver->get_m() != m){ - cb(91, "Rebuilding Factorization..."); + cb(70, "Rebuilding Factorization..."); HMSolver->rebuildFactorization(m); } } - cb(95, "Computing Geodesic Distance..."); + cb(85, "Computing Geodesic Distance..."); Eigen::VectorXd geodesicDistance = HMSolver->solve(mesh, sourcePoints); if (HMSolver->solver_failed()) log("Warning: solver has failed."); diff --git a/src/meshlabplugins/filter_heatgeodesic/trimesh_heatmethod.h b/src/meshlabplugins/filter_heatgeodesic/trimesh_heatmethod.h index 4de5d1901..0887341a4 100644 --- a/src/meshlabplugins/filter_heatgeodesic/trimesh_heatmethod.h +++ b/src/meshlabplugins/filter_heatgeodesic/trimesh_heatmethod.h @@ -27,10 +27,18 @@ namespace { }; } -// GLOBAL FUNCTIONS +// simple struct to track progress bar status +struct ProgressBarCallbackT { + vcg::CallBackPos* callback; + int current_progress; + int allocated_progress; +}; + inline void buildMassMatrix(CMeshO &mesh, Eigen::SparseMatrix &mass){ mass.resize(mesh.VN(), mesh.VN()); + mass.reserve(Eigen::VectorXi::Constant(mesh.VN(),1)); + // compute area of all faces for (CMeshO::FaceIterator fi = mesh.face.begin(); fi != mesh.face.end(); ++fi) { @@ -60,29 +68,22 @@ inline void buildMassMatrix(CMeshO &mesh, Eigen::SparseMatrix &mass){ area += faces[j]->Q(); } area /= 3; - mass.coeffRef(i, i) = area; + mass.insert(i, i) = area; } + + mass.makeCompressed(); } -inline void buildCotanLowerTriMatrix(CMeshO &mesh, Eigen::SparseMatrix &cotanOperator, vcg::CallBackPos* cb = NULL){ +inline void buildCotanLowerTriMatrix(CMeshO &mesh, Eigen::SparseMatrix &cotanOperator){ cotanOperator.resize(mesh.VN(), mesh.VN()); - // initialize a hashtable from vertex pointers to ids - std::unordered_map vertex_ids; - for (int i = 0; i < mesh.VN(); ++i){ - vertex_ids[&mesh.vert[i]] = i; - } - // progress bar variables - int update_size = mesh.FN() / 90; // 90 updates (90% weight of the progress bar) - int progress = 0; + typedef Eigen::Triplet T; + std::vector tripletList; + tripletList.reserve(3*mesh.VN() + 3*mesh.FN()); // compute cotan weights for (int i = 0; i < mesh.FN(); ++i){ - // progress bar update - if (cb != NULL && i % update_size == 89){ - cb(++progress, "Computing Cotan Weights..."); - } CMeshO::FaceType* fi = &mesh.face[i]; CMeshO::VertexType* v0 = fi->V(0); @@ -102,28 +103,31 @@ inline void buildCotanLowerTriMatrix(CMeshO &mesh, Eigen::SparseMatrix & double alpha1 = cotan(-e2, e0) / 2; double alpha2 = cotan(-e0, e1) / 2; - int i0 = vertex_ids[v0]; - int i1 = vertex_ids[v1]; - int i2 = vertex_ids[v2]; + int i0 = vcg::tri::Index(mesh,v0); + int i1 = vcg::tri::Index(mesh,v1); + int i2 = vcg::tri::Index(mesh,v2); // save only lower triangular part if (i0 > i1) - cotanOperator.coeffRef(i0, i1) += alpha2; + tripletList.push_back(T(i0,i1,alpha2)); else - cotanOperator.coeffRef(i1, i0) += alpha2; + tripletList.push_back(T(i1,i0,alpha2)); if (i0 > i2) - cotanOperator.coeffRef(i0, i2) += alpha1; + tripletList.push_back(T(i0,i2,alpha1)); else - cotanOperator.coeffRef(i2, i0) += alpha1; + tripletList.push_back(T(i2,i0,alpha1)); if (i1 > i2) - cotanOperator.coeffRef(i1, i2) += alpha0; + tripletList.push_back(T(i1,i2,alpha0)); else - cotanOperator.coeffRef(i2, i1) += alpha0; + tripletList.push_back(T(i2,i1,alpha0)); - cotanOperator.coeffRef(i0, i0) -= (alpha1 + alpha2); - cotanOperator.coeffRef(i1, i1) -= (alpha0 + alpha2); - cotanOperator.coeffRef(i2, i2) -= (alpha0 + alpha1); + tripletList.push_back(T(i0,i0,-(alpha1 + alpha2))); + tripletList.push_back(T(i1,i1,-(alpha0 + alpha2))); + tripletList.push_back(T(i2,i2,-(alpha0 + alpha1))); } + + cotanOperator.setFromTriplets(tripletList.begin(), tripletList.end()); + cotanOperator.makeCompressed(); } @@ -146,18 +150,21 @@ inline double computeAverageEdgeLength(CMeshO &mesh){ inline Eigen::MatrixX3d computeVertexGradient(CMeshO &mesh, const Eigen::VectorXd &heat){ Eigen::MatrixX3d heatGradientField(mesh.FN(), 3); - // initialize a hashtable from vertex pointers to ids - std::unordered_map vertex_ids; - for (int i = 0; i < mesh.VN(); ++i){ - vertex_ids[&mesh.vert[i]] = i; - } // compute gradient of heat function at each vertex for (int i = 0; i < mesh.FN(); ++i){ CMeshO::FaceType *fp = &mesh.face[i]; - vcg::Point3f p0 = fp->V(0)->P(); - vcg::Point3f p1 = fp->V(1)->P(); - vcg::Point3f p2 = fp->V(2)->P(); + CMeshO::VertexType* v0 = fp->V(0); + CMeshO::VertexType* v1 = fp->V(1); + CMeshO::VertexType* v2 = fp->V(2); + + vcg::Point3f p0 = v0->P(); + vcg::Point3f p1 = v1->P(); + vcg::Point3f p2 = v2->P(); + + int i0 = vcg::tri::Index(mesh,v0); + int i1 = vcg::tri::Index(mesh,v1); + int i2 = vcg::tri::Index(mesh,v2); // normal unit vector Eigen::Vector3d n = toEigen(fp->N()); @@ -177,11 +184,7 @@ inline Eigen::MatrixX3d computeVertexGradient(CMeshO &mesh, const Eigen::VectorX Eigen::Vector3d g2 = n.cross(e2); //v2 grad // add vertex gradient contributions - Eigen::Vector3d tri_grad = ( - g0 * heat(vertex_ids[fp->V(0)]) + - g1 * heat(vertex_ids[fp->V(1)]) + - g2 * heat(vertex_ids[fp->V(2)]) - ) / (2 * faceArea); + Eigen::Vector3d tri_grad = (g0 * heat(i0) + g1 * heat(i1) + g2 * heat(i2)) / (2 * faceArea); heatGradientField.row(i) = tri_grad; } @@ -204,11 +207,6 @@ inline Eigen::MatrixX3d normalizeVectorField(const Eigen::MatrixX3d &field){ inline Eigen::VectorXd computeVertexDivergence(CMeshO &mesh, const Eigen::MatrixX3d &field){ Eigen::VectorXd divergence(mesh.VN()); divergence.setZero(); - // initialize a hashtable from face pointers to ids - std::unordered_map face_ids; - for (int i = 0; i < mesh.FN(); ++i){ - face_ids[&mesh.face[i]] = i; - } // compute divergence of vector field at each vertex for (int i = 0; i < mesh.VN(); ++i){ @@ -221,9 +219,11 @@ inline Eigen::VectorXd computeVertexDivergence(CMeshO &mesh, const Eigen::Matrix { CMeshO::FaceType *fp = faces[j]; int index = indices[j]; + vcg::Point3f p0 = fp->V(0)->P(); vcg::Point3f p1 = fp->V(1)->P(); vcg::Point3f p2 = fp->V(2)->P(); + // (ORDERING) edge vectors Eigen::Vector3d el, er, eo; //left, right, opposite to vp if (index == 0){ @@ -246,7 +246,7 @@ inline Eigen::VectorXd computeVertexDivergence(CMeshO &mesh, const Eigen::Matrix el /= el.norm(); er /= er.norm(); // add divergence contribution of given face - Eigen::Vector3d x = field.row(face_ids[fp]); + Eigen::Vector3d x = field.row(vcg::tri::Index(mesh,fp)); divergence(i) += (cotl * er.dot(x) + cotr * el.dot(x)) / 2; } } From f14dca369360d0ade59eeb2bda42887876827811 Mon Sep 17 00:00:00 2001 From: davidboening Date: Wed, 12 Jul 2023 09:39:22 +0200 Subject: [PATCH 41/54] version 1.4.1 - removed matrix caching - updated description - minor refactoring --- .../filter_heatgeodesic.cpp | 62 ++++++++----------- .../filter_heatgeodesic/trimesh_heatmethod.h | 46 +++++++++----- 2 files changed, 54 insertions(+), 54 deletions(-) diff --git a/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.cpp b/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.cpp index a2da10cf0..ec435b462 100644 --- a/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.cpp +++ b/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.cpp @@ -72,7 +72,12 @@ QString FilterHeatGeodesicPlugin::pythonFilterName(ActionIDType f) const { switch(filterId) { case FP_COMPUTE_HEATGEODESIC_FROM_SELECTION : - return QString("Computes an approximated geodesic distance from the selected vertices to all others. This algorithm implements the heat method."); + return QString( + "Computes an approximated geodesic distance from the selected vertices to all others. " + "This algorithm implements the heat method. " + "If the mesh has changed, update m to a different value to reflect these changes as" + " this rebuilds the existing cache." + ); default : assert(0); return QString("Unknown Filter"); @@ -198,51 +203,34 @@ inline void FilterHeatGeodesicPlugin::computeHeatGeodesicFromSelection(CMeshO& m return; } + // TODO detect if the mesh has changed and if so delete state + // NOW changing the value of m rebuilds the cache + // + // if (meshHasChanged) { + // auto handle = vcg::tri::Allocator::GetPerMeshAttribute>(mesh, std::string("HeatMethodSolver")); + // vcg::tri::Allocator::DeletePerMeshAttribute>(mesh, handle); + // } + std::shared_ptr HMSolver; - - // delete state if the mesh has changed - bool meshHasChanged = false; // Update with appropriate function - if (meshHasChanged) { - auto handle = vcg::tri::Allocator::GetPerMeshAttribute>(mesh, std::string("HeatMethodSolver")); - vcg::tri::Allocator::DeletePerMeshAttribute>(mesh, handle); - } - - // recover state if it exists + // if no state can be recovered, initialize it if (!vcg::tri::HasPerMeshAttribute(mesh, "HeatMethodSolver")){ - cb(10, "Updating Topology..."); - // update topology and face normals - vcg::tri::UpdateTopology::VertexFace(mesh); - vcg::tri::UpdateTopology::FaceFace(mesh); - vcg::tri::UpdateNormal::PerFaceNormalized(mesh); - - // state variables - Eigen::SparseMatrix massMatrix; - Eigen::SparseMatrix cotanMatrix; - double averageEdgeLength; - - buildMassMatrix(mesh, massMatrix); - // this step is very slow (95% of total time) hence we pass the callback - // to update progress and avoid freezes - cb(20, "Building Cotan Matrix..."); - buildCotanLowerTriMatrix(mesh, cotanMatrix); - averageEdgeLength = computeAverageEdgeLength(mesh); - - cb(70, "Matrix Factorization..."); - HMSolver = std::make_shared(HeatMethodSolver(std::move(massMatrix), std::move(cotanMatrix), averageEdgeLength, m)); + HMSolver = std::make_shared(HeatMethodSolver(mesh, m, cb)); if (HMSolver->factorization_failed()) - log("Warning: factorization has failed. The mesh is non-manifold or badly conditioned (e.g. angles ~ 0deg) or has disconnected components"); + log("Warning: factorization has failed. The mesh is non-manifold or badly conditioned (e.g. angles ~ 0deg) or disconnected components"); auto HMSolverHandle = vcg::tri::Allocator::GetPerMeshAttribute>(mesh, std::string("HeatMethodSolver")); HMSolverHandle() = HMSolver; } else { - cb(10, "Recovering State..."); + cb(5, "Attempting to Recover State..."); // recover solver - HMSolver = vcg::tri::Allocator::GetPerMeshAttribute>(mesh, std::string("HeatMethodSolver"))(); - // if m has changed rebuild factorizations from existing matrices - if (HMSolver->get_m() != m){ - cb(70, "Rebuilding Factorization..."); - HMSolver->rebuildFactorization(m); + auto HMSolverHandle = vcg::tri::Allocator::GetPerMeshAttribute>(mesh, std::string("HeatMethodSolver")); + // if m has changed rebuild everything + if (HMSolverHandle()->get_m() != m){ + HMSolverHandle() = std::make_shared(HeatMethodSolver(mesh, m, cb)); + if (HMSolverHandle()->factorization_failed()) + log("Warning: factorization has failed. The changed mesh is non-manifold or badly conditioned (e.g. angles ~ 0deg) or disconnected components"); } + HMSolver = HMSolverHandle(); } cb(85, "Computing Geodesic Distance..."); diff --git a/src/meshlabplugins/filter_heatgeodesic/trimesh_heatmethod.h b/src/meshlabplugins/filter_heatgeodesic/trimesh_heatmethod.h index 0887341a4..348c628ca 100644 --- a/src/meshlabplugins/filter_heatgeodesic/trimesh_heatmethod.h +++ b/src/meshlabplugins/filter_heatgeodesic/trimesh_heatmethod.h @@ -256,34 +256,47 @@ inline Eigen::VectorXd computeVertexDivergence(CMeshO &mesh, const Eigen::Matrix class HeatMethodSolver { public: - HeatMethodSolver(Eigen::SparseMatrix &&massMatrix, Eigen::SparseMatrix &&cotanMatrix, double averageEdgeLength, double m) - : massMatrix(std::move(massMatrix)), cotanMatrix(std::move(cotanMatrix)), averageEdgeLength(averageEdgeLength), m(m) + HeatMethodSolver(CMeshO& mesh, float m, vcg::CallBackPos* cb = NULL) + : m(m) { + if(cb != NULL) cb(10, "Updating Topology..."); + // update topology and face normals + vcg::tri::UpdateTopology::VertexFace(mesh); + vcg::tri::UpdateTopology::FaceFace(mesh); + vcg::tri::UpdateNormal::PerFaceNormalized(mesh); + + // state variables + Eigen::SparseMatrix massMatrix; + Eigen::SparseMatrix cotanMatrix; + double averageEdgeLength; + + buildMassMatrix(mesh, massMatrix); + // this step is very slow (95% of total time) hence we pass the callback + // to update progress and avoid freezes + if(cb != NULL) cb(20, "Building Cotan Matrix..."); + buildCotanLowerTriMatrix(mesh, cotanMatrix); + averageEdgeLength = computeAverageEdgeLength(mesh); + // initialize pointers solver1 = std::shared_ptr>>(new Eigen::SimplicialLDLT>); solver2 = std::shared_ptr>>(new Eigen::SimplicialLDLT>); // build factorization double timestep = m * averageEdgeLength * averageEdgeLength; + if(cb != NULL) cb(50, "Matrix Factorization..."); solver1->compute(massMatrix - timestep * cotanMatrix); + if(cb != NULL) cb(75, "Matrix Factorization..."); solver2->compute(cotanMatrix); if((solver1->info() != Eigen::Success) || (solver2->info() != Eigen::Success)) factorizationFailed = true; else factorizationFailed = false; } + HeatMethodSolver() = delete; + HeatMethodSolver& operator=(const HeatMethodSolver&) = delete; - void rebuildFactorization(double new_m){ - m = new_m; - double timestep = m * averageEdgeLength * averageEdgeLength; - solver1->compute(massMatrix - timestep * cotanMatrix); - solver2->compute(cotanMatrix); - if((solver1->info() != Eigen::Success) || (solver2->info() != Eigen::Success)) - factorizationFailed = true; - else - factorizationFailed = false; - } + Eigen::VectorXd solve(CMeshO &mesh, Eigen::VectorXd &sourcePoints){ Eigen::VectorXd heatflow = solver1->solve(sourcePoints); // (VN) Eigen::MatrixX3d heatGradient = computeVertexGradient(mesh, heatflow); // (FN, 3) @@ -294,22 +307,21 @@ public: solverFailed = true; else solverFailed = false; - // shift to impose dist(d) = 0 \forall d \in sourcePoints + // shift to impose dist(source) = 0 geodesicDistance.array() -= geodesicDistance.minCoeff(); return geodesicDistance; } + double get_m(){return m;} + bool factorization_failed(){return factorizationFailed;} + bool solver_failed(){return solverFailed;} private: // Eigen::SimplicialLDLT has no copy/move constructor std::shared_ptr>> solver1; std::shared_ptr>> solver2; - Eigen::SparseMatrix massMatrix; - Eigen::SparseMatrix cotanMatrix; - double averageEdgeLength; double m; - bool factorizationFailed; bool solverFailed; }; From 32f3723869d48444576d6ba0fa68fc37b93f43ff Mon Sep 17 00:00:00 2001 From: Alessandro Muntoni Date: Thu, 13 Jul 2023 11:03:37 +0200 Subject: [PATCH 42/54] fix pymeshlab issue 137 - filter plymc uses tmp dir to save temporary files --- .../filter_plymc/filter_plymc.cpp | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/src/meshlabplugins/filter_plymc/filter_plymc.cpp b/src/meshlabplugins/filter_plymc/filter_plymc.cpp index e3730ad71..e5031ab1d 100644 --- a/src/meshlabplugins/filter_plymc/filter_plymc.cpp +++ b/src/meshlabplugins/filter_plymc/filter_plymc.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include using namespace vcg; @@ -142,13 +143,21 @@ std::map PlyMCPlugin::applyFilter( case FP_PLYMC: { srand(time(NULL)); - - //check if folder is writable - QTemporaryFile file("./_tmp_XXXXXX.tmp"); - if (!file.open()) - { - log("ERROR - current folder is not writable. VCG Merging needs to save intermediate files in the current working folder. Project and meshes must be in a write-enabled folder. Please save your data in a suitable folder before applying."); - throw MLException("current folder is not writable.
VCG Merging needs to save intermediate files in the current working folder.
Project and meshes must be in a write-enabled folder.
Please save your data in a suitable folder before applying."); + + QDir currDir = QDir::current(); + + //Using tmp dir + QTemporaryDir tmpdir; + QTemporaryFile file(tmpdir.path()); + if (!file.open()) { + log("ERROR - tmp folder is not writable. VCG Merging needs to save intermediate files " + "in the tmp folder."); + throw MLException( + "tmp folder is not writable.
VCG Merging needs to save intermediate files in " + "the tmp folder."); + } + else { + QDir::setCurrent(tmpdir.path()); } tri::PlyMC > pmc; @@ -170,10 +179,8 @@ std::map PlyMCPlugin::applyFilter( p.FullyPreprocessedFlag=true; p.MergeColor=p.VertSplatFlag=par.getBool("mergeColor"); p.SimplificationFlag = par.getBool("simplification"); - for(MeshModel& mm: md.meshIterator()) - { - if(mm.isVisible()) - { + for(MeshModel& mm: md.meshIterator()) { + if(mm.isVisible()) { SMesh sm; mm.updateDataMask(MeshModel::MM_FACEQUALITY); tri::Append::Mesh(sm, mm.cm/*,false,p.VertSplatFlag*/); // note the last parameter of the append to prevent removal of unreferenced vertices... @@ -189,8 +196,7 @@ std::map PlyMCPlugin::applyFilter( QString mshTmpPath=QString("__TMP")+QString(mm.shortName())+QString(".vmi"); qDebug("Saving tmp file %s",qUtf8Printable(mshTmpPath)); int retVal = tri::io::ExporterVMI::Save(sm,qUtf8Printable(mshTmpPath) ); - if(retVal!=0) - { + if(retVal!=0) { qDebug("Failed to write vmi temp file %s",qUtf8Printable(mshTmpPath)); log("ERROR - Failed to write vmi temp file %s", qUtf8Printable(mshTmpPath)); @@ -201,14 +207,12 @@ std::map PlyMCPlugin::applyFilter( } } - if(pmc.Process(cb)==false) - { + if(pmc.Process(cb)==false) { throw MLException(pmc.errorMessage.c_str()); } - if(par.getBool("openResult")) - { + if(par.getBool("openResult")) { for(size_t i=0;i PlyMCPlugin::applyFilter( for(int i=0;i Date: Thu, 20 Jul 2023 23:01:18 +0200 Subject: [PATCH 43/54] candidate final - removed heatgeodesic filter - merged heatgeodesic filter into geodesic filter - moved algorithmic part to vcglib --- src/CMakeLists.txt | 3 +- .../filter_geodesic/filter_geodesic.cpp | 93 ++++- .../filter_geodesic/filter_geodesic.h | 4 +- .../filter_heatgeodesic/CMakeLists.txt | 5 - .../filter_heatgeodesic.cpp | 249 ------------- .../filter_heatgeodesic/filter_heatgeodesic.h | 39 --- .../filter_heatgeodesic/trimesh_heatmethod.h | 329 ------------------ 7 files changed, 81 insertions(+), 641 deletions(-) delete mode 100644 src/meshlabplugins/filter_heatgeodesic/CMakeLists.txt delete mode 100644 src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.cpp delete mode 100644 src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.h delete mode 100644 src/meshlabplugins/filter_heatgeodesic/trimesh_heatmethod.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f13281016..87d461de4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -152,8 +152,7 @@ if(NOT DEFINED MESHLAB_PLUGINS) # it may be already defined in parent directory meshlabplugins/filter_developability meshlabplugins/filter_dirt meshlabplugins/filter_fractal - meshlabplugins/filter_func - meshlabplugins/filter_heatgeodesic # added + meshlabplugins/filter_func meshlabplugins/filter_img_patch_param meshlabplugins/filter_icp meshlabplugins/filter_io_nxs diff --git a/src/meshlabplugins/filter_geodesic/filter_geodesic.cpp b/src/meshlabplugins/filter_geodesic/filter_geodesic.cpp index a5149e09b..34493709b 100644 --- a/src/meshlabplugins/filter_geodesic/filter_geodesic.cpp +++ b/src/meshlabplugins/filter_geodesic/filter_geodesic.cpp @@ -44,7 +44,8 @@ FilterGeodesic::FilterGeodesic() typeList = { FP_QUALITY_BORDER_GEODESIC, FP_QUALITY_POINT_GEODESIC, - FP_QUALITY_SELECTED_GEODESIC + FP_QUALITY_SELECTED_GEODESIC, + FP_QUALITY_SELECTED_GEODESIC_HEAT }; for(ActionIDType tt : types()) @@ -67,6 +68,8 @@ QString FilterGeodesic::filterName(ActionIDType filter) const return QString("Colorize by geodesic distance from a given point"); case FP_QUALITY_SELECTED_GEODESIC: return QString("Colorize by geodesic distance from the selected points"); + case FP_QUALITY_SELECTED_GEODESIC_HEAT: + return QString("Colorize by approximated geodesic distance from the selected points"); default: assert(0); return QString(); } } @@ -79,6 +82,8 @@ QString FilterGeodesic::pythonFilterName(ActionIDType f) const return QString("compute_scalar_by_geodesic_distance_from_given_point_per_vertex"); case FP_QUALITY_SELECTED_GEODESIC: return QString("compute_scalar_by_geodesic_distance_from_selection_per_vertex"); + case FP_QUALITY_SELECTED_GEODESIC_HEAT: + return QString("compute_scalar_by_heat_geodesic_distance_from_selection_per_vertex"); default: assert(0); return QString(); } } @@ -87,9 +92,10 @@ QString FilterGeodesic::filterInfo(ActionIDType filterId) const { switch(filterId) { - case FP_QUALITY_BORDER_GEODESIC : return tr("Store in the quality field the geodesic distance from borders and color the mesh accordingly."); - case FP_QUALITY_POINT_GEODESIC : return tr("Store in the quality field the geodesic distance from a given point on the mesh surface and color the mesh accordingly."); - case FP_QUALITY_SELECTED_GEODESIC : return tr("Store in the quality field the geodesic distance from the selected points on the mesh surface and color the mesh accordingly."); + case FP_QUALITY_BORDER_GEODESIC : return tr("Store in the quality field the geodesic distance from borders and color the mesh accordingly."); + case FP_QUALITY_POINT_GEODESIC : return tr("Store in the quality field the geodesic distance from a given point on the mesh surface and color the mesh accordingly."); + case FP_QUALITY_SELECTED_GEODESIC : return tr("Store in the quality field the geodesic distance from the selected points on the mesh surface and color the mesh accordingly."); + case FP_QUALITY_SELECTED_GEODESIC_HEAT : return tr("Store in the quality field the approximated geodesic distance, computed via heat method, from the selected points on the mesh surface and color the mesh accordingly. This method is very sensitive to triangulation and currently does not support disconnected components."); default : assert(0); } return QString("error!"); @@ -99,9 +105,10 @@ FilterGeodesic::FilterClass FilterGeodesic::getClass(const QAction *a) const { switch(ID(a)) { - case FP_QUALITY_BORDER_GEODESIC : - case FP_QUALITY_SELECTED_GEODESIC : - case FP_QUALITY_POINT_GEODESIC : return FilterGeodesic::FilterClass(FilterPlugin::VertexColoring + FilterPlugin::Quality); + case FP_QUALITY_BORDER_GEODESIC : + case FP_QUALITY_SELECTED_GEODESIC : + case FP_QUALITY_POINT_GEODESIC : return FilterGeodesic::FilterClass(FilterPlugin::VertexColoring + FilterPlugin::Quality); + case FP_QUALITY_SELECTED_GEODESIC_HEAT : return FilterGeodesic::FilterClass(FilterPlugin::VertexColoring + FilterPlugin::Quality); default : assert(0); } return FilterPlugin::Generic; @@ -111,9 +118,10 @@ int FilterGeodesic::getRequirements(const QAction *action) { switch(ID(action)) { - case FP_QUALITY_BORDER_GEODESIC : - case FP_QUALITY_SELECTED_GEODESIC: - case FP_QUALITY_POINT_GEODESIC : return MeshModel::MM_VERTFACETOPO; + case FP_QUALITY_BORDER_GEODESIC : + case FP_QUALITY_SELECTED_GEODESIC : + case FP_QUALITY_POINT_GEODESIC : return MeshModel::MM_VERTFACETOPO; + case FP_QUALITY_SELECTED_GEODESIC_HEAT : return MeshModel::MM_VERTFACETOPO + MeshModel::MM_FACEFACETOPO; default: assert(0); } return 0; @@ -236,7 +244,56 @@ std::map FilterGeodesic::applyFilter(const QAction *filte else log("Warning: no vertices are selected! aborting geodesic computation."); } - break; + break; + case FP_QUALITY_SELECTED_GEODESIC_HEAT: + { + m.updateDataMask(MeshModel::MM_FACEFACETOPO); + m.updateDataMask(MeshModel::MM_VERTFACETOPO); + m.updateDataMask(MeshModel::MM_FACEQUALITY); + m.updateDataMask(MeshModel::MM_FACENORMAL); + m.updateDataMask(MeshModel::MM_VERTQUALITY); + m.updateDataMask(MeshModel::MM_VERTCOLOR); + + std::vector seedVec; + ForEachVertex(m.cm, [&seedVec] (CMeshO::VertexType & v) { + if (v.IsS()) seedVec.push_back(&v); + }); + + float param_m = par.getFloat("m"); + + if (seedVec.size() > 0){ + // cache factorizations to reduce runtime on successive computations + std::pair cache; + if (!vcg::tri::HasPerMeshAttribute(m.cm, "GeodesicHeatCache")){ + cache = std::make_pair(param_m, tri::GeodesicHeat::BuildCache(m.cm, param_m)); + // save cache for next compute + auto GeodesicHeatCacheHandle = tri::Allocator::GetPerMeshAttribute>(m.cm, std::string("GeodesicHeatCache")); + GeodesicHeatCacheHandle() = cache; + } + else { + // recover cache + auto GeodesicHeatCacheHandle = vcg::tri::Allocator::GetPerMeshAttribute>(m.cm, std::string("GeodesicHeatCache")); + // if m has changed rebuild everything + if (std::get<0>(GeodesicHeatCacheHandle()) != param_m){ + GeodesicHeatCacheHandle() = std::make_pair(param_m, tri::GeodesicHeat::BuildCache(m.cm, param_m)); + } + cache = GeodesicHeatCacheHandle(); + } + + if (tri::GeodesicHeat::ComputeFromCache(m.cm, seedVec, std::get<1>(cache))){ + tri::UpdateColor::PerVertexQualityRamp(m.cm); + } + else{ + log("Warning: heat method has failed. The mesh is most likely badly conditioned (e.g. angles ~ 0deg) or has disconnected components"); + // delete cache as its most likely useless after failure + auto GeodesicHeatCacheHandle = vcg::tri::Allocator::GetPerMeshAttribute>(m.cm, std::string("GeodesicHeatCache")); + tri::Allocator::DeletePerMeshAttribute>(m.cm, GeodesicHeatCacheHandle); + } + } + else + log("Warning: no vertices are selected! aborting geodesic computation."); + } + break; default: wrongActionCalled(filter); break; @@ -255,7 +312,10 @@ RichParameterList FilterGeodesic::initParameterList(const QAction *action, const break; case FP_QUALITY_SELECTED_GEODESIC : parlst.addParam(RichPercentage("maxDistance",m.cm.bbox.Diag(),0,m.cm.bbox.Diag()*2,"Max Distance","If not zero it indicates a cut off value to be used during geodesic distance computation.")); - break; + break; + case FP_QUALITY_SELECTED_GEODESIC_HEAT : + parlst.addParam(RichFloat("m", 1, "m", "Multiplier used in backward Euler timestep.")); + break; default: break; // do not add any parameter for the other filters } return parlst; @@ -265,10 +325,11 @@ int FilterGeodesic::postCondition(const QAction * filter) const { switch (ID(filter)) { - case FP_QUALITY_BORDER_GEODESIC : - case FP_QUALITY_SELECTED_GEODESIC : - case FP_QUALITY_POINT_GEODESIC : return MeshModel::MM_VERTCOLOR + MeshModel::MM_VERTQUALITY; - default : return MeshModel::MM_ALL; + case FP_QUALITY_BORDER_GEODESIC : + case FP_QUALITY_SELECTED_GEODESIC : + case FP_QUALITY_POINT_GEODESIC : return MeshModel::MM_VERTCOLOR + MeshModel::MM_VERTQUALITY; + case FP_QUALITY_SELECTED_GEODESIC_HEAT : return MeshModel::MM_VERTCOLOR + MeshModel::MM_VERTQUALITY; + default : return MeshModel::MM_ALL; } } MESHLAB_PLUGIN_NAME_EXPORTER(FilterGeodesic) diff --git a/src/meshlabplugins/filter_geodesic/filter_geodesic.h b/src/meshlabplugins/filter_geodesic/filter_geodesic.h index 6fe6d24c2..498b43aba 100644 --- a/src/meshlabplugins/filter_geodesic/filter_geodesic.h +++ b/src/meshlabplugins/filter_geodesic/filter_geodesic.h @@ -26,6 +26,7 @@ #include #include #include +#include class FilterGeodesic : public QObject, public FilterPlugin @@ -42,7 +43,8 @@ class FilterGeodesic : public QObject, public FilterPlugin enum { FP_QUALITY_BORDER_GEODESIC, FP_QUALITY_POINT_GEODESIC, - FP_QUALITY_SELECTED_GEODESIC + FP_QUALITY_SELECTED_GEODESIC, + FP_QUALITY_SELECTED_GEODESIC_HEAT } ; diff --git a/src/meshlabplugins/filter_heatgeodesic/CMakeLists.txt b/src/meshlabplugins/filter_heatgeodesic/CMakeLists.txt deleted file mode 100644 index 732bcaacc..000000000 --- a/src/meshlabplugins/filter_heatgeodesic/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -set(SOURCES filter_heatgeodesic.cpp) - -set(HEADERS filter_heatgeodesic.h trimesh_heatmethod.h) - -add_meshlab_plugin(filter_heatgeodesic ${SOURCES} ${HEADERS}) diff --git a/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.cpp b/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.cpp deleted file mode 100644 index ec435b462..000000000 --- a/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.cpp +++ /dev/null @@ -1,249 +0,0 @@ -#include "trimesh_heatmethod.h" - -#include "filter_heatgeodesic.h" - -#include - -/** - * @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 - */ -FilterHeatGeodesicPlugin::FilterHeatGeodesicPlugin() -{ - typeList = {FP_COMPUTE_HEATGEODESIC_FROM_SELECTION}; - - for(ActionIDType tt : types()) - actionList.push_back(new QAction(filterName(tt), this)); -} - -FilterHeatGeodesicPlugin::~FilterHeatGeodesicPlugin() -{ -} - -QString FilterHeatGeodesicPlugin::pluginName() const -{ - return "FilterHeatGeodesic"; -} - -/** - * @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 FilterHeatGeodesicPlugin::filterName(ActionIDType filterId) const -{ - switch(filterId) { - case FP_COMPUTE_HEATGEODESIC_FROM_SELECTION : - return QString("Compute Geodesic From Selection (Heat)"); - default : - assert(0); - return QString(); - } -} - -/** - * @brief FilterHeatGeodesicPlugin::pythonFilterName if you want that your filter should have a different - * name on pymeshlab, use this function to return its python name. - * @param f - * @return - */ -QString FilterHeatGeodesicPlugin::pythonFilterName(ActionIDType f) const -{ - switch(f) { - case FP_COMPUTE_HEATGEODESIC_FROM_SELECTION : - return "compute_approximate_geodesic_from_selection"; - 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 FilterHeatGeodesicPlugin::filterInfo(ActionIDType filterId) const -{ - switch(filterId) { - case FP_COMPUTE_HEATGEODESIC_FROM_SELECTION : - return QString( - "Computes an approximated geodesic distance from the selected vertices to all others. " - "This algorithm implements the heat method. " - "If the mesh has changed, update m to a different value to reflect these changes as" - " this rebuilds the existing cache." - ); - default : - assert(0); - return QString("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 - */ -FilterHeatGeodesicPlugin::FilterClass FilterHeatGeodesicPlugin::getClass(const QAction *a) const -{ - switch(ID(a)) { - case FP_COMPUTE_HEATGEODESIC_FROM_SELECTION : - return FilterPlugin::Measure; // Note: not sure - // return FilterGeodesic::FilterClass(FilterPlugin::VertexColoring + FilterPlugin::Quality); - default : - assert(0); - return FilterPlugin::Generic; - } -} - -/** - * @brief FilterSamplePlugin::filterArity - * @return - */ -FilterPlugin::FilterArity FilterHeatGeodesicPlugin::filterArity(const QAction*) const -{ - return SINGLE_MESH; -} - -/** - * @brief FilterSamplePlugin::getPreConditions - * @return - */ -int FilterHeatGeodesicPlugin::getPreConditions(const QAction*) const -{ - // NOTE: note sure about these - return MeshModel::MM_FACEFACETOPO | MeshModel::MM_VERTFACETOPO; -} - -/** - * @brief FilterSamplePlugin::postCondition - * @return - */ -int FilterHeatGeodesicPlugin::postCondition(const QAction*) const -{ - // NOTE: note sure about these - return MeshModel::MM_VERTQUALITY | MeshModel::MM_VERTCOLOR; -} - -/** - * @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 FilterHeatGeodesicPlugin::initParameterList(const QAction *action,const MeshModel &m) -{ - RichParameterList parlst; - switch(ID(action)) { - case FP_COMPUTE_HEATGEODESIC_FROM_SELECTION : - parlst.addParam(RichFloat("m", 1, "m", "Multiplier used in backward Euler timestep.")); - 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 FilterHeatGeodesicPlugin::applyFilter(const QAction * action, const RichParameterList & parameters, MeshDocument &md, unsigned int& /*postConditionMask*/, vcg::CallBackPos *cb) -{ - MeshModel &mm=*(md.mm()); - switch(ID(action)) { - case FP_COMPUTE_HEATGEODESIC_FROM_SELECTION : - { - mm.updateDataMask(MeshModel::MM_FACEFACETOPO); - mm.updateDataMask(MeshModel::MM_VERTFACETOPO); - mm.updateDataMask(MeshModel::MM_FACEQUALITY); - mm.updateDataMask(MeshModel::MM_FACENORMAL); - mm.updateDataMask(MeshModel::MM_VERTQUALITY); - mm.updateDataMask(MeshModel::MM_VERTCOLOR); - CMeshO &mesh = mm.cm; - // TODO: compact all vectors - computeHeatGeodesicFromSelection(mesh, cb, parameters.getFloat("m")); - } - break; - default : - wrongActionCalled(action); - } - return std::map(); -} - -inline void FilterHeatGeodesicPlugin::computeHeatGeodesicFromSelection(CMeshO& mesh, vcg::CallBackPos* cb, float m){ - vcg::tri::Allocator::CompactEveryVector(mesh); - - cb(1, "Checking Selection..."); - // build and check source vertices - Eigen::VectorXd sourcePoints(mesh.VN()); - int selection_count = 0; - for(int i=0; i < mesh.VN(); i++){ - sourcePoints(i) = mesh.vert[i].IsS() ? (++selection_count, 1) : 0; - } - if (selection_count < 1){ - log("Warning: no vertices are selected! aborting computation."); - return; - } - - // TODO detect if the mesh has changed and if so delete state - // NOW changing the value of m rebuilds the cache - // - // if (meshHasChanged) { - // auto handle = vcg::tri::Allocator::GetPerMeshAttribute>(mesh, std::string("HeatMethodSolver")); - // vcg::tri::Allocator::DeletePerMeshAttribute>(mesh, handle); - // } - - std::shared_ptr HMSolver; - // if no state can be recovered, initialize it - if (!vcg::tri::HasPerMeshAttribute(mesh, "HeatMethodSolver")){ - HMSolver = std::make_shared(HeatMethodSolver(mesh, m, cb)); - if (HMSolver->factorization_failed()) - log("Warning: factorization has failed. The mesh is non-manifold or badly conditioned (e.g. angles ~ 0deg) or disconnected components"); - auto HMSolverHandle = vcg::tri::Allocator::GetPerMeshAttribute>(mesh, std::string("HeatMethodSolver")); - HMSolverHandle() = HMSolver; - } - else { - cb(5, "Attempting to Recover State..."); - // recover solver - auto HMSolverHandle = vcg::tri::Allocator::GetPerMeshAttribute>(mesh, std::string("HeatMethodSolver")); - // if m has changed rebuild everything - if (HMSolverHandle()->get_m() != m){ - HMSolverHandle() = std::make_shared(HeatMethodSolver(mesh, m, cb)); - if (HMSolverHandle()->factorization_failed()) - log("Warning: factorization has failed. The changed mesh is non-manifold or badly conditioned (e.g. angles ~ 0deg) or disconnected components"); - } - HMSolver = HMSolverHandle(); - } - - cb(85, "Computing Geodesic Distance..."); - Eigen::VectorXd geodesicDistance = HMSolver->solve(mesh, sourcePoints); - if (HMSolver->solver_failed()) - log("Warning: solver has failed."); - - cb(99, "Updating Mesh..."); - // set geodesic distance as quality and color - for(int i=0; i < mesh.VN(); i++){ - mesh.vert[i].Q() = geodesicDistance(i); - } - vcg::tri::UpdateColor::PerVertexQualityRamp(mesh); -} - -MESHLAB_PLUGIN_NAME_EXPORTER(FilterSamplePlugin) diff --git a/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.h b/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.h deleted file mode 100644 index b421550ab..000000000 --- a/src/meshlabplugins/filter_heatgeodesic/filter_heatgeodesic.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef FILTERSAMPLE_PLUGIN_H -#define FILTERSAMPLE_PLUGIN_H - -#include - -class FilterHeatGeodesicPlugin : public QObject, public FilterPlugin -{ - Q_OBJECT - MESHLAB_PLUGIN_IID_EXPORTER(FILTER_PLUGIN_IID) - Q_INTERFACES(FilterPlugin) - -public: - enum { FP_COMPUTE_HEATGEODESIC_FROM_SELECTION } ; - - FilterHeatGeodesicPlugin(); - virtual ~FilterHeatGeodesicPlugin(); - - QString pluginName() 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 MeshModel &/*m*/); - std::map applyFilter( - const QAction* action, - const RichParameterList & parameters, - MeshDocument &md, - unsigned int& postConditionMask, - vcg::CallBackPos * cb); - -private: - void computeHeatGeodesicFromSelection(CMeshO& mesh, vcg::CallBackPos* cb, float m); -}; - -#endif diff --git a/src/meshlabplugins/filter_heatgeodesic/trimesh_heatmethod.h b/src/meshlabplugins/filter_heatgeodesic/trimesh_heatmethod.h deleted file mode 100644 index 348c628ca..000000000 --- a/src/meshlabplugins/filter_heatgeodesic/trimesh_heatmethod.h +++ /dev/null @@ -1,329 +0,0 @@ -#ifndef TRIMESH_HEAT_METHOD -#define TRIMESH_HEAT_METHOD - -#include -#include - -#include - -#include - -#include - - -// LOCAL FUNCTIONS - -namespace { - inline Eigen::Vector3d toEigen(const vcg::Point3f& p) - { - return Eigen::Vector3d(p.X(), p.Y(), p.Z()); - }; - - - inline double cotan(const Eigen::Vector3d& v0, const Eigen::Vector3d& v1) - { - // cos(theta) / sin(theta) - return v0.dot(v1) / v0.cross(v1).norm(); - }; -} - -// simple struct to track progress bar status -struct ProgressBarCallbackT { - vcg::CallBackPos* callback; - int current_progress; - int allocated_progress; -}; - - -inline void buildMassMatrix(CMeshO &mesh, Eigen::SparseMatrix &mass){ - mass.resize(mesh.VN(), mesh.VN()); - mass.reserve(Eigen::VectorXi::Constant(mesh.VN(),1)); - - // compute area of all faces - for (CMeshO::FaceIterator fi = mesh.face.begin(); fi != mesh.face.end(); ++fi) - { - vcg::Point3f p0 = fi->V(0)->P(); - vcg::Point3f p1 = fi->V(1)->P(); - vcg::Point3f p2 = fi->V(2)->P(); - double e0 = toEigen(p1 - p0).norm(); - double e1 = toEigen(p2 - p0).norm(); - double e2 = toEigen(p2 - p1).norm(); - double s = (e0 + e1 + e2) / 2; - double area = std::sqrt(s * (s - e0) * (s - e1) * (s - e2)); - // we store face areas in quality field to avoid a hash table - // this will also be useful for the gradient computation - fi->Q() = area; - } - // compute area of the dual cell for each vertex - for (int i = 0; i < mesh.VN(); ++i){ - CMeshO::VertexType *vp = &mesh.vert[i]; - - std::vector faces; - std::vector indices; - vcg::face::VFStarVF(vp, faces, indices); - - double area = 0; - for (int j = 0; j < faces.size(); ++j) - { - area += faces[j]->Q(); - } - area /= 3; - mass.insert(i, i) = area; - } - - mass.makeCompressed(); -} - - -inline void buildCotanLowerTriMatrix(CMeshO &mesh, Eigen::SparseMatrix &cotanOperator){ - cotanOperator.resize(mesh.VN(), mesh.VN()); - - typedef Eigen::Triplet T; - std::vector tripletList; - tripletList.reserve(3*mesh.VN() + 3*mesh.FN()); - - // compute cotan weights - for (int i = 0; i < mesh.FN(); ++i){ - CMeshO::FaceType* fi = &mesh.face[i]; - - CMeshO::VertexType* v0 = fi->V(0); - CMeshO::VertexType* v1 = fi->V(1); - CMeshO::VertexType* v2 = fi->V(2); - - vcg::Point3f p0 = v0->P(); - vcg::Point3f p1 = v1->P(); - vcg::Point3f p2 = v2->P(); - - Eigen::Vector3d e0 = toEigen(p2 - p1); - Eigen::Vector3d e1 = toEigen(p0 - p2); - Eigen::Vector3d e2 = toEigen(p1 - p0); - - // first edge is inverted to get correct orientation - double alpha0 = cotan(-e1, e2) / 2; - double alpha1 = cotan(-e2, e0) / 2; - double alpha2 = cotan(-e0, e1) / 2; - - int i0 = vcg::tri::Index(mesh,v0); - int i1 = vcg::tri::Index(mesh,v1); - int i2 = vcg::tri::Index(mesh,v2); - - // save only lower triangular part - if (i0 > i1) - tripletList.push_back(T(i0,i1,alpha2)); - else - tripletList.push_back(T(i1,i0,alpha2)); - if (i0 > i2) - tripletList.push_back(T(i0,i2,alpha1)); - else - tripletList.push_back(T(i2,i0,alpha1)); - if (i1 > i2) - tripletList.push_back(T(i1,i2,alpha0)); - else - tripletList.push_back(T(i2,i1,alpha0)); - - tripletList.push_back(T(i0,i0,-(alpha1 + alpha2))); - tripletList.push_back(T(i1,i1,-(alpha0 + alpha2))); - tripletList.push_back(T(i2,i2,-(alpha0 + alpha1))); - } - - cotanOperator.setFromTriplets(tripletList.begin(), tripletList.end()); - cotanOperator.makeCompressed(); -} - - -inline double computeAverageEdgeLength(CMeshO &mesh){ - // compute total length of all edges - double total_length = 0; - for (CMeshO::FaceIterator fi = mesh.face.begin(); fi != mesh.face.end(); ++fi) - { - vcg::Point3f p0 = fi->V(0)->P(); - vcg::Point3f p1 = fi->V(1)->P(); - vcg::Point3f p2 = fi->V(2)->P(); - double e2 = toEigen(p1 - p0).norm(); - double e1 = toEigen(p2 - p0).norm(); - double e0 = toEigen(p2 - p1).norm(); - total_length += (e0 + e1 + e2) / 2; - } - return total_length / (3./2. * mesh.FN()); -} - - -inline Eigen::MatrixX3d computeVertexGradient(CMeshO &mesh, const Eigen::VectorXd &heat){ - Eigen::MatrixX3d heatGradientField(mesh.FN(), 3); - // compute gradient of heat function at each vertex - for (int i = 0; i < mesh.FN(); ++i){ - CMeshO::FaceType *fp = &mesh.face[i]; - - CMeshO::VertexType* v0 = fp->V(0); - CMeshO::VertexType* v1 = fp->V(1); - CMeshO::VertexType* v2 = fp->V(2); - - vcg::Point3f p0 = v0->P(); - vcg::Point3f p1 = v1->P(); - vcg::Point3f p2 = v2->P(); - - int i0 = vcg::tri::Index(mesh,v0); - int i1 = vcg::tri::Index(mesh,v1); - int i2 = vcg::tri::Index(mesh,v2); - - // normal unit vector - Eigen::Vector3d n = toEigen(fp->N()); - n /= n.norm(); - // face area - double faceArea = fp->Q(); - // edge unit vectors (counter-clockwise) - Eigen::Vector3d e0 = toEigen(p2 - p1); - e0 /= e0.norm(); - Eigen::Vector3d e1 = toEigen(p0 - p2); - e1 /= e1.norm(); - Eigen::Vector3d e2 = toEigen(p1 - p0); - e2 /= e2.norm(); - // gradient unit vectors - Eigen::Vector3d g0 = n.cross(e0); //v0 grad - Eigen::Vector3d g1 = n.cross(e1); //v1 grad - Eigen::Vector3d g2 = n.cross(e2); //v2 grad - - // add vertex gradient contributions - Eigen::Vector3d tri_grad = (g0 * heat(i0) + g1 * heat(i1) + g2 * heat(i2)) / (2 * faceArea); - - heatGradientField.row(i) = tri_grad; - } - return heatGradientField; -} - - -inline Eigen::MatrixX3d normalizeVectorField(const Eigen::MatrixX3d &field){ - Eigen::MatrixX3d normalizedField(field.rows(), 3); - normalizedField.setZero(); - // normalize vector field at each vertex - for (int i = 0; i < field.rows(); ++i){ - Eigen::Vector3d v = field.row(i); - normalizedField.row(i) = v / v.norm(); - } - return normalizedField; -} - - -inline Eigen::VectorXd computeVertexDivergence(CMeshO &mesh, const Eigen::MatrixX3d &field){ - Eigen::VectorXd divergence(mesh.VN()); - divergence.setZero(); - - // compute divergence of vector field at each vertex - for (int i = 0; i < mesh.VN(); ++i){ - CMeshO::VertexType *vp = &mesh.vert[i]; - - std::vector faces; - std::vector indices; - vcg::face::VFStarVF(vp, faces, indices); - for (int j = 0; j < faces.size(); ++j) - { - CMeshO::FaceType *fp = faces[j]; - int index = indices[j]; - - vcg::Point3f p0 = fp->V(0)->P(); - vcg::Point3f p1 = fp->V(1)->P(); - vcg::Point3f p2 = fp->V(2)->P(); - - // (ORDERING) edge vectors - Eigen::Vector3d el, er, eo; //left, right, opposite to vp - if (index == 0){ - el = toEigen(p2 - p0); //-e1 - er = toEigen(p1 - p0); //e2 - eo = toEigen(p2 - p1); //e0 - } else if (index == 1){ - el = toEigen(p0 - p1); //-e2 - er = toEigen(p2 - p1); //e0 - eo = toEigen(p0 - p2); //e1 - } else if (index == 2){ - el = toEigen(p1 - p2); //-e0 - er = toEigen(p0 - p2); //e1 - eo = toEigen(p1 - p0); //e2 - } - // compute left and right cotangents - double cotl = cotan(-el, -eo); - double cotr = cotan(-er, eo); - // normalize edge vectors after cotangent computation - el /= el.norm(); - er /= er.norm(); - // add divergence contribution of given face - Eigen::Vector3d x = field.row(vcg::tri::Index(mesh,fp)); - divergence(i) += (cotl * er.dot(x) + cotr * el.dot(x)) / 2; - } - } - return divergence; -} - - -class HeatMethodSolver { -public: - HeatMethodSolver(CMeshO& mesh, float m, vcg::CallBackPos* cb = NULL) - : m(m) - { - if(cb != NULL) cb(10, "Updating Topology..."); - // update topology and face normals - vcg::tri::UpdateTopology::VertexFace(mesh); - vcg::tri::UpdateTopology::FaceFace(mesh); - vcg::tri::UpdateNormal::PerFaceNormalized(mesh); - - // state variables - Eigen::SparseMatrix massMatrix; - Eigen::SparseMatrix cotanMatrix; - double averageEdgeLength; - - buildMassMatrix(mesh, massMatrix); - // this step is very slow (95% of total time) hence we pass the callback - // to update progress and avoid freezes - if(cb != NULL) cb(20, "Building Cotan Matrix..."); - buildCotanLowerTriMatrix(mesh, cotanMatrix); - averageEdgeLength = computeAverageEdgeLength(mesh); - - // initialize pointers - solver1 = std::shared_ptr>>(new Eigen::SimplicialLDLT>); - solver2 = std::shared_ptr>>(new Eigen::SimplicialLDLT>); - - // build factorization - double timestep = m * averageEdgeLength * averageEdgeLength; - if(cb != NULL) cb(50, "Matrix Factorization..."); - solver1->compute(massMatrix - timestep * cotanMatrix); - if(cb != NULL) cb(75, "Matrix Factorization..."); - solver2->compute(cotanMatrix); - if((solver1->info() != Eigen::Success) || (solver2->info() != Eigen::Success)) - factorizationFailed = true; - else - factorizationFailed = false; - } - - HeatMethodSolver() = delete; - - HeatMethodSolver& operator=(const HeatMethodSolver&) = delete; - - Eigen::VectorXd solve(CMeshO &mesh, Eigen::VectorXd &sourcePoints){ - Eigen::VectorXd heatflow = solver1->solve(sourcePoints); // (VN) - Eigen::MatrixX3d heatGradient = computeVertexGradient(mesh, heatflow); // (FN, 3) - Eigen::MatrixX3d unitVectorField = normalizeVectorField(-heatGradient); // (FN, 3) - Eigen::VectorXd divergence = computeVertexDivergence(mesh, unitVectorField); // (VN) - Eigen::VectorXd geodesicDistance = solver2->solve(divergence); // (VN) - if((solver1->info() != Eigen::Success) || (solver2->info() != Eigen::Success)) - solverFailed = true; - else - solverFailed = false; - // shift to impose dist(source) = 0 - geodesicDistance.array() -= geodesicDistance.minCoeff(); - return geodesicDistance; - } - - double get_m(){return m;} - - bool factorization_failed(){return factorizationFailed;} - - bool solver_failed(){return solverFailed;} -private: - // Eigen::SimplicialLDLT has no copy/move constructor - std::shared_ptr>> solver1; - std::shared_ptr>> solver2; - double m; - bool factorizationFailed; - bool solverFailed; -}; - -#endif From 7d9b26dfe8c84cbd675c5e6cf6efc6b91eee3057 Mon Sep 17 00:00:00 2001 From: davidboening Date: Fri, 21 Jul 2023 10:40:05 +0200 Subject: [PATCH 44/54] Updated descriptions, added progress bar. --- .../filter_geodesic/filter_geodesic.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/meshlabplugins/filter_geodesic/filter_geodesic.cpp b/src/meshlabplugins/filter_geodesic/filter_geodesic.cpp index 34493709b..5a73e102d 100644 --- a/src/meshlabplugins/filter_geodesic/filter_geodesic.cpp +++ b/src/meshlabplugins/filter_geodesic/filter_geodesic.cpp @@ -95,7 +95,7 @@ QString FilterGeodesic::filterInfo(ActionIDType filterId) const case FP_QUALITY_BORDER_GEODESIC : return tr("Store in the quality field the geodesic distance from borders and color the mesh accordingly."); case FP_QUALITY_POINT_GEODESIC : return tr("Store in the quality field the geodesic distance from a given point on the mesh surface and color the mesh accordingly."); case FP_QUALITY_SELECTED_GEODESIC : return tr("Store in the quality field the geodesic distance from the selected points on the mesh surface and color the mesh accordingly."); - case FP_QUALITY_SELECTED_GEODESIC_HEAT : return tr("Store in the quality field the approximated geodesic distance, computed via heat method, from the selected points on the mesh surface and color the mesh accordingly. This method is very sensitive to triangulation and currently does not support disconnected components."); + case FP_QUALITY_SELECTED_GEODESIC_HEAT : return tr("Store in the quality field the approximated geodesic distance, computed via heat method (Crane et al.), from the selected points on the mesh surface and color the mesh accordingly. As this implementation does not use intrinsic triangulation it is very sensitive to trinagulation. First run takes longer as factorization has to be build. "); default : assert(0); } return QString("error!"); @@ -107,7 +107,7 @@ FilterGeodesic::FilterClass FilterGeodesic::getClass(const QAction *a) const { case FP_QUALITY_BORDER_GEODESIC : case FP_QUALITY_SELECTED_GEODESIC : - case FP_QUALITY_POINT_GEODESIC : return FilterGeodesic::FilterClass(FilterPlugin::VertexColoring + FilterPlugin::Quality); + case FP_QUALITY_POINT_GEODESIC : case FP_QUALITY_SELECTED_GEODESIC_HEAT : return FilterGeodesic::FilterClass(FilterPlugin::VertexColoring + FilterPlugin::Quality); default : assert(0); } @@ -127,7 +127,7 @@ int FilterGeodesic::getRequirements(const QAction *action) return 0; } -std::map FilterGeodesic::applyFilter(const QAction *filter, const RichParameterList & par, MeshDocument &md, unsigned int& /*postConditionMask*/, vcg::CallBackPos * /*cb*/) +std::map FilterGeodesic::applyFilter(const QAction *filter, const RichParameterList & par, MeshDocument &md, unsigned int& /*postConditionMask*/, vcg::CallBackPos *cb) { MeshModel &m=*(md.mm()); CMeshO::VertexIterator vi; @@ -265,12 +265,14 @@ std::map FilterGeodesic::applyFilter(const QAction *filte // cache factorizations to reduce runtime on successive computations std::pair cache; if (!vcg::tri::HasPerMeshAttribute(m.cm, "GeodesicHeatCache")){ + cb(10, "Building Cache: Computing Factorizations."); cache = std::make_pair(param_m, tri::GeodesicHeat::BuildCache(m.cm, param_m)); // save cache for next compute auto GeodesicHeatCacheHandle = tri::Allocator::GetPerMeshAttribute>(m.cm, std::string("GeodesicHeatCache")); GeodesicHeatCacheHandle() = cache; } else { + cb(70, "Recovering Cache."); // recover cache auto GeodesicHeatCacheHandle = vcg::tri::Allocator::GetPerMeshAttribute>(m.cm, std::string("GeodesicHeatCache")); // if m has changed rebuild everything @@ -279,6 +281,7 @@ std::map FilterGeodesic::applyFilter(const QAction *filte } cache = GeodesicHeatCacheHandle(); } + cb(80, "Computing Geodesic Distance."); if (tri::GeodesicHeat::ComputeFromCache(m.cm, seedVec, std::get<1>(cache))){ tri::UpdateColor::PerVertexQualityRamp(m.cm); @@ -314,7 +317,7 @@ RichParameterList FilterGeodesic::initParameterList(const QAction *action, const parlst.addParam(RichPercentage("maxDistance",m.cm.bbox.Diag(),0,m.cm.bbox.Diag()*2,"Max Distance","If not zero it indicates a cut off value to be used during geodesic distance computation.")); break; case FP_QUALITY_SELECTED_GEODESIC_HEAT : - parlst.addParam(RichFloat("m", 1, "m", "Multiplier used in backward Euler timestep.")); + parlst.addParam(RichFloat("m", 1.0, tr("Euler Step"), tr("Multiplier used in backward Euler timestep. Changing this value will reset the cache."))); break; default: break; // do not add any parameter for the other filters } @@ -328,7 +331,7 @@ int FilterGeodesic::postCondition(const QAction * filter) const case FP_QUALITY_BORDER_GEODESIC : case FP_QUALITY_SELECTED_GEODESIC : case FP_QUALITY_POINT_GEODESIC : return MeshModel::MM_VERTCOLOR + MeshModel::MM_VERTQUALITY; - case FP_QUALITY_SELECTED_GEODESIC_HEAT : return MeshModel::MM_VERTCOLOR + MeshModel::MM_VERTQUALITY; + case FP_QUALITY_SELECTED_GEODESIC_HEAT : return MeshModel::MM_VERTCOLOR + MeshModel::MM_VERTQUALITY + MeshModel::MM_FACEQUALITY; default : return MeshModel::MM_ALL; } } From ae6212cb8f910c121e3bc8f1d8064ba49fdb44ff Mon Sep 17 00:00:00 2001 From: davidboening Date: Fri, 21 Jul 2023 19:33:00 +0200 Subject: [PATCH 45/54] reflecting vcglib changes --- .../filter_geodesic/filter_geodesic.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/meshlabplugins/filter_geodesic/filter_geodesic.cpp b/src/meshlabplugins/filter_geodesic/filter_geodesic.cpp index 5a73e102d..dd600f699 100644 --- a/src/meshlabplugins/filter_geodesic/filter_geodesic.cpp +++ b/src/meshlabplugins/filter_geodesic/filter_geodesic.cpp @@ -263,25 +263,26 @@ std::map FilterGeodesic::applyFilter(const QAction *filte if (seedVec.size() > 0){ // cache factorizations to reduce runtime on successive computations - std::pair cache; + std::pair::GeodesicHeatCache> cache; if (!vcg::tri::HasPerMeshAttribute(m.cm, "GeodesicHeatCache")){ - cb(10, "Building Cache: Computing Factorizations."); + cb(20, "Building Cache: Computing Factorizations..."); cache = std::make_pair(param_m, tri::GeodesicHeat::BuildCache(m.cm, param_m)); // save cache for next compute - auto GeodesicHeatCacheHandle = tri::Allocator::GetPerMeshAttribute>(m.cm, std::string("GeodesicHeatCache")); + auto GeodesicHeatCacheHandle = tri::Allocator::GetPerMeshAttribute::GeodesicHeatCache>>(m.cm, std::string("GeodesicHeatCache")); GeodesicHeatCacheHandle() = cache; } else { - cb(70, "Recovering Cache."); + cb(10, "Recovering Cache..."); // recover cache - auto GeodesicHeatCacheHandle = vcg::tri::Allocator::GetPerMeshAttribute>(m.cm, std::string("GeodesicHeatCache")); + auto GeodesicHeatCacheHandle = vcg::tri::Allocator::GetPerMeshAttribute::GeodesicHeatCache>>(m.cm, std::string("GeodesicHeatCache")); // if m has changed rebuild everything if (std::get<0>(GeodesicHeatCacheHandle()) != param_m){ + cb(20, "Parameter Changed: Rebuilding Factorizations..."); GeodesicHeatCacheHandle() = std::make_pair(param_m, tri::GeodesicHeat::BuildCache(m.cm, param_m)); } cache = GeodesicHeatCacheHandle(); } - cb(80, "Computing Geodesic Distance."); + cb(80, "Computing Geodesic Distance..."); if (tri::GeodesicHeat::ComputeFromCache(m.cm, seedVec, std::get<1>(cache))){ tri::UpdateColor::PerVertexQualityRamp(m.cm); @@ -289,8 +290,8 @@ std::map FilterGeodesic::applyFilter(const QAction *filte else{ log("Warning: heat method has failed. The mesh is most likely badly conditioned (e.g. angles ~ 0deg) or has disconnected components"); // delete cache as its most likely useless after failure - auto GeodesicHeatCacheHandle = vcg::tri::Allocator::GetPerMeshAttribute>(m.cm, std::string("GeodesicHeatCache")); - tri::Allocator::DeletePerMeshAttribute>(m.cm, GeodesicHeatCacheHandle); + auto GeodesicHeatCacheHandle = vcg::tri::Allocator::GetPerMeshAttribute::GeodesicHeatCache>>(m.cm, std::string("GeodesicHeatCache")); + tri::Allocator::DeletePerMeshAttribute::GeodesicHeatCache>>(m.cm, GeodesicHeatCacheHandle); } } else From 0e2a8aa9ec13bbb20624c14db03d418f4e1fabcf Mon Sep 17 00:00:00 2001 From: jmespadero Date: Fri, 28 Jul 2023 10:24:02 +0200 Subject: [PATCH 46/54] link to muparser help --- src/meshlabplugins/filter_func/filter_func.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/meshlabplugins/filter_func/filter_func.cpp b/src/meshlabplugins/filter_func/filter_func.cpp index dde18d949..bfe4fe2bd 100644 --- a/src/meshlabplugins/filter_func/filter_func.cpp +++ b/src/meshlabplugins/filter_func/filter_func.cpp @@ -120,20 +120,22 @@ QString FilterFunctionPlugin::pythonFilterName(ActionIDType f) const } const QString PossibleOperators( - "
It's possible to use parenthesis (), and predefined operators:
" + "
It's possible to use parenthesis (), and predefined " + "muparser built-in operators, like:
" "&& (logic and), || (logic or), <, <=, >, >=, " - "!= (not equal), == (equal), _?_:_ (c/c++ ternary operator)

"); + "!= (not equal), == (equal), _?_:_ (c/c++ ternary operator).

"); const QString PerVertexAttributeString( - "It's possible to use the following per-vertex variables in the expression:
" + "It's possible to use muparser built-in functions" + "and the following per-vertex variables in the expression:
" "x,y,z (position), nx,ny,nz (normal), r,g,b,a (color), q " "(quality), vi (vertex index), vtu,vtv,ti (texture coords and texture " "index), vsel (is the vertex selected? 1 yes, 0 no) " "and all custom vertex attributes already defined by user.
"); const QString PerFaceAttributeString( - "It's possible to use the following per-face variables, or variables associated to the three " - "vertex of every face:
" + "It's possible to use muparser built-in functions" + "and the following per-face or per-vertex variables:
" "x0,y0,z0 for the first vertex position, x1,y1,z1 for the second vertex " "position, x2,y2,z2 for the third vertex position, " "nx0,ny0,nz0 nx1,ny1,nz1 nx2,ny2,nz2 for vertex normals, r0,g0,b0,a0 r1,g1,b1,a1 " From 267e31b383e8c17866c7c9b9b7e6209fffd89d73 Mon Sep 17 00:00:00 2001 From: Alessandro Muntoni Date: Fri, 28 Jul 2023 10:40:33 +0200 Subject: [PATCH 47/54] Update stale.yml --- .github/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/stale.yml b/.github/stale.yml index e50822a1b..a985a99e7 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -51,7 +51,7 @@ markComment: > limitPerRun: 10 # Limit to only `issues` or `pulls` -# only: issues +only: issues # Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': # pulls: From 1bc6afaafdc5c56994bda8549d50b2fdff9860e9 Mon Sep 17 00:00:00 2001 From: jmespadero Date: Fri, 28 Jul 2023 13:42:38 +0200 Subject: [PATCH 48/54] Add rnd() and randInt(n) functions to muparse Add two functions to muParse to generate random numbers * rnd() will generate a random double value in [0.0 .. 1.0) interval * randInt(n) will generate a random integer value in [0 .. n) interval Those functions may be used to generate noise custom noise algorithm that can be aplied to geometry, colors, normals, etc. or select a random number of vertex/faces than can be erased/processed in posterior filters. --- .../filter_func/filter_func.cpp | 56 ++++++++++++++++++- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/src/meshlabplugins/filter_func/filter_func.cpp b/src/meshlabplugins/filter_func/filter_func.cpp index 34c693fa6..329aba22c 100644 --- a/src/meshlabplugins/filter_func/filter_func.cpp +++ b/src/meshlabplugins/filter_func/filter_func.cpp @@ -33,6 +33,20 @@ using namespace mu; using namespace vcg; +std::random_device randomDev; +std::default_random_engine rndEngine(randomDev()); +//Function to generate a random double number in [0..1) interval +double ML_Rnd() { return std::generate_canonical(rndEngine); } +//Function to generate a random integer number in [0..a) interval +double ML_RandInt(const double a) { return std::floor(a*MyRnd()); } + +//Add rnd() and randint() custom functions to a mu::Parser +void setCustomFunctions(mu::Parser& p) +{ + p.DefineFun("rnd", ML_Rnd); + p.DefineFun("randInt", ML_RandInt); +} + // Constructor FilterFunctionPlugin::FilterFunctionPlugin() { @@ -703,7 +717,9 @@ std::map FilterFunctionPlugin::applyFilter( // muparser initialization and explicitly define parser variables Parser p; + setPerVertexVariables(p, m.cm); + setCustomFunctions(p); // set expression inserted by user as string (required by muparser) p.SetExpr(wexpr); @@ -749,7 +765,8 @@ std::map FilterFunctionPlugin::applyFilter( // muparser initialization and explicitly define parser variables Parser p; setPerFaceVariables(p, m.cm); - + setCustomFunctions(p); + // set expression inserted by user as string (required by muparser) p.SetExpr(conversion::fromStringToWString(select.toStdString())); @@ -817,12 +834,16 @@ std::map FilterFunctionPlugin::applyFilter( // muparser initialization and explicitly define parser variables // function for x,y and z must use different parser and variables Parser p1, p2, p3, p4; - setPerVertexVariables(p1, m.cm); setPerVertexVariables(p2, m.cm); setPerVertexVariables(p3, m.cm); setPerVertexVariables(p4, m.cm); + setCustomFunctions(p1); + setCustomFunctions(p2); + setCustomFunctions(p3); + setCustomFunctions(p4); + p1.SetExpr(conversion::fromStringToWString(func_x)); p2.SetExpr(conversion::fromStringToWString(func_y)); p3.SetExpr(conversion::fromStringToWString(func_z)); @@ -918,6 +939,9 @@ std::map FilterFunctionPlugin::applyFilter( Parser p; setPerVertexVariables(p, m.cm); + //Add rnd() and randInt() internal functions + setCustomFunctions(p); + // set expression to calc with parser p.SetExpr(conversion::fromStringToWString(func_q)); @@ -977,6 +1001,8 @@ std::map FilterFunctionPlugin::applyFilter( Parser pu, pv; setPerVertexVariables(pu, m.cm); setPerVertexVariables(pv, m.cm); + setCustomFunctions(pu); + setCustomFunctions(pv); // set expression to calc with parser #ifdef _UNICODE @@ -1035,6 +1061,12 @@ std::map FilterFunctionPlugin::applyFilter( setPerFaceVariables(pv1, m.cm); setPerFaceVariables(pu2, m.cm); setPerFaceVariables(pv2, m.cm); + setCustomFunctions(pu0); + setCustomFunctions(pv0); + setCustomFunctions(pu1); + setCustomFunctions(pv1); + setCustomFunctions(pu2); + setCustomFunctions(pv2); // set expression to calc with parser pu0.SetExpr(conversion::fromStringToWString(func_u0)); @@ -1084,6 +1116,10 @@ std::map FilterFunctionPlugin::applyFilter( setPerFaceVariables(p_nx, m.cm); setPerFaceVariables(p_ny, m.cm); setPerFaceVariables(p_nz, m.cm); + setCustomFunctions(p_nx); + setCustomFunctions(p_ny); + setCustomFunctions(p_nz); + p_nx.SetExpr(conversion::fromStringToWString(func_nx)); p_ny.SetExpr(conversion::fromStringToWString(func_ny)); p_nz.SetExpr(conversion::fromStringToWString(func_nz)); @@ -1156,6 +1192,10 @@ std::map FilterFunctionPlugin::applyFilter( setPerFaceVariables(p2, m.cm); setPerFaceVariables(p3, m.cm); setPerFaceVariables(p4, m.cm); + setCustomFunctions(p1); + setCustomFunctions(p2); + setCustomFunctions(p3); + setCustomFunctions(p4); p1.SetExpr(conversion::fromStringToWString(func_r)); p2.SetExpr(conversion::fromStringToWString(func_g)); @@ -1229,6 +1269,7 @@ std::map FilterFunctionPlugin::applyFilter( // muparser initialization and define custom variables Parser pf; setPerFaceVariables(pf, m.cm); + setCustomFunctions(pf); // set expression to calc with parser pf.SetExpr(conversion::fromStringToWString(func_q)); @@ -1288,6 +1329,7 @@ std::map FilterFunctionPlugin::applyFilter( Parser p; setPerVertexVariables(p, m.cm); + setCustomFunctions(p); p.SetExpr(conversion::fromStringToWString(expr)); @@ -1340,6 +1382,7 @@ std::map FilterFunctionPlugin::applyFilter( h = tri::Allocator::AddPerFaceAttribute(m.cm, name); Parser p; setPerFaceVariables(p, m.cm); + setCustomFunctions(p); p.SetExpr(conversion::fromStringToWString(expr)); time_t start = clock(); @@ -1386,6 +1429,10 @@ std::map FilterFunctionPlugin::applyFilter( setPerVertexVariables(p_x, m.cm); setPerVertexVariables(p_y, m.cm); setPerVertexVariables(p_z, m.cm); + setCustomFunctions(p_x); + setCustomFunctions(p_y); + setCustomFunctions(p_z); + p_x.SetExpr(conversion::fromStringToWString(x_expr)); p_y.SetExpr(conversion::fromStringToWString(y_expr)); p_z.SetExpr(conversion::fromStringToWString(z_expr)); @@ -1445,6 +1492,9 @@ std::map FilterFunctionPlugin::applyFilter( setPerFaceVariables(p_x, m.cm); setPerFaceVariables(p_y, m.cm); setPerFaceVariables(p_z, m.cm); + setCustomFunctions(p_x); + setCustomFunctions(p_y); + setCustomFunctions(p_z); p_x.SetExpr(conversion::fromStringToWString(x_expr)); p_y.SetExpr(conversion::fromStringToWString(y_expr)); p_z.SetExpr(conversion::fromStringToWString(z_expr)); @@ -1526,6 +1576,8 @@ std::map FilterFunctionPlugin::applyFilter( Parser p; double x, y, z; + setCustomFunctions(p); + p.DefineVar(conversion::fromStringToWString("x"), &x); p.DefineVar(conversion::fromStringToWString("y"), &y); p.DefineVar(conversion::fromStringToWString("z"), &z); From 07cb607f582feda2b9e05c1eeb6afa213ad07b15 Mon Sep 17 00:00:00 2001 From: jmespadero Date: Fri, 28 Jul 2023 13:47:32 +0200 Subject: [PATCH 49/54] include ramdom --- src/meshlabplugins/filter_func/filter_func.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/meshlabplugins/filter_func/filter_func.cpp b/src/meshlabplugins/filter_func/filter_func.cpp index 329aba22c..ffb45dc8b 100644 --- a/src/meshlabplugins/filter_func/filter_func.cpp +++ b/src/meshlabplugins/filter_func/filter_func.cpp @@ -22,8 +22,8 @@ ****************************************************************************/ #include "filter_func.h" +#include #include - #include #include From 8a51f54ef1a665a323b62b51856bd2f33530c2cc Mon Sep 17 00:00:00 2001 From: jmespadero Date: Fri, 28 Jul 2023 14:16:31 +0200 Subject: [PATCH 50/54] include rnd() and randInt() functions to muparser small bugfix --- src/meshlabplugins/filter_func/filter_func.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/meshlabplugins/filter_func/filter_func.cpp b/src/meshlabplugins/filter_func/filter_func.cpp index ffb45dc8b..0acca0fed 100644 --- a/src/meshlabplugins/filter_func/filter_func.cpp +++ b/src/meshlabplugins/filter_func/filter_func.cpp @@ -38,7 +38,7 @@ std::default_random_engine rndEngine(randomDev()); //Function to generate a random double number in [0..1) interval double ML_Rnd() { return std::generate_canonical(rndEngine); } //Function to generate a random integer number in [0..a) interval -double ML_RandInt(const double a) { return std::floor(a*MyRnd()); } +double ML_RandInt(const double a) { return std::floor(a*ML_Rnd()); } //Add rnd() and randint() custom functions to a mu::Parser void setCustomFunctions(mu::Parser& p) From d7166cfdf5c54824f81759a17a15f033535c2c37 Mon Sep 17 00:00:00 2001 From: Alessandro Muntoni Date: Mon, 31 Jul 2023 10:43:30 +0200 Subject: [PATCH 51/54] update vcg submodule --- src/vcglib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vcglib b/src/vcglib index 87edc09b9..80b1d21bd 160000 --- a/src/vcglib +++ b/src/vcglib @@ -1 +1 @@ -Subproject commit 87edc09b9077b9e9b5fc474544ea76b17b665399 +Subproject commit 80b1d21bdab5031c2ffdba971ff8b0b7c7a76911 From 74be3fc6fccefae9df8b8b795aef6d2da137efaa Mon Sep 17 00:00:00 2001 From: jmespadero Date: Wed, 4 Oct 2023 15:44:37 +0200 Subject: [PATCH 52/54] bbox_tokens --- .../filter_func/filter_func.cpp | 118 ++++++++++++++---- src/meshlabplugins/filter_func/filter_func.h | 7 ++ 2 files changed, 104 insertions(+), 21 deletions(-) diff --git a/src/meshlabplugins/filter_func/filter_func.cpp b/src/meshlabplugins/filter_func/filter_func.cpp index 34c693fa6..e035357ae 100644 --- a/src/meshlabplugins/filter_func/filter_func.cpp +++ b/src/meshlabplugins/filter_func/filter_func.cpp @@ -30,6 +30,8 @@ #include "muParser.h" #include "string_conversion.h" +#include + using namespace mu; using namespace vcg; @@ -123,34 +125,45 @@ QString FilterFunctionPlugin::pythonFilterName(ActionIDType f) const } const QString PossibleOperators( - "
It's possible to use parenthesis (), and predefined " - "muparser built-in operators, like:
" + "
It's possible to use any of the predefined muparser built-in " + "operators and " + "functions, like: " "&& (logic and), || (logic or), <, <=, >, >=, " - "!= (not equal), == (equal), _?_:_ (c/c++ ternary operator).

"); + "!= (not equal), == (equal), _?_:_ (c/c++ ternary operator), and " + "rnd() (random value in [0..1])" ); const QString PerVertexAttributeString( - "It's possible to use muparser built-in functions" - "and the following per-vertex variables in the expression:
" + "
  • Per-vertex variables:
    " "x,y,z (position), nx,ny,nz (normal), r,g,b,a (color), q " "(quality), vi (vertex index), vtu,vtv,ti (texture coords and texture " - "index), vsel (is the vertex selected? 1 yes, 0 no) " - "and all custom vertex attributes already defined by user." - "Point3 attribute are available as three variables with _x, _y, _z appended to the attribute name.
    "); + "index), vsel ( 1 if selected, 0 if not selected)." + "
  • Bounding Box variables:
    " + "xmin,ymin,zmin (min coordinates), xmax,ymax,zmax (max coordinates), " + "xmid,ymid,zmid (midpoint coordinates), xdim,ydim,zdim (dimensions), bbdiag (diagonal length)" + "
  • User-defined attributes:
    " + "All user defined custom vertex attributes are available. " + "Point3 attribute are available as 3 variables with _x, _y, _z appended to the attribute name." + "
"); const QString PerFaceAttributeString( - "It's possible to use muparser built-in functions" - "and the following per-face or per-vertex variables:
" - "x0,y0,z0 for the first vertex position, x1,y1,z1 for the second vertex " - "position, x2,y2,z2 for the third vertex position, " - "nx0,ny0,nz0 nx1,ny1,nz1 nx2,ny2,nz2 for vertex normals, r0,g0,b0,a0 r1,g1,b1,a1 " - "r2,g2,b2,a2 for vertex colors, vi0, vi1, vi2 for vertex indices, " - "q0,q1,q2 for vertex quality, wtu0,wtv0 wtu1,wtv1 wtu2,wtv2 for per-wedge " - "texture coords, ti for face texture index, vsel0,vsel1,vsel2 for vertex " - "selection (1 yes, 0 no) " - "fi for face index, fr,fg,fb,fa for face color, fq for face quality, " - "fnx,fny,fnz for face normal, fsel face selection (1 yes, 0 no)." - "All user defined face scalar attributes are available." - "Point3 attribute are available as three variables with _x, _y, _z appended to the attribute name.

"); + "
  • Per-face variables:
    " + "fi (face index), fr,fg,fb,fa (face color), fq (face quality), " + "fnx,fny,fnz (face normal), fsel ( 1 if selected, 0 if not selected)." + "
  • Per-vertex variables:
    " + "x0,y0,z0 (first vertex position), x1,y1,z1 (second vertex position)," + "x2,y2,z2 (third vertex position), " + "nx0,ny0,nz0 nx1,ny1,nz1 nx2,ny2,nz2 (vertex normals), r0,g0,b0,a0 r1,g1,b1,a1 " + "r2,g2,b2,a2 (vertex colors), vi0, vi1, vi2 (vertex indices), " + "q0,q1,q2 (vertex quality), wtu0,wtv0 wtu1,wtv1 wtu2,wtv2 (per-wedge " + "texture coords), ti (face texture index), vsel0,vsel1,vsel2 (vertex selected (1 yes, 0 no))." + "
  • Bounding Box variables:
    " + "xmin,ymin,zmin (min coordinates), xmax,ymax,zmax (max coordinates), " + "xmid,ymid,zmid (midpoint coordinates), xdim,ydim,zdim (dimensions), bbdiag (diagonal length)." + "
  • User-defined attributes:
    " + "All user defined custom face scalar attributes are available. " + "Point3 attribute are available as 3 variables with _x, _y, _z appended to the attribute name." + "
"); + // long string describing each filtering action QString FilterFunctionPlugin::filterInfo(ActionIDType filterId) const @@ -696,6 +709,24 @@ std::map FilterFunctionPlugin::applyFilter( md.addNewMesh("", this->filterName(ID(filter))); MeshModel& m = *(md.mm()); Q_UNUSED(cb); + + //Set values to parser variables related to BBox + vcg::Box3f bbox = m.cm.bbox; + xmin = bbox.min.X(); + ymin = bbox.min.Y(); + zmin = bbox.min.Z(); + xmax = bbox.max.X(); + ymax = bbox.max.Y(); + zmax = bbox.max.Z(); + xdim = bbox.DimX(); + ydim = bbox.DimY(); + zdim = bbox.DimZ(); + bbdiag = bbox.Diag(); + vcg::Point3f bbCenter = bbox.Center(); + xmid = bbCenter.X(); + ymid = bbCenter.Y(); + zmid = bbCenter.Z(); + switch (ID(filter)) { case FF_VERT_SELECTION: { std::string expr = par.getString("condSelect").toStdString(); @@ -1762,6 +1793,15 @@ void FilterFunctionPlugin::setAttributes(CMeshO::FaceIterator& fi, CMeshO& m) } + // Generate a random double in [0.0, 1.0] interval +double FilterFunctionPlugin::random() +{ + std::random_device rd; // Seed for the random number engine + std::mt19937 gen(rd()); // Mersenne Twister engine + std::uniform_real_distribution dis(0.0, 1.0); + return dis(gen); // Generate a random double in [0.0, 1.0] +} + // Function explicitly define parser variables to perform per-vertex filter action // x, y, z for vertex coord, nx, ny, nz for normal coord, r, g ,b for color // and q for quality @@ -1784,6 +1824,24 @@ void FilterFunctionPlugin::setPerVertexVariables(Parser& p, CMeshO& m) p.DefineVar(conversion::fromStringToWString("ti"), &ti); p.DefineVar(conversion::fromStringToWString("vsel"), &vsel); + //Add tokens related to mesh bounding box + p.DefineVar(conversion::fromStringToWString("xmin"), &xmin); + p.DefineVar(conversion::fromStringToWString("ymin"), &ymin); + p.DefineVar(conversion::fromStringToWString("zmin"), &zmin); + p.DefineVar(conversion::fromStringToWString("xmax"), &xmax); + p.DefineVar(conversion::fromStringToWString("ymax"), &ymax); + p.DefineVar(conversion::fromStringToWString("zmax"), &zmax); + p.DefineVar(conversion::fromStringToWString("bbdiag"), &bbdiag); + p.DefineVar(conversion::fromStringToWString("xdim"), &xdim); + p.DefineVar(conversion::fromStringToWString("ydim"), &ydim); + p.DefineVar(conversion::fromStringToWString("zdim"), &zdim); + p.DefineVar(conversion::fromStringToWString("xmid"), &xmid); + p.DefineVar(conversion::fromStringToWString("ymid"), &ymid); + p.DefineVar(conversion::fromStringToWString("zmid"), &zmid); + + //Add function rnd() + p.DefineFun("rnd", random); + // define var for user-defined attributes (if any exists) // if vector is empty, code won't be executed v_handlers.clear(); @@ -1912,6 +1970,24 @@ void FilterFunctionPlugin::setPerFaceVariables(Parser& p, CMeshO& m) p.DefineVar(conversion::fromStringToWString("vsel2"), &vsel2); p.DefineVar(conversion::fromStringToWString("fsel"), &fsel); + //Add tokens related to mesh bounding box + p.DefineVar(conversion::fromStringToWString("xmin"), &xmin); + p.DefineVar(conversion::fromStringToWString("ymin"), &ymin); + p.DefineVar(conversion::fromStringToWString("zmin"), &zmin); + p.DefineVar(conversion::fromStringToWString("xmax"), &xmax); + p.DefineVar(conversion::fromStringToWString("ymax"), &ymax); + p.DefineVar(conversion::fromStringToWString("zmax"), &zmax); + p.DefineVar(conversion::fromStringToWString("bbdiag"), &bbdiag); + p.DefineVar(conversion::fromStringToWString("xdim"), &xdim); + p.DefineVar(conversion::fromStringToWString("ydim"), &ydim); + p.DefineVar(conversion::fromStringToWString("zdim"), &zdim); + p.DefineVar(conversion::fromStringToWString("xmid"), &xmid); + p.DefineVar(conversion::fromStringToWString("ymid"), &ymid); + p.DefineVar(conversion::fromStringToWString("zmid"), &zmid); + + //Add function rnd() + p.DefineFun("rnd", random); + // define var for user-defined attributes (if any exists) // if vector is empty, code won't be executed f_handlers.clear(); diff --git a/src/meshlabplugins/filter_func/filter_func.h b/src/meshlabplugins/filter_func/filter_func.h index 4e6a0c53e..c1ca56608 100644 --- a/src/meshlabplugins/filter_func/filter_func.h +++ b/src/meshlabplugins/filter_func/filter_func.h @@ -44,6 +44,10 @@ protected: vsel0, vsel1, vsel2; double fr, fg, fb, fa, fnx, fny, fnz, fq, fsel; double v, f, v0i, v1i, v2i, ti; + + //Bounding Box of the mesh + double xmin, ymin, zmin, xmax, ymax, zmax, xdim, ydim, zdim, bbdiag, xmid, ymid, zmid; + std::vector v_attrNames; // names of the per vertex attributes std::vector v_attrValue; // values of the per vertex attributes std::vector v3_attrNames; // names of the per vertex attributes There are @@ -62,6 +66,9 @@ protected: QString errorMsg; + // Generate a random double in [0.0, 1.0] interval + static double random(); + public: enum { FF_VERT_SELECTION, From 0b44eb991c4b0bd49dff319a0cdfba61542440cc Mon Sep 17 00:00:00 2001 From: jmespadero Date: Wed, 4 Oct 2023 15:52:42 +0200 Subject: [PATCH 53/54] Add bbox tokens to muparse --- src/meshlabplugins/filter_func/filter_func.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/meshlabplugins/filter_func/filter_func.cpp b/src/meshlabplugins/filter_func/filter_func.cpp index e035357ae..31f1c278d 100644 --- a/src/meshlabplugins/filter_func/filter_func.cpp +++ b/src/meshlabplugins/filter_func/filter_func.cpp @@ -130,7 +130,7 @@ const QString PossibleOperators( "functions, like: " "&& (logic and), || (logic or), <, <=, >, >=, " "!= (not equal), == (equal), _?_:_ (c/c++ ternary operator), and " - "rnd() (random value in [0..1])" ); + "rnd() (random value in [0..1]), and these values:" ); const QString PerVertexAttributeString( "
  • Per-vertex variables:
    " @@ -139,7 +139,8 @@ const QString PerVertexAttributeString( "index), vsel ( 1 if selected, 0 if not selected)." "
  • Bounding Box variables:
    " "xmin,ymin,zmin (min coordinates), xmax,ymax,zmax (max coordinates), " - "xmid,ymid,zmid (midpoint coordinates), xdim,ydim,zdim (dimensions), bbdiag (diagonal length)" + "xmid,ymid,zmid (midpoint coordinates), xdim,ydim,zdim (dimensions), " + "bbdiag (diagonal length)" "
  • User-defined attributes:
    " "All user defined custom vertex attributes are available. " "Point3 attribute are available as 3 variables with _x, _y, _z appended to the attribute name." @@ -148,17 +149,18 @@ const QString PerVertexAttributeString( const QString PerFaceAttributeString( "
    • Per-face variables:
      " "fi (face index), fr,fg,fb,fa (face color), fq (face quality), " - "fnx,fny,fnz (face normal), fsel ( 1 if selected, 0 if not selected)." + "fnx,fny,fnz (face normal), fsel ( 1 if face is selected, 0 if not selected)." "
    • Per-vertex variables:
      " "x0,y0,z0 (first vertex position), x1,y1,z1 (second vertex position)," "x2,y2,z2 (third vertex position), " "nx0,ny0,nz0 nx1,ny1,nz1 nx2,ny2,nz2 (vertex normals), r0,g0,b0,a0 r1,g1,b1,a1 " "r2,g2,b2,a2 (vertex colors), vi0, vi1, vi2 (vertex indices), " - "q0,q1,q2 (vertex quality), wtu0,wtv0 wtu1,wtv1 wtu2,wtv2 (per-wedge " - "texture coords), ti (face texture index), vsel0,vsel1,vsel2 (vertex selected (1 yes, 0 no))." + "q0,q1,q2 (vertex quality), wtu0,wtv0 wtu1,wtv1 wtu2,wtv2 (per-wedge texture coords), " + "ti (face texture index), vsel0,vsel1,vsel2 (1 if vertex is selected, 0 if not)." "
    • Bounding Box variables:
      " "xmin,ymin,zmin (min coordinates), xmax,ymax,zmax (max coordinates), " - "xmid,ymid,zmid (midpoint coordinates), xdim,ydim,zdim (dimensions), bbdiag (diagonal length)." + "xmid,ymid,zmid (midpoint coordinates), xdim,ydim,zdim (dimensions), " + "bbdiag (diagonal length)." "
    • User-defined attributes:
      " "All user defined custom face scalar attributes are available. " "Point3 attribute are available as 3 variables with _x, _y, _z appended to the attribute name." @@ -711,7 +713,7 @@ std::map FilterFunctionPlugin::applyFilter( Q_UNUSED(cb); //Set values to parser variables related to BBox - vcg::Box3f bbox = m.cm.bbox; + const auto &bbox = m.cm.bbox; xmin = bbox.min.X(); ymin = bbox.min.Y(); zmin = bbox.min.Z(); @@ -722,7 +724,7 @@ std::map FilterFunctionPlugin::applyFilter( ydim = bbox.DimY(); zdim = bbox.DimZ(); bbdiag = bbox.Diag(); - vcg::Point3f bbCenter = bbox.Center(); + auto bbCenter = bbox.Center(); xmid = bbCenter.X(); ymid = bbCenter.Y(); zmid = bbCenter.Z(); From f43e82908257e18fa268128f9fd877aba506505d Mon Sep 17 00:00:00 2001 From: alemuntoni Date: Mon, 16 Oct 2023 12:19:30 +0200 Subject: [PATCH 54/54] fix #1421 --- src/meshlabplugins/io_u3d/io_u3d.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/meshlabplugins/io_u3d/io_u3d.cpp b/src/meshlabplugins/io_u3d/io_u3d.cpp index 9df0b0fdb..0c90d3bcf 100644 --- a/src/meshlabplugins/io_u3d/io_u3d.cpp +++ b/src/meshlabplugins/io_u3d/io_u3d.cpp @@ -296,19 +296,19 @@ void U3DIOPlugin::saveLatex(const QString& file,const vcg::tri::io::u3dparameter std::string u3d_final = vcg::tri::io::QtUtilityFunctions::fileNameFromTrimmedPath(file_trim).toStdString(); latex.write(0,"\\documentclass[a4paper]{article}"); - latex.write(0,"\\usepackage[3D]{movie15}"); + latex.write(0,"\\usepackage[3Dmenu]{media9}"); latex.write(0,"\\usepackage{hyperref}"); latex.write(0,"\\usepackage[UKenglish]{babel}"); latex.write(0,"\\begin{document}"); - latex.write(0,"\\includemovie["); - latex.write(1,"poster,"); - latex.write(1,"toolbar, %same as `controls\'"); + latex.write(0,"\\includemedia["); + latex.write(1,"width=\\linewidth,height=\\linewidth,"); + latex.write(1,"3Dmenu,'"); QString u3d_text = QString::fromStdString(u3d_final); substituteChar(u3d_text,QChar('_'),QString("")); latex.write(1,"label=" + u3d_text.toStdString() + ","); - latex.write(1,"text=(" + u3d_text.toStdString() + "),"); + latex.write(1,"activate=pageopen,"); std::string cam_string; typename vcg::tri::io::u3dparametersclasses::Movie15Parameters::CameraParameters* cam = mov_par._campar; if (cam != NULL) { @@ -319,8 +319,8 @@ void U3DIOPlugin::saveLatex(const QString& file,const vcg::tri::io::u3dparameter ", 3Dcoo=" + TextUtility::nmbToStr(-cam->_obj_pos.X()) + " " + TextUtility::nmbToStr(cam->_obj_pos.Z()) + " " + TextUtility::nmbToStr(cam->_obj_pos.Y()) + ","; latex.write(1,cam_string); } - latex.write(1,"3Dlights=CAD,"); - latex.write(0,"]{\\linewidth}{\\linewidth}{" + u3d_final + "}"); + latex.write(1,"3Dlights=CAD"); + latex.write(0,"]{}{" + u3d_final + "}"); latex.write(0,"\\end{document}"); }