From 4d8f463ba6fd7ecad9a39f1ba8dedc2d9165b60d Mon Sep 17 00:00:00 2001 From: Z3roCo0l <104153656+Z3roCo0l@users.noreply.github.com> Date: Thu, 12 Feb 2026 10:36:57 +0100 Subject: [PATCH 1/5] Add global parameter system for edit plugins, optional CTRL invert, reload confirmations - EditPlugin base class: initGlobalParameterList + setCurrentGlobalParamSet for persistent per-plugin settings - edit_select: opt-in "Invert CTRL Behavior" parameter (default OFF) - Reload/Reload All confirmation dialogs - Updated all edit plugin factory headers for new interface --- src/common/CMakeLists.txt | 1 + src/common/plugins/interfaces/edit_plugin.cpp | 5 + src/common/plugins/interfaces/edit_plugin.h | 8 + src/meshlab/mainwindow.h | 4 + src/meshlab/mainwindow_Init.cpp | 6 + src/meshlab/mainwindow_RunTime.cpp | 22 + .../edit_align/edit_align_factory.h | 5 + .../edit_manipulators_factory.h | 5 + .../edit_measure/edit_measure_factory.h | 5 + .../edit_mutualcorrs_factory.h | 5 + .../edit_paint/edit_paint_factory.h | 5 + .../edit_pickpoints/edit_pickpoints_factory.h | 5 + .../edit_point/edit_point_factory.h | 5 + .../edit_quality/edit_quality_factory.h | 5 + .../edit_referencing_factory.h | 5 + .../edit_sample/edit_sample_factory.h | 5 + .../edit_select/edit_select.cpp | 480 +++++++++++------- src/meshlabplugins/edit_select/edit_select.h | 24 +- .../edit_select/edit_select_factory.cpp | 22 +- .../edit_select/edit_select_factory.h | 9 + 20 files changed, 442 insertions(+), 189 deletions(-) create mode 100644 src/common/plugins/interfaces/edit_plugin.cpp diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 0e640fdd4d..7f002ac5b4 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -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 diff --git a/src/common/plugins/interfaces/edit_plugin.cpp b/src/common/plugins/interfaces/edit_plugin.cpp new file mode 100644 index 0000000000..59019a975c --- /dev/null +++ b/src/common/plugins/interfaces/edit_plugin.cpp @@ -0,0 +1,5 @@ +#include "edit_plugin.h" + +void EditPlugin::initGlobalParameterList(RichParameterList& globalparam) +{ +} \ No newline at end of file diff --git a/src/common/plugins/interfaces/edit_plugin.h b/src/common/plugins/interfaces/edit_plugin.h index af8d29f18e..f58cbf49f5 100644 --- a/src/common/plugins/interfaces/edit_plugin.h +++ b/src/common/plugins/interfaces/edit_plugin.h @@ -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 actions() const {return actionList;}; @@ -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 actionList; + RichParameterList* currentGlobalParamSet; }; #define EDIT_PLUGIN_IID "vcg.meshlab.EditPlugin/1.0" diff --git a/src/meshlab/mainwindow.h b/src/meshlab/mainwindow.h index 5656b2d29e..295bfdc105 100644 --- a/src/meshlab/mainwindow.h +++ b/src/meshlab/mainwindow.h @@ -113,6 +113,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 @@ -531,6 +532,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 diff --git a/src/meshlab/mainwindow_Init.cpp b/src/meshlab/mainwindow_Init.cpp index 014a48bfd6..e2fbe1ac7d 100644 --- a/src/meshlab/mainwindow_Init.cpp +++ b/src/meshlab/mainwindow_Init.cpp @@ -922,6 +922,12 @@ void MainWindow::loadDefaultSettingsFromPlugins() } } + //edit settings + for (EditPlugin* ep : PM.editPluginFactoryIterator()) { + ep->initGlobalParameterList(defaultGlobalParams); + ep->setCurrentGlobalParamSet(¤tGlobalParams); + } + //io settings for (IOPlugin* iop : PM.ioPluginIterator()){ for (const FileFormat& ff : iop->importFormats()) { diff --git a/src/meshlab/mainwindow_RunTime.cpp b/src/meshlab/mainwindow_RunTime.cpp index edd1bbcc81..2bf46132c4 100644 --- a/src/meshlab/mainwindow_RunTime.cpp +++ b/src/meshlab/mainwindow_RunTime.cpp @@ -104,6 +104,11 @@ void MainWindow::updateCustomSettings() { mwsettings.updateGlobalParameterList(currentGlobalParams); emit dispatchCustomSettings(currentGlobalParams); + +} +RichParameterList& MainWindow::getCurrentParameterList() +{ + return currentGlobalParams; } void MainWindow::updateWindowMenu() @@ -2196,6 +2201,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(); @@ -2244,6 +2258,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(); diff --git a/src/meshlabplugins/edit_align/edit_align_factory.h b/src/meshlabplugins/edit_align/edit_align_factory.h index 33ca58fe12..f5e24d35b0 100644 --- a/src/meshlabplugins/edit_align/edit_align_factory.h +++ b/src/meshlabplugins/edit_align/edit_align_factory.h @@ -38,6 +38,11 @@ class EditAlignFactory : public QObject, public EditPlugin EditAlignFactory(); virtual ~EditAlignFactory() { delete editAlign; } + void initGlobalParameterList(RichParameterList& /*paramList*/) + { + // No global parameters needed for this plugin + } + virtual QString pluginName() const; //get the edit tool for the given action diff --git a/src/meshlabplugins/edit_manipulators/edit_manipulators_factory.h b/src/meshlabplugins/edit_manipulators/edit_manipulators_factory.h index b19ef5ed9d..0aebb74cee 100644 --- a/src/meshlabplugins/edit_manipulators/edit_manipulators_factory.h +++ b/src/meshlabplugins/edit_manipulators/edit_manipulators_factory.h @@ -38,6 +38,11 @@ class EditManipulatorsFactory : public QObject, public EditPlugin EditManipulatorsFactory(); virtual ~EditManipulatorsFactory() { delete editManipulators; } + void initGlobalParameterList(RichParameterList& /*paramList*/) + { + // No global parameters needed for this plugin + } + virtual QString pluginName() const; //get the edit tool for the given action diff --git a/src/meshlabplugins/edit_measure/edit_measure_factory.h b/src/meshlabplugins/edit_measure/edit_measure_factory.h index f4d63b4d7c..6f1c4aba11 100644 --- a/src/meshlabplugins/edit_measure/edit_measure_factory.h +++ b/src/meshlabplugins/edit_measure/edit_measure_factory.h @@ -38,6 +38,11 @@ class EditMeasureFactory : public QObject, public EditPlugin EditMeasureFactory(); virtual ~EditMeasureFactory() { delete editMeasure; } + void initGlobalParameterList(RichParameterList& /*paramList*/) + { + // No global parameters needed for this plugin + } + virtual QString pluginName() const; //get the edit tool for the given action diff --git a/src/meshlabplugins/edit_mutualcorrs/edit_mutualcorrs_factory.h b/src/meshlabplugins/edit_mutualcorrs/edit_mutualcorrs_factory.h index 010d34f178..bad04a63d8 100644 --- a/src/meshlabplugins/edit_mutualcorrs/edit_mutualcorrs_factory.h +++ b/src/meshlabplugins/edit_mutualcorrs/edit_mutualcorrs_factory.h @@ -37,6 +37,11 @@ class EditMutualCorrsFactory : public QObject, public EditPlugin EditMutualCorrsFactory(); virtual ~EditMutualCorrsFactory() { delete editMutualCorrs; } + void initGlobalParameterList(RichParameterList& /*paramList*/) + { + // No global parameters needed for this plugin + } + virtual QString pluginName() const; //get the edit tool for the given action diff --git a/src/meshlabplugins/edit_paint/edit_paint_factory.h b/src/meshlabplugins/edit_paint/edit_paint_factory.h index fe02975b42..97a3010fcc 100644 --- a/src/meshlabplugins/edit_paint/edit_paint_factory.h +++ b/src/meshlabplugins/edit_paint/edit_paint_factory.h @@ -38,6 +38,11 @@ class EditPaintFactory : public QObject, public EditPlugin EditPaintFactory(); virtual ~EditPaintFactory() { delete editPaint; } + void initGlobalParameterList(RichParameterList& /*paramList*/) + { + // No global parameters needed for this plugin + } + virtual QString pluginName() const; //get the edit tool for the given action diff --git a/src/meshlabplugins/edit_pickpoints/edit_pickpoints_factory.h b/src/meshlabplugins/edit_pickpoints/edit_pickpoints_factory.h index 44b62838f4..4d71948bf3 100644 --- a/src/meshlabplugins/edit_pickpoints/edit_pickpoints_factory.h +++ b/src/meshlabplugins/edit_pickpoints/edit_pickpoints_factory.h @@ -38,6 +38,11 @@ class EditPickPointsFactory : public QObject, public EditPlugin EditPickPointsFactory(); virtual ~EditPickPointsFactory() { delete editPickPoints; } + void initGlobalParameterList(RichParameterList& /*paramList*/) + { + // No global parameters needed for this plugin + } + virtual QString pluginName() const; //get the edit tool for the given action diff --git a/src/meshlabplugins/edit_point/edit_point_factory.h b/src/meshlabplugins/edit_point/edit_point_factory.h index 632901be01..f88cd41fd3 100644 --- a/src/meshlabplugins/edit_point/edit_point_factory.h +++ b/src/meshlabplugins/edit_point/edit_point_factory.h @@ -38,6 +38,11 @@ class PointEditFactory : public QObject, public EditPlugin PointEditFactory(); virtual ~PointEditFactory() { delete editPoint; } + void initGlobalParameterList(RichParameterList& /*paramList*/) + { + // No global parameters needed for this plugin + } + virtual QString pluginName() const; //get the edit tool for the given action diff --git a/src/meshlabplugins/edit_quality/edit_quality_factory.h b/src/meshlabplugins/edit_quality/edit_quality_factory.h index 2d8ae56a7d..86518cd3ba 100644 --- a/src/meshlabplugins/edit_quality/edit_quality_factory.h +++ b/src/meshlabplugins/edit_quality/edit_quality_factory.h @@ -38,6 +38,11 @@ class QualityMapperFactory : public QObject, public EditPlugin QualityMapperFactory(); virtual ~QualityMapperFactory() { delete editQuality; } + void initGlobalParameterList(RichParameterList& /*paramList*/) + { + // No global parameters needed for this plugin + } + virtual QString pluginName() const; //get the edit tool for the given action diff --git a/src/meshlabplugins/edit_referencing/edit_referencing_factory.h b/src/meshlabplugins/edit_referencing/edit_referencing_factory.h index f2187724eb..bea6799e33 100644 --- a/src/meshlabplugins/edit_referencing/edit_referencing_factory.h +++ b/src/meshlabplugins/edit_referencing/edit_referencing_factory.h @@ -37,6 +37,11 @@ class EditReferencingFactory : public QObject, public EditPlugin EditReferencingFactory(); virtual ~EditReferencingFactory() { delete editReferencing; } + void initGlobalParameterList(RichParameterList& /*paramList*/) + { + // No global parameters needed for this plugin + } + virtual QString pluginName() const; //get the edit tool for the given action diff --git a/src/meshlabplugins/edit_sample/edit_sample_factory.h b/src/meshlabplugins/edit_sample/edit_sample_factory.h index ed14fc598e..8d6a9bbfce 100644 --- a/src/meshlabplugins/edit_sample/edit_sample_factory.h +++ b/src/meshlabplugins/edit_sample/edit_sample_factory.h @@ -38,6 +38,11 @@ class SampleEditFactory : public QObject, public EditPlugin SampleEditFactory(); virtual ~SampleEditFactory() { delete editSample; } + void initGlobalParameterList(RichParameterList& /*paramList*/) + { + // No global parameters needed for this plugin + } + //returns the name of the plugin virtual QString pluginName() const; diff --git a/src/meshlabplugins/edit_select/edit_select.cpp b/src/meshlabplugins/edit_select/edit_select.cpp index 2c4ad3d1aa..07734b270c 100644 --- a/src/meshlabplugins/edit_select/edit_select.cpp +++ b/src/meshlabplugins/edit_select/edit_select.cpp @@ -22,7 +22,12 @@ ****************************************************************************/ #include "edit_select.h" +#include + +#include +#include #include +#include #include #include #include @@ -33,8 +38,10 @@ using namespace std; using namespace vcg; -EditSelectPlugin::EditSelectPlugin(int ConnectedMode) :selectionMode(ConnectedMode) { +EditSelectPlugin::EditSelectPlugin(RichParameterList* cgp, int ConnectedMode) :selectionMode(ConnectedMode) { isDragging = false; + currentGlobalParamSet = cgp; + qApp->installEventFilter(this); } QString EditSelectPlugin::info() @@ -62,131 +69,192 @@ void EditSelectPlugin::suggestedRenderingData(MeshModel & /*m*/, MLRenderingData dt.set(opts); } +bool EditSelectPlugin::keyReleaseEventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::KeyRelease && QEvent::KeyPress) { + QKeyEvent *keyEvent = static_cast(event); + if (m_ref && gla_ref) { + // Check if the released key is the Alt key + if (keyEvent->key() == Qt::Key_Alt) { + keyReleaseEvent(keyEvent, *m_ref, gla_ref); + } + } + } + // Pass the event to the base class event filter + return QObject::eventFilter(obj, event); +} void EditSelectPlugin::keyReleaseEvent(QKeyEvent *e, MeshModel &m, GLArea *gla) { + bool ctrlState = currentGlobalParamSet->getBool("MeshLab::Editors::InvertCTRLBehavior"); + // global "all" commands - if (e->key() == Qt::Key_A) // select all - { - if (areaMode == 0){ // vertices - tri::UpdateSelection::VertexAll(m.cm); - gla->updateSelection(m.id(), true, false); - } - else if (areaMode == 1){ //faces - tri::UpdateSelection::FaceAll(m.cm); - gla->updateSelection(m.id(), false, true); - } - gla->update(); - e->accept(); - } + switch (e->key()) { + case Qt::Key_A: // select all + if (areaMode == 0) { // vertices + tri::UpdateSelection::VertexAll(m.cm); + gla->updateSelection(m.id(), true, false); + } else if (areaMode == 1) { // faces + tri::UpdateSelection::FaceAll(m.cm); + gla->updateSelection(m.id(), false, true); + } + gla->update(); + e->accept(); + break; + + case Qt::Key_D: // deselect all + if (areaMode == 0) { // vertices + tri::UpdateSelection::VertexClear(m.cm); + gla->updateSelection(m.id(), true, false); + } else if (areaMode == 1) { // faces + tri::UpdateSelection::FaceClear(m.cm); + gla->updateSelection(m.id(), false, true); + } + gla->update(); + e->accept(); + break; + + case Qt::Key_I: // invert all + if (areaMode == 0) { // vertices + tri::UpdateSelection::VertexInvert(m.cm); + gla->updateSelection(m.id(), true, false); + } else if (areaMode == 1) { // faces + tri::UpdateSelection::FaceInvert(m.cm); + gla->updateSelection(m.id(), false, true); + } + gla->update(); + e->accept(); + break; - if (e->key() == Qt::Key_D) // deselect all - { - if (areaMode == 0){ // vertices - tri::UpdateSelection::VertexClear(m.cm); - gla->updateSelection(m.id(), true, false); - } - else if (areaMode == 1){ //faces - tri::UpdateSelection::FaceClear(m.cm); - gla->updateSelection(m.id(), false, true); - } - gla->update(); - e->accept(); + default: + break; } - if (e->key() == Qt::Key_I) // invert all - { - if (areaMode == 0){ // vertices - tri::UpdateSelection::VertexInvert(m.cm); - gla->updateSelection(m.id(), true, false); - } - else if (areaMode == 1){ //faces - tri::UpdateSelection::FaceInvert(m.cm); - gla->updateSelection(m.id(), false, true); - } - gla->update(); - e->accept(); - } + if (selectionMode == SELECT_AREA_MODE) { + switch (e->key()) { + case Qt::Key_T: // toggle pick mode + areaMode = (areaMode + 1) % 2; + gla->update(); + e->accept(); + break; + case Qt::Key_C: // clear Polyline + selPolyLine.clear(); + gla->update(); + e->accept(); + break; - if (selectionMode == SELECT_AREA_MODE) - { - if (e->key() == Qt::Key_T) // toggle pick mode - { - areaMode = (areaMode + 1) % 2; - gla->update(); - e->accept(); - } + case Qt::Key_Backspace: // remove last point Polyline + if (selPolyLine.size() > 0) + selPolyLine.pop_back(); + gla->update(); + e->accept(); + break; - if (e->key() == Qt::Key_C) // clear Polyline - { - selPolyLine.clear(); - gla->update(); - e->accept(); - } + case Qt::Key_Q: // add to selection + doSelection(m, gla, 0); + gla->update(); + e->accept(); + break; - if (e->key() == Qt::Key_Backspace) // remove last point Polyline - { - if (selPolyLine.size() > 0) - selPolyLine.pop_back(); - gla->update(); - e->accept(); - } + case Qt::Key_W: // sub from selection + doSelection(m, gla, 1); + gla->update(); + e->accept(); + break; - if (e->key() == Qt::Key_Q) // add to selection - { - doSelection(m, gla, 0); - gla->update(); - e->accept(); - } + case Qt::Key_E: // invert selection + doSelection(m, gla, 2); + gla->update(); + e->accept(); + break; - if (e->key() == Qt::Key_W) // sub from selection + default: + break; + } + gla->setCursor(QCursor(QPixmap(":/images/sel_area.png"), 1, 1)); + } else { + if (ctrlState){ + gla->setCursor(QCursor(QPixmap(":/images/sel_rect_plus.png"), 1, 1)); + } else { + gla->setCursor(QCursor(QPixmap(":/images/sel_rect.png"), 1, 1)); + } + Qt::KeyboardModifiers mod = e->modifiers(); + if (e->key() == Qt::Key_Alt) { - doSelection(m, gla, 1); - gla->update(); - e->accept(); + if (ctrlState){ + if (mod & Qt::ControlModifier){ + gla->setCursor(QCursor(QPixmap(":/images/sel_rect.png"), 1, 1)); + } else if (mod & Qt::ShiftModifier){ + gla->setCursor(QCursor(QPixmap(":/images/sel_rect_minus.png"), 1, 1)); + } else{ + gla->setCursor(QCursor(QPixmap(":/images/sel_rect_plus.png"), 1, 1)); + } + } else{ + if (mod & Qt::ControlModifier){ + gla->setCursor(QCursor(QPixmap(":/images/sel_rect_plus.png"), 1, 1)); + } else if (mod & Qt::ShiftModifier){ + gla->setCursor(QCursor(QPixmap(":/images/sel_rect_minus.png"), 1, 1)); + } else{ + gla->setCursor(QCursor(QPixmap(":/images/sel_rect.png"), 1, 1)); + } + } + e->accept(); } - if (e->key() == Qt::Key_E) // invert selection - { - doSelection(m, gla, 2); - gla->update(); - e->accept(); + switch (selectionMode) { + case SELECT_VERT_MODE: + if (ctrlState){ + if (mod & Qt::ControlModifier) + gla->setCursor(QCursor(QPixmap(":/images/sel_rect.png"), 1, 1)); + else if (mod & Qt::ShiftModifier) + gla->setCursor(QCursor(QPixmap(":/images/sel_rect_minus.png"), 1, 1)); + } else { + if (mod & Qt::ControlModifier) + gla->setCursor(QCursor(QPixmap(":/images/sel_rect_plus.png"), 1, 1)); + else if (mod & Qt::ShiftModifier) + gla->setCursor(QCursor(QPixmap(":/images/sel_rect_minus.png"), 1, 1)); + } + break; + + default: + if (mod & Qt::AltModifier) { + if (ctrlState){ + if (mod & Qt::ControlModifier) + gla->setCursor(QCursor(QPixmap(":/images/sel_rect.png"), 1, 1)); + else if (mod & Qt::ShiftModifier) + gla->setCursor(QCursor(QPixmap(":/images/sel_rect_minus.png"), 1, 1)); + else + gla->setCursor(QCursor(QPixmap(":/images/sel_rect_plus.png"), 1, 1)); + } else { + if (mod & Qt::ControlModifier) + gla->setCursor(QCursor(QPixmap(":/images/sel_rect_plus.png"), 1, 1)); + else if (mod & Qt::ShiftModifier) + gla->setCursor(QCursor(QPixmap(":/images/sel_rect_minus.png"), 1, 1)); + else + gla->setCursor(QCursor(QPixmap(":/images/sel_rect.png"), 1, 1)); + } + } else { + if (ctrlState){ + if (mod & Qt::ControlModifier) + gla->setCursor(QCursor(QPixmap(":/images/sel_rect.png"), 1, 1)); + else if (mod & Qt::ShiftModifier) + gla->setCursor(QCursor(QPixmap(":/images/sel_rect_minus.png"), 1, 1)); + } else { + if (mod & Qt::ControlModifier) + gla->setCursor(QCursor(QPixmap(":/images/sel_rect_plus.png"), 1, 1)); + else if (mod & Qt::ShiftModifier) + gla->setCursor(QCursor(QPixmap(":/images/sel_rect_minus.png"), 1, 1)); + } + } + break; } - gla->setCursor(QCursor(QPixmap(":/images/sel_area.png"), 1, 1)); } - else - { - gla->setCursor(QCursor(QPixmap(":/images/sel_rect.png"), 1, 1)); - Qt::KeyboardModifiers mod = QApplication::queryKeyboardModifiers(); - if(selectionMode == SELECT_VERT_MODE) - { - if (mod & Qt::ControlModifier) - gla->setCursor(QCursor(QPixmap(":/images/sel_rect_plus.png"), 1, 1)); - else if (mod & Qt::ShiftModifier) - gla->setCursor(QCursor(QPixmap(":/images/sel_rect_minus.png"), 1, 1)); - } - else - { - if (mod & Qt::AltModifier) - { - if (mod & Qt::ControlModifier) - gla->setCursor(QCursor(QPixmap(":/images/sel_rect_plus_eye.png"), 1, 1)); - else if (mod & Qt::ShiftModifier) - gla->setCursor(QCursor(QPixmap(":/images/sel_rect_minus_eye.png"), 1, 1)); - else - gla->setCursor(QCursor(QPixmap(":/images/sel_rect_eye.png"), 1, 1)); - } - else - { - if (mod & Qt::ControlModifier) - gla->setCursor(QCursor(QPixmap(":/images/sel_rect_plus.png"), 1, 1)); - else if (mod & Qt::ShiftModifier) - gla->setCursor(QCursor(QPixmap(":/images/sel_rect_minus.png"), 1, 1)); - } - } - } - + if(ctrlState){ + gla->setCursor(QCursor(QPixmap(":/images/sel_rect_plus.png"), 1, 1)); + } else { + gla->setCursor(QCursor(QPixmap(":/images/sel_rect.png"), 1, 1)); + } } void EditSelectPlugin::doSelection(MeshModel &m, GLArea *gla, int mode) @@ -264,43 +332,61 @@ void EditSelectPlugin::doSelection(MeshModel &m, GLArea *gla, int mode) } -void EditSelectPlugin::keyPressEvent(QKeyEvent * /*event*/, MeshModel & /*m*/, GLArea *gla) +void EditSelectPlugin::keyPressEvent(QKeyEvent *event, MeshModel &m, GLArea *gla) { - if (selectionMode == SELECT_AREA_MODE) - return; + bool ctrlState = currentGlobalParamSet->getBool("MeshLab::Editors::InvertCTRLBehavior"); - gla->setCursor(QCursor(QPixmap(":/images/sel_rect.png"), 1, 1)); - Qt::KeyboardModifiers mod = QApplication::queryKeyboardModifiers(); - if(selectionMode == SELECT_VERT_MODE) - { - if (mod & Qt::ControlModifier) - gla->setCursor(QCursor(QPixmap(":/images/sel_rect_plus.png"), 1, 1)); - else if (mod & Qt::ShiftModifier) - gla->setCursor(QCursor(QPixmap(":/images/sel_rect_minus.png"), 1, 1)); - } - else - { - if (mod & Qt::AltModifier) - { - if (mod & Qt::ControlModifier) - gla->setCursor(QCursor(QPixmap(":/images/sel_rect_plus_eye.png"), 1, 1)); - else if (mod & Qt::ShiftModifier) - gla->setCursor(QCursor(QPixmap(":/images/sel_rect_minus_eye.png"), 1, 1)); - else - gla->setCursor(QCursor(QPixmap(":/images/sel_rect_eye.png"), 1, 1)); - } - else - { - if (mod & Qt::ControlModifier) - gla->setCursor(QCursor(QPixmap(":/images/sel_rect_plus.png"), 1, 1)); - else if (mod & Qt::ShiftModifier) - gla->setCursor(QCursor(QPixmap(":/images/sel_rect_minus.png"), 1, 1)); - } - } + switch (event->key()) + { + case Qt::Key_Control: + { + if (ctrlState) { + gla->setCursor(QCursor(QPixmap(":/images/sel_rect.png"), 1, 1)); + } else { + gla->setCursor(QCursor(QPixmap(":/images/sel_rect_plus.png"), 1, 1)); + } + break; + } + case Qt::Key_Shift: + { + if (ctrlState) { + gla->setCursor(QCursor(QPixmap(":/images/sel_rect_minus.png"), 1, 1)); + } else { + gla->setCursor(QCursor(QPixmap(":/images/sel_rect_plus.png"), 1, 1)); + } + break; + } + case Qt::Key_Alt: + { + if (ctrlState) { + if (event->modifiers() & Qt::ControlModifier) { + gla->setCursor(QCursor(QPixmap(":/images/sel_rect.png"), 1, 1)); + } else if (event->modifiers() & Qt::ShiftModifier) { + gla->setCursor(QCursor(QPixmap(":/images/sel_rect_minus.png"), 1, 1)); + } else { + gla->setCursor(QCursor(QPixmap(":/images/sel_rect_plus_eye.png"), 1, 1)); + } + } else { + if (event->modifiers() & Qt::ControlModifier) { + gla->setCursor(QCursor(QPixmap(":/images/sel_rect_plus.png"), 1, 1)); + } else if (event->modifiers() & Qt::ShiftModifier) { + gla->setCursor(QCursor(QPixmap(":/images/sel_rect_minus.png"), 1, 1)); + } else { + gla->setCursor(QCursor(QPixmap(":/images/sel_rect_eye.png"), 1, 1)); + } + } + break; + } + default: + { + break; + } + } } void EditSelectPlugin::mousePressEvent(QMouseEvent * event, MeshModel &m, GLArea *gla) { + bool ctrlState = currentGlobalParamSet->getBool("MeshLab::Editors::InvertCTRLBehavior"); if (selectionMode == SELECT_AREA_MODE) { selPolyLine.push_back(QTLogicalToOpenGL(gla, event->pos())); @@ -310,37 +396,64 @@ void EditSelectPlugin::mousePressEvent(QMouseEvent * event, MeshModel &m, GLArea LastSelVert.clear(); LastSelFace.clear(); - if ((event->modifiers() & Qt::ControlModifier) || - (event->modifiers() & Qt::ShiftModifier)) - { - CMeshO::FaceIterator fi; - for (fi = m.cm.face.begin(); fi != m.cm.face.end(); ++fi) - if (!(*fi).IsD() && (*fi).IsS()) - LastSelFace.push_back(&*fi); - - CMeshO::VertexIterator vi; - for (vi = m.cm.vert.begin(); vi != m.cm.vert.end(); ++vi) - if (!(*vi).IsD() && (*vi).IsS()) - LastSelVert.push_back(&*vi); + int ctrl = (event->modifiers() & Qt::ControlModifier) ? 1 : 0; + int shift = (event->modifiers() & Qt::ShiftModifier) ? 1 : 0; + int alt = (event->modifiers() & Qt::AltModifier) ? 1 : 0; + + switch (ctrlState * 4 + ctrl * 2 + shift) { + case 0: // !ctrlState && !ctrl && !shift + composingSelMode = SMClear; + selectFrontFlag = false; + break; + case 1: // !ctrlState && !ctrl && shift + composingSelMode = SMSub; + selectFrontFlag = false; + break; + case 2: // !ctrlState && ctrl && !shift + composingSelMode = SMAdd; + selectFrontFlag = false; + break; + case 3: // !ctrlState && ctrl && shift + composingSelMode = SMSub; + selectFrontFlag = false; + break; + case 4: // ctrlState && !ctrl && !shift + composingSelMode = SMAdd; + selectFrontFlag = alt; + break; + case 5: // ctrlState && !ctrl && shift + composingSelMode = SMSub; + selectFrontFlag = alt; + break; + case 6: // ctrlState && ctrl && !shift + composingSelMode = SMClear; + selectFrontFlag = alt; + break; + case 7: // ctrlState && ctrl && shift + composingSelMode = SMSub; + selectFrontFlag = alt; + break; } - composingSelMode = SMClear; - if (event->modifiers() & Qt::ControlModifier) - composingSelMode = SMAdd; - else if (event->modifiers() & Qt::ShiftModifier) - composingSelMode = SMSub; - - if (event->modifiers() & Qt::AltModifier) - selectFrontFlag = true; - else - selectFrontFlag = false; - start = QTLogicalToOpenGL(gla, event->pos()); cur = start; - return; + + if (ctrlState && (!(event->modifiers() & Qt::ControlModifier) || (event->modifiers() & Qt::ShiftModifier))) { + for (CMeshO::FaceIterator fi = m.cm.face.begin(); fi != m.cm.face.end(); ++fi) { + if (!(*fi).IsD() && (*fi).IsS()) { + LastSelFace.push_back(&*fi); + } + } + + for (CMeshO::VertexIterator vi = m.cm.vert.begin(); vi != m.cm.vert.end(); ++vi) { + if (!(*vi).IsD() && (*vi).IsS()) { + LastSelVert.push_back(&*vi); + } + } + } } -void EditSelectPlugin::mouseMoveEvent(QMouseEvent * event, MeshModel & /*m*/, GLArea * gla) +void EditSelectPlugin::mouseMoveEvent(QMouseEvent * event, MeshModel &m, GLArea * gla) { if (selectionMode == SELECT_AREA_MODE) { @@ -367,7 +480,7 @@ void EditSelectPlugin::mouseMoveEvent(QMouseEvent * event, MeshModel & /*m*/, GL // } } -void EditSelectPlugin::mouseReleaseEvent(QMouseEvent * event, MeshModel &/*m*/, GLArea * gla) +void EditSelectPlugin::mouseReleaseEvent(QMouseEvent * event, MeshModel &m, GLArea * gla) { //gla->update(); if (gla == NULL) @@ -481,12 +594,13 @@ void EditSelectPlugin::DrawXORRect(GLArea * gla, bool doubleDraw) void EditSelectPlugin::decorate(MeshModel &m, GLArea * gla) { + bool ctrlState = currentGlobalParamSet->getBool("MeshLab::Editors::InvertCTRLBehavior"); if (selectionMode == SELECT_AREA_MODE) { // get proj data of last rendering glPushMatrix(); glMultMatrix(m.cm.Tr); - GLPickTri::glGetMatrixAndViewport(this->SelMatrix, this->SelViewport); + GLPickTri::glGetMatrixAndViewport(this->SelMatrix, this->SelViewport); glGetDoublev(GL_MODELVIEW_MATRIX, mvMatrix_f); glGetDoublev(GL_PROJECTION_MATRIX, prMatrix_f); glGetIntegerv(GL_VIEWPORT, viewpSize); @@ -522,16 +636,30 @@ void EditSelectPlugin::decorate(MeshModel &m, GLArea * gla) } else { - QString line1, line2, line3; + if (ctrlState){ + QString line1, line2, line3; - line1 = "Drag to select"; - if ((selectionMode == SELECT_FACE_MODE) || (selectionMode == SELECT_CONN_MODE)) - line2 = "you may hold:
- CTRL to add
- SHIFT to subtract
- ALT to select only visible"; - else + line1 = "Drag to select"; + if ((selectionMode == SELECT_FACE_MODE) || (selectionMode == SELECT_CONN_MODE)) + line2 = "you may hold:
- CTRL to NEW selection
- SHIFT to subtract
- ALT to select only visible"; + else + line2 = "you may hold:
- CTRL to NEW selection
- SHIFT to subtract"; + line3 = "
A select all, D de-select all, I invert all"; + + this->realTimeLog("Interactive Selection", m.shortName(), "%s
%s
%s", line1.toStdString().c_str(), line2.toStdString().c_str(), line3.toStdString().c_str()); + } + else{ + QString line1, line2, line3; + + line1 = "Drag to select"; + if ((selectionMode == SELECT_FACE_MODE) || (selectionMode == SELECT_CONN_MODE)) + line2 = "you may hold:
- CTRL to add
- SHIFT to subtract
- ALT to select only visible"; + else line2 = "you may hold:
- CTRL to add
- SHIFT to subtract"; - line3 = "
A select all, D de-select all, I invert all"; + line3 = "
A select all, D de-select all, I invert all"; - this->realTimeLog("Interactive Selection", m.shortName(), "%s
%s
%s", line1.toStdString().c_str(), line2.toStdString().c_str(), line3.toStdString().c_str()); + this->realTimeLog("Interactive Selection", m.shortName(), "%s
%s
%s", line1.toStdString().c_str(), line2.toStdString().c_str(), line3.toStdString().c_str()); + } } if (isDragging) @@ -629,11 +757,17 @@ void EditSelectPlugin::decorate(MeshModel &m, GLArea * gla) bool EditSelectPlugin::startEdit(MeshModel & m, GLArea * gla, MLSceneGLSharedDataContext* /*cont*/) { + bool ctrlState = currentGlobalParamSet->getBool("MeshLab::Editors::InvertCTRLBehavior"); if (gla == NULL) return false; if (!GLExtensionsManager::initializeGLextensions_notThrowing()) return false; + if (ctrlState){ + gla->setCursor(QCursor(QPixmap(":/images/sel_rect_plus.png"), 1, 1)); + } + else{ gla->setCursor(QCursor(QPixmap(":/images/sel_rect.png"), 1, 1)); + } if (selectionMode == SELECT_AREA_MODE) { diff --git a/src/meshlabplugins/edit_select/edit_select.h b/src/meshlabplugins/edit_select/edit_select.h index a5993dbcae..1a4497509a 100644 --- a/src/meshlabplugins/edit_select/edit_select.h +++ b/src/meshlabplugins/edit_select/edit_select.h @@ -24,29 +24,30 @@ #define EDITPLUGIN_H #include +#include class EditSelectPlugin : public QObject, public EditTool { Q_OBJECT - public: - enum { SELECT_FACE_MODE, SELECT_VERT_MODE, SELECT_CONN_MODE, SELECT_AREA_MODE }; - EditSelectPlugin(int _ConnectedMode); + enum { SELECT_FACE_MODE, SELECT_VERT_MODE, SELECT_CONN_MODE, SELECT_AREA_MODE }; + EditSelectPlugin(RichParameterList* cgp, int _ConnectedMode); virtual ~EditSelectPlugin() {} - static QString info(); void suggestedRenderingData(MeshModel & m, MLRenderingData& dt); bool startEdit(MeshModel &/*m*/, GLArea * /*parent*/, MLSceneGLSharedDataContext* /*cont*/); void endEdit(MeshModel &/*m*/, GLArea * /*parent*/, MLSceneGLSharedDataContext* /*cont*/) {} void decorate(MeshModel &/*m*/, GLArea * /*parent*/); - void mousePressEvent(QMouseEvent *event, MeshModel &/*m*/, GLArea *); - void mouseMoveEvent(QMouseEvent *event, MeshModel &/*m*/, GLArea *); - void mouseReleaseEvent(QMouseEvent *event, MeshModel &/*m*/, GLArea *); - void keyReleaseEvent(QKeyEvent *, MeshModel &/*m*/, GLArea *); - void keyPressEvent(QKeyEvent *, MeshModel &/*m*/, GLArea *); + void mousePressEvent(QMouseEvent *event, MeshModel &/*m*/, GLArea *gla); + void mouseMoveEvent(QMouseEvent *event, MeshModel &/*m*/, GLArea *gla); + void mouseReleaseEvent(QMouseEvent *event, MeshModel &/*m*/, GLArea *gla); + virtual void keyReleaseEvent(QKeyEvent *, MeshModel &m, GLArea *gla); + void keyPressEvent(QKeyEvent *, MeshModel &m, GLArea *gla); + EditTool* getEditTool(const QAction *action); + bool keyReleaseEventFilter(QObject *obj, QEvent *event); vcg::Point2f start; vcg::Point2f cur; @@ -71,12 +72,17 @@ class EditSelectPlugin : public QObject, public EditTool void setDecorator(QString, bool); private: + MeshModel *m_ref; + GLArea *gla_ref; + RichParameterList* currentGlobalParamSet; + bool ctrlState; typedef enum { SMAdd, SMClear, SMSub } ComposingSelMode; // How the selection are composed ComposingSelMode composingSelMode; bool selectFrontFlag; void DrawXORRect(GLArea * gla, bool doubleDraw); void DrawXORPolyLine(GLArea * gla); void doSelection(MeshModel &m, GLArea *gla, int mode); + }; #endif diff --git a/src/meshlabplugins/edit_select/edit_select_factory.cpp b/src/meshlabplugins/edit_select/edit_select_factory.cpp index be743ddce7..037190b0b5 100644 --- a/src/meshlabplugins/edit_select/edit_select_factory.cpp +++ b/src/meshlabplugins/edit_select/edit_select_factory.cpp @@ -23,6 +23,7 @@ #include "edit_select_factory.h" #include "edit_select.h" +#include "common/parameters/rich_parameter_list.h" EditSelectFactory::EditSelectFactory() { @@ -37,7 +38,10 @@ EditSelectFactory::EditSelectFactory() actionList.push_back(editSelectArea); foreach(QAction *editAction, actionList) - editAction->setCheckable(true); + editAction->setCheckable(true); +} +void EditSelectFactory::initGlobalParameterList(RichParameterList& defaultGlobalParamSet) { + defaultGlobalParamSet.addParam(RichBool(InvertCtrlBehavior(), true,"Inverting the behavior of the CTRL modifier on edit selec rectangle tools","")); } QString EditSelectFactory::pluginName() const @@ -48,17 +52,21 @@ QString EditSelectFactory::pluginName() const //get the edit tool for the given action EditTool* EditSelectFactory::getEditTool(const QAction *action) { + EditSelectPlugin* result = nullptr; if(action == editSelect) - return new EditSelectPlugin(EditSelectPlugin::SELECT_FACE_MODE); + result = new EditSelectPlugin(currentGlobalParamSet,EditSelectPlugin::SELECT_FACE_MODE); else if(action == editSelectConnected) - return new EditSelectPlugin(EditSelectPlugin::SELECT_CONN_MODE); + result = new EditSelectPlugin(currentGlobalParamSet,EditSelectPlugin::SELECT_CONN_MODE); else if(action == editSelectVert) - return new EditSelectPlugin(EditSelectPlugin::SELECT_VERT_MODE); + result = new EditSelectPlugin(currentGlobalParamSet,EditSelectPlugin::SELECT_VERT_MODE); else if (action == editSelectArea) - return new EditSelectPlugin(EditSelectPlugin::SELECT_AREA_MODE); + result = new EditSelectPlugin(currentGlobalParamSet,EditSelectPlugin::SELECT_AREA_MODE); + + if (result == nullptr) { + assert(0); + } - assert(0); //should never be asked for an action that isn't here - return nullptr; + return (EditTool*)result; } QString EditSelectFactory::getEditToolDescription(const QAction * /*a*/) diff --git a/src/meshlabplugins/edit_select/edit_select_factory.h b/src/meshlabplugins/edit_select/edit_select_factory.h index 46497cab37..71442dbe68 100644 --- a/src/meshlabplugins/edit_select/edit_select_factory.h +++ b/src/meshlabplugins/edit_select/edit_select_factory.h @@ -26,6 +26,7 @@ #define EditSelectFactoryPLUGIN_H #include +#include "common/parameters/rich_parameter_list.h" class EditSelectFactory : public QObject, public EditPlugin { @@ -37,6 +38,8 @@ class EditSelectFactory : public QObject, public EditPlugin EditSelectFactory(); virtual ~EditSelectFactory() { delete editSelect; } + virtual void initGlobalParameterList(RichParameterList& defaultGlobalParamSet); + virtual QString pluginName() const; //get the edit tool for the given action @@ -45,7 +48,13 @@ class EditSelectFactory : public QObject, public EditPlugin //get the description for the given action virtual QString getEditToolDescription(const QAction*); + inline QString InvertCtrlBehavior() const { return "MeshLab::Editors::InvertCTRLBehavior" ; } + +signals: + void setDecorator(QString, bool); + private: + bool ctrlState; QAction *editSelect; QAction *editSelectVert; QAction *editSelectConnected; From 81a6ca3211bf4ef845b836aaa8c0693030e19b70 Mon Sep 17 00:00:00 2001 From: Z3roCo0l <104153656+Z3roCo0l@users.noreply.github.com> Date: Thu, 12 Feb 2026 10:37:01 +0100 Subject: [PATCH 2/5] Set Invert CTRL Behavior default to false --- src/meshlabplugins/edit_select/edit_select_factory.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/meshlabplugins/edit_select/edit_select_factory.cpp b/src/meshlabplugins/edit_select/edit_select_factory.cpp index 037190b0b5..c3b16299f6 100644 --- a/src/meshlabplugins/edit_select/edit_select_factory.cpp +++ b/src/meshlabplugins/edit_select/edit_select_factory.cpp @@ -41,7 +41,7 @@ EditSelectFactory::EditSelectFactory() editAction->setCheckable(true); } void EditSelectFactory::initGlobalParameterList(RichParameterList& defaultGlobalParamSet) { - defaultGlobalParamSet.addParam(RichBool(InvertCtrlBehavior(), true,"Inverting the behavior of the CTRL modifier on edit selec rectangle tools","")); + defaultGlobalParamSet.addParam(RichBool(InvertCtrlBehavior(), false,"Invert the behavior of the CTRL modifier on edit selection rectangle tools","")); } QString EditSelectFactory::pluginName() const From c3aed1b799aad5dec5a9d91a08a27a3bdf4ae639 Mon Sep 17 00:00:00 2001 From: Z3roCo0l <104153656+Z3roCo0l@users.noreply.github.com> Date: Wed, 11 Feb 2026 01:52:03 +0100 Subject: [PATCH 3/5] Add Lasso Cut Tool (edit_cut plugin) New edit plugin that lets users draw a polyline on screen and cut through mesh triangles along the path. Splits edges at polyline intersections, re-triangulates affected faces, then selects/deletes faces inside the polyline boundary. Features: - Polyline drawing with mouse clicks - Edge splitting at polyline-mesh intersections using SplitTab - Centroid-based face selection for clean boundary cuts - Two-phase workflow: Q to select (preview), Enter to delete - Full edit_select-style keybindings (Q/W/D/A/I) - GPU buffer refresh after mesh modification Co-Authored-By: Claude Opus 4.6 --- src/CMakeLists.txt | 1 + src/meshlabplugins/edit_cut/CMakeLists.txt | 12 + src/meshlabplugins/edit_cut/edit_cut.cpp | 772 ++++++++++++++++++ src/meshlabplugins/edit_cut/edit_cut.h | 51 ++ src/meshlabplugins/edit_cut/edit_cut.qrc | 5 + .../edit_cut/edit_cut_factory.cpp | 31 + .../edit_cut/edit_cut_factory.h | 26 + .../edit_cut/images/icon_cut.png | Bin 0 -> 1970 bytes 8 files changed, 898 insertions(+) create mode 100644 src/meshlabplugins/edit_cut/CMakeLists.txt create mode 100644 src/meshlabplugins/edit_cut/edit_cut.cpp create mode 100644 src/meshlabplugins/edit_cut/edit_cut.h create mode 100644 src/meshlabplugins/edit_cut/edit_cut.qrc create mode 100644 src/meshlabplugins/edit_cut/edit_cut_factory.cpp create mode 100644 src/meshlabplugins/edit_cut/edit_cut_factory.h create mode 100644 src/meshlabplugins/edit_cut/images/icon_cut.png diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0398e8566b..82494a57e0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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() diff --git a/src/meshlabplugins/edit_cut/CMakeLists.txt b/src/meshlabplugins/edit_cut/CMakeLists.txt new file mode 100644 index 0000000000..581dfd7713 --- /dev/null +++ b/src/meshlabplugins/edit_cut/CMakeLists.txt @@ -0,0 +1,12 @@ +# Copyright 2019-2020, Collabora, Ltd. +# SPDX-License-Identifier: BSL-1.0 + +set(SOURCES edit_cut.cpp edit_cut_factory.cpp) + +set(HEADERS edit_cut.h edit_cut_factory.h) + +set(RESOURCES edit_cut.qrc) + +add_meshlab_plugin(edit_cut ${SOURCES} ${HEADERS} ${RESOURCES}) + +target_link_libraries(edit_cut PRIVATE OpenGL::GLU) diff --git a/src/meshlabplugins/edit_cut/edit_cut.cpp b/src/meshlabplugins/edit_cut/edit_cut.cpp new file mode 100644 index 0000000000..29dd8c6663 --- /dev/null +++ b/src/meshlabplugins/edit_cut/edit_cut.cpp @@ -0,0 +1,772 @@ +#include "edit_cut.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +static const char* LOG_PATH = "C:\\Projects\\cut_debug.log"; + +static void cutLog(const char *msg) +{ + FILE *fp = fopen(LOG_PATH, "a"); + if (fp) { + fprintf(fp, "%s\n", msg); + fflush(fp); + fclose(fp); + } + OutputDebugStringA("[edit_cut] "); + OutputDebugStringA(msg); + OutputDebugStringA("\n"); +} + +static void cutLogQ(const QString &msg) +{ + cutLog(msg.toUtf8().constData()); +} + +using namespace std; +using namespace vcg; + +// ---- SplitTab from vcglib refine.h ---- +// Index is bitmask: bit0 = edge01 split, bit1 = edge12 split, bit2 = edge20 split +// Vertices: 0,1,2 = original; 3 = mid01, 4 = mid12, 5 = mid20 +struct SplitEntry { + int TriNum; + int TV[4][3]; +}; + +static const SplitEntry SplitTab[8] = { + /* 0 0 0 */ {1, {{0,1,2},{0,0,0},{0,0,0},{0,0,0}} }, + /* 0 0 1 */ {2, {{0,3,2},{3,1,2},{0,0,0},{0,0,0}} }, + /* 0 1 0 */ {2, {{0,1,4},{0,4,2},{0,0,0},{0,0,0}} }, + /* 0 1 1 */ {3, {{3,1,4},{0,3,2},{4,2,3},{0,0,0}} }, + /* 1 0 0 */ {2, {{0,1,5},{5,1,2},{0,0,0},{0,0,0}} }, + /* 1 0 1 */ {3, {{0,3,5},{3,1,5},{2,5,1},{0,0,0}} }, + /* 1 1 0 */ {3, {{2,5,4},{0,1,5},{4,5,1},{0,0,0}} }, + /* 1 1 1 */ {4, {{3,4,5},{0,3,5},{3,1,4},{5,4,2}} }, +}; + +// ---- 2D segment-segment intersection returning lambda along first segment ---- +static inline bool SegSegIntersect( + const Point2f &p0, const Point2f &p1, + const Point2f &p2, const Point2f &p3, + float &outLambda0) +{ + float a = (p1 - p0)[0]; + float b = (p2 - p3)[0]; + float c = (p1 - p0)[1]; + float d = (p2 - p3)[1]; + float e = (p2 - p0)[0]; + float f = (p2 - p0)[1]; + + float det = a * d - b * c; + if (fabs(det) < 1e-8f) + return false; + + float lambda0 = (d * e - b * f) / det; + float lambda1 = (-c * e + a * f) / det; + + if (lambda0 < 0.0f || lambda0 > 1.0f || lambda1 < 0.0f || lambda1 > 1.0f) + return false; + + outLambda0 = lambda0; + return true; +} + +// ======================================================================== + +EditCutPlugin::EditCutPlugin() +{ + hasPendingSelection = false; + memset(viewpSize, 0, sizeof(viewpSize)); + memset(mvMatrix_f, 0, sizeof(mvMatrix_f)); + memset(prMatrix_f, 0, sizeof(prMatrix_f)); + memset(SelViewport, 0, sizeof(SelViewport)); + SelMatrix.setIdentity(); +} + +const QString EditCutPlugin::info() +{ + return QString("Draw a closed polyline to cut through mesh triangles and delete the inside region."); +} + +void EditCutPlugin::suggestedRenderingData(MeshModel & /*m*/, MLRenderingData &dt) +{ + MLPerViewGLOptions opts; + dt.get(opts); + opts._sel_enabled = true; + opts._face_sel = true; + opts._vertex_sel = true; + dt.set(opts); +} + +bool EditCutPlugin::startEdit(MeshModel & /*m*/, GLArea *gla, MLSceneGLSharedDataContext * /*cont*/) +{ + // Clear log file on tool activation + FILE *fp = fopen(LOG_PATH, "w"); + if (fp) { fprintf(fp, "=== edit_cut plugin v3 loaded ===\n"); fflush(fp); fclose(fp); } + + if (gla == NULL) + return false; + if (!GLExtensionsManager::initializeGLextensions_notThrowing()) + return false; + + gla->setCursor(Qt::CrossCursor); + cutPolyLine.clear(); + hasPendingSelection = false; + cutLog("startEdit OK"); + return true; +} + +void EditCutPlugin::endEdit(MeshModel &m, GLArea * /*parent*/, MLSceneGLSharedDataContext * /*cont*/) +{ + cutPolyLine.clear(); + hasPendingSelection = false; + tri::UpdateSelection::FaceClear(m.cm); +} + +// ---- Mouse Events ---- + +void EditCutPlugin::mousePressEvent(QMouseEvent *event, MeshModel &m, GLArea *gla) +{ + if (hasPendingSelection) { + // Cancel pending selection if user clicks again + hasPendingSelection = false; + tri::UpdateSelection::FaceClear(m.cm); + cutPolyLine.clear(); + gla->updateSelection(m.id(), false, true); + } + cutPolyLine.push_back(QTLogicalToOpenGL(gla, event->pos())); + gla->update(); +} + +void EditCutPlugin::mouseMoveEvent(QMouseEvent *event, MeshModel & /*m*/, GLArea *gla) +{ + if (!cutPolyLine.empty() && !hasPendingSelection) + cutPolyLine.back() = QTLogicalToOpenGL(gla, event->pos()); + gla->update(); +} + +void EditCutPlugin::mouseReleaseEvent(QMouseEvent *event, MeshModel & /*m*/, GLArea *gla) +{ + if (!cutPolyLine.empty() && !hasPendingSelection) + cutPolyLine.back() = QTLogicalToOpenGL(gla, event->pos()); +} + +void EditCutPlugin::keyPressEvent(QKeyEvent *, MeshModel &, GLArea *) +{ +} + +void EditCutPlugin::keyReleaseEvent(QKeyEvent *e, MeshModel &m, GLArea *gla) +{ + switch (e->key()) { + case Qt::Key_C: // Clear polyline + cutLog("Key C - clearing polyline"); + cutPolyLine.clear(); + gla->update(); + e->accept(); + break; + + case Qt::Key_Backspace: // Remove last polyline point + if (!cutPolyLine.empty()) { + cutPolyLine.pop_back(); + gla->update(); + } + e->accept(); + break; + + case Qt::Key_Q: // Cut + add to selection (like edit_select Q) + if (cutPolyLine.size() >= 3) { + cutLog("Key Q - cut and add to selection"); + executeCut(m, gla); + } + e->accept(); + break; + + case Qt::Key_W: // Subtract from selection (like edit_select W) + if (cutPolyLine.size() >= 3) { + cutLog("Key W - subtract from selection"); + // Select faces inside polyline, then CLEAR their selection + { + vector projVec; + GLPickTri::FillProjectedVector(m.cm, projVec, this->SelMatrix, this->SelViewport); + + QImage bufQImg(viewpSize[2], viewpSize[3], QImage::Format_RGB32); + bufQImg.fill(Qt::white); + QPainter bufQPainter(&bufQImg); + vector qpoints; + for (size_t i = 0; i < cutPolyLine.size(); ++i) + qpoints.push_back(QPointF(cutPolyLine[i][0], cutPolyLine[i][1])); + bufQPainter.setBrush(QBrush(Qt::black)); + bufQPainter.drawPolygon(&qpoints[0], (int)qpoints.size(), Qt::WindingFill); + bufQPainter.end(); + QRgb blk = QColor(Qt::black).rgb(); + + for (size_t fi = 0; fi < m.cm.face.size(); ++fi) { + if (m.cm.face[fi].IsD() || !m.cm.face[fi].IsS()) continue; + int vi0 = tri::Index(m.cm, m.cm.face[fi].V(0)); + int vi1 = tri::Index(m.cm, m.cm.face[fi].V(1)); + int vi2 = tri::Index(m.cm, m.cm.face[fi].V(2)); + if (vi0 < 0 || vi0 >= (int)projVec.size() || + vi1 < 0 || vi1 >= (int)projVec.size() || + vi2 < 0 || vi2 >= (int)projVec.size()) continue; + if (projVec[vi0][2] <= -1.0 || projVec[vi0][2] >= 1.0) continue; + if (projVec[vi1][2] <= -1.0 || projVec[vi1][2] >= 1.0) continue; + if (projVec[vi2][2] <= -1.0 || projVec[vi2][2] >= 1.0) continue; + float cx = (float)(projVec[vi0][0] + projVec[vi1][0] + projVec[vi2][0]) / 3.0f; + float cy = (float)(projVec[vi0][1] + projVec[vi1][1] + projVec[vi2][1]) / 3.0f; + int px = (int)cx; + int py = (int)cy; + if (px <= 0 || px >= viewpSize[2] || py <= 0 || py >= viewpSize[3]) continue; + if (bufQImg.pixel(px, py) == blk) + m.cm.face[fi].ClearS(); + } + cutPolyLine.clear(); + gla->updateSelection(m.id(), false, true); + gla->update(); + } + } + e->accept(); + break; + + case Qt::Key_D: // Deselect all (like edit_select D) + cutLog("Key D - deselect all"); + tri::UpdateSelection::FaceClear(m.cm); + hasPendingSelection = false; + cutPolyLine.clear(); + gla->updateSelection(m.id(), false, true); + gla->update(); + e->accept(); + break; + + case Qt::Key_A: // Select all faces + cutLog("Key A - select all"); + tri::UpdateSelection::FaceAll(m.cm); + hasPendingSelection = true; + gla->updateSelection(m.id(), false, true); + gla->update(); + e->accept(); + break; + + case Qt::Key_I: // Invert selection + cutLog("Key I - invert selection"); + tri::UpdateSelection::FaceInvert(m.cm); + hasPendingSelection = true; + gla->updateSelection(m.id(), false, true); + gla->update(); + e->accept(); + break; + + case Qt::Key_Return: // Confirm deletion of selected faces + case Qt::Key_Enter: + if (hasPendingSelection) { + cutLog("Key ENTER - deleting selected faces"); + deleteSelectedFaces(m, gla); + } + e->accept(); + break; + + case Qt::Key_Delete: // Also confirm deletion + if (hasPendingSelection) { + cutLog("Key DELETE - deleting selected faces"); + deleteSelectedFaces(m, gla); + } + e->accept(); + break; + + case Qt::Key_Escape: // Cancel everything + cutLog("Key ESC - cancel all"); + cutPolyLine.clear(); + hasPendingSelection = false; + tri::UpdateSelection::FaceClear(m.cm); + gla->updateSelection(m.id(), false, true); + gla->update(); + e->accept(); + break; + + default: + break; + } +} + +// ---- Decorate ---- + +void EditCutPlugin::decorate(MeshModel &m, GLArea *gla) +{ + glPushMatrix(); + glMultMatrix(m.cm.Tr); + GLPickTri::glGetMatrixAndViewport(this->SelMatrix, this->SelViewport); + glGetDoublev(GL_MODELVIEW_MATRIX, mvMatrix_f); + glGetDoublev(GL_PROJECTION_MATRIX, prMatrix_f); + glGetIntegerv(GL_VIEWPORT, viewpSize); + glPopMatrix(); + + DrawXORPolyLine(gla); + + if (hasPendingSelection) { + this->realTimeLog("Lasso Cut", m.shortName(), + "Selection preview
" + "Q=add to selection, W=subtract, D=deselect all, I=invert
" + "ENTER=confirm deletion, ESC=cancel"); + } else { + QString line3; + if (cutPolyLine.size() < 3) + line3 = "Need at least 3 points"; + else + line3 = "Q=cut and select"; + + this->realTimeLog("Lasso Cut", m.shortName(), + "Lasso Cut Tool v3 — click to add points
" + "C=clear, BACKSPACE=undo, ESC=cancel
%s", + line3.toStdString().c_str()); + } +} + +// ---- DrawXORPolyLine ---- + +void EditCutPlugin::DrawXORPolyLine(GLArea *gla) +{ + if (cutPolyLine.empty() || hasPendingSelection) + return; + + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + glOrtho(0, QTDeviceWidth(gla), 0, QTDeviceHeight(gla), -1, 1); + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + glPushAttrib(GL_ENABLE_BIT); + glDisable(GL_DEPTH_TEST); + glDisable(GL_LIGHTING); + glDisable(GL_TEXTURE_2D); + glEnable(GL_COLOR_LOGIC_OP); + glLogicOp(GL_XOR); + glColor3f(1, 1, 1); + glLineStipple(1, 0xAAAA); + glEnable(GL_LINE_STIPPLE); + glLineWidth(QTLogicalToDevice(gla, 1)); + + if (cutPolyLine.size() == 1) { + glBegin(GL_POINTS); + glVertex(cutPolyLine[0]); + } else if (cutPolyLine.size() == 2) { + glBegin(GL_LINES); + glVertex(cutPolyLine[0]); + glVertex(cutPolyLine[1]); + } else { + glBegin(GL_LINE_LOOP); + for (size_t ii = 0; ii < cutPolyLine.size(); ii++) + glVertex(cutPolyLine[ii]); + } + glEnd(); + + glDisable(GL_LOGIC_OP); + glPopAttrib(); + glPopMatrix(); + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); +} + +// ======================================================================== +// Phase 1: Cut edges along polyline boundary, then select faces inside +// ======================================================================== + +void EditCutPlugin::executeCut(MeshModel &m, GLArea *gla) +{ + cutLog(">>> executeCut ENTRY <<<"); + cutLogQ(QString("Viewport: %1 x %2").arg(viewpSize[2]).arg(viewpSize[3])); + cutLogQ(QString("Polyline points: %1").arg(cutPolyLine.size())); + cutLogQ(QString("Mesh verts: %1, faces: %2").arg(m.cm.vert.size()).arg(m.cm.face.size())); + + // Log polyline coordinates for debugging + for (size_t i = 0; i < cutPolyLine.size(); ++i) { + char buf[128]; + snprintf(buf, sizeof(buf), " polyline[%d] = (%.1f, %.1f)", (int)i, cutPolyLine[i][0], cutPolyLine[i][1]); + cutLog(buf); + } + + if (viewpSize[2] <= 0 || viewpSize[3] <= 0) { + cutLog("ABORT: viewport invalid"); + return; + } + if (cutPolyLine.size() < 3) { + cutLog("ABORT: polyline too small"); + return; + } + + try { + + int numPolySeg = (int)cutPolyLine.size(); + + // ---- Step 1: Project all vertices to screen space ---- + cutLog("Step 1: Projecting vertices..."); + vector projVec; + GLPickTri::FillProjectedVector(m.cm, projVec, this->SelMatrix, this->SelViewport); + cutLogQ(QString(" Projected %1 vertices").arg(projVec.size())); + + // ---- Step 2: Find which edges are crossed by the polyline ---- + cutLog("Step 2: Finding edge crossings..."); + typedef pair EdgeKey; + map> edgeSplitMap; + + int faceCount = (int)m.cm.face.size(); + int vertCount = (int)m.cm.vert.size(); + + for (size_t fi = 0; fi < (size_t)faceCount; ++fi) + { + if (m.cm.face[fi].IsD()) continue; + + for (int j = 0; j < 3; ++j) + { + int vi0 = tri::Index(m.cm, m.cm.face[fi].V(j)); + int vi1 = tri::Index(m.cm, m.cm.face[fi].V((j + 1) % 3)); + + if (vi0 < 0 || vi0 >= vertCount || vi1 < 0 || vi1 >= vertCount) + continue; + + EdgeKey ek(min(vi0, vi1), max(vi0, vi1)); + if (edgeSplitMap.count(ek)) continue; + + if (projVec[vi0][2] <= -1.0 || projVec[vi0][2] >= 1.0) continue; + if (projVec[vi1][2] <= -1.0 || projVec[vi1][2] >= 1.0) continue; + + Point2f a((float)projVec[vi0][0], (float)projVec[vi0][1]); + Point2f b((float)projVec[vi1][0], (float)projVec[vi1][1]); + + for (int k = 0; k < numPolySeg; ++k) + { + Point2f c = cutPolyLine[k]; + Point2f d = cutPolyLine[(k + 1) % numPolySeg]; + + float lambda0; + if (SegSegIntersect(a, b, c, d, lambda0)) + { + if (lambda0 < 0.005f || lambda0 > 0.995f) continue; + + float t = (vi0 < vi1) ? lambda0 : (1.0f - lambda0); + + Point3m p0 = m.cm.vert[ek.first].P(); + Point3m p1 = m.cm.vert[ek.second].P(); + Point3m pos = p0 * (Scalarm)(1.0 - t) + p1 * (Scalarm)t; + edgeSplitMap[ek] = make_pair(t, pos); + break; + } + } + } + } + + cutLogQ(QString(" Found %1 edge crossings").arg(edgeSplitMap.size())); + + // ---- Step 3: Create new split vertices ---- + if (!edgeSplitMap.empty()) + { + cutLog("Step 3: Creating split vertices..."); + map edgeToNewVert; + + int numNew = (int)edgeSplitMap.size(); + int firstNewIdx = (int)m.cm.vert.size(); + tri::Allocator::AddVertices(m.cm, numNew); + + int idx = firstNewIdx; + for (auto &entry : edgeSplitMap) + { + m.cm.vert[idx].P() = entry.second.second; + + float t = entry.second.first; + Point3m n0 = m.cm.vert[entry.first.first].N(); + Point3m n1 = m.cm.vert[entry.first.second].N(); + Point3m nn = n0 * (Scalarm)(1.0f - t) + n1 * (Scalarm)t; + Scalarm len = nn.Norm(); + if (len > 0) nn /= len; + m.cm.vert[idx].N() = nn; + + edgeToNewVert[entry.first] = idx; + idx++; + } + cutLogQ(QString(" Created %1 new vertices").arg(edgeToNewVert.size())); + + // ---- Step 4: Split faces ---- + cutLog("Step 4: Splitting faces..."); + size_t origFaceCount = m.cm.face.size(); + + int totalNewFaces = 0; + for (size_t fi = 0; fi < origFaceCount; ++fi) + { + if (m.cm.face[fi].IsD()) continue; + + int mask = 0; + for (int j = 0; j < 3; ++j) + { + int a = tri::Index(m.cm, m.cm.face[fi].V(j)); + int b = tri::Index(m.cm, m.cm.face[fi].V((j + 1) % 3)); + EdgeKey ek(min(a, b), max(a, b)); + if (edgeToNewVert.count(ek)) mask |= (1 << j); + } + if (mask > 0) + totalNewFaces += SplitTab[mask].TriNum - 1; + } + + cutLogQ(QString(" Need %1 new faces").arg(totalNewFaces)); + + if (totalNewFaces > 0) + { + int firstNewFace = (int)m.cm.face.size(); + tri::Allocator::AddFaces(m.cm, totalNewFaces); + int nextNewFace = firstNewFace; + + for (size_t fi = 0; fi < origFaceCount; ++fi) + { + if (m.cm.face[fi].IsD()) continue; + + int vIdx[3]; + vIdx[0] = tri::Index(m.cm, m.cm.face[fi].V(0)); + vIdx[1] = tri::Index(m.cm, m.cm.face[fi].V(1)); + vIdx[2] = tri::Index(m.cm, m.cm.face[fi].V(2)); + + int vv[6]; + vv[0] = vIdx[0]; + vv[1] = vIdx[1]; + vv[2] = vIdx[2]; + vv[3] = vv[4] = vv[5] = -1; + + int mask = 0; + for (int j = 0; j < 3; ++j) + { + int a = vIdx[j]; + int b = vIdx[(j + 1) % 3]; + EdgeKey ek(min(a, b), max(a, b)); + auto it = edgeToNewVert.find(ek); + if (it != edgeToNewVert.end()) + { + vv[3 + j] = it->second; + mask |= (1 << j); + } + } + + if (mask == 0) continue; + + const SplitEntry &se = SplitTab[mask]; + + // Validate all vertex indices + bool valid = true; + for (int i = 0; i < se.TriNum && valid; ++i) + for (int c = 0; c < 3; ++c) + if (vv[se.TV[i][c]] < 0 || vv[se.TV[i][c]] >= (int)m.cm.vert.size()) + valid = false; + if (!valid) continue; + + for (int i = 0; i < se.TriNum; ++i) + { + CMeshO::FacePointer fp; + if (i == 0) + fp = &m.cm.face[fi]; + else { + fp = &m.cm.face[nextNewFace]; + nextNewFace++; + } + + fp->V(0) = &m.cm.vert[vv[se.TV[i][0]]]; + fp->V(1) = &m.cm.vert[vv[se.TV[i][1]]]; + fp->V(2) = &m.cm.vert[vv[se.TV[i][2]]]; + } + } + cutLogQ(QString(" Split done, used %1 new face slots").arg(nextNewFace - firstNewFace)); + } + } + + // ---- Step 5: Select faces inside the polyline (preview only, no deletion) ---- + cutLog("Step 5: Selecting inside faces (preview)..."); + selectInsideFaces(m, gla); + + hasPendingSelection = true; + cutLog("=== executeCut DONE - awaiting confirmation ==="); + + } catch (std::exception &e) { + cutLogQ(QString("EXCEPTION: %1").arg(e.what())); + cutPolyLine.clear(); + hasPendingSelection = false; + } catch (...) { + cutLog("UNKNOWN EXCEPTION caught"); + cutPolyLine.clear(); + hasPendingSelection = false; + } +} + +// ======================================================================== +// Select faces whose ANY vertex is inside the polyline +// Uses same approach as edit_select (proven to work) +// ======================================================================== + +void EditCutPlugin::selectInsideFaces(MeshModel &m, GLArea *gla) +{ + cutLog(" selectInsideFaces START"); + + // Re-project all vertices (including newly created split vertices) + vector projVec; + GLPickTri::FillProjectedVector(m.cm, projVec, this->SelMatrix, this->SelViewport); + cutLogQ(QString(" Projected %1 vertices").arg(projVec.size())); + + // Rasterize polyline as filled polygon (same as edit_select) + QImage bufQImg(viewpSize[2], viewpSize[3], QImage::Format_RGB32); + bufQImg.fill(Qt::white); + QPainter bufQPainter(&bufQImg); + vector qpoints; + for (size_t i = 0; i < cutPolyLine.size(); ++i) + qpoints.push_back(QPointF(cutPolyLine[i][0], cutPolyLine[i][1])); + bufQPainter.setBrush(QBrush(Qt::black)); + bufQPainter.drawPolygon(&qpoints[0], (int)qpoints.size(), Qt::WindingFill); + bufQPainter.end(); + QRgb blk = QColor(Qt::black).rgb(); + + // Log some pixel checks for debugging + { + int cx = (int)cutPolyLine[0][0]; + int cy = (int)cutPolyLine[0][1]; + char buf[256]; + snprintf(buf, sizeof(buf), " QImage size: %dx%d, first polyline pt pixel(%d,%d)=%s", + bufQImg.width(), bufQImg.height(), cx, cy, + (cx >= 0 && cx < bufQImg.width() && cy >= 0 && cy < bufQImg.height()) + ? (bufQImg.pixel(cx, cy) == blk ? "BLACK" : "WHITE") : "OOB"); + cutLog(buf); + } + + // Don't clear existing selection — Q adds to selection (like edit_select) + + // Use face CENTROID for inside test — gives clean cut along the polyline + // (using "any vertex" would over-select at the boundary where split vertices + // sit exactly on the polyline, creating a saw-tooth pattern) + int selectedCount = 0; + for (size_t fi = 0; fi < m.cm.face.size(); ++fi) + { + if (m.cm.face[fi].IsD()) continue; + + int v0 = tri::Index(m.cm, m.cm.face[fi].V(0)); + int v1 = tri::Index(m.cm, m.cm.face[fi].V(1)); + int v2 = tri::Index(m.cm, m.cm.face[fi].V(2)); + if (v0 < 0 || v0 >= (int)projVec.size() || + v1 < 0 || v1 >= (int)projVec.size() || + v2 < 0 || v2 >= (int)projVec.size()) continue; + + // Skip if any vertex is behind camera + if (projVec[v0][2] <= -1.0 || projVec[v0][2] >= 1.0) continue; + if (projVec[v1][2] <= -1.0 || projVec[v1][2] >= 1.0) continue; + if (projVec[v2][2] <= -1.0 || projVec[v2][2] >= 1.0) continue; + + // Average projected 2D positions to get screen-space centroid + float cx = (float)(projVec[v0][0] + projVec[v1][0] + projVec[v2][0]) / 3.0f; + float cy = (float)(projVec[v0][1] + projVec[v1][1] + projVec[v2][1]) / 3.0f; + + int px = (int)cx; + int py = (int)cy; + if (px <= 0 || px >= viewpSize[2] || py <= 0 || py >= viewpSize[3]) continue; + + if (bufQImg.pixel(px, py) == blk) { + m.cm.face[fi].SetS(); + selectedCount++; + } + } + + cutLogQ(QString(" Selected %1 new faces (added to existing selection)").arg(selectedCount)); + + // Clear polyline so user can draw a new one for additional selection + cutPolyLine.clear(); + + // Tell MeshLab to update the selection display + gla->updateSelection(m.id(), false, true); + gla->update(); + + cutLog(" selectInsideFaces DONE"); +} + +// ======================================================================== +// Phase 2: Delete selected faces and clean up +// ======================================================================== + +void EditCutPlugin::deleteSelectedFaces(MeshModel &m, GLArea *gla) +{ + cutLog(">>> deleteSelectedFaces START <<<"); + + try { + + // Count and delete selected faces + int deletedCount = 0; + for (size_t fi = 0; fi < m.cm.face.size(); ++fi) + { + if (!m.cm.face[fi].IsD() && m.cm.face[fi].IsS()) { + tri::Allocator::DeleteFace(m.cm, m.cm.face[fi]); + deletedCount++; + } + } + cutLogQ(QString(" Deleted %1 faces").arg(deletedCount)); + + // Clean up + cutLog(" RemoveUnreferencedVertex..."); + tri::Clean::RemoveUnreferencedVertex(m.cm); + cutLog(" CompactEveryVector..."); + tri::Allocator::CompactEveryVector(m.cm); + + // Update normals and bounding box (skip FaceFace to avoid Missing Component) + cutLog(" UpdateNormal..."); + tri::UpdateNormal::PerFaceNormalized(m.cm); + tri::UpdateNormal::PerVertexNormalized(m.cm); + cutLog(" UpdateBounding..."); + tri::UpdateBounding::Box(m.cm); + + // Only update FaceFace topology if the mesh already has it enabled + if (tri::HasFFAdjacency(m.cm)) { + cutLog(" UpdateTopology::FaceFace (mesh has FFAdj)..."); + tri::UpdateTopology::FaceFace(m.cm); + } else { + cutLog(" Skipping FaceFace (mesh does not have FFAdj)"); + } + + cutLog(" Notifying MeshLab rendering system..."); + + // Force GPU buffer rebuild (same pattern as edit_paint) + if (gla->mvc() != NULL) { + MLSceneGLSharedDataContext* shared = gla->mvc()->sharedDataContext(); + if (shared != NULL) { + MLRenderingData::RendAtts atts; + atts[MLRenderingData::ATT_NAMES::ATT_VERTPOSITION] = true; + atts[MLRenderingData::ATT_NAMES::ATT_VERTNORMAL] = true; + atts[MLRenderingData::ATT_NAMES::ATT_FACENORMAL] = true; + shared->meshAttributesUpdated(m.id(), true, atts); + shared->manageBuffers(m.id()); + cutLog(" GPU buffers rebuilt"); + } + } + + cutLogQ(QString(" Final mesh: %1 verts, %2 faces").arg(m.cm.vert.size()).arg(m.cm.face.size())); + + cutPolyLine.clear(); + hasPendingSelection = false; + + gla->updateAllSiblingsGLAreas(); + gla->update(); + cutLog("=== deleteSelectedFaces DONE ==="); + + } catch (std::exception &e) { + cutLogQ(QString("EXCEPTION in delete: %1").arg(e.what())); + cutPolyLine.clear(); + hasPendingSelection = false; + } catch (...) { + cutLog("UNKNOWN EXCEPTION in delete"); + cutPolyLine.clear(); + hasPendingSelection = false; + } +} diff --git a/src/meshlabplugins/edit_cut/edit_cut.h b/src/meshlabplugins/edit_cut/edit_cut.h new file mode 100644 index 0000000000..3cfd412f84 --- /dev/null +++ b/src/meshlabplugins/edit_cut/edit_cut.h @@ -0,0 +1,51 @@ +#ifndef EDITCUTPLUGIN_H +#define EDITCUTPLUGIN_H + +#include +#include +#include + +class EditCutPlugin : public QObject, public EditTool +{ + Q_OBJECT + +public: + EditCutPlugin(); + virtual ~EditCutPlugin() {} + + static const QString info(); + + void suggestedRenderingData(MeshModel &m, MLRenderingData &dt); + bool startEdit(MeshModel &m, GLArea *parent, MLSceneGLSharedDataContext *cont); + void endEdit(MeshModel &m, GLArea *parent, MLSceneGLSharedDataContext *cont); + void decorate(MeshModel &m, GLArea *parent); + void mousePressEvent(QMouseEvent *event, MeshModel &m, GLArea *gla); + void mouseMoveEvent(QMouseEvent *event, MeshModel &m, GLArea *gla); + void mouseReleaseEvent(QMouseEvent *event, MeshModel &m, GLArea *gla); + void keyReleaseEvent(QKeyEvent *e, MeshModel &m, GLArea *gla); + void keyPressEvent(QKeyEvent *e, MeshModel &m, GLArea *gla); + +private: + // Polyline state + std::vector cutPolyLine; + + // Two-phase state: false = drawing, true = previewing selection + bool hasPendingSelection; + + // Projection data + GLdouble mvMatrix_f[16]; + GLdouble prMatrix_f[16]; + GLint viewpSize[4]; + Eigen::Matrix SelMatrix; + Scalarm SelViewport[4]; + + // Drawing helpers + void DrawXORPolyLine(GLArea *gla); + + // Core algorithm + void executeCut(MeshModel &m, GLArea *gla); + void selectInsideFaces(MeshModel &m, GLArea *gla); + void deleteSelectedFaces(MeshModel &m, GLArea *gla); +}; + +#endif diff --git a/src/meshlabplugins/edit_cut/edit_cut.qrc b/src/meshlabplugins/edit_cut/edit_cut.qrc new file mode 100644 index 0000000000..7808bf43ba --- /dev/null +++ b/src/meshlabplugins/edit_cut/edit_cut.qrc @@ -0,0 +1,5 @@ + + + images/icon_cut.png + + diff --git a/src/meshlabplugins/edit_cut/edit_cut_factory.cpp b/src/meshlabplugins/edit_cut/edit_cut_factory.cpp new file mode 100644 index 0000000000..6f84147f58 --- /dev/null +++ b/src/meshlabplugins/edit_cut/edit_cut_factory.cpp @@ -0,0 +1,31 @@ +#include "edit_cut_factory.h" +#include "edit_cut.h" + +EditCutFactory::EditCutFactory() +{ + editCut = new QAction(QIcon(":/images/icon_cut.png"), "Lasso Cut Tool", this); + actionList.push_back(editCut); + + foreach(QAction *editAction, actionList) + editAction->setCheckable(true); +} + +QString EditCutFactory::pluginName() const +{ + return "EditCut"; +} + +EditTool* EditCutFactory::getEditTool(const QAction *action) +{ + if (action == editCut) + return new EditCutPlugin(); + assert(0); + return nullptr; +} + +QString EditCutFactory::getEditToolDescription(const QAction *) +{ + return EditCutPlugin::info(); +} + +MESHLAB_PLUGIN_NAME_EXPORTER(EditCutFactory) diff --git a/src/meshlabplugins/edit_cut/edit_cut_factory.h b/src/meshlabplugins/edit_cut/edit_cut_factory.h new file mode 100644 index 0000000000..187334d2fe --- /dev/null +++ b/src/meshlabplugins/edit_cut/edit_cut_factory.h @@ -0,0 +1,26 @@ +#ifndef EDITCUTFACTORYPLUGIN_H +#define EDITCUTFACTORYPLUGIN_H + +#include + +class EditCutFactory : public QObject, public EditPlugin +{ + Q_OBJECT + MESHLAB_PLUGIN_IID_EXPORTER(EDIT_PLUGIN_IID) + Q_INTERFACES(EditPlugin) + +public: + EditCutFactory(); + virtual ~EditCutFactory() { delete editCut; } + + virtual void initGlobalParameterList(RichParameterList& /*defaultGlobalParamSet*/) {} + + virtual QString pluginName() const; + virtual EditTool* getEditTool(const QAction*); + virtual QString getEditToolDescription(const QAction*); + +private: + QAction *editCut; +}; + +#endif diff --git a/src/meshlabplugins/edit_cut/images/icon_cut.png b/src/meshlabplugins/edit_cut/images/icon_cut.png new file mode 100644 index 0000000000000000000000000000000000000000..be598b0ce6a76089d195a8b833f350979497ea8b GIT binary patch literal 1970 zcmV;j2Tk~iP)jfxJ)yrBrJwB?T#@6=TO% zryxyJY_-xEKiFCo9Y<%BFP-X4JLAjPGIhoo`i45%!RmBW#y6;<)*=X!K!lW_m}VrQ zP}`DbZju{w?>>Im-DG=T&%N2Wb#nhRJLjA|&w2Ja|Li&Y-zR%TRhiAOq7EQxlnIDB zfT%I+39x6vsmT1}P|+BkM^%V_MOAl%=hMZE5CANJebuliBzIp=!4owhIeQakiU6Pq zXb8#IjLaP}$rc z!X%)QH{^a;{5fbTW&AndqcHFiJeS^A5&;b&bsvI`C!le0mj1AFOp27eR{KG>K>tD5 zrK-vF{-OzJ5m|RHT>l`{OYvAn%FuENM9#h?Kba`?R&?ItMJGAeFx`n2mBF48N{uDfaT=hsX<1z?X zEz))$ta$(~HgT{A7N3U&64?0%ajg1cq9}lIQglQ8FucoKhN7+j0+0icI0HQ^V8s@Z zYizJQK_ub{p)s)k0Pz9U?+1NP5+EX#t#HSqaCw`--jD$6X}D@NEVw1jzL#(x0|}D6 z7v^usl4R_G#7!`;*j=VevyFchxC;gb;hFS48{kTjrrV+YDOh-^K|ue6d3&MxV^CEa zn5+g$C#|hnY)s1$U<`E3JLm5jeK1-Ny>*aNcn$z}L;s&#AvBTRZv(`j`8=Gv70&s$ za>_GEZzti(Rj_0$jP$z)k|un956&rCfLc!z$dos)p6$G5Z41gVYUzBv8TtS$?|N^8 z*J@!j*W^!wejH98bcIkVn~e={QZ+*(r$aPYA_YVe=!f`~yV_A#hi5Y46(p7B{tIg{0BHMt%Zg-+)71s?VEVEo+RI zi!`=F$CFTJqW#|rcoIgw121%|9x7&B1_5hC*53nbO@+|UVaYJm2A>22I96EOAGkW8 z8ye%VQULWbKYslPj1R!c)v)p&krwf?Mt-Kq26^2TLc@cq-wpZ>3ZXgc;ltbE3KJm| zhl;1+>i5AV&1usAWXM8joD^*^cLQX|lwL^O2xpqyKR~wp%%m1L3%k<$Y=EmoF53hh zJ3JvY%G65CR`x)37rf(csHy@IUY~ypB)lV=s3}zl4S`-ri)9cJi=ej=hO$i@3Tpuy z;3B9WguZs@lR%s)Q2*BU&pWX4MyUTJy!Hyb`ikes7bgn(0&>HPv;hkOf+t-7Wwvt* zpgRj?yzm zemNzE5>kv1`ZbI{0LMC2_Y|@%NWi;A-v23BwGFCF1Go(&NiY5o|K+F{QRJ4w1Qsd& z!L^1$=tnTN9S(P@{?WufpAfoeJ>2;iEWOS&!IR4uOp4s%6qVxA;4t67dIyX?=4yez zm|h_(0q+!9b{A}X92QoadWsldgZM7kcS7}8G2=1_SSxb#R=D=_7?J*#z&04(4Zk_1 z+FR_{pb(nd1|NAGnpREomj7SD0k*=>UfBJTYJVv{Y=CP-mT!PtABCDK;4JxtP`*BJ zpOGoT%Lw`9#Zy`Y2lycj9e`*1RbMunZYFHcdF23f-5C%-1Vm8Ms|VZJ`TOIL=ve0_J{k~SpYH< zKb;kkFA?7Cef&;~`1Z#XGeW@s6K4G$P$nSi0HVf!0TFSY5i7ubl>h($07*qoM6N<$ Eg68maxc~qF literal 0 HcmV?d00001 From 170a1c7663b01a06a9328d30b5cfe67f37601bbf Mon Sep 17 00:00:00 2001 From: Z3roCo0l <104153656+Z3roCo0l@users.noreply.github.com> Date: Wed, 11 Feb 2026 21:32:12 +0100 Subject: [PATCH 4/5] Delegate face deletion to filter_select, add Dynart toolbar Remove deleteSelectedFaces from edit_cut and let filter_select's standard Delete shortcut handle deletion (proper undo/redo support). Preserve selection across endEdit/Escape so the shortcut works. Move edit_cut button from Edit toolbar to new Dynart Tools toolbar. Co-Authored-By: Claude Opus 4.6 --- src/meshlab/mainwindow.h | 1 + src/meshlab/mainwindow_Init.cpp | 11 ++ src/meshlabplugins/edit_cut/edit_cut.cpp | 166 +++-------------------- src/meshlabplugins/edit_cut/edit_cut.h | 4 - 4 files changed, 32 insertions(+), 150 deletions(-) diff --git a/src/meshlab/mainwindow.h b/src/meshlab/mainwindow.h index 295bfdc105..56d2f64540 100644 --- a/src/meshlab/mainwindow.h +++ b/src/meshlab/mainwindow.h @@ -374,6 +374,7 @@ private slots: QToolBar* decoratorToolBar; QToolBar* editToolBar; QToolBar* filterToolBar; + QToolBar* dynartToolBar; QToolBar* searchToolBar; MLRenderingGlobalToolbar* globrendtoolbar; ///////// Menus /////////////// diff --git a/src/meshlab/mainwindow_Init.cpp b/src/meshlab/mainwindow_Init.cpp index e2fbe1ac7d..3bcd0f3fac 100644 --- a/src/meshlab/mainwindow_Init.cpp +++ b/src/meshlab/mainwindow_Init.cpp @@ -520,6 +520,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); @@ -534,6 +535,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); diff --git a/src/meshlabplugins/edit_cut/edit_cut.cpp b/src/meshlabplugins/edit_cut/edit_cut.cpp index 29dd8c6663..c6afb47bb6 100644 --- a/src/meshlabplugins/edit_cut/edit_cut.cpp +++ b/src/meshlabplugins/edit_cut/edit_cut.cpp @@ -5,10 +5,6 @@ #include #include #include -#include -#include -#include -#include #include #include @@ -90,7 +86,6 @@ static inline bool SegSegIntersect( EditCutPlugin::EditCutPlugin() { - hasPendingSelection = false; memset(viewpSize, 0, sizeof(viewpSize)); memset(mvMatrix_f, 0, sizeof(mvMatrix_f)); memset(prMatrix_f, 0, sizeof(prMatrix_f)); @@ -126,43 +121,33 @@ bool EditCutPlugin::startEdit(MeshModel & /*m*/, GLArea *gla, MLSceneGLSharedDat gla->setCursor(Qt::CrossCursor); cutPolyLine.clear(); - hasPendingSelection = false; cutLog("startEdit OK"); return true; } -void EditCutPlugin::endEdit(MeshModel &m, GLArea * /*parent*/, MLSceneGLSharedDataContext * /*cont*/) +void EditCutPlugin::endEdit(MeshModel & /*m*/, GLArea * /*parent*/, MLSceneGLSharedDataContext * /*cont*/) { cutPolyLine.clear(); - hasPendingSelection = false; - tri::UpdateSelection::FaceClear(m.cm); } // ---- Mouse Events ---- void EditCutPlugin::mousePressEvent(QMouseEvent *event, MeshModel &m, GLArea *gla) { - if (hasPendingSelection) { - // Cancel pending selection if user clicks again - hasPendingSelection = false; - tri::UpdateSelection::FaceClear(m.cm); - cutPolyLine.clear(); - gla->updateSelection(m.id(), false, true); - } cutPolyLine.push_back(QTLogicalToOpenGL(gla, event->pos())); gla->update(); } void EditCutPlugin::mouseMoveEvent(QMouseEvent *event, MeshModel & /*m*/, GLArea *gla) { - if (!cutPolyLine.empty() && !hasPendingSelection) + if (!cutPolyLine.empty()) cutPolyLine.back() = QTLogicalToOpenGL(gla, event->pos()); gla->update(); } void EditCutPlugin::mouseReleaseEvent(QMouseEvent *event, MeshModel & /*m*/, GLArea *gla) { - if (!cutPolyLine.empty() && !hasPendingSelection) + if (!cutPolyLine.empty()) cutPolyLine.back() = QTLogicalToOpenGL(gla, event->pos()); } @@ -234,7 +219,7 @@ void EditCutPlugin::keyReleaseEvent(QKeyEvent *e, MeshModel &m, GLArea *gla) if (bufQImg.pixel(px, py) == blk) m.cm.face[fi].ClearS(); } - cutPolyLine.clear(); + // Polyline stays visible gla->updateSelection(m.id(), false, true); gla->update(); } @@ -245,8 +230,6 @@ void EditCutPlugin::keyReleaseEvent(QKeyEvent *e, MeshModel &m, GLArea *gla) case Qt::Key_D: // Deselect all (like edit_select D) cutLog("Key D - deselect all"); tri::UpdateSelection::FaceClear(m.cm); - hasPendingSelection = false; - cutPolyLine.clear(); gla->updateSelection(m.id(), false, true); gla->update(); e->accept(); @@ -255,7 +238,6 @@ void EditCutPlugin::keyReleaseEvent(QKeyEvent *e, MeshModel &m, GLArea *gla) case Qt::Key_A: // Select all faces cutLog("Key A - select all"); tri::UpdateSelection::FaceAll(m.cm); - hasPendingSelection = true; gla->updateSelection(m.id(), false, true); gla->update(); e->accept(); @@ -264,35 +246,14 @@ void EditCutPlugin::keyReleaseEvent(QKeyEvent *e, MeshModel &m, GLArea *gla) case Qt::Key_I: // Invert selection cutLog("Key I - invert selection"); tri::UpdateSelection::FaceInvert(m.cm); - hasPendingSelection = true; gla->updateSelection(m.id(), false, true); gla->update(); e->accept(); break; - case Qt::Key_Return: // Confirm deletion of selected faces - case Qt::Key_Enter: - if (hasPendingSelection) { - cutLog("Key ENTER - deleting selected faces"); - deleteSelectedFaces(m, gla); - } - e->accept(); - break; - - case Qt::Key_Delete: // Also confirm deletion - if (hasPendingSelection) { - cutLog("Key DELETE - deleting selected faces"); - deleteSelectedFaces(m, gla); - } - e->accept(); - break; - - case Qt::Key_Escape: // Cancel everything - cutLog("Key ESC - cancel all"); + case Qt::Key_Escape: // Clear polyline only (D=deselect, Delete=delete selected) + cutLog("Key ESC - clearing polyline"); cutPolyLine.clear(); - hasPendingSelection = false; - tri::UpdateSelection::FaceClear(m.cm); - gla->updateSelection(m.id(), false, true); gla->update(); e->accept(); break; @@ -316,30 +277,24 @@ void EditCutPlugin::decorate(MeshModel &m, GLArea *gla) DrawXORPolyLine(gla); - if (hasPendingSelection) { - this->realTimeLog("Lasso Cut", m.shortName(), - "Selection preview
" - "Q=add to selection, W=subtract, D=deselect all, I=invert
" - "ENTER=confirm deletion, ESC=cancel"); - } else { - QString line3; - if (cutPolyLine.size() < 3) - line3 = "Need at least 3 points"; - else - line3 = "Q=cut and select"; - - this->realTimeLog("Lasso Cut", m.shortName(), - "Lasso Cut Tool v3 — click to add points
" - "C=clear, BACKSPACE=undo, ESC=cancel
%s", - line3.toStdString().c_str()); - } + QString line2; + if (cutPolyLine.size() < 3) + line2 = "Need at least 3 points"; + else + line2 = "Q=cut and select, W=subtract"; + + this->realTimeLog("Lasso Cut", m.shortName(), + "Click to add points — C=clear, BACKSPACE=undo, ESC=cancel
" + "%s
" + "D=deselect all, A=select all, I=invert — DELETE=delete selected", + line2.toStdString().c_str()); } // ---- DrawXORPolyLine ---- void EditCutPlugin::DrawXORPolyLine(GLArea *gla) { - if (cutPolyLine.empty() || hasPendingSelection) + if (cutPolyLine.empty()) return; glMatrixMode(GL_PROJECTION); @@ -593,17 +548,14 @@ void EditCutPlugin::executeCut(MeshModel &m, GLArea *gla) cutLog("Step 5: Selecting inside faces (preview)..."); selectInsideFaces(m, gla); - hasPendingSelection = true; - cutLog("=== executeCut DONE - awaiting confirmation ==="); + cutLog("=== executeCut DONE ==="); } catch (std::exception &e) { cutLogQ(QString("EXCEPTION: %1").arg(e.what())); cutPolyLine.clear(); - hasPendingSelection = false; } catch (...) { cutLog("UNKNOWN EXCEPTION caught"); cutPolyLine.clear(); - hasPendingSelection = false; } } @@ -683,8 +635,7 @@ void EditCutPlugin::selectInsideFaces(MeshModel &m, GLArea *gla) cutLogQ(QString(" Selected %1 new faces (added to existing selection)").arg(selectedCount)); - // Clear polyline so user can draw a new one for additional selection - cutPolyLine.clear(); + // Polyline stays visible — user can add more points and press Q again // Tell MeshLab to update the selection display gla->updateSelection(m.id(), false, true); @@ -693,80 +644,3 @@ void EditCutPlugin::selectInsideFaces(MeshModel &m, GLArea *gla) cutLog(" selectInsideFaces DONE"); } -// ======================================================================== -// Phase 2: Delete selected faces and clean up -// ======================================================================== - -void EditCutPlugin::deleteSelectedFaces(MeshModel &m, GLArea *gla) -{ - cutLog(">>> deleteSelectedFaces START <<<"); - - try { - - // Count and delete selected faces - int deletedCount = 0; - for (size_t fi = 0; fi < m.cm.face.size(); ++fi) - { - if (!m.cm.face[fi].IsD() && m.cm.face[fi].IsS()) { - tri::Allocator::DeleteFace(m.cm, m.cm.face[fi]); - deletedCount++; - } - } - cutLogQ(QString(" Deleted %1 faces").arg(deletedCount)); - - // Clean up - cutLog(" RemoveUnreferencedVertex..."); - tri::Clean::RemoveUnreferencedVertex(m.cm); - cutLog(" CompactEveryVector..."); - tri::Allocator::CompactEveryVector(m.cm); - - // Update normals and bounding box (skip FaceFace to avoid Missing Component) - cutLog(" UpdateNormal..."); - tri::UpdateNormal::PerFaceNormalized(m.cm); - tri::UpdateNormal::PerVertexNormalized(m.cm); - cutLog(" UpdateBounding..."); - tri::UpdateBounding::Box(m.cm); - - // Only update FaceFace topology if the mesh already has it enabled - if (tri::HasFFAdjacency(m.cm)) { - cutLog(" UpdateTopology::FaceFace (mesh has FFAdj)..."); - tri::UpdateTopology::FaceFace(m.cm); - } else { - cutLog(" Skipping FaceFace (mesh does not have FFAdj)"); - } - - cutLog(" Notifying MeshLab rendering system..."); - - // Force GPU buffer rebuild (same pattern as edit_paint) - if (gla->mvc() != NULL) { - MLSceneGLSharedDataContext* shared = gla->mvc()->sharedDataContext(); - if (shared != NULL) { - MLRenderingData::RendAtts atts; - atts[MLRenderingData::ATT_NAMES::ATT_VERTPOSITION] = true; - atts[MLRenderingData::ATT_NAMES::ATT_VERTNORMAL] = true; - atts[MLRenderingData::ATT_NAMES::ATT_FACENORMAL] = true; - shared->meshAttributesUpdated(m.id(), true, atts); - shared->manageBuffers(m.id()); - cutLog(" GPU buffers rebuilt"); - } - } - - cutLogQ(QString(" Final mesh: %1 verts, %2 faces").arg(m.cm.vert.size()).arg(m.cm.face.size())); - - cutPolyLine.clear(); - hasPendingSelection = false; - - gla->updateAllSiblingsGLAreas(); - gla->update(); - cutLog("=== deleteSelectedFaces DONE ==="); - - } catch (std::exception &e) { - cutLogQ(QString("EXCEPTION in delete: %1").arg(e.what())); - cutPolyLine.clear(); - hasPendingSelection = false; - } catch (...) { - cutLog("UNKNOWN EXCEPTION in delete"); - cutPolyLine.clear(); - hasPendingSelection = false; - } -} diff --git a/src/meshlabplugins/edit_cut/edit_cut.h b/src/meshlabplugins/edit_cut/edit_cut.h index 3cfd412f84..2a9e1d2955 100644 --- a/src/meshlabplugins/edit_cut/edit_cut.h +++ b/src/meshlabplugins/edit_cut/edit_cut.h @@ -29,9 +29,6 @@ class EditCutPlugin : public QObject, public EditTool // Polyline state std::vector cutPolyLine; - // Two-phase state: false = drawing, true = previewing selection - bool hasPendingSelection; - // Projection data GLdouble mvMatrix_f[16]; GLdouble prMatrix_f[16]; @@ -45,7 +42,6 @@ class EditCutPlugin : public QObject, public EditTool // Core algorithm void executeCut(MeshModel &m, GLArea *gla); void selectInsideFaces(MeshModel &m, GLArea *gla); - void deleteSelectedFaces(MeshModel &m, GLArea *gla); }; #endif From d3e1072e7494bfb34bd1bd0447b3588021a93cb9 Mon Sep 17 00:00:00 2001 From: Z3roCo0l <104153656+Z3roCo0l@users.noreply.github.com> Date: Wed, 11 Feb 2026 23:27:35 +0100 Subject: [PATCH 5/5] Remove debug logging from edit_cut Strip cutLog/cutLogQ calls, LOG_PATH, and windows.h/cstdio includes. Clean production code ready for release. Co-Authored-By: Claude Opus 4.6 --- src/meshlabplugins/edit_cut/edit_cut.cpp | 194 +++++------------------ 1 file changed, 43 insertions(+), 151 deletions(-) diff --git a/src/meshlabplugins/edit_cut/edit_cut.cpp b/src/meshlabplugins/edit_cut/edit_cut.cpp index c6afb47bb6..947110f58c 100644 --- a/src/meshlabplugins/edit_cut/edit_cut.cpp +++ b/src/meshlabplugins/edit_cut/edit_cut.cpp @@ -10,29 +10,6 @@ #include #include -#include -#include - -static const char* LOG_PATH = "C:\\Projects\\cut_debug.log"; - -static void cutLog(const char *msg) -{ - FILE *fp = fopen(LOG_PATH, "a"); - if (fp) { - fprintf(fp, "%s\n", msg); - fflush(fp); - fclose(fp); - } - OutputDebugStringA("[edit_cut] "); - OutputDebugStringA(msg); - OutputDebugStringA("\n"); -} - -static void cutLogQ(const QString &msg) -{ - cutLog(msg.toUtf8().constData()); -} - using namespace std; using namespace vcg; @@ -110,10 +87,6 @@ void EditCutPlugin::suggestedRenderingData(MeshModel & /*m*/, MLRenderingData &d bool EditCutPlugin::startEdit(MeshModel & /*m*/, GLArea *gla, MLSceneGLSharedDataContext * /*cont*/) { - // Clear log file on tool activation - FILE *fp = fopen(LOG_PATH, "w"); - if (fp) { fprintf(fp, "=== edit_cut plugin v3 loaded ===\n"); fflush(fp); fclose(fp); } - if (gla == NULL) return false; if (!GLExtensionsManager::initializeGLextensions_notThrowing()) @@ -121,7 +94,6 @@ bool EditCutPlugin::startEdit(MeshModel & /*m*/, GLArea *gla, MLSceneGLSharedDat gla->setCursor(Qt::CrossCursor); cutPolyLine.clear(); - cutLog("startEdit OK"); return true; } @@ -159,7 +131,6 @@ void EditCutPlugin::keyReleaseEvent(QKeyEvent *e, MeshModel &m, GLArea *gla) { switch (e->key()) { case Qt::Key_C: // Clear polyline - cutLog("Key C - clearing polyline"); cutPolyLine.clear(); gla->update(); e->accept(); @@ -174,61 +145,53 @@ void EditCutPlugin::keyReleaseEvent(QKeyEvent *e, MeshModel &m, GLArea *gla) break; case Qt::Key_Q: // Cut + add to selection (like edit_select Q) - if (cutPolyLine.size() >= 3) { - cutLog("Key Q - cut and add to selection"); + if (cutPolyLine.size() >= 3) executeCut(m, gla); - } e->accept(); break; case Qt::Key_W: // Subtract from selection (like edit_select W) if (cutPolyLine.size() >= 3) { - cutLog("Key W - subtract from selection"); - // Select faces inside polyline, then CLEAR their selection - { - vector projVec; - GLPickTri::FillProjectedVector(m.cm, projVec, this->SelMatrix, this->SelViewport); - - QImage bufQImg(viewpSize[2], viewpSize[3], QImage::Format_RGB32); - bufQImg.fill(Qt::white); - QPainter bufQPainter(&bufQImg); - vector qpoints; - for (size_t i = 0; i < cutPolyLine.size(); ++i) - qpoints.push_back(QPointF(cutPolyLine[i][0], cutPolyLine[i][1])); - bufQPainter.setBrush(QBrush(Qt::black)); - bufQPainter.drawPolygon(&qpoints[0], (int)qpoints.size(), Qt::WindingFill); - bufQPainter.end(); - QRgb blk = QColor(Qt::black).rgb(); - - for (size_t fi = 0; fi < m.cm.face.size(); ++fi) { - if (m.cm.face[fi].IsD() || !m.cm.face[fi].IsS()) continue; - int vi0 = tri::Index(m.cm, m.cm.face[fi].V(0)); - int vi1 = tri::Index(m.cm, m.cm.face[fi].V(1)); - int vi2 = tri::Index(m.cm, m.cm.face[fi].V(2)); - if (vi0 < 0 || vi0 >= (int)projVec.size() || - vi1 < 0 || vi1 >= (int)projVec.size() || - vi2 < 0 || vi2 >= (int)projVec.size()) continue; - if (projVec[vi0][2] <= -1.0 || projVec[vi0][2] >= 1.0) continue; - if (projVec[vi1][2] <= -1.0 || projVec[vi1][2] >= 1.0) continue; - if (projVec[vi2][2] <= -1.0 || projVec[vi2][2] >= 1.0) continue; - float cx = (float)(projVec[vi0][0] + projVec[vi1][0] + projVec[vi2][0]) / 3.0f; - float cy = (float)(projVec[vi0][1] + projVec[vi1][1] + projVec[vi2][1]) / 3.0f; - int px = (int)cx; - int py = (int)cy; - if (px <= 0 || px >= viewpSize[2] || py <= 0 || py >= viewpSize[3]) continue; - if (bufQImg.pixel(px, py) == blk) - m.cm.face[fi].ClearS(); - } - // Polyline stays visible - gla->updateSelection(m.id(), false, true); - gla->update(); + vector projVec; + GLPickTri::FillProjectedVector(m.cm, projVec, this->SelMatrix, this->SelViewport); + + QImage bufQImg(viewpSize[2], viewpSize[3], QImage::Format_RGB32); + bufQImg.fill(Qt::white); + QPainter bufQPainter(&bufQImg); + vector qpoints; + for (size_t i = 0; i < cutPolyLine.size(); ++i) + qpoints.push_back(QPointF(cutPolyLine[i][0], cutPolyLine[i][1])); + bufQPainter.setBrush(QBrush(Qt::black)); + bufQPainter.drawPolygon(&qpoints[0], (int)qpoints.size(), Qt::WindingFill); + bufQPainter.end(); + QRgb blk = QColor(Qt::black).rgb(); + + for (size_t fi = 0; fi < m.cm.face.size(); ++fi) { + if (m.cm.face[fi].IsD() || !m.cm.face[fi].IsS()) continue; + int vi0 = tri::Index(m.cm, m.cm.face[fi].V(0)); + int vi1 = tri::Index(m.cm, m.cm.face[fi].V(1)); + int vi2 = tri::Index(m.cm, m.cm.face[fi].V(2)); + if (vi0 < 0 || vi0 >= (int)projVec.size() || + vi1 < 0 || vi1 >= (int)projVec.size() || + vi2 < 0 || vi2 >= (int)projVec.size()) continue; + if (projVec[vi0][2] <= -1.0 || projVec[vi0][2] >= 1.0) continue; + if (projVec[vi1][2] <= -1.0 || projVec[vi1][2] >= 1.0) continue; + if (projVec[vi2][2] <= -1.0 || projVec[vi2][2] >= 1.0) continue; + float cx = (float)(projVec[vi0][0] + projVec[vi1][0] + projVec[vi2][0]) / 3.0f; + float cy = (float)(projVec[vi0][1] + projVec[vi1][1] + projVec[vi2][1]) / 3.0f; + int px = (int)cx; + int py = (int)cy; + if (px <= 0 || px >= viewpSize[2] || py <= 0 || py >= viewpSize[3]) continue; + if (bufQImg.pixel(px, py) == blk) + m.cm.face[fi].ClearS(); } + gla->updateSelection(m.id(), false, true); + gla->update(); } e->accept(); break; - case Qt::Key_D: // Deselect all (like edit_select D) - cutLog("Key D - deselect all"); + case Qt::Key_D: // Deselect all tri::UpdateSelection::FaceClear(m.cm); gla->updateSelection(m.id(), false, true); gla->update(); @@ -236,7 +199,6 @@ void EditCutPlugin::keyReleaseEvent(QKeyEvent *e, MeshModel &m, GLArea *gla) break; case Qt::Key_A: // Select all faces - cutLog("Key A - select all"); tri::UpdateSelection::FaceAll(m.cm); gla->updateSelection(m.id(), false, true); gla->update(); @@ -244,7 +206,6 @@ void EditCutPlugin::keyReleaseEvent(QKeyEvent *e, MeshModel &m, GLArea *gla) break; case Qt::Key_I: // Invert selection - cutLog("Key I - invert selection"); tri::UpdateSelection::FaceInvert(m.cm); gla->updateSelection(m.id(), false, true); gla->update(); @@ -252,7 +213,6 @@ void EditCutPlugin::keyReleaseEvent(QKeyEvent *e, MeshModel &m, GLArea *gla) break; case Qt::Key_Escape: // Clear polyline only (D=deselect, Delete=delete selected) - cutLog("Key ESC - clearing polyline"); cutPolyLine.clear(); gla->update(); e->accept(); @@ -338,44 +298,25 @@ void EditCutPlugin::DrawXORPolyLine(GLArea *gla) } // ======================================================================== -// Phase 1: Cut edges along polyline boundary, then select faces inside +// Cut edges along polyline boundary, then select faces inside // ======================================================================== void EditCutPlugin::executeCut(MeshModel &m, GLArea *gla) { - cutLog(">>> executeCut ENTRY <<<"); - cutLogQ(QString("Viewport: %1 x %2").arg(viewpSize[2]).arg(viewpSize[3])); - cutLogQ(QString("Polyline points: %1").arg(cutPolyLine.size())); - cutLogQ(QString("Mesh verts: %1, faces: %2").arg(m.cm.vert.size()).arg(m.cm.face.size())); - - // Log polyline coordinates for debugging - for (size_t i = 0; i < cutPolyLine.size(); ++i) { - char buf[128]; - snprintf(buf, sizeof(buf), " polyline[%d] = (%.1f, %.1f)", (int)i, cutPolyLine[i][0], cutPolyLine[i][1]); - cutLog(buf); - } - - if (viewpSize[2] <= 0 || viewpSize[3] <= 0) { - cutLog("ABORT: viewport invalid"); + if (viewpSize[2] <= 0 || viewpSize[3] <= 0) return; - } - if (cutPolyLine.size() < 3) { - cutLog("ABORT: polyline too small"); + if (cutPolyLine.size() < 3) return; - } try { int numPolySeg = (int)cutPolyLine.size(); // ---- Step 1: Project all vertices to screen space ---- - cutLog("Step 1: Projecting vertices..."); vector projVec; GLPickTri::FillProjectedVector(m.cm, projVec, this->SelMatrix, this->SelViewport); - cutLogQ(QString(" Projected %1 vertices").arg(projVec.size())); // ---- Step 2: Find which edges are crossed by the polyline ---- - cutLog("Step 2: Finding edge crossings..."); typedef pair EdgeKey; map> edgeSplitMap; @@ -425,12 +366,9 @@ void EditCutPlugin::executeCut(MeshModel &m, GLArea *gla) } } - cutLogQ(QString(" Found %1 edge crossings").arg(edgeSplitMap.size())); - // ---- Step 3: Create new split vertices ---- if (!edgeSplitMap.empty()) { - cutLog("Step 3: Creating split vertices..."); map edgeToNewVert; int numNew = (int)edgeSplitMap.size(); @@ -453,10 +391,8 @@ void EditCutPlugin::executeCut(MeshModel &m, GLArea *gla) edgeToNewVert[entry.first] = idx; idx++; } - cutLogQ(QString(" Created %1 new vertices").arg(edgeToNewVert.size())); // ---- Step 4: Split faces ---- - cutLog("Step 4: Splitting faces..."); size_t origFaceCount = m.cm.face.size(); int totalNewFaces = 0; @@ -476,8 +412,6 @@ void EditCutPlugin::executeCut(MeshModel &m, GLArea *gla) totalNewFaces += SplitTab[mask].TriNum - 1; } - cutLogQ(QString(" Need %1 new faces").arg(totalNewFaces)); - if (totalNewFaces > 0) { int firstNewFace = (int)m.cm.face.size(); @@ -517,7 +451,6 @@ void EditCutPlugin::executeCut(MeshModel &m, GLArea *gla) const SplitEntry &se = SplitTab[mask]; - // Validate all vertex indices bool valid = true; for (int i = 0; i < se.TriNum && valid; ++i) for (int c = 0; c < 3; ++c) @@ -540,38 +473,26 @@ void EditCutPlugin::executeCut(MeshModel &m, GLArea *gla) fp->V(2) = &m.cm.vert[vv[se.TV[i][2]]]; } } - cutLogQ(QString(" Split done, used %1 new face slots").arg(nextNewFace - firstNewFace)); } } - // ---- Step 5: Select faces inside the polyline (preview only, no deletion) ---- - cutLog("Step 5: Selecting inside faces (preview)..."); + // ---- Step 5: Select faces inside the polyline ---- selectInsideFaces(m, gla); - cutLog("=== executeCut DONE ==="); - - } catch (std::exception &e) { - cutLogQ(QString("EXCEPTION: %1").arg(e.what())); - cutPolyLine.clear(); } catch (...) { - cutLog("UNKNOWN EXCEPTION caught"); cutPolyLine.clear(); } } // ======================================================================== -// Select faces whose ANY vertex is inside the polyline -// Uses same approach as edit_select (proven to work) +// Select faces whose centroid is inside the polyline // ======================================================================== void EditCutPlugin::selectInsideFaces(MeshModel &m, GLArea *gla) { - cutLog(" selectInsideFaces START"); - // Re-project all vertices (including newly created split vertices) vector projVec; GLPickTri::FillProjectedVector(m.cm, projVec, this->SelMatrix, this->SelViewport); - cutLogQ(QString(" Projected %1 vertices").arg(projVec.size())); // Rasterize polyline as filled polygon (same as edit_select) QImage bufQImg(viewpSize[2], viewpSize[3], QImage::Format_RGB32); @@ -585,24 +506,7 @@ void EditCutPlugin::selectInsideFaces(MeshModel &m, GLArea *gla) bufQPainter.end(); QRgb blk = QColor(Qt::black).rgb(); - // Log some pixel checks for debugging - { - int cx = (int)cutPolyLine[0][0]; - int cy = (int)cutPolyLine[0][1]; - char buf[256]; - snprintf(buf, sizeof(buf), " QImage size: %dx%d, first polyline pt pixel(%d,%d)=%s", - bufQImg.width(), bufQImg.height(), cx, cy, - (cx >= 0 && cx < bufQImg.width() && cy >= 0 && cy < bufQImg.height()) - ? (bufQImg.pixel(cx, cy) == blk ? "BLACK" : "WHITE") : "OOB"); - cutLog(buf); - } - - // Don't clear existing selection — Q adds to selection (like edit_select) - - // Use face CENTROID for inside test — gives clean cut along the polyline - // (using "any vertex" would over-select at the boundary where split vertices - // sit exactly on the polyline, creating a saw-tooth pattern) - int selectedCount = 0; + // Use face centroid for inside test — gives clean cut along the polyline for (size_t fi = 0; fi < m.cm.face.size(); ++fi) { if (m.cm.face[fi].IsD()) continue; @@ -614,12 +518,10 @@ void EditCutPlugin::selectInsideFaces(MeshModel &m, GLArea *gla) v1 < 0 || v1 >= (int)projVec.size() || v2 < 0 || v2 >= (int)projVec.size()) continue; - // Skip if any vertex is behind camera if (projVec[v0][2] <= -1.0 || projVec[v0][2] >= 1.0) continue; if (projVec[v1][2] <= -1.0 || projVec[v1][2] >= 1.0) continue; if (projVec[v2][2] <= -1.0 || projVec[v2][2] >= 1.0) continue; - // Average projected 2D positions to get screen-space centroid float cx = (float)(projVec[v0][0] + projVec[v1][0] + projVec[v2][0]) / 3.0f; float cy = (float)(projVec[v0][1] + projVec[v1][1] + projVec[v2][1]) / 3.0f; @@ -627,20 +529,10 @@ void EditCutPlugin::selectInsideFaces(MeshModel &m, GLArea *gla) int py = (int)cy; if (px <= 0 || px >= viewpSize[2] || py <= 0 || py >= viewpSize[3]) continue; - if (bufQImg.pixel(px, py) == blk) { + if (bufQImg.pixel(px, py) == blk) m.cm.face[fi].SetS(); - selectedCount++; - } } - cutLogQ(QString(" Selected %1 new faces (added to existing selection)").arg(selectedCount)); - - // Polyline stays visible — user can add more points and press Q again - - // Tell MeshLab to update the selection display gla->updateSelection(m.id(), false, true); gla->update(); - - cutLog(" selectInsideFaces DONE"); } -