diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index dc0d9d1e5..1e9a1153d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -32,6 +32,7 @@ if (Qt5_VERSION VERSION_LESS 5.15.0) message(FATAL_ERROR "Minimum supported Qt5 version is 5.15!") endif() find_package(OpenMP) +find_package(Threads) message(STATUS "Searching for required components with bundled fallback") find_package(GLEW) @@ -172,6 +173,7 @@ if(NOT DEFINED MESHLAB_PLUGINS) # it may be already defined in parent directory meshlabplugins/filter_mls meshlabplugins/filter_mutualglobal meshlabplugins/filter_mutualinfo + meshlabplugins/filter_parametrization meshlabplugins/filter_plymc meshlabplugins/filter_qhull meshlabplugins/filter_quality diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index aaae96398..ad1c62a0b 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -65,6 +65,7 @@ set(HEADERS plugins/interfaces/filter_plugin.h plugins/interfaces/io_plugin.h plugins/interfaces/render_plugin.h + plugins/action_searcher.h plugins/meshlab_plugin_type.h plugins/plugin_manager.h python/function.h @@ -81,8 +82,7 @@ set(HEADERS ml_selection_buffers.h ml_thread_safe_memory_info.h mlapplication.h - mlexception.h - searcher.h) + mlexception.h) set(SOURCES ml_document/helpers/mesh_document_state_data.cpp @@ -107,6 +107,7 @@ set(SOURCES plugins/interfaces/decorate_plugin.cpp plugins/interfaces/filter_plugin.cpp plugins/interfaces/io_plugin.cpp + plugins/action_searcher.cpp plugins/meshlab_plugin_type.cpp plugins/plugin_manager.cpp python/function.cpp @@ -121,8 +122,7 @@ set(SOURCES filterscript.cpp ml_selection_buffers.cpp ml_thread_safe_memory_info.cpp - mlapplication.cpp - searcher.cpp) + mlapplication.cpp) set(RESOURCES meshlab-common.qrc) diff --git a/src/common/globals.cpp b/src/common/globals.cpp index 1c31602fd..64f6f398a 100644 --- a/src/common/globals.cpp +++ b/src/common/globals.cpp @@ -29,6 +29,7 @@ #include "parameters/rich_parameter_list.h" #include "plugins/plugin_manager.h" +#include "plugins/action_searcher.h" #include "python/function_set.h" QString basePath() @@ -141,6 +142,12 @@ PluginManager& meshlab::pluginManagerInstance() return pm; } +ActionSearcher& meshlab::actionSearcherInstance() +{ + static ActionSearcher as; + return as; +} + pymeshlab::FunctionSet& pymeshlab::functionSetInstance() { static FunctionSet fs(meshlab::pluginManagerInstance()); diff --git a/src/common/globals.h b/src/common/globals.h index a529ca326..1d073b931 100644 --- a/src/common/globals.h +++ b/src/common/globals.h @@ -37,6 +37,7 @@ class RichParameterList; class PluginManager; +class ActionSearcher; namespace meshlab { @@ -46,6 +47,7 @@ QString logDebugFileName(); RichParameterList& defaultGlobalParameterList(); PluginManager& pluginManagerInstance(); +ActionSearcher& actionSearcherInstance(); // keep these functions inlined please // each plugin that uses them need to have their own definition diff --git a/src/common/plugins/action_searcher.cpp b/src/common/plugins/action_searcher.cpp new file mode 100644 index 000000000..1799de16c --- /dev/null +++ b/src/common/plugins/action_searcher.cpp @@ -0,0 +1,175 @@ +/***************************************************************************** + * MeshLab o o * + * A versatile mesh processing toolbox o o * + * _ O _ * + * Copyright(C) 2005-2022 \/)\/ * + * Visual Computing Lab /\/| * + * ISTI - Italian National Research Council | * + * \ * + * All rights reserved. * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License (http://www.gnu.org/licenses/gpl.txt) * + * for more details. * + * * + ****************************************************************************/ + +#include "action_searcher.h" + +#include +#include + +#include "interfaces/filter_plugin.h" + +ActionSearcher::ActionSearcher() +{ +} + +void ActionSearcher::clear() +{ + titleActionsMap.clear(); +} + +/** + * @brief Adds the given action to the ActionSearcher, allowing queries to it. + * @param action + */ +void ActionSearcher::addAction(QAction *action, bool usePythonFilterNames) +{ + if (action != nullptr) { + + // add title to the action map + QString title = action->text(); + title = title.toLower(); + title.remove(ignexp); + QStringList res = title.split(sepexp, Qt::SkipEmptyParts); + res.removeDuplicates(); + addSubStrings(res); + for (const QString& str : qAsConst(res)) { + titleActionsMap[str].push_back(action); + } + if (usePythonFilterNames) { + // if the action is a filter, we should add also the python name to the search + QObject* parent = action->parent(); + FilterPlugin* fp = qobject_cast(parent); + if (fp) { + QString title = fp->pythonFilterName(action); + title.replace("_", " "); + title.remove(ignexp); + QStringList res = title.split(sepexp, Qt::SkipEmptyParts); + res.removeDuplicates(); + addSubStrings(res); + for (const QString& str : qAsConst(res)) { + titleActionsMap[str].push_back(action); + } + } + } + + // add info to the action map + QString info = action->toolTip(); + info = info.toLower(); + info.remove(ignexp); + res = info.split(sepexp, Qt::SkipEmptyParts); + res.removeDuplicates(); + addSubStrings(res); + for (const QString& str : qAsConst(res)) { + infoActionsMap[str].push_back(action); + } + } +} + +/** + * @brief Performs a query using the inputString and returns an array containing the best matching + * actions matching to the input string. + * + * The array will have maximum size equal to maxNumberactions. + * @param inputString: query string + * @param maxNumberActions: maximum size of the output array + * @return array containing the best matching actions + */ +std::vector ActionSearcher::bestMatchingActions(QString inputString, int maxNumberActions) const +{ + std::vector res; + + // clean the input string + inputString = inputString.toLower(); + inputString.replace("_", " "); // to allow python search + inputString.remove(ignexp); + + // split the input string + QStringList inputList = inputString.split(sepexp, Qt::SkipEmptyParts); + inputList.removeDuplicates(); + + const float bonuspertitleword = 1.0f / std::pow(10,inputList.size()); + + // store the ranking for each action + std::map actionRanking; + + // for each input string + for (const QString& str : qAsConst(inputList)) { + auto it = titleActionsMap.find(str); + // if matches in a title of an action + if (it != titleActionsMap.end()) { + for (QAction* act : it->second) { // for each matching action, increment its ranking + actionRanking[act] += bonuspertitleword; + } + } + it = infoActionsMap.find(str); + // if matches in a info of an action + if (it != infoActionsMap.end()) { + for (QAction* act : it->second) { // for each matching action, increment its ranking + actionRanking[act]++; + } + } + } + + std::map> rankingMap; // flipped map of actionRanking + // populate the flipped map + for (const auto& p : actionRanking) { + // for each ranking, push another action havint that ranking + rankingMap[p.second].push_back(p.first); + } + // at the end, the map will store the actions stored by ranking + + int count = 0; // used to stop the number of inserted actions in the resulting vector + + // reverse iteration: start from the biggest ranking + for (auto it = rankingMap.rbegin(); it != rankingMap.rend() && count < maxNumberActions; ++it) { + auto v1 = it->second; + // need to sort properly actions having the same ranking, since they would be sorted using + // memory addresses, that are not deterministic: we use action titles + std::sort(v1.begin(), v1.end(), ActionComparator()); + // insert the sorted actions to the result vector + res.insert(res.end(), v1.begin(), v1.end()); + count += it->second.size(); + } + // if at the end the number exceeded the maximum number, we truncate the array + if (count >= maxNumberActions) { + res.resize(maxNumberActions); + } + return res; +} + +void ActionSearcher::addSubStrings(QStringList &res) +{ + QStringList resWithPrefix; + foreach(QString str, res) + { + QString strPref = str; + resWithPrefix.push_back(strPref); + for(int i=0;i +#include +#include +#include + +class ActionSearcher +{ +public: + ActionSearcher(); + + void clear(); + void addAction(QAction* action, bool usePythonFilterNames = false); + + std::vector bestMatchingActions(QString inputString, int maxNumberActions) const; + +private: + const QRegExp sepexp = QRegExp("\\W+"); + const QRegExp ignexp = QRegExp( + "\\b(an|the|of|it|as|in|by|and|or|for)\\b|\\b[a-z]\\b|'s\\b|\\.|<[^>]*>"); + + // map that stores, for each string, all the actions that store that string in their titles + std::map> titleActionsMap; + + // map that stores, for each stirng, all the actions that store that stirng in their info + std::map> infoActionsMap; + + struct ActionComparator { + bool operator()(QAction* a1, QAction* a2) { + return a1->text() < a2->text(); + } + }; + + static void addSubStrings(QStringList& res); +}; + +#endif // ACTION_SEARCHER_H diff --git a/src/common/plugins/containers/filter_plugin_container.cpp b/src/common/plugins/containers/filter_plugin_container.cpp index d79524c6b..a0244fc53 100644 --- a/src/common/plugins/containers/filter_plugin_container.cpp +++ b/src/common/plugins/containers/filter_plugin_container.cpp @@ -61,6 +61,17 @@ QAction* FilterPluginContainer::filterAction(const QString& name) return nullptr; } +FilterPlugin *FilterPluginContainer::pluginOfFilter(const QAction *action) const +{ + for (FilterPlugin* fp : filterPlugins) { + std::list al = fp->actions(); + auto it = std::find(al.begin(), al.end(), action); + if (it != al.end()) + return fp; + } + return nullptr; +} + FilterPluginContainer::FilterPluginRangeIterator FilterPluginContainer::filterPluginIterator(bool iterateAlsoDisabledPlugins) const { return FilterPluginRangeIterator(this, iterateAlsoDisabledPlugins); diff --git a/src/common/plugins/containers/filter_plugin_container.h b/src/common/plugins/containers/filter_plugin_container.h index 02d6e7de7..b6c684f66 100644 --- a/src/common/plugins/containers/filter_plugin_container.h +++ b/src/common/plugins/containers/filter_plugin_container.h @@ -45,6 +45,7 @@ public: void eraseFilterPlugin(FilterPlugin* iFilter); QAction* filterAction(const QString& name); + FilterPlugin* pluginOfFilter(const QAction* action) const; FilterPluginRangeIterator filterPluginIterator(bool iterateAlsoDisabledPlugins = false) const; diff --git a/src/common/plugins/plugin_manager.cpp b/src/common/plugins/plugin_manager.cpp index 3d2aa08c5..0d0c447b8 100644 --- a/src/common/plugins/plugin_manager.cpp +++ b/src/common/plugins/plugin_manager.cpp @@ -291,6 +291,11 @@ QAction* PluginManager::filterAction(const QString& name) return filterPlugins.filterAction(name); } +FilterPlugin* PluginManager::getFilterPluginFromAction(const QAction *action) const +{ + return filterPlugins.pluginOfFilter(action); +} + IOPlugin* PluginManager::inputMeshPlugin(const QString& inputFormat) const { return ioPlugins.inputMeshPlugin(inputFormat); diff --git a/src/common/plugins/plugin_manager.h b/src/common/plugins/plugin_manager.h index ed44098b3..1bb1769c5 100644 --- a/src/common/plugins/plugin_manager.h +++ b/src/common/plugins/plugin_manager.h @@ -62,6 +62,7 @@ public: DecoratePlugin* getDecoratePlugin(const QString& name); QAction* filterAction(const QString& name); + FilterPlugin* getFilterPluginFromAction(const QAction* action) const; IOPlugin* inputMeshPlugin(const QString& inputFormat) const; IOPlugin* outputMeshPlugin(const QString& outputFormat) const; diff --git a/src/common/searcher.cpp b/src/common/searcher.cpp deleted file mode 100644 index 37754dd4c..000000000 --- a/src/common/searcher.cpp +++ /dev/null @@ -1,151 +0,0 @@ -#include "searcher.h" -#include "mlexception.h" - -#include - -WordActionsMap::WordActionsMap() -:wordacts() -{ - -} - -void WordActionsMap::addWordsPerAction(QAction& act,const QStringList& words) -{ - foreach(QString word,words) - wordacts[word].push_back(&act); -} - -void WordActionsMap::removeActionReferences(QAction& act ) -{ - for(QMap >::iterator it = wordacts.begin();it != wordacts.end();++it) - it.value().removeAll(&act); -} - -bool WordActionsMap::getActionsPerWord( const QString& word,QList& res ) const -{ - QMap< QString,QList >::const_iterator it = wordacts.find(word); - if (it != wordacts.end()) - { - res = it.value(); - return true; - } - return false; -} - -void WordActionsMap::clear() -{ - wordacts.clear(); -} - -WordActionsMapAccessor::WordActionsMapAccessor() -:map(),sepexp(),ignexp() -{ - sepexp.setPattern("\\W+"); - ignexp.setPattern("\\b(an|the|of|it|as|in|by|and|or|for)\\b|\\b[a-z]\\b|'s\\b|\\.|<[^>]*>"); -} - -void WordActionsMapAccessor::addWordsPerAction(QAction& act,const QString& st ) -{ - QStringList wlist; - purifiedSplit(st,wlist); - addSubStrings(wlist); - map.addWordsPerAction(act,wlist); -} - -int WordActionsMapAccessor::rankedMatchesPerInputString( const QString& input,RankedMatches& rm ) const -{ - QStringList inputlist; - purifiedSplit(input,inputlist); - return rm.computeRankedMatches(inputlist,map); -} - -void WordActionsMapAccessor::purifiedSplit( const QString& input,QStringList& res ) const -{ - res.clear(); - QString tmp = input; - tmp = tmp.toLower(); - tmp.remove(ignexp); - res = tmp.split(sepexp,QString::SkipEmptyParts); - res.removeDuplicates(); -} - -void WordActionsMapAccessor::addSubStrings( QStringList& res ) const -{ - QStringList resWithPrefix; - foreach(QString str, res) - { - QString strPref = str; - resWithPrefix.push_back(strPref); - for(int i=0;i wordmatchesperaction; - ranking.clear(); - int inputstsize = inputst.size(); - ranking.resize(inputstsize); - float bonuspertitleword = 0.0f; - if (matchesontitlearemoreimportant) - bonuspertitleword = 1.0f / std::pow(10,inputstsize); - foreach(const QString& st,inputst) - { - QList res; - bool found = map.getActionsPerWord(st,res); - if (found) - { - foreach(QAction* act, res) - { - ++wordmatchesperaction[act]; - QString title = act->text().toLower().trimmed(); - if (title.contains(st)) - wordmatchesperaction[act] = wordmatchesperaction[act] + bonuspertitleword; - } - } - } - - QMap > rankedmatches; - for (QMap::iterator it = wordmatchesperaction.begin(); it != wordmatchesperaction.end(); ++it) - rankedmatches[it.value()].push_back(it.key()); - - int maxindex = -1; - for(QMap >::iterator it = rankedmatches.end() - 1;it != rankedmatches.begin() - 1;--it) - { - int index = std::floor(it.key()) - 1; - if (index >= ranking.size()) - { - throw InvalidInvariantException("WARNING! Index contained in wordmatchesperaction it's out-of-bound."); - return 0; - } - if (index > maxindex) - maxindex = index; - ranking[index].append(it.value()); - } - return maxindex + 1; -} - -void RankedMatches::getActionsWithNMatches( const int n,QList& res ) -{ - res.clear(); - int index = n -1; - if ((index >= ranking.size()) || (n < 1)) - { - throw InvalidInvariantException(QString("WARNING! Parameter n MUST be in the range [1..") + QString::number(ranking.size()) + "]."); - return; - } - res = ranking[index]; -} - - diff --git a/src/common/searcher.h b/src/common/searcher.h deleted file mode 100644 index ce01d4d2a..000000000 --- a/src/common/searcher.h +++ /dev/null @@ -1,58 +0,0 @@ -#ifndef SEARCHER_H -#define SEARCHER_H - -#include -#include -#include -#include -#include -#include -#include - -class WordActionsMap -{ -public: - WordActionsMap(); - void addWordsPerAction(QAction& act,const QStringList& words); - void removeActionReferences(QAction& act); - bool getActionsPerWord( const QString& word,QList& res ) const; - void clear(); -private: - QMap > wordacts; -}; - -class RankedMatches; - -class WordActionsMapAccessor -{ -public: - WordActionsMapAccessor(); - void addWordsPerAction(QAction& act,const QString& st); - inline void removeActionReferences(QAction& act) {map.removeActionReferences(act);} - inline void setSeparator(const QRegExp& sep) {sepexp = sep;} - inline void setIgnoredWords(const QRegExp& ign) {ignexp = ign;} - int rankedMatchesPerInputString(const QString& input,RankedMatches& rm) const; - inline QRegExp separtor() const {return sepexp;} - inline QRegExp ignored() const {return ignexp;} - void clear() {map.clear(); sepexp = QRegExp(); ignexp = QRegExp();} - -private: - void purifiedSplit(const QString& input,QStringList& res) const; - void addSubStrings(QStringList& res) const; - WordActionsMap map; - QRegExp sepexp; - QRegExp ignexp; -}; - -class RankedMatches -{ -public: - RankedMatches(); - void getActionsWithNMatches(const int n,QList& res); -private: - friend int WordActionsMapAccessor::rankedMatchesPerInputString(const QString& input,RankedMatches& rm) const; - int computeRankedMatches(const QStringList& inputst,const WordActionsMap& map,bool matchesontitlearemoreimportant = true); - QVector > ranking; -}; - -#endif diff --git a/src/common/utilities/eigen_mesh_conversions.h b/src/common/utilities/eigen_mesh_conversions.h index 7bf55913e..4ad5a0e06 100644 --- a/src/common/utilities/eigen_mesh_conversions.h +++ b/src/common/utilities/eigen_mesh_conversions.h @@ -31,9 +31,12 @@ typedef Eigen::Matrix EigenVectorXm; typedef Eigen::Matrix EigenVectorXb; typedef Eigen::Matrix EigenVectorXui; -typedef Eigen::Matrix EigenMatrixX2m; -typedef Eigen::Matrix EigenMatrixX3m; -typedef Eigen::Matrix EigenMatrixX4m; + +typedef Eigen::Matrix EigenMatrixX2m; +typedef Eigen::Matrix EigenMatrixX3m; +typedef Eigen::Matrix EigenMatrixX4m; + +typedef Eigen::Matrix EigenMatrixXm; namespace meshlab { diff --git a/src/meshlab/additionalgui.cpp b/src/meshlab/additionalgui.cpp index 64550695f..ed52e6557 100644 --- a/src/meshlab/additionalgui.cpp +++ b/src/meshlab/additionalgui.cpp @@ -8,7 +8,7 @@ #include #include -SearchMenu::SearchMenu(const WordActionsMapAccessor& wm,const int max,QWidget* parent,const int fixedwidth) +SearchMenu::SearchMenu(const ActionSearcher& wm,const int max,QWidget* parent,const int fixedwidth) :MenuWithToolTip(QString(),parent),searchline(NULL),wama(wm),maxres(max),fixedwidthsize(fixedwidth) { searchline = new MenuLineEdit(this); @@ -24,29 +24,14 @@ SearchMenu::SearchMenu(const WordActionsMapAccessor& wm,const int max,QWidget* p void SearchMenu::getResults(const QString& text,QList& result) { - try - { - RankedMatches rm; - int ii = wama.rankedMatchesPerInputString(text,rm); - int inserted = 0; - while(ii > 0) - { - QList myacts; - rm.getActionsWithNMatches(ii,myacts); - if (inserted + myacts.size() > maxres) - myacts = myacts.mid(0,myacts.size() - (inserted + myacts.size() - maxres)); - result.append(myacts); - QAction* sep = new QAction(this); - sep->setSeparator(true); - result.append(sep); - inserted += myacts.size(); - --ii; - } - } - catch(InvalidInvariantException& e) - { - qDebug() << "WARNING!!!!!!!!!!!!!!!!!!!" << e.what() << "\n"; - } + std::vector rm = wama.bestMatchingActions(text, 15); + + for (QAction* act : rm) { + result.append(act); + } + QAction* sep = new QAction(this); + sep->setSeparator(true); + result.append(sep); } void SearchMenu::updateGUI( const QList& results ) diff --git a/src/meshlab/additionalgui.h b/src/meshlab/additionalgui.h index bf5667dc5..c076803de 100644 --- a/src/meshlab/additionalgui.h +++ b/src/meshlab/additionalgui.h @@ -20,7 +20,7 @@ #include #include #include -#include "../common/searcher.h" +#include "../common/plugins/action_searcher.h" #include #include #include @@ -75,7 +75,7 @@ class SearchMenu : public MenuWithToolTip { Q_OBJECT public: - SearchMenu(const WordActionsMapAccessor& wm,const int max,QWidget* parent,const int fixedwidth = -1); + SearchMenu(const ActionSearcher& wm,const int max,QWidget* parent,const int fixedwidth = -1); int& searchLineWidth(); void clearResults(); QSize sizeHint () const; @@ -84,7 +84,7 @@ protected: void resizeEvent ( QResizeEvent * event); private: MenuLineEdit* searchline; - const WordActionsMapAccessor& wama; + const ActionSearcher& wama; int maxres; int fixedwidthsize; diff --git a/src/meshlab/dialogs/plugin_info_dialog.cpp b/src/meshlab/dialogs/plugin_info_dialog.cpp index 6859183f0..e17c565a8 100644 --- a/src/meshlab/dialogs/plugin_info_dialog.cpp +++ b/src/meshlab/dialogs/plugin_info_dialog.cpp @@ -119,7 +119,7 @@ void PluginInfoDialog::on_loadPluginsPushButton_clicked() QStringList fileList = QFileDialog::getOpenFileNames(this, "Load Plugins", "", pluginFileFormat); PluginManager& pm = meshlab::pluginManagerInstance(); bool loadOk = false; - for (const QString& fileName : fileList){ + for (const QString& fileName : qAsConst(fileList)){ QFileInfo finfo(fileName); try { diff --git a/src/meshlab/mainwindow.h b/src/meshlab/mainwindow.h index 3f06f612e..582d9ad38 100644 --- a/src/meshlab/mainwindow.h +++ b/src/meshlab/mainwindow.h @@ -256,8 +256,6 @@ private: void createActions(); void createMenus(); void initSearchEngine(); - void initItemForSearching(QAction* act); - void initMenuForSearching(QMenu* menu); void fillFilterMenu(); void fillRenderMenu(); void fillShadersMenu(); @@ -361,7 +359,7 @@ public: QMenu* rasterLayerMenu() { return filterMenuRasterLayer; } private: - WordActionsMapAccessor wama; + ActionSearcher& searcher; //////// ToolBars /////////////// QToolBar* mainToolBar; QToolBar* decoratorToolBar; diff --git a/src/meshlab/mainwindow_Init.cpp b/src/meshlab/mainwindow_Init.cpp index 56bd70b14..eb4a63a24 100644 --- a/src/meshlab/mainwindow_Init.cpp +++ b/src/meshlab/mainwindow_Init.cpp @@ -36,7 +36,6 @@ #include #include #include "mainwindow.h" -#include #include #include #include @@ -47,13 +46,14 @@ QProgressBar *MainWindow::qb; -MainWindow::MainWindow(): - httpReq(this), - gpumeminfo(NULL), - defaultGlobalParams(meshlab::defaultGlobalParameterList()), - lastUsedDirectory(QDir::home()), - PM(meshlab::pluginManagerInstance()), - _currviewcontainer(NULL) +MainWindow::MainWindow() : + searcher(meshlab::actionSearcherInstance()), + httpReq(this), + gpumeminfo(NULL), + defaultGlobalParams(meshlab::defaultGlobalParameterList()), + lastUsedDirectory(QDir::home()), + PM(meshlab::pluginManagerInstance()), + _currviewcontainer(NULL) { QSettings settings; //toDelete plugins, flagged in the last session @@ -651,7 +651,7 @@ void MainWindow::createMenus() { initSearchEngine(); int longest = longestActionWidthInAllMenus(); - searchMenu = new SearchMenu(wama, 15, searchButton, longest); + searchMenu = new SearchMenu(searcher, 15, searchButton, longest); searchButton->setMenu(searchMenu); connect(searchShortCut, SIGNAL(activated()), searchButton, SLOT(openMenu())); } @@ -659,41 +659,23 @@ void MainWindow::createMenus() void MainWindow::initSearchEngine() { + searcher.clear(); for (const auto& p : PM.filterPluginIterator()){ for (QAction* act : p->actions()) - initItemForSearching(act); + searcher.addAction(act); } - /*for (const auto& p : PM.editPluginFactoryIterator()){ + for (const auto& p : PM.editPluginFactoryIterator()){ for (QAction* act : p->actions()) - initItemForSearching(act); + searcher.addAction(act); } for (const auto& p : PM.renderPluginIterator()){ for (QAction* act : p->actions()) - initItemForSearching(act); - }*/ - - initMenuForSearching(editMenu); - initMenuForSearching(renderMenu); -} - -void MainWindow::initMenuForSearching(QMenu* menu) -{ - if (menu == NULL) - return; - const QList& acts = menu->actions(); - for(QAction* act: acts) { - QMenu* submenu = act->menu(); - if (!act->isSeparator() && (submenu == NULL)) - initItemForSearching(act); - else if (!act->isSeparator()) - initMenuForSearching(submenu); + searcher.addAction(act); + } + for (const auto& p : PM.decoratePluginIterator()){ + for (QAction* act : p->actions()) + searcher.addAction(act); } -} - -void MainWindow::initItemForSearching(QAction* act) -{ - QString tx = act->text() + " " + act->toolTip(); - wama.addWordsPerAction(*act, tx); } QString MainWindow::getDecoratedFileName(const QString& name) @@ -930,20 +912,6 @@ void MainWindow::updateAllPluginsActions() filterToolBar->clear(); updateFilterToolBar(); - - //TODO update the searcher: this seems to be an impossible task due to unreadable code. - //for now, just close and reopen meshlab.... - /* - disconnect(searchShortCut, SIGNAL(activated()), searchButton, SLOT(openMenu())); - wama.clear(); - delete searchMenu; - - initSearchEngine(); - int longest = longestActionWidthInAllMenus(); - searchMenu = new SearchMenu(wama, 15, searchButton, longest); - searchButton->setMenu(searchMenu); - connect(searchShortCut, SIGNAL(activated()), searchButton, SLOT(openMenu())); - */ } void MainWindow::loadDefaultSettingsFromPlugins() diff --git a/src/meshlab/mainwindow_RunTime.cpp b/src/meshlab/mainwindow_RunTime.cpp index 69e7e87c9..658ed4e7d 100644 --- a/src/meshlab/mainwindow_RunTime.cpp +++ b/src/meshlab/mainwindow_RunTime.cpp @@ -2576,6 +2576,7 @@ void MainWindow::aboutPlugins() PluginInfoDialog dialog(this); dialog.exec(); updateAllPluginsActions(); + initSearchEngine(); QSettings settings; QStringList disabledPlugins; for (MeshLabPlugin* pf : PM.pluginIterator(true)){ diff --git a/src/meshlabplugins/filter_parametrization/CMakeLists.txt b/src/meshlabplugins/filter_parametrization/CMakeLists.txt new file mode 100644 index 000000000..0e61e4b58 --- /dev/null +++ b/src/meshlabplugins/filter_parametrization/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright 2019, 2020, Visual Computing Lab, ISTI - Italian National Research Council + +if (TARGET external-libigl AND TARGET Threads::Threads) + + set(SOURCES filter_parametrization.cpp) + + set(HEADERS filter_parametrization.h) + + add_meshlab_plugin(filter_parametrization ${SOURCES} ${HEADERS}) + + target_link_libraries(filter_parametrization PRIVATE external-libigl Threads::Threads) +else() + message( + STATUS "Skipping filter_parametrization - don't know about libigl or Threads::Threads on this system.") +endif() diff --git a/src/meshlabplugins/filter_parametrization/filter_parametrization.cpp b/src/meshlabplugins/filter_parametrization/filter_parametrization.cpp new file mode 100644 index 000000000..51eaffcda --- /dev/null +++ b/src/meshlabplugins/filter_parametrization/filter_parametrization.cpp @@ -0,0 +1,219 @@ +/**************************************************************************** +* MeshLab o o * +* A versatile mesh processing toolbox o o * +* _ O _ * +* Copyright(C) 2005-2021 \/)\/ * +* Visual Computing Lab /\/| * +* ISTI - Italian National Research Council | * +* \ * +* All rights reserved. * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License (http://www.gnu.org/licenses/gpl.txt) * +* for more details. * +* * +****************************************************************************/ + +#include "filter_parametrization.h" + +#include + +#include +#include +#include +#include + +FilterParametrizationPlugin::FilterParametrizationPlugin() +{ + typeList = { FP_HARMONIC_PARAM, FP_LEAST_SQUARES_PARAM}; + + for(const ActionIDType& tt : typeList) + actionList.push_back(new QAction(filterName(tt), this)); +} + +QString FilterParametrizationPlugin::pluginName() const +{ + return "FilterParametrization"; +} + +QString FilterParametrizationPlugin::vendor() const +{ + return "CNR-ISTI-VCLab"; +} + +QString FilterParametrizationPlugin::filterName(ActionIDType filterId) const +{ + switch(filterId) { + case FP_HARMONIC_PARAM : + return "Harmonic Parametrization"; + case FP_LEAST_SQUARES_PARAM: + return "Least Squares Conformal Maps Parametrization"; + default : + assert(0); + return ""; + } +} + +QString FilterParametrizationPlugin::pythonFilterName(ActionIDType filter) const +{ + switch(filter) { + case FP_HARMONIC_PARAM : + return "compute_texcoord_parametrization_harmonic"; + case FP_LEAST_SQUARES_PARAM: + return "compute_texcoord_parametrization_least_squares_conformal_maps"; + default : + assert(0); + return ""; + } +} + +QString FilterParametrizationPlugin::filterInfo(ActionIDType filterId) const +{ + QString commonDescription = + "The resulting parametrization is saved in the per vertex texture coordinates.
" + "The filter uses the original code provided in the " + "libigl library.
"; + switch(filterId) { + case FP_HARMONIC_PARAM : + return "Computes a single patch, fixed boundary harmonic parametrization of a mesh. The " + "filter requires that the input mesh has a single fixed boundary." + + commonDescription; + case FP_LEAST_SQUARES_PARAM: + return "Compuites a least squares conformal maps parametrization of a mesh. " + + commonDescription; + default : + assert(0); + return "Unknown Filter"; + } +} + +FilterParametrizationPlugin::FilterClass FilterParametrizationPlugin::getClass(const QAction *a) const +{ + switch(ID(a)) { + case FP_HARMONIC_PARAM : + case FP_LEAST_SQUARES_PARAM: + return FilterPlugin::Texture; + default : + assert(0); + return FilterPlugin::Generic; + } +} + +FilterPlugin::FilterArity FilterParametrizationPlugin::filterArity(const QAction*) const +{ + return SINGLE_MESH; +} + +int FilterParametrizationPlugin::getPreConditions(const QAction*) const +{ + return MeshModel::MM_VERTCOORD | MeshModel::MM_FACEVERT; +} + +int FilterParametrizationPlugin::getRequirements(const QAction*) +{ + return MeshModel::MM_VERTTEXCOORD; +} + +int FilterParametrizationPlugin::postCondition(const QAction*) const +{ + return MeshModel::MM_VERTTEXCOORD; +} + +RichParameterList FilterParametrizationPlugin::initParameterList(const QAction *action, const MeshModel &) +{ + RichParameterList parlst; + switch(ID(action)) { + case FP_HARMONIC_PARAM : + parlst.addParam(RichInt("harm_function", 1,"N-Harmonic Function", "1 denotes harmonic function, 2 biharmonic, 3 triharmonic, etc.")); + break; + case FP_LEAST_SQUARES_PARAM: + + break; + default : + assert(0); + } + return parlst; +} + +std::map FilterParametrizationPlugin::applyFilter( + const QAction * action, + const RichParameterList & par, + MeshDocument &md, + unsigned int& /*postConditionMask*/, + vcg::CallBackPos *) +{ + switch(ID(action)) { + case FP_HARMONIC_PARAM : { + int f = par.getInt("harm_function"); + if (f < 1) + throw MLException("Invalid N-Harmonic Function value. Must be >= 1."); + + EigenMatrixX3m v = meshlab::vertexMatrix(md.mm()->cm); + Eigen::MatrixX3d verts = v.cast(); + Eigen::MatrixX3i faces = meshlab::faceMatrix(md.mm()->cm); + + Eigen::MatrixXd V_uv, bnd_uv; + Eigen::VectorXi bnd; + + igl::boundary_loop(faces,bnd); + if (bnd.size() == 0) + throw MLException( + "Harmonic Parametrization can be applied only on meshes that have a boundary."); + igl::map_vertices_to_circle(verts, bnd, bnd_uv); + igl::harmonic(verts,faces,bnd,bnd_uv,1,V_uv); + + unsigned int i = 0; + for (auto& v : md.mm()->cm.vert){ + v.T().u() =V_uv(i, 0); + v.T().v() =V_uv(i, 1); + i++; + } + + break; + } + case FP_LEAST_SQUARES_PARAM: { + EigenMatrixX3m v = meshlab::vertexMatrix(md.mm()->cm); + Eigen::MatrixX3d verts = v.cast(); + Eigen::MatrixX3i faces = meshlab::faceMatrix(md.mm()->cm); + Eigen::VectorXi bnd, boundaryPoints(2, 1); + + Eigen::MatrixXd V_uv; + + igl::boundary_loop(faces,bnd); + if (bnd.size() == 0) + throw MLException( + "Least Squares Conformal Maps Parametrization can be applied only on meshes that " + "have a boundary."); + + boundaryPoints(0) = bnd(0); + boundaryPoints(1) = bnd(bnd.size()/2); + + Eigen::MatrixXd bc(2,2); + bc<<0,0,1,0; + + // LSCM parametrization + igl::lscm(verts,faces,boundaryPoints,bc,V_uv); + + unsigned int i = 0; + for (auto& v : md.mm()->cm.vert){ + v.T().u() =V_uv(i, 0); + v.T().v() =V_uv(i, 1); + i++; + } + + break; + } + default : + wrongActionCalled(action); + } + return std::map(); +} + +MESHLAB_PLUGIN_NAME_EXPORTER(FilterSamplePlugin) diff --git a/src/meshlabplugins/filter_parametrization/filter_parametrization.h b/src/meshlabplugins/filter_parametrization/filter_parametrization.h new file mode 100644 index 000000000..6d1c69150 --- /dev/null +++ b/src/meshlabplugins/filter_parametrization/filter_parametrization.h @@ -0,0 +1,66 @@ +/**************************************************************************** +* MeshLab o o * +* A versatile mesh processing toolbox o o * +* _ O _ * +* Copyright(C) 2005-2021 \/)\/ * +* Visual Computing Lab /\/| * +* ISTI - Italian National Research Council | * +* \ * +* All rights reserved. * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License (http://www.gnu.org/licenses/gpl.txt) * +* for more details. * +* * +****************************************************************************/ + +#ifndef MESHLAB_FILTER_PARAMETRIZATION_PLUGIN_H +#define MESHLAB_FILTER_PARAMETRIZATION_PLUGIN_H + +#include + +class FilterParametrizationPlugin : public QObject, public FilterPlugin +{ + //keep these three lines unchanged + Q_OBJECT + MESHLAB_PLUGIN_IID_EXPORTER(FILTER_PLUGIN_IID) + Q_INTERFACES(FilterPlugin) + +public: + //enum used to give an ID to every filter implemented in the plugin + enum FileterIds { + FP_HARMONIC_PARAM, + FP_LEAST_SQUARES_PARAM}; + + FilterParametrizationPlugin(); + + QString pluginName() const; + QString vendor() const; + + QString filterName(ActionIDType filter) const; + QString pythonFilterName(ActionIDType filter) const; + QString filterInfo(ActionIDType filter) const; + FilterClass getClass(const QAction* a) const; + FilterArity filterArity(const QAction*) const; + int getPreConditions(const QAction *) const; + int getRequirements(const QAction *); + int postCondition(const QAction* ) const; + RichParameterList initParameterList(const QAction*, const MeshModel &/*m*/); + std::map applyFilter( + const QAction* action, + const RichParameterList & params, + MeshDocument &md, + unsigned int& postConditionMask, + vcg::CallBackPos * cb); + +private: +}; + +#endif //MESHLAB_FILTER_PARAMETRIZATION_PLUGIN_H diff --git a/src/meshlabplugins/filter_sample/filter_sample.cpp b/src/meshlabplugins/filter_sample/filter_sample.cpp index f875b5f56..c658256f1 100644 --- a/src/meshlabplugins/filter_sample/filter_sample.cpp +++ b/src/meshlabplugins/filter_sample/filter_sample.cpp @@ -162,6 +162,7 @@ RichParameterList FilterSamplePlugin::initParameterList(const QAction *action,co case FP_MOVE_VERTEX : parlst.addParam(RichBool ("UpdateNormals", true, "Recompute normals", "Toggle the recomputation of the normals after the random displacement.\n\nIf disabled the face normals will remains unchanged resulting in a visually pleasant effect.")); parlst.addParam(RichAbsPerc("Displacement", m.cm.bbox.Diag()/100.0f,0.0f,m.cm.bbox.Diag(), "Max displacement", "The vertex are displaced of a vector whose norm is bounded by this value")); + parlst.addParam(RichInt("RandomSeed", 0, "Random Seed", "The seed used to generate random values. If seed is zero no random seed is used")); break; default : assert(0); @@ -181,7 +182,7 @@ std::map FilterSamplePlugin::applyFilter(const QAction * { switch(ID(action)) { case FP_MOVE_VERTEX : - vertexDisplacement(md, cb, parameters.getBool("UpdateNormals"), parameters.getAbsPerc("Displacement")); + vertexDisplacement(md, cb, parameters.getInt("RandomSeed"), parameters.getBool("UpdateNormals"), parameters.getAbsPerc("Displacement")); break; default : wrongActionCalled(action); @@ -190,23 +191,25 @@ std::map FilterSamplePlugin::applyFilter(const QAction * } bool FilterSamplePlugin::vertexDisplacement( - MeshDocument &md, - vcg::CallBackPos *cb, - bool updateNormals, - Scalarm max_displacement) + MeshDocument &md, + vcg::CallBackPos *cb, + int randomSeed, + bool updateNormals, + Scalarm max_displacement) { CMeshO &m = md.mm()->cm; - srand(time(NULL)); + if(randomSeed==0) srand(time(NULL)); + else srand(randomSeed); for(unsigned int i = 0; i< m.vert.size(); i++){ // Typical usage of the callback for showing a nice progress bar in the bottom. // First parameter is a 0..100 number indicating percentage of completion, the second is an info string. cb(100*i/m.vert.size(), "Randomly Displacing..."); - - Scalarm rndax = (Scalarm(2.0*rand())/RAND_MAX - 1.0 ) *max_displacement; - Scalarm rnday = (Scalarm(2.0*rand())/RAND_MAX - 1.0 ) *max_displacement; - Scalarm rndaz = (Scalarm(2.0*rand())/RAND_MAX - 1.0 ) *max_displacement; - m.vert[i].P() += Point3m(rndax,rnday,rndaz); + + Scalarm rndax = (Scalarm(2.0*rand())/float(RAND_MAX) - 1.0 ) *max_displacement; + Scalarm rnday = (Scalarm(2.0*rand())/float(RAND_MAX) - 1.0 ) *max_displacement; + Scalarm rndaz = (Scalarm(2.0*rand())/float(RAND_MAX) - 1.0 ) *max_displacement; + m.vert[i].P() += Point3m(rndax,rnday,rndaz); } // Log function dump textual info in the lower part of the MeshLab screen. diff --git a/src/meshlabplugins/filter_sample/filter_sample.h b/src/meshlabplugins/filter_sample/filter_sample.h index 408c1ed30..4e8c145b5 100644 --- a/src/meshlabplugins/filter_sample/filter_sample.h +++ b/src/meshlabplugins/filter_sample/filter_sample.h @@ -74,6 +74,7 @@ private: bool vertexDisplacement( MeshDocument &md, vcg::CallBackPos *cb, + int randomSeed, bool updateNormals, Scalarm max_displacement); };