Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 37 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,47 @@
# ![MeshLab Logo](src/meshlab/images/eye64.png) MeshLab
# ![MeshLab Logo](src/meshlab/images/eye64.png) MeshLab by Dynart

*A custom fork of [MeshLab](https://www.MeshLab.net) with enhanced selection workflow and usability improvements.*

[![BuildMeshLab](https://github.com/cnr-isti-vclab/meshlab/actions/workflows/BuildMeshLab.yml/badge.svg)](https://github.com/cnr-isti-vclab/meshlab/actions/workflows/BuildMeshLab.yml)
---

[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.5114037.svg)](https://doi.org/10.5281/zenodo.5114037)
Based on MeshLab — an open source, portable, and extensible system for the processing and editing of unstructured large 3D triangular meshes. Built on top of the [VCGlib](http://www.vcglib.net) C++ mesh processing library developed at the [Visual Computing Lab](http://vcg.isti.cnr.it) of [ISTI - CNR](http://www.isti.cnr.it).

## New Features

### Inverted CTRL Selection Behavior
The default MeshLab selection workflow requires holding CTRL to **add** to an existing selection, while a bare drag **replaces** the selection. This fork inverts that behavior:
- **Drag** now **adds** to the current selection by default
- **CTRL + Drag** starts a **new** selection (clearing the previous one)
- This is configurable via a global parameter (`Invert CTRL Behavior`) accessible in MeshLab settings, so you can switch back to the original behavior at any time

This change applies to all rectangle-based selection tools: **Select Faces**, **Select Vertices**, **Select Connected Components**, and **Select Faces/Vertices inside polyline area**.

### Global Parameter System for Edit Plugins
Edit plugins now support persistent global parameters through the MeshLab settings system. This allows edit tools to expose user-configurable options that are saved across sessions. The `EditPlugin` base class provides `initGlobalParameterList` and `setCurrentGlobalParamSet` for any plugin to register its own settings.

### Lasso Cut Tool (`edit_cut`)
A new editing tool that draws a closed polyline to cut through mesh triangles and select the inside region:
- **Click** to add polyline points
- **Q** — cut edges along the polyline and add inside faces to selection
- **W** — subtract inside faces from selection
- **D** / **A** / **I** — deselect all / select all / invert selection
- **Delete** — delete selected faces (uses MeshLab's standard filter with full undo/redo support)
- **C** — clear polyline, **Backspace** — undo last point, **Escape** — clear polyline

This is the official repository for the source and the binaries of [MeshLab](https://www.MeshLab.net).
The tool appears in its own **Dynart Tools** toolbar section, separate from the standard Edit toolbar.

MeshLab is an open source, portable, and extensible system for the processing and editing of unstructured large 3D triangular meshes. It is aimed to help the processing of the typical not-so-small unstructured models arising in 3D scanning, providing a set of tools for editing, cleaning, healing, inspecting, rendering and converting this kind of meshes.
### Global Undo/Redo (Ctrl+Z / Ctrl+Y)
MeshLab has no built-in undo system. This fork adds a full-mesh snapshot undo/redo stack that works across **all filters and edit tools**:
- **Ctrl+Z** — undo the last mesh-modifying operation
- **Ctrl+Y** — redo the last undone operation
- Available in the **Edit** menu as Undo/Redo entries
- Supports up to **10 undo levels**
- Covers all filter operations (delete, remesh, smooth, clean, etc.) and the Lasso Cut tool

MeshLab is mostly based on the open source C++ mesh processing library [VCGlib](http://www.vcglib.net) developed at the [Visual Computing Lab](http://vcg.isti.cnr.it) of [ISTI - CNR](http://www.isti.cnr.it). VCG can be used as a stand-alone large-scale automated mesh processing pipeline, while MeshLab makes it easy to experiment with its algorithms interactively.
### Reload Confirmation Dialogs
Added confirmation prompts before reloading meshes (`Reload` and `Reload All`) to prevent accidental loss of unsaved work.

MeshLab is available for Windows, macOS, and Linux.
---

# Releases

Expand Down
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ if(NOT DEFINED MESHLAB_PLUGINS) # it may be already defined in parent directory
meshlabplugins/edit_referencing
meshlabplugins/edit_quality
meshlabplugins/edit_select
meshlabplugins/edit_cut
)
endif()

Expand Down
1 change: 1 addition & 0 deletions src/common/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ set(SOURCES
plugins/containers/render_plugin_container.cpp
plugins/interfaces/meshlab_plugin_logger.cpp
plugins/interfaces/decorate_plugin.cpp
plugins/interfaces/edit_plugin.cpp
plugins/interfaces/filter_plugin.cpp
plugins/interfaces/io_plugin.cpp
plugins/action_searcher.cpp
Expand Down
5 changes: 5 additions & 0 deletions src/common/plugins/interfaces/edit_plugin.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#include "edit_plugin.h"

void EditPlugin::initGlobalParameterList(RichParameterList& globalparam)
{
}
8 changes: 8 additions & 0 deletions src/common/plugins/interfaces/edit_plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ class EditPlugin : public MeshLabPlugin
EditPlugin() {}
virtual ~EditPlugin() {}

virtual void initGlobalParameterList(RichParameterList& defaultGlobalParamSet);

//gets a list of actions available from this plugin
virtual std::list<QAction *> actions() const {return actionList;};

Expand All @@ -117,8 +119,14 @@ class EditPlugin : public MeshLabPlugin
//get the description for the given action
virtual QString getEditToolDescription(const QAction *) = 0;

void setCurrentGlobalParamSet(RichParameterList* cgp)
{
currentGlobalParamSet = cgp;
}

protected:
std::list<QAction*> actionList;
RichParameterList* currentGlobalParamSet;
};

#define EDIT_PLUGIN_IID "vcg.meshlab.EditPlugin/1.0"
Expand Down
16 changes: 16 additions & 0 deletions src/meshlab/mainwindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#include "dialogs/filter_dock_dialog.h"
#include "multiViewer_Container.h"
#include "ml_render_gui.h"
#include "mesh_undo_stack.h"

#include <QDir>
#include <QMainWindow>
Expand Down Expand Up @@ -113,6 +114,7 @@ class MainWindow : public QMainWindow
static bool QCallBack(const int pos, const char * str);
//const QString appName() const {return tr("MeshLab v")+appVer(); }
//const QString appVer() const {return tr("1.3.2"); }
RichParameterList& getCurrentParameterList();
MainWindowSetting mwsettings;
public slots:
// callback function to execute a filter
Expand All @@ -125,6 +127,11 @@ public slots:
protected:
void showEvent(QShowEvent *event);

public slots:
void meshUndo();
void meshRedo();
void pushUndoForCurrentMesh(const QString& desc);

private slots:
void newProject(const QString& projName = QString());
void saveProject();
Expand Down Expand Up @@ -162,6 +169,7 @@ private slots:
void updateProgressBar(const int pos,const QString& text);
void updateTexture(int meshid);
public:
MeshUndoStack undoStack;

bool exportMesh(QString fileName,MeshModel* mod,const bool saveAllPossibleAttributes);

Expand Down Expand Up @@ -373,6 +381,7 @@ private slots:
QToolBar* decoratorToolBar;
QToolBar* editToolBar;
QToolBar* filterToolBar;
QToolBar* dynartToolBar;
QToolBar* searchToolBar;
MLRenderingGlobalToolbar* globrendtoolbar;
///////// Menus ///////////////
Expand Down Expand Up @@ -424,6 +433,10 @@ private slots:
MyToolButton* searchButton;
SearchMenu* searchMenu;

//////////// Actions Menu Edit ///////////////////////
QAction* undoAct;
QAction* redoAct;

//////////// Actions Menu File ///////////////////////
QAction* newProjectAct;
QAction* openProjectAct;
Expand Down Expand Up @@ -531,6 +544,9 @@ private slots:
static QString getDecoratedFileName(const QString& name);

MultiViewer_Container* _currviewcontainer;

Q_SIGNALS:
void customSettingsChanged(const RichParameterList &rpl);
};

/// Event filter that is installed to intercept the open events sent directly by the Operative System
Expand Down
32 changes: 31 additions & 1 deletion src/meshlab/mainwindow_Init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,16 @@ connectRenderModeActionList(rendlist);*/
connect(showRasterAct, SIGNAL(triggered()), this, SLOT(showRaster()));

//////////////Action Menu EDIT /////////////////////////////////////////////////////////////////////////
undoAct = new QAction(tr("Undo"), this);
undoAct->setShortcut(QKeySequence::Undo);
undoAct->setShortcutContext(Qt::ApplicationShortcut);
connect(undoAct, SIGNAL(triggered()), this, SLOT(meshUndo()));

redoAct = new QAction(tr("Redo"), this);
redoAct->setShortcut(QKeySequence::Redo);
redoAct->setShortcutContext(Qt::ApplicationShortcut);
connect(redoAct, SIGNAL(triggered()), this, SLOT(meshRedo()));

suspendEditModeAct = new QAction(QIcon(":/images/no_edit.png"), tr("Not editing"), this);
suspendEditModeAct->setShortcut(Qt::Key_Escape);
suspendEditModeAct->setCheckable(true);
Expand Down Expand Up @@ -520,6 +530,7 @@ void MainWindow::createToolBars()
editToolBar = addToolBar(tr("Edit"));
editToolBar->addAction(suspendEditModeAct);
for(EditPlugin *iEditFactory: PM.editPluginFactoryIterator()) {
if (iEditFactory->pluginName() == "EditCut") continue;
for(QAction* editAction: iEditFactory->actions()){
if (!editAction->icon().isNull()) {
editToolBar->addAction(editAction);
Expand All @@ -534,6 +545,16 @@ void MainWindow::createToolBars()

updateFilterToolBar();

dynartToolBar = addToolBar(tr("Dynart Tools"));
for(EditPlugin *iEditFactory: PM.editPluginFactoryIterator()) {
if (iEditFactory->pluginName() != "EditCut") continue;
for(QAction* editAction: iEditFactory->actions()){
if (!editAction->icon().isNull()) {
dynartToolBar->addAction(editAction);
}
}
}

QWidget *spacerWidget = new QWidget();
spacerWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
spacerWidget->setVisible(true);
Expand Down Expand Up @@ -858,6 +879,9 @@ void MainWindow::fillShadersMenu()
void MainWindow::fillEditMenu()
{
clearMenu(editMenu);
editMenu->addAction(undoAct);
editMenu->addAction(redoAct);
editMenu->addSeparator();
editMenu->addAction(suspendEditModeAct);
for(EditPlugin *iEditFactory: PM.editPluginFactoryIterator())
{
Expand All @@ -874,7 +898,7 @@ void MainWindow::clearMenu(QMenu* menu)
for (QAction *action : menu->actions()) {
if (action->menu()) {
clearMenu(action->menu());
} else if (!action->isSeparator() && !(action==suspendEditModeAct)){
} else if (!action->isSeparator() && action != suspendEditModeAct && action != undoAct && action != redoAct){
disconnect(action, SIGNAL(triggered()), 0, 0);
}
}
Expand Down Expand Up @@ -922,6 +946,12 @@ void MainWindow::loadDefaultSettingsFromPlugins()
}
}

//edit settings
for (EditPlugin* ep : PM.editPluginFactoryIterator()) {
ep->initGlobalParameterList(defaultGlobalParams);
ep->setCurrentGlobalParamSet(&currentGlobalParams);
}

//io settings
for (IOPlugin* iop : PM.ioPluginIterator()){
for (const FileFormat& ff : iop->importFormats()) {
Expand Down
106 changes: 106 additions & 0 deletions src/meshlab/mainwindow_RunTime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ void MainWindow::updateCustomSettings()
{
mwsettings.updateGlobalParameterList(currentGlobalParams);
emit dispatchCustomSettings(currentGlobalParams);

}
RichParameterList& MainWindow::getCurrentParameterList()
{
return currentGlobalParams;
}

void MainWindow::updateWindowMenu()
Expand Down Expand Up @@ -787,6 +792,88 @@ void MainWindow::showTooltip(QAction* q)
QToolTip::showText(QCursor::pos(), tip);
}

// /////////////////////////////////////////////////
// Undo / Redo
// /////////////////////////////////////////////////

void MainWindow::pushUndoForCurrentMesh(const QString& desc)
{
if (meshDoc() != nullptr && meshDoc()->mm() != nullptr)
undoStack.pushUndo(*meshDoc()->mm(), desc);
}

void MainWindow::meshUndo()
{
if (!undoStack.canUndo()) return;
if (meshDoc() == nullptr || meshDoc()->mm() == nullptr) return;

int restoredId = undoStack.undo(*meshDoc());
if (restoredId < 0) return;

MeshModel* mm = meshDoc()->getMesh(restoredId);
if (mm == nullptr) return;

vcg::tri::Allocator<CMeshO>::CompactEveryVector(mm->cm);

MultiViewer_Container* mvc = currentViewContainer();
if (mvc != nullptr) {
MLSceneGLSharedDataContext* shared = mvc->sharedDataContext();
if (shared != nullptr) {
MLRenderingData::RendAtts atts;
atts[MLRenderingData::ATT_NAMES::ATT_VERTPOSITION] = true;
atts[MLRenderingData::ATT_NAMES::ATT_VERTNORMAL] = true;
atts[MLRenderingData::ATT_NAMES::ATT_FACENORMAL] = true;
atts[MLRenderingData::ATT_NAMES::ATT_VERTCOLOR] = true;
atts[MLRenderingData::ATT_NAMES::ATT_FACECOLOR] = true;
shared->meshAttributesUpdated(restoredId, true, atts);
shared->manageBuffers(restoredId);
}
}

if (GLA()) {
GLA()->updateAllSiblingsGLAreas();
GLA()->update();
}
updateLayerDialog();
MainWindow::globalStatusBar()->showMessage("Undo", 2000);
}

void MainWindow::meshRedo()
{
if (!undoStack.canRedo()) return;
if (meshDoc() == nullptr || meshDoc()->mm() == nullptr) return;

int restoredId = undoStack.redo(*meshDoc());
if (restoredId < 0) return;

MeshModel* mm = meshDoc()->getMesh(restoredId);
if (mm == nullptr) return;

vcg::tri::Allocator<CMeshO>::CompactEveryVector(mm->cm);

MultiViewer_Container* mvc = currentViewContainer();
if (mvc != nullptr) {
MLSceneGLSharedDataContext* shared = mvc->sharedDataContext();
if (shared != nullptr) {
MLRenderingData::RendAtts atts;
atts[MLRenderingData::ATT_NAMES::ATT_VERTPOSITION] = true;
atts[MLRenderingData::ATT_NAMES::ATT_VERTNORMAL] = true;
atts[MLRenderingData::ATT_NAMES::ATT_FACENORMAL] = true;
atts[MLRenderingData::ATT_NAMES::ATT_VERTCOLOR] = true;
atts[MLRenderingData::ATT_NAMES::ATT_FACECOLOR] = true;
shared->meshAttributesUpdated(restoredId, true, atts);
shared->manageBuffers(restoredId);
}
}

if (GLA()) {
GLA()->updateAllSiblingsGLAreas();
GLA()->update();
}
updateLayerDialog();
MainWindow::globalStatusBar()->showMessage("Redo", 2000);
}

// /////////////////////////////////////////////////
// The Very Important Procedure of applying a filter
// /////////////////////////////////////////////////
Expand Down Expand Up @@ -1080,6 +1167,8 @@ void MainWindow::executeFilter(
try {
meshDoc()->meshDocStateData().clear();
meshDoc()->meshDocStateData().create(*meshDoc());
if (!isPreview && meshDoc()->mm() != nullptr)
undoStack.pushUndo(*meshDoc()->mm(), action->text());
unsigned int postCondMask = MeshModel::MM_UNKNOWN;
iFilter->applyFilter(action, mergedenvironment, *(meshDoc()), postCondMask, QCallBack);
if (postCondMask == MeshModel::MM_UNKNOWN)
Expand Down Expand Up @@ -2196,6 +2285,15 @@ void MainWindow::reloadAllMesh()
{
// Discards changes and reloads current file
// save current file name
QMessageBox::StandardButton reply;
reply = QMessageBox::question(
this,
tr("You are reloading all mesh!"),
tr("Are You sure to Reload?"),
QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::No) {
return;
}
qb->show();
QElapsedTimer t;
t.start();
Expand Down Expand Up @@ -2244,6 +2342,14 @@ void MainWindow::reload()
return;
// Discards changes and reloads current file
// save current file name
QMessageBox::StandardButton reply;
reply = QMessageBox::question(this,
tr("You are reloading the current mesh"),
tr("Are you sure to reload?"),
QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::No) {
return;
}
qb->show();

QString fileName = meshDoc()->mm()->fullName();
Expand Down
Loading