diff --git a/.github/actions/0_setup/action.yml b/.github/actions/0_setup/action.yml new file mode 100644 index 000000000..24bcf9db8 --- /dev/null +++ b/.github/actions/0_setup/action.yml @@ -0,0 +1,23 @@ +name: 'Setup Environment' +description: 'Setup Environment' + +inputs: + qt-version: + description: 'Qt Version' + required: false + default: '5.15.2' + +runs: + using: "composite" + steps: + - name: Setup MSVC + uses: ilammy/msvc-dev-cmd@v1 + - name: Install Qt + uses: jurplel/install-qt-action@v3 + 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/actions/2_deploy/action.yml b/.github/actions/2_deploy/action.yml new file mode 100644 index 000000000..69b582081 --- /dev/null +++ b/.github/actions/2_deploy/action.yml @@ -0,0 +1,65 @@ +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 + 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 + default: '' + win-certificate-pssw: + description: 'Windows Certificate Password' + required: false + default: '' + +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 + 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 }}' ${{ steps.notarization.outputs.not_setting }} \ No newline at end of file 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: diff --git a/.github/workflows/BuildMeshLab.yml b/.github/workflows/BuildMeshLab.yml index 4ae5d49ae..df616376e 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,29 +22,8 @@ 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 - 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: | - 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 @@ -53,24 +33,20 @@ 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 }} --nightly --ccache --use_brew_llvm + - name: Build + uses: ./.github/actions/1_build + cache-path: src/external/downloads/* + cache-key: external-libraries + 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: diff --git a/.github/workflows/CreateRelease.yml b/.github/workflows/CreateRelease.yml index 63bc27f0d..ea091dc19 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,44 +52,23 @@ 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 }} --ccache + 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 }} - - 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 + uses: ./.github/actions/2_deploy 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}}" + 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: @@ -242,7 +200,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}}" 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 diff --git a/scripts/macOS/2_deploy.sh b/scripts/macOS/2_deploy.sh index b3a3a791a..f359e86e6 100755 --- a/scripts/macOS/2_deploy.sh +++ b/scripts/macOS/2_deploy.sh @@ -6,7 +6,11 @@ INSTALL_PATH=$SCRIPTS_PATH/../../install QT_DIR_OPTION="" PACKAGES_PATH=$SCRIPTS_PATH/../../packages SIGN=false +NOTARIZE=false CERT_ID="" +NOTAR_USER="" +NOTAR_TEAM_ID="" +NOTAR_PASSWORD="" #checking for parameters for i in "$@" @@ -25,12 +29,27 @@ case $i in shift # past argument=value ;; -ci=*|--cert_id=*) - if [ -z "${i#*=}" ]; then - SIGN=true - CERT_ID="${i#*=}" + CERT_ID="${i#*=}" + if [ -n "$CERT_ID" ]; then + SIGN=true fi shift # past argument=value ;; + -nu=*|--notarization_user=*) + NOTAR_USER="${i#*=}" + if [ -n "$NOTAR_USER" ]; then + NOTARIZE=true + fi + shift # past argument=value + ;; + -np=*|--notarization_pssw=*) + NOTAR_PASSWORD="${i#*=}" + shift # past argument=value + ;; + -nt=*|--notarization_team=*) + NOTAR_TEAM_ID="${i#*=}" + shift # past argument=value + ;; *) # unknown option ;; @@ -47,6 +66,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=$NOTAR_USER -nt=$NOTAR_TEAM_ID -np=$NOTAR_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/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 diff --git a/scripts/macOS/internal/2c_notarize_appbundle.sh b/scripts/macOS/internal/2c_notarize_appbundle.sh new file mode 100644 index 000000000..3eb6346ca --- /dev/null +++ b/scripts/macOS/internal/2c_notarize_appbundle.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +SCRIPTS_PATH="$(dirname "$(realpath "$0")")"/.. + +INSTALL_PATH=$SCRIPTS_PATH/../../install +NOTAR_USER="" +NOTAR_PASSWORD="" +NOTAR_TEAM_ID="" + +#checking for parameters +for i in "$@" +do +case $i in + -i=*|--install_path=*) + INSTALL_PATH="${i#*=}" + shift # past argument=value + ;; + -nu=*|--notarization_user=*) + 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 + ;; + *) + # unknown option + ;; +esac +done + +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" + +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 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1559c19c9..e6c2764d1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -153,7 +153,7 @@ if(NOT DEFINED MESHLAB_PLUGINS) # it may be already defined in parent directory meshlabplugins/filter_dirt meshlabplugins/filter_embree meshlabplugins/filter_fractal - meshlabplugins/filter_func + meshlabplugins/filter_func meshlabplugins/filter_img_patch_param meshlabplugins/filter_icp meshlabplugins/filter_io_nxs 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) diff --git a/src/meshlab/layerDialog.cpp b/src/meshlab/layerDialog.cpp index caa587402..f79208e79 100644 --- a/src/meshlab/layerDialog.cpp +++ b/src/meshlab/layerDialog.cpp @@ -82,18 +82,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); @@ -219,6 +232,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) mw->GLA()->meshSetVisibility(*md->getMesh(clickedId), !md->getMesh(clickedId)->isVisible()); } 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 : @@ -676,6 +751,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; + for(MeshModel& m : md->meshIterator()) + { + if (m.id() == *animIter) + { + foundInMeshList = true; + break; + } + } + if (foundInMeshList) + { + ++animIter; + } + else + { + animIter = animMeshIDs.erase(animIter); + } + } + updateTreeWidgetSizes(ui->meshTreeWidget); updatePerMeshItemVisibility(); updatePerMeshItemSelectionStatus(); @@ -861,6 +958,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->meshNumber() > 1; + + if (canAnimate) + { + int visibleCount = 0; + for(MeshModel& m : md->meshIterator()) + { + if (m.isVisible()) + { + ++visibleCount; + } + } + // If fewer than two meshes were visible select all meshes, else select only the visible ones + animIndex = -1; + animMeshIDs.clear(); + for(MeshModel& m : md->meshIterator()) + { + if (m.isVisible() && animIndex < 0) + { + animIndex = animMeshIDs.size(); // Remember first visible mesh + } + if (m.isVisible() || visibleCount < 2) + { + animMeshIDs.push_back(m.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->meshNumber() > 1) + { + MeshModel* lastMP = nullptr; + + while (!foundVisible && animMeshIDs.size() > 1) + { + animIndex = (animMeshIDs.size() + animIndex + offset) % animMeshIDs.size(); + animatingCount = 0; + for (const auto& id : animMeshIDs) + { + for(MeshModel& m : md->meshIterator()) + { + if (m.id() == id) + { + bool makeVisible = m.id() == animMeshIDs[animIndex]; + mw->GLA()->meshSetVisibility(m, makeVisible); + foundVisible |= makeVisible; + ++animatingCount; + lastMP = &m; + } + } + } + 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 != nullptr) + { + 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 4847aa565..0996c0791 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" @@ -140,6 +141,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(); @@ -177,6 +184,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 ae1dde12e..121b3000b 100644 --- a/src/meshlab/ui/layerDialog.ui +++ b/src/meshlab/ui/layerDialog.ui @@ -139,7 +139,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 diff --git a/src/meshlabplugins/filter_func/filter_func.cpp b/src/meshlabplugins/filter_func/filter_func.cpp index c6ad16711..530f15e60 100644 --- a/src/meshlabplugins/filter_func/filter_func.cpp +++ b/src/meshlabplugins/filter_func/filter_func.cpp @@ -22,17 +22,33 @@ ****************************************************************************/ #include "filter_func.h" +#include #include - #include #include #include "muParser.h" #include "string_conversion.h" +#include + 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*ML_Rnd()); } + +//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() { @@ -123,32 +139,47 @@ QString FilterFunctionPlugin::pythonFilterName(ActionIDType f) const } const QString PossibleOperators( - "
It's possible to use parenthesis (), and predefined operators:
" + "
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]), and these values:" ); const QString PerVertexAttributeString( - "It's possible to use 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 the following per-face variables, or variables associated to the three " - "vertex of every face:
" - "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 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 (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)." + "
  • 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 @@ -694,6 +725,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 + const auto &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(); + auto 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(); @@ -701,7 +750,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); @@ -747,7 +798,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())); @@ -815,12 +867,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)); @@ -916,6 +972,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)); @@ -975,6 +1034,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 @@ -1033,6 +1094,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)); @@ -1082,6 +1149,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)); @@ -1154,6 +1225,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)); @@ -1227,6 +1302,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)); @@ -1286,6 +1362,7 @@ std::map FilterFunctionPlugin::applyFilter( Parser p; setPerVertexVariables(p, m.cm); + setCustomFunctions(p); p.SetExpr(conversion::fromStringToWString(expr)); @@ -1338,6 +1415,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(); @@ -1384,6 +1462,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)); @@ -1443,6 +1525,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)); @@ -1524,6 +1609,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); @@ -1760,6 +1847,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 @@ -1782,6 +1878,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(); @@ -1910,6 +2024,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, diff --git a/src/meshlabplugins/filter_geodesic/filter_geodesic.cpp b/src/meshlabplugins/filter_geodesic/filter_geodesic.cpp index a5149e09b..dd600f699 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 (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!"); @@ -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 : + case FP_QUALITY_SELECTED_GEODESIC_HEAT : return FilterGeodesic::FilterClass(FilterPlugin::VertexColoring + FilterPlugin::Quality); default : assert(0); } return FilterPlugin::Generic; @@ -111,15 +118,16 @@ 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; } -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; @@ -236,7 +244,60 @@ 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::GeodesicHeatCache> cache; + if (!vcg::tri::HasPerMeshAttribute(m.cm, "GeodesicHeatCache")){ + 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::GeodesicHeatCache>>(m.cm, std::string("GeodesicHeatCache")); + GeodesicHeatCacheHandle() = cache; + } + else { + cb(10, "Recovering Cache..."); + // recover cache + 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..."); + + 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::GeodesicHeatCache>>(m.cm, std::string("GeodesicHeatCache")); + tri::Allocator::DeletePerMeshAttribute::GeodesicHeatCache>>(m.cm, GeodesicHeatCacheHandle); + } + } + else + log("Warning: no vertices are selected! aborting geodesic computation."); + } + break; default: wrongActionCalled(filter); break; @@ -255,7 +316,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.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 } return parlst; @@ -265,10 +329,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 + MeshModel::MM_FACEQUALITY; + 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_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::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}"); } diff --git a/src/vcglib b/src/vcglib index d3822ec0c..97adf9466 160000 --- a/src/vcglib +++ b/src/vcglib @@ -1 +1 @@ -Subproject commit d3822ec0cd1161de2223ddc4f126c413d398dad1 +Subproject commit 97adf94662ed4e54752c456d6ebfa97d7bf92ebd