1 /***************************************************************************
2   qgsmaptooleditmeshframe.cpp - QgsMapToolEditMeshFrame
3 
4  ---------------------
5  begin                : 24.6.2021
6  copyright            : (C) 2021 by Vincent Cloarec
7  email                : vcloarec at gmail dot com
8  ***************************************************************************
9  *                                                                         *
10  *   This program is free software; you can redistribute it and/or modify  *
11  *   it under the terms of the GNU General Public License as published by  *
12  *   the Free Software Foundation; either version 2 of the License, or     *
13  *   (at your option) any later version.                                   *
14  *                                                                         *
15  ***************************************************************************/
16 #include "qgsmaptooleditmeshframe.h"
17 
18 #include <QMessageBox>
19 
20 #include "qgis.h"
21 #include "qgisapp.h"
22 #include "qgsapplication.h"
23 
24 #include "qgsadvanceddigitizingdockwidget.h"
25 #include "qgsdoublespinbox.h"
26 #include "qgsmeshdataprovider.h"
27 #include "qgsmapcanvas.h"
28 #include "qgsmapmouseevent.h"
29 #include "qgsmessagebar.h"
30 #include "qgsmeshlayer.h"
31 #include "qgsmeshlayerutils.h"
32 #include "qgsmesheditor.h"
33 #include "qgspolygon.h"
34 #include "qgstriangularmesh.h"
35 #include "qgsrubberband.h"
36 #include "qgssnapindicator.h"
37 #include "qgsvertexmarker.h"
38 #include "qgsguiutils.h"
39 #include "qgsmeshtriangulation.h"
40 #include "qgsmeshtransformcoordinatesdockwidget.h"
41 #include "qgsmeshforcebypolylines.h"
42 #include "qgsmaptoolselectionhandler.h"
43 #include "qgsvectorlayer.h"
44 #include "qgsunitselectionwidget.h"
45 #include "qgssettingsregistrycore.h"
46 
47 #include "qgsexpressionbuilderwidget.h"
48 #include "qgsmeshselectbyexpressiondialog.h"
49 #include "qgsexpressioncontext.h"
50 #include "qgsexpressioncontextutils.h"
51 #include "qgsexpressionutils.h"
52 #include "qgssettingsregistrycore.h"
53 #include "qgsmaptoolidentify.h"
54 #include "qgsidentifymenu.h"
55 
56 
57 //
58 // QgsZValueWidget
59 //
60 
61 
QgsZValueWidget(const QString & label,QWidget * parent)62 QgsZValueWidget::QgsZValueWidget( const QString &label, QWidget *parent ): QWidget( parent )
63 {
64   QHBoxLayout *layout = new QHBoxLayout( this );
65   layout->setContentsMargins( 0, 0, 0, 0 );
66   setLayout( layout );
67 
68   if ( !label.isEmpty() )
69   {
70     QLabel *lbl = new QLabel( label, this );
71     lbl->setAlignment( Qt::AlignRight | Qt::AlignCenter );
72     layout->addWidget( lbl );
73   }
74 
75   mZValueSpinBox = new QgsDoubleSpinBox( this );
76   mZValueSpinBox->setSingleStep( 1 );
77 
78   mZValueSpinBox->setMinimum( -std::numeric_limits<double>::max() );
79   mZValueSpinBox->setMaximum( std::numeric_limits<double>::max() );
80   mZValueSpinBox->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred );
81   layout->addWidget( mZValueSpinBox );
82   mZValueSpinBox->setClearValue( 0.0 );
83   mZValueSpinBox->clear();
84 
85   mZValueSpinBox->setFocusPolicy( Qt::NoFocus );
86 }
87 
zValue() const88 double QgsZValueWidget::zValue() const
89 {
90   mZValueSpinBox->interpretText();
91   return mZValueSpinBox->value();
92 }
93 
setZValue(double z)94 void QgsZValueWidget::setZValue( double z )
95 {
96   mZValueSpinBox->setValue( z );
97   mZValueSpinBox->selectAll();
98 }
99 
setDefaultValue(double z)100 void QgsZValueWidget::setDefaultValue( double z )
101 {
102   mZValueSpinBox->setClearValue( z );
103   mZValueSpinBox->clear();
104   mZValueSpinBox->selectAll();
105 }
106 
keyboardEntryWidget() const107 QWidget *QgsZValueWidget::keyboardEntryWidget() const
108 {
109   return mZValueSpinBox;
110 }
111 
QgsMeshEditForceByLineAction(QObject * parent)112 QgsMeshEditForceByLineAction::QgsMeshEditForceByLineAction( QObject *parent )
113   : QWidgetAction( parent )
114 {
115   QGridLayout *gLayout = new QGridLayout();
116   gLayout->setContentsMargins( 3, 2, 3, 2 );
117 
118   QgsSettings settings;
119 
120   mCheckBoxNewVertex = new QCheckBox( tr( "Add new vertex on intersecting edges" ) );
121 
122   bool newVertex = settings.value( QStringLiteral( "UI/Mesh/ForceByLineNewVertex" ) ).toBool();
123   mCheckBoxNewVertex->setChecked( newVertex );
124 
125   QLabel *labelInterpolation = new QLabel( tr( "Interpolate Z value from" ) );
126   mComboInterpolateFrom = new QComboBox();
127   mComboInterpolateFrom->addItem( tr( "Mesh" ), Mesh );
128   mComboInterpolateFrom->addItem( tr( "Forcing line" ), Lines );
129 
130   int interpolateFromValue = settings.enumValue( QStringLiteral( "UI/Mesh/ForceByLineInterpolateFrom" ), Mesh );
131   mComboInterpolateFrom->setCurrentIndex( interpolateFromValue );
132 
133   QLabel *labelTolerance = new QLabel( tr( "Tolerance" ) );
134   mToleranceSpinBox = new QgsDoubleSpinBox();
135 
136   bool ok;
137   double toleranceValue = settings.value( QStringLiteral( "UI/Mesh/ForceByLineToleranceValue" ), QgsUnitTypes::RenderMapUnits ).toDouble( &ok );
138   if ( !ok )
139     toleranceValue = 1.0;
140   mToleranceSpinBox->setValue( toleranceValue );
141   mToleranceSpinBox->setKeyboardTracking( false );
142   mToleranceSpinBox->setWrapping( false );
143   mToleranceSpinBox->setSingleStep( 0.1 );
144   mToleranceSpinBox->setClearValue( 1.0 );
145 
146   mUnitSelecionWidget = new QgsUnitSelectionWidget();
147   mUnitSelecionWidget->setUnits( QgsUnitTypes::RenderUnitList() <<
148                                  QgsUnitTypes::RenderMetersInMapUnits <<
149                                  QgsUnitTypes::RenderMapUnits );
150 
151   QgsUnitTypes::RenderUnit toleranceUnit = settings.enumValue( QStringLiteral( "UI/Mesh/ForceByLineToleranceUnit" ), QgsUnitTypes::RenderMapUnits );
152   mUnitSelecionWidget->setUnit( toleranceUnit );
153 
154   gLayout->addWidget( mCheckBoxNewVertex, 1, 0, 1, 4 );
155   gLayout->addWidget( labelInterpolation, 2, 0, 1, 3 );
156   gLayout->addWidget( mComboInterpolateFrom, 2, 3, 1, 1 );
157   gLayout->addWidget( labelTolerance, 3, 0, 1, 2 );
158   gLayout->addWidget( mToleranceSpinBox, 3, 2, 1, 1 );
159   gLayout->addWidget( mUnitSelecionWidget, 3, 3, 1, 1 );
160 
161   QWidget *w = new QWidget();
162   w->setLayout( gLayout );
163   setDefaultWidget( w );
164 
165   connect( mCheckBoxNewVertex, &QCheckBox::toggled, this, &QgsMeshEditForceByLineAction::updateSettings );
166   connect( mComboInterpolateFrom, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsMeshEditForceByLineAction::updateSettings );
167   connect( mToleranceSpinBox, qOverload<double>( &QgsDoubleSpinBox::valueChanged ), this, &QgsMeshEditForceByLineAction::updateSettings );
168   connect( mUnitSelecionWidget, &QgsUnitSelectionWidget::changed, this, &QgsMeshEditForceByLineAction::updateSettings );
169 }
170 
setMapCanvas(QgsMapCanvas * canvas)171 void QgsMeshEditForceByLineAction::setMapCanvas( QgsMapCanvas *canvas )
172 {
173   mUnitSelecionWidget->setMapCanvas( canvas );
174 }
175 
interpolationMode() const176 QgsMeshEditForceByLineAction::IntepolationMode QgsMeshEditForceByLineAction::interpolationMode() const
177 {
178   return static_cast<IntepolationMode>( mComboInterpolateFrom->currentData().toInt() );
179 }
180 
newVertexOnIntersectingEdge() const181 bool QgsMeshEditForceByLineAction::newVertexOnIntersectingEdge() const
182 {
183   return mCheckBoxNewVertex->isChecked();
184 }
185 
toleranceValue() const186 double QgsMeshEditForceByLineAction::toleranceValue() const
187 {
188   return mToleranceSpinBox->value();
189 }
190 
toleranceUnit() const191 QgsUnitTypes::RenderUnit QgsMeshEditForceByLineAction::toleranceUnit() const
192 {
193   return mUnitSelecionWidget->unit();
194 }
195 
updateSettings()196 void QgsMeshEditForceByLineAction::updateSettings()
197 {
198   QgsSettings settings;
199 
200   settings.setValue( QStringLiteral( "UI/Mesh/ForceByLineNewVertex" ), mCheckBoxNewVertex->isChecked() );
201   settings.setEnumValue( QStringLiteral( "UI/Mesh/ForceByLineInterpolateFrom" ),
202                          static_cast<IntepolationMode>( mComboInterpolateFrom->currentData().toInt() ) );
203   settings.setValue( QStringLiteral( "UI/Mesh/ForceByLineToleranceValue" ), mToleranceSpinBox->value() );
204   settings.setEnumValue( QStringLiteral( "UI/Mesh/ForceByLineToleranceUnit" ), mUnitSelecionWidget->unit() );
205 }
206 
207 //
208 // QgsMapToolEditMeshFrame
209 //
210 
QgsMapToolEditMeshFrame(QgsMapCanvas * canvas)211 QgsMapToolEditMeshFrame::QgsMapToolEditMeshFrame( QgsMapCanvas *canvas )
212   : QgsMapToolAdvancedDigitizing( canvas, QgisApp::instance()->cadDockWidget() )
213   , mSnapIndicator( new QgsSnapIndicator( canvas ) )
214 {
215   mActionDigitizing = new QAction( QgsApplication::getThemePixmap( QStringLiteral( "/mActionMeshDigitizing.svg" ) ), tr( "Digitize Mesh elements" ), this );
216   mActionDigitizing->setCheckable( true );
217 
218   mActionSelectByPolygon = new QAction( QgsApplication::getThemePixmap( QStringLiteral( "/mActionMeshSelectPolygon.svg" ) ), tr( "Select Mesh Elements by Polygon" ), this );
219   mActionSelectByPolygon->setCheckable( true );
220   mActionSelectByPolygon->setObjectName( QStringLiteral( "ActionMeshSelectByPolygon" ) );
221   mActionSelectByExpression = new QAction( QgsApplication::getThemePixmap( QStringLiteral( "/mActionMeshSelectExpression.svg" ) ), tr( "Select Mesh Elements by Expression" ), this );
222   mActionSelectByExpression->setObjectName( QStringLiteral( "ActionMeshSelectByExpression" ) );
223 
224   mSelectionHandler = std::make_unique<QgsMapToolSelectionHandler>( canvas, QgsMapToolSelectionHandler::SelectPolygon );
225 
226   mSelectActions << mActionSelectByPolygon
227                  << mActionSelectByExpression;
228 
229   mActionTransformCoordinates = new QAction( QgsApplication::getThemePixmap( QStringLiteral( "/mActionMeshTransformByExpression.svg" ) ), tr( "Transform Vertices Coordinates" ), this );
230   mActionTransformCoordinates->setCheckable( true );
231 
232   mActionForceByLines = new QAction( QgsApplication::getThemePixmap( QStringLiteral( "/mActionMeshEditForceByVectorLines.svg" ) ), tr( "Force by Selected Geometries" ), this );
233   mActionForceByLines->setCheckable( true );
234 
235   mWidgetActionForceByLine = new QgsMeshEditForceByLineAction( this );
236   mWidgetActionForceByLine->setMapCanvas( canvas );
237 
238   mActionReindexMesh = new QAction( QgsApplication::getThemePixmap( QStringLiteral( "/mActionMeshReindex.svg" ) ), tr( "Reindex Faces and Vertices" ), this );
239 
240   mActionRemoveVerticesFillingHole = new QAction( this );
241   mActionDelaunayTriangulation = new QAction( tr( "Delaunay Triangulation with Selected Vertices" ), this );
242   mActionFacesRefinement = new QAction( tr( "Refine Current Face" ), this );
243   mActionRemoveVerticesWithoutFillingHole = new QAction( this );
244   mActionRemoveFaces = new QAction( tr( "Remove Current Face" ), this );
245   mActionSplitFaces = new QAction( tr( "Split Current Face" ), this );
246 
247   connect( mActionRemoveVerticesFillingHole, &QAction::triggered, this, [this] {removeSelectedVerticesFromMesh( true );} );
248   connect( mActionRemoveVerticesWithoutFillingHole, &QAction::triggered, this, [this] {removeSelectedVerticesFromMesh( false );} );
249   connect( mActionRemoveFaces, &QAction::triggered, this, &QgsMapToolEditMeshFrame::removeFacesFromMesh );
250   connect( mActionSplitFaces, &QAction::triggered, this, &QgsMapToolEditMeshFrame::splitSelectedFaces );
251 
252   connect( mActionDigitizing, &QAction::toggled, this, [this]( bool checked )
253   {
254     if ( checked )
255       activateWithState( Digitizing );
256   } );
257 
258   for ( int i = 0; i < mSelectActions.count(); ++i )
259   {
260     connect( mSelectActions.at( i ), &QAction::triggered, this, [i]
261     {
262       QgsSettings settings;
263       settings.setValue( QStringLiteral( "UI/Mesh/defaultSelection" ), i );
264     } );
265   }
266 
267   connect( mActionSelectByPolygon, &QAction::triggered, this, [this]
268   {
269     if ( mActionSelectByPolygon->isChecked() )
270     {
271       activateWithState( SelectingByPolygon );
272     }
273     else
274       mSelectionBand->reset( QgsWkbTypes::PolygonGeometry );
275   } );
276 
277   connect( mActionSelectByExpression, &QAction::triggered, this, &QgsMapToolEditMeshFrame::showSelectByExpressionDialog );
278   connect( mActionTransformCoordinates, &QAction::triggered, this, &QgsMapToolEditMeshFrame::triggerTransformCoordinatesDockWidget );
279   connect( mActionReindexMesh, &QAction::triggered, this, &QgsMapToolEditMeshFrame::reindexMesh );
280   connect( mActionDelaunayTriangulation, &QAction::triggered, this, [this]
281   {
282     if ( mCurrentEditor && mSelectedVertices.count() >= 3 )
283     {
284       QgsTemporaryCursorOverride waitCursor( Qt::WaitCursor );
285       QgsMeshEditingDelaunayTriangulation triangulation;
286       triangulation.setInputVertices( mSelectedVertices.keys() );
287       mCurrentEditor->advancedEdit( &triangulation );
288 
289       if ( !triangulation.message().isEmpty() )
290         QgisApp::instance()->messageBar()->pushInfo( tr( "Delaunay triangulation" ), triangulation.message() );
291     }
292   } );
293   connect( mActionFacesRefinement, &QAction::triggered, this, [this]
294   {
295     QgsTemporaryCursorOverride waitCursor( Qt::WaitCursor );
296     QgsMeshEditRefineFaces refinement;
297     if ( mCurrentEditor && mSelectedFaces.count() > 0 )
298     {
299       refinement.setInputFaces( mSelectedFaces.values() );
300       mCurrentEditor->advancedEdit( &refinement );
301     }
302     else if ( mCurrentFaceIndex != -1 )
303     {
304       refinement.setInputFaces( {mCurrentFaceIndex} );
305       mCurrentEditor->advancedEdit( &refinement );
306     }
307   } );
308 
309   connect( mSelectionHandler.get(), &QgsMapToolSelectionHandler::geometryChanged, this, [this]( Qt::KeyboardModifiers modifiers )
310   {
311     mIsSelectingPolygonInProgress = false;
312     selectByGeometry( mSelectionHandler->selectedGeometry(), modifiers );
313   } );
314 
315   connect( mActionForceByLines, &QAction::toggled, this, [this]( bool checked )
316   {
317     if ( mIsInitialized )
318       mForceByLineRubberBand->reset( QgsWkbTypes::LineGeometry );
319     mForcingLineZValue.clear();
320     if ( checked )
321     {
322       onEditingStarted();
323       clearCanvasHelpers();
324       activateWithState( ForceByLines );
325     }
326   } );
327 
328   connect( cadDockWidget(), &QgsAdvancedDigitizingDockWidget::cadEnabledChanged, this, [this]( bool enable )
329   {
330     if ( !isActive() || !mCurrentEditor )
331       return;
332 
333     if ( enable && mSelectedVertices.isEmpty() )
334       deleteZValueWidget();
335     else if ( !mZValueWidget )
336       createZValueWidget();
337   } );
338 
339   setAutoSnapEnabled( true );
340 }
341 
activateWithState(State state)342 void QgsMapToolEditMeshFrame::activateWithState( State state )
343 {
344   if ( mCanvas->mapTool() != this )
345   {
346     mCanvas->setMapTool( this );
347     mCanvas->setFocus();
348     onEditingStarted();
349   }
350   mCurrentState = state;
351 }
352 
backToDigitizing()353 void QgsMapToolEditMeshFrame::backToDigitizing()
354 {
355   activateWithState( Digitizing );
356   mActionDigitizing->setChecked( true );
357 }
358 
~QgsMapToolEditMeshFrame()359 QgsMapToolEditMeshFrame::~QgsMapToolEditMeshFrame()
360 {
361   deleteZValueWidget();
362 }
363 
setActionsEnable(bool enable)364 void QgsMapToolEditMeshFrame::setActionsEnable( bool enable )
365 {
366   QList<QAction *> actions;
367   actions
368       << mActionDigitizing
369       << mActionSelectByPolygon
370       << mActionSelectByExpression
371       << mActionTransformCoordinates
372       << mActionForceByLines
373       << mActionReindexMesh;
374 
375   for ( QAction *action : std::as_const( actions ) )
376     action->setEnabled( enable );
377 }
378 
379 
mapToolActions()380 QList<QAction *> QgsMapToolEditMeshFrame::mapToolActions()
381 {
382   return  QList<QAction *>()
383           << mActionDigitizing
384           << mActionSelectByPolygon
385           << mActionForceByLines;
386 }
387 
digitizeAction() const388 QAction *QgsMapToolEditMeshFrame::digitizeAction() const
389 {
390   return  mActionDigitizing;
391 }
392 
selectActions() const393 QList<QAction *> QgsMapToolEditMeshFrame::selectActions() const
394 {
395   return  mSelectActions;
396 }
397 
defaultSelectActions() const398 QAction *QgsMapToolEditMeshFrame::defaultSelectActions() const
399 {
400   QgsSettings settings;
401   bool ok = false;
402   int defaultIndex = settings.value( QStringLiteral( "UI/Mesh/defaultSelection" ) ).toInt( &ok );
403 
404   if ( ok )
405     return mSelectActions.at( defaultIndex );
406 
407   return mActionSelectByPolygon;
408 }
409 
transformAction() const410 QAction *QgsMapToolEditMeshFrame::transformAction() const
411 {
412   return mActionTransformCoordinates;
413 }
414 
forceByLinesActions() const415 QList<QAction *> QgsMapToolEditMeshFrame::forceByLinesActions() const
416 {
417   return  QList<QAction *>()
418           << mActionForceByLines;
419 }
420 
defaultForceAction() const421 QAction *QgsMapToolEditMeshFrame::defaultForceAction() const
422 {
423   return mActionForceByLines;
424 }
425 
forceByLineWidgetActionSettings() const426 QWidgetAction *QgsMapToolEditMeshFrame::forceByLineWidgetActionSettings() const
427 {
428   return mWidgetActionForceByLine;
429 }
430 
reindexAction() const431 QAction *QgsMapToolEditMeshFrame::reindexAction() const
432 {
433   return mActionReindexMesh;
434 }
435 
initialize()436 void QgsMapToolEditMeshFrame::initialize()
437 {
438   if ( !mFaceRubberBand )
439     mFaceRubberBand = createRubberBand( QgsWkbTypes::PolygonGeometry );
440   mFaceRubberBand->setVisible( false );
441   mFaceRubberBand->setZValue( 5 );
442 
443   QColor color = digitizingStrokeColor();
444   if ( !mFaceVerticesBand )
445     mFaceVerticesBand = new QgsRubberBand( mCanvas );
446   mFaceVerticesBand->setIcon( QgsRubberBand::ICON_CIRCLE );
447   mFaceVerticesBand->setColor( color );
448   mFaceVerticesBand->setWidth( QgsGuiUtils::scaleIconSize( 2 ) );
449   mFaceVerticesBand->setBrushStyle( Qt::NoBrush );
450   mFaceVerticesBand->setIconSize( QgsGuiUtils::scaleIconSize( 6 ) );
451   mFaceVerticesBand->setVisible( false );
452   mFaceVerticesBand->setZValue( 5 );
453 
454   if ( !mVertexBand )
455     mVertexBand = new QgsRubberBand( mCanvas );
456   mVertexBand->setIcon( QgsRubberBand::ICON_CIRCLE );
457   mVertexBand->setColor( color );
458   mVertexBand->setWidth( QgsGuiUtils::scaleIconSize( 2 ) );
459   mVertexBand->setBrushStyle( Qt::NoBrush );
460   mVertexBand->setIconSize( QgsGuiUtils::scaleIconSize( 15 ) );
461   mVertexBand->setVisible( false );
462   mVertexBand->setZValue( 5 );
463 
464   if ( !mEdgeBand )
465     mEdgeBand = new QgsRubberBand( mCanvas );
466   QColor color2( color );
467   color2.setAlpha( color2.alpha() / 3 );
468   mEdgeBand->setColor( color2 );
469   mEdgeBand->setWidth( QgsGuiUtils::scaleIconSize( 10 ) );
470   mEdgeBand->setVisible( false );
471 
472   if ( !mNewFaceBand )
473     mNewFaceBand = createRubberBand( QgsWkbTypes::PolygonGeometry );
474   mInvalidFaceColor = QColor( 255, 0, 0, mNewFaceBand->fillColor().alpha() ); //override color and keep only the transparency
475   mValidFaceColor = QColor( 0, 255, 0, mNewFaceBand->fillColor().alpha() ); //override color and keep only the transparency
476   mNewFaceBand->setFillColor( mInvalidFaceColor );
477   mNewFaceBand->setVisible( false );
478   mNewFaceBand->setZValue( 10 );
479 
480   if ( !mSelectionBand )
481     mSelectionBand = new QgsRubberBand( mCanvas, QgsWkbTypes::PolygonGeometry );
482   mSelectionBand->setFillColor( QColor( 254, 178, 76, 63 ) );
483   mSelectionBand->setStrokeColor( QColor( 254, 58, 29, 100 ) );
484   mSelectionBand->setZValue( 10 );
485 
486   if ( !mSelectedFacesRubberband )
487     mSelectedFacesRubberband = new QgsRubberBand( mCanvas, QgsWkbTypes::PolygonGeometry );
488   mSelectedFacesRubberband->setZValue( 1 );
489 
490   if ( !mNewFaceMarker )
491     mNewFaceMarker = new QgsVertexMarker( canvas() );
492   mNewFaceMarker->setIconType( QgsVertexMarker::ICON_TRIANGLE );
493   mNewFaceMarker->setIconSize( QgsGuiUtils::scaleIconSize( 12 ) );
494   mNewFaceMarker->setColor( Qt::gray );
495   mNewFaceMarker->setVisible( false );
496   mNewFaceMarker->setPenWidth( 3 );
497 
498   if ( !mSelectFaceMarker )
499     mSelectFaceMarker = new QgsVertexMarker( canvas() );
500   mSelectFaceMarker->setIconType( QgsVertexMarker::ICON_BOX );
501   mSelectFaceMarker->setIconSize( QgsGuiUtils::scaleIconSize( 10 ) );
502   mSelectFaceMarker->setColor( Qt::gray );
503   mSelectFaceMarker->setFillColor( Qt::gray );
504   mSelectFaceMarker->setVisible( false );
505   mSelectFaceMarker->setPenWidth( 3 );
506   mSelectFaceMarker->setZValue( 10 );
507 
508   if ( !mSelectEdgeMarker )
509     mSelectEdgeMarker = new QgsVertexMarker( canvas() );
510   mSelectEdgeMarker->setIconType( QgsVertexMarker::ICON_BOX );
511   mSelectEdgeMarker->setIconSize( QgsGuiUtils::scaleIconSize( 10 ) );
512   mSelectEdgeMarker->setColor( Qt::gray );
513   mSelectEdgeMarker->setFillColor( Qt::gray );
514   mSelectEdgeMarker->setVisible( false );
515   mSelectEdgeMarker->setPenWidth( 3 );
516   mSelectEdgeMarker->setZValue( 10 );
517 
518   if ( !mMovingEdgesRubberband )
519     mMovingEdgesRubberband = createRubberBand( QgsWkbTypes::LineGeometry );
520 
521   if ( !mMovingFacesRubberband )
522     mMovingFacesRubberband = createRubberBand( QgsWkbTypes::PolygonGeometry );
523 
524   if ( !mMovingFreeVertexRubberband )
525   {
526     mMovingFreeVertexRubberband = createRubberBand( QgsWkbTypes::PointGeometry );
527     mMovingFreeVertexRubberband->setIcon( QgsRubberBand::ICON_X );
528     mMovingFreeVertexRubberband->setIconSize( QgsGuiUtils::scaleIconSize( 10 ) );
529     mMovingFreeVertexRubberband->setWidth( QgsGuiUtils::scaleIconSize( 3 ) );
530     mMovingFreeVertexRubberband->setVisible( true );
531   }
532 
533   if ( !mFlipEdgeMarker )
534     mFlipEdgeMarker = new QgsVertexMarker( canvas() );
535   mFlipEdgeMarker->setIconType( QgsVertexMarker::ICON_CIRCLE );
536   mFlipEdgeMarker->setIconSize( QgsGuiUtils::scaleIconSize( 12 ) );
537   mFlipEdgeMarker->setColor( Qt::gray );
538   mFlipEdgeMarker->setVisible( false );
539   mFlipEdgeMarker->setPenWidth( 3 );
540   mFlipEdgeMarker->setZValue( 10 );
541 
542   if ( !mMergeFaceMarker )
543     mMergeFaceMarker = new QgsVertexMarker( canvas() );
544   mMergeFaceMarker->setIconType( QgsVertexMarker::ICON_X );
545   mMergeFaceMarker->setIconSize( QgsGuiUtils::scaleIconSize( 12 ) );
546   mMergeFaceMarker->setColor( Qt::gray );
547   mMergeFaceMarker->setVisible( false );
548   mMergeFaceMarker->setPenWidth( 3 );
549   mMergeFaceMarker->setZValue( 10 );
550 
551   if ( !mForceByLineRubberBand )
552     mForceByLineRubberBand = createRubberBand( QgsWkbTypes::LineGeometry );
553 
554   connect( mCanvas, &QgsMapCanvas::currentLayerChanged, this, &QgsMapToolEditMeshFrame::setCurrentLayer );
555 
556   mUserZValue = defaultZValue();
557   createZValueWidget();
558   updateFreeVertices();
559 
560   mIsInitialized = true;
561 }
562 
deactivate()563 void QgsMapToolEditMeshFrame::deactivate()
564 {
565   QgsMapToolAdvancedDigitizing::deactivate();
566   clearSelection();
567   clearCanvasHelpers();
568   deleteZValueWidget();
569   qDeleteAll( mFreeVertexMarker );
570   mFreeVertexMarker.clear();
571 
572 }
573 
clearAll()574 void QgsMapToolEditMeshFrame::clearAll()
575 {
576   delete mNewFaceMarker;
577   mNewFaceMarker = nullptr;
578 
579   delete mSelectFaceMarker;
580   mSelectFaceMarker = nullptr;
581 
582   delete mSelectEdgeMarker;
583   mSelectEdgeMarker = nullptr;
584 
585   delete mFlipEdgeMarker;
586   mFlipEdgeMarker = nullptr;
587 
588   delete mMergeFaceMarker;
589   mMergeFaceMarker = nullptr;
590 
591   mFaceRubberBand->deleteLater();
592   mFaceRubberBand = nullptr;
593 
594   mFaceVerticesBand ->deleteLater();
595   mFaceVerticesBand = nullptr;
596 
597   mVertexBand->deleteLater();
598   mVertexBand = nullptr;
599 
600   mNewFaceBand->deleteLater();
601   mNewFaceBand = nullptr;
602 
603   mSelectionBand->deleteLater();
604   mSelectionBand = nullptr;
605 
606   mSelectedFacesRubberband->deleteLater();
607   mSelectedFacesRubberband = nullptr;
608 
609   deleteZValueWidget();
610 }
611 
activate()612 void QgsMapToolEditMeshFrame::activate()
613 {
614   QgsMapToolAdvancedDigitizing::activate();
615   if ( !cadDockWidget()->cadEnabled() )
616     createZValueWidget();
617 }
618 
populateContextMenuWithEvent(QMenu * menu,QgsMapMouseEvent * event)619 bool QgsMapToolEditMeshFrame::populateContextMenuWithEvent( QMenu *menu, QgsMapMouseEvent *event )
620 {
621   Q_UNUSED( event );
622 
623   switch ( mCurrentState )
624   {
625     case Digitizing:
626     case SelectingByPolygon:
627     {
628       QList<QAction * >  newActions;
629       QList<QAction * >  lastActions;
630 
631       if ( !mSelectedVertices.isEmpty() )
632       {
633         if ( mSelectedVertices.count() >= 3 )
634           lastActions << mActionDelaunayTriangulation;
635 
636         newActions << mActionRemoveVerticesFillingHole << mActionRemoveVerticesWithoutFillingHole;
637       }
638 
639       if ( !mSelectedFaces.isEmpty() ||
640            ( mCurrentFaceIndex != -1 && mCurrentState == Digitizing ) )
641       {
642         newActions << mActionRemoveFaces;
643         lastActions << mActionFacesRefinement;
644       }
645 
646       if ( mSplittableFaceCount > 0 ||
647            ( mCurrentFaceIndex != -1 && mCurrentEditor->faceCanBeSplit( mCurrentFaceIndex ) ) )
648         newActions << mActionSplitFaces;
649 
650       QList<QAction * > existingActions = menu->actions();
651       if ( !newActions.isEmpty() )
652       {
653         if ( existingActions.isEmpty() )
654         {
655           menu->addActions( newActions );
656           if ( !lastActions.empty() )
657           {
658             menu->addSeparator();
659             menu->addActions( lastActions );
660           }
661         }
662         else
663         {
664           menu->insertActions( existingActions.first(), newActions );
665           menu->insertSeparator( existingActions.first() );
666           menu->insertActions( existingActions.first(), lastActions );
667           menu->insertSeparator( existingActions.first() );
668         }
669         return true;
670       }
671       return false;
672     }
673     case AddingNewFace:
674     case Selecting:
675     case MovingSelection:
676     case ForceByLines:
677       return false;
678   }
679 
680   return false;
681 }
682 
flags() const683 QgsMapTool::Flags QgsMapToolEditMeshFrame::flags() const
684 {
685   switch ( mCurrentState )
686   {
687     case Digitizing:
688       if ( !mCadDockWidget->cadEnabled() || !mSelectedVertices.isEmpty() || mCurrentFaceIndex != -1 )
689         return QgsMapTool::Flags() | QgsMapTool::ShowContextMenu;
690       FALLTHROUGH
691     case AddingNewFace:
692     case Selecting:
693     case MovingSelection:
694     case SelectingByPolygon:
695     case ForceByLines:
696       return QgsMapTool::Flags();
697       break;
698   }
699 
700   return QgsMapTool::Flags();
701 }
702 
searchFeatureOnMap(QgsMapMouseEvent * e,QgsMapCanvas * canvas,const QList<QgsWkbTypes::GeometryType> & geomType)703 static QList<QgsMapToolIdentify::IdentifyResult> searchFeatureOnMap( QgsMapMouseEvent *e, QgsMapCanvas *canvas, const QList<QgsWkbTypes::GeometryType> &geomType )
704 {
705   QList<QgsMapToolIdentify::IdentifyResult> results;
706   const QMap< QString, QString > derivedAttributes;
707 
708   QgsPointXY mapPoint = e->mapPoint();
709   double x = mapPoint.x(), y = mapPoint.y();
710   const double sr = QgsMapTool::searchRadiusMU( canvas );
711 
712   const QList<QgsMapLayer *> layers = canvas->layers();
713   for ( QgsMapLayer *layer : layers )
714   {
715     if ( layer->type() == QgsMapLayerType::VectorLayer )
716     {
717       QgsVectorLayer *vectorLayer = static_cast<QgsVectorLayer *>( layer );
718 
719       bool typeIsSelectable = false;
720       for ( const QgsWkbTypes::GeometryType &type : geomType )
721         if ( vectorLayer->geometryType() == type )
722         {
723           typeIsSelectable = true;
724           break;
725         }
726       if ( typeIsSelectable )
727       {
728         QgsRectangle rect( x - sr, y - sr, x + sr, y + sr );
729         QgsCoordinateTransform transform = canvas->mapSettings().layerTransform( vectorLayer );
730 
731         try
732         {
733           rect = transform.transformBoundingBox( rect, Qgis::TransformDirection::Reverse );
734         }
735         catch ( QgsCsException & )
736         {
737           QgsDebugMsg( QStringLiteral( "Could not transform geometry to layer CRS" ) );
738         }
739 
740         QgsFeatureIterator fit = vectorLayer->getFeatures( QgsFeatureRequest()
741                                  .setFilterRect( rect )
742                                  .setFlags( QgsFeatureRequest::ExactIntersect ) );
743         QgsFeature f;
744         while ( fit.nextFeature( f ) )
745         {
746           results << QgsMapToolIdentify::IdentifyResult( vectorLayer, f, derivedAttributes );
747         }
748       }
749     }
750   }
751 
752   return results;
753 }
754 
forceByLineBySelectedFeature(QgsMapMouseEvent * e)755 void QgsMapToolEditMeshFrame::forceByLineBySelectedFeature( QgsMapMouseEvent *e )
756 {
757   const QList<QgsMapToolIdentify::IdentifyResult> &results =
758     searchFeatureOnMap( e, mCanvas, QList<QgsWkbTypes::GeometryType>() << QgsWkbTypes::PolygonGeometry << QgsWkbTypes::LineGeometry );
759 
760   QgsIdentifyMenu *menu = new QgsIdentifyMenu( mCanvas );
761   menu->setExecWithSingleResult( true );
762   menu->setAllowMultipleReturn( false );
763   const QPoint globalPos = mCanvas->mapToGlobal( QPoint( e->pos().x() + 5, e->pos().y() + 5 ) );
764   const QList<QgsMapToolIdentify::IdentifyResult> selectedFeatures = menu->exec( results, globalPos );
765   menu->deleteLater();
766 
767   if ( !selectedFeatures.empty() && selectedFeatures[0].mFeature.hasGeometry() )
768   {
769     QgsCoordinateTransform transform = mCanvas->mapSettings().layerTransform( selectedFeatures.at( 0 ).mLayer );
770     QgsGeometry geom = selectedFeatures[0].mFeature.geometry();
771     try
772     {
773       geom.transform( transform );
774     }
775     catch ( QgsCsException & )
776     {
777       QgsDebugMsg( QStringLiteral( "Could not transform geometry to layer CRS" ) );
778     }
779     forceByLine( geom );
780   }
781 
782   return;
783 }
784 
cadCanvasPressEvent(QgsMapMouseEvent * e)785 void QgsMapToolEditMeshFrame::cadCanvasPressEvent( QgsMapMouseEvent *e )
786 {
787   if ( !mCurrentEditor )
788     return;
789 
790   if ( e->button() == Qt::LeftButton &&
791        ( !mCadDockWidget->cadEnabled() ||
792          mCadDockWidget->additionalConstraint() == QgsAdvancedDigitizingDockWidget::AdditionalConstraint::NoConstraint ) )
793     mLeftButtonPressed = true;
794 
795   switch ( mCurrentState )
796   {
797     case Digitizing:
798       if ( e->button() == Qt::LeftButton )
799         mStartSelectionPos = e->pos();
800       break;
801     case AddingNewFace:
802     case Selecting:
803     case MovingSelection:
804     case ForceByLines:
805       if ( e->button() == Qt::LeftButton )
806         mSelectionBand->reset( QgsWkbTypes::PolygonGeometry );
807       break;
808     case SelectingByPolygon:
809       if ( mSelectionHandler )
810       {
811         if ( e->button() == Qt::RightButton )
812         {
813           // here, quite tricky because 3 possibilities:
814           // - a polygon has started to be digitized for selection -> right click validate the selection
815           // - right click on an existing vector layer feature -> a menu is executed to choose a feature
816           // - other case -> context menu of mesh editing to apply an edit on selected element
817           // The last case is launched only if the other cases do not appears
818           // With the selection handler, if we can know if the selecting polygon change, that means the first case appears,
819           // we can't know if a feature is found or not (if the user do not choose a feature, nothing happen like if no feature was found).
820           // The workaround is to check if a feature exist under the mouse before sending the event to the selection handler.
821           // This is not ideal because that leads to a double search but no better idea for now to allow the editing context menu with selecting by polygon
822 
823           bool hasSelectableFeature = !searchFeatureOnMap( e, mCanvas, QList<QgsWkbTypes::GeometryType>() << QgsWkbTypes::PolygonGeometry ).isEmpty();
824 
825           if ( hasSelectableFeature || mIsSelectingPolygonInProgress )
826             mSelectionHandler->canvasPressEvent( e );
827           else
828           {
829             QMenu menu;
830             populateContextMenuWithEvent( &menu, e );
831             menu.exec( e->globalPos() );
832           }
833         }
834         else
835         {
836           mIsSelectingPolygonInProgress = true;
837           mSelectionHandler->canvasPressEvent( e );
838         }
839       }
840 
841       break;
842   }
843 
844   QgsMapToolAdvancedDigitizing::cadCanvasPressEvent( e );
845 }
846 
cadCanvasMoveEvent(QgsMapMouseEvent * e)847 void QgsMapToolEditMeshFrame::cadCanvasMoveEvent( QgsMapMouseEvent *e )
848 {
849   if ( !mCurrentEditor )
850     return;
851 
852   const QgsPointXY &mapPoint = e->mapPoint();
853 
854   mSnapIndicator->setMatch( e->mapPointMatch() );
855 
856   if ( mLeftButtonPressed && mCurrentState == Digitizing )
857   {
858     mCurrentState = Selecting;
859   }
860 
861   switch ( mCurrentState )
862   {
863     case Digitizing:
864       highLight( mapPoint );
865       break;
866     case AddingNewFace:
867       mNewFaceBand->movePoint( mapPoint );
868       highLight( mapPoint );
869       if ( testNewVertexInFaceCanditate( mCurrentVertexIndex ) )
870         mNewFaceBand->setColor( mValidFaceColor );
871       else
872         mNewFaceBand->setColor( mInvalidFaceColor );
873       break;
874     case Selecting:
875     {
876       const QRect &rect = QRect( e->pos(), mStartSelectionPos );
877       mSelectionBand->setToCanvasRectangle( rect );
878     }
879     break;
880     case MovingSelection:
881     {
882       moveSelection( mapPoint );
883     }
884     break;
885     case SelectingByPolygon:
886       if ( mSelectionHandler )
887         mSelectionHandler->canvasMoveEvent( e );
888       break;
889     case ForceByLines:
890       searchFace( mapPoint );
891       searchEdge( mapPoint );
892       highlightCloseVertex( mapPoint );
893 
894       const QgsPointLocator::Match &matchPoint = e->mapPointMatch();
895 
896       if ( mCurrentVertexIndex != -1 )
897       {
898         mForceByLineRubberBand->movePoint( mapVertexXY( mCurrentVertexIndex ) );
899         if ( mZValueWidget )
900           mZValueWidget->setZValue( mapVertex( mCurrentVertexIndex ).z() );
901       }
902       else if ( matchPoint.isValid() && matchPoint.layer() && QgsWkbTypes::hasZ( matchPoint.layer()->wkbType() ) )
903       {
904         mForceByLineRubberBand->movePoint( mapPoint );
905         if ( mZValueWidget )
906           mZValueWidget->setZValue( e->mapPointMatch().interpolatedPoint( mCanvas->mapSettings().destinationCrs() ).z() );
907       }
908       else
909       {
910         mForceByLineRubberBand->movePoint( mapPoint );
911         if ( mZValueWidget )
912           mZValueWidget->setZValue( mUserZValue );
913       }
914       break;
915   }
916 
917   QgsMapToolAdvancedDigitizing::cadCanvasMoveEvent( e );
918 }
919 
cadCanvasReleaseEvent(QgsMapMouseEvent * e)920 void QgsMapToolEditMeshFrame::cadCanvasReleaseEvent( QgsMapMouseEvent *e )
921 {
922   if ( !mCurrentEditor )
923     return;
924   double tolerance = QgsTolerance::vertexSearchRadius( canvas()->mapSettings() );
925 
926   QgsPointXY mapPoint = e->mapPoint();
927 
928   // advanced digitizing constraint only the first release of double clicks
929   // so we need to store the click point for the next one that could be a double clicks
930   if ( !mDoubleClicks )
931   {
932     mFirstClickPoint = mapPoint;
933     mFirstClickZValue = currentZValue();
934   }
935 
936   if ( e->button() == Qt::LeftButton )
937     mLeftButtonPressed = false;
938 
939   switch ( mCurrentState )
940   {
941     case Digitizing:
942       if ( e->button() == Qt::LeftButton )
943       {
944         if ( mDoubleClicks )  //double clicks --> add a vertex
945         {
946           addVertex( mFirstClickPoint, e->mapPointMatch() );
947           mCadDockWidget->setPoints( QList<QgsPointXY>() << mFirstClickPoint << mFirstClickPoint );
948         }
949         else if ( mNewFaceMarker->isVisible() &&
950                   mapPoint.distance( mNewFaceMarker->center() ) < tolerance
951                   && mCurrentVertexIndex >= 0 )  //new face marker clicked --> start adding a new face
952         {
953           clearSelection();
954           mCurrentState = AddingNewFace;
955           mNewFaceMarker->setVisible( false );
956           mNewFaceBand->setVisible( true );
957           mNewFaceBand->reset( QgsWkbTypes::PolygonGeometry );
958           addVertexToFaceCanditate( mCurrentVertexIndex );
959           const QgsPointXY &currentPoint = mapVertexXY( mCurrentVertexIndex );
960           cadDockWidget()->setPoints( QList<QgsPointXY>() << currentPoint << currentPoint );
961         }
962         else if ( isSelectionGrapped( mapPoint )  && //click on a selected vertex, an edge or face box
963                   !( e->modifiers() &Qt::ControlModifier ) ) // without control modifier that is used to remove from the selection
964         {
965           mCurrentState = MovingSelection;
966           mCadDockWidget->setEnabledZ( false );
967           mStartMovingPoint = mapPoint;
968           cadDockWidget()->setPoints( QList<QgsPointXY>() << mapPoint << mapPoint );
969         }
970         else if ( mFlipEdgeMarker->isVisible() &&
971                   e->mapPoint().distance( mFlipEdgeMarker->center() ) < tolerance &&
972                   mCurrentEdge.first != -1 && mCurrentEdge.second != -1 )  // flip edge
973         {
974           clearSelection();
975           QVector<int> edgeVert = edgeVertices( mCurrentEdge );
976           mCurrentEditor->flipEdge( edgeVert.at( 0 ), edgeVert.at( 1 ) );
977           mCurrentEdge = {-1, -1};
978           highLight( mapPoint );
979         }
980         else if ( mMergeFaceMarker->isVisible() &&
981                   e->mapPoint().distance( mMergeFaceMarker->center() ) < tolerance &&
982                   mCurrentEdge.first != -1 && mCurrentEdge.second != -1 ) // merge two faces
983         {
984           clearSelection();
985           QVector<int> edgeVert = edgeVertices( mCurrentEdge );
986           mCurrentEditor->merge( edgeVert.at( 0 ), edgeVert.at( 1 ) );
987           mCurrentEdge = {-1, -1};
988           highLight( mapPoint );
989         }
990         else
991           select( mapPoint, e->modifiers(), tolerance );
992       }
993       break;
994     case AddingNewFace:
995       if ( e->button() == Qt::LeftButton ) //eventually add a vertex to the face
996       {
997         if ( mDoubleClicks )
998         {
999           addVertex( mFirstClickPoint, e->mapPointMatch() );
1000           highlightCloseVertex( mFirstClickPoint );
1001         }
1002 
1003         if ( mCurrentVertexIndex != -1 )
1004         {
1005           addVertexToFaceCanditate( mCurrentVertexIndex );
1006           QgsPointXY currentPoint = mapVertexXY( mCurrentVertexIndex );
1007           cadDockWidget()->setPoints( QList<QgsPointXY>() << currentPoint << currentPoint );
1008         }
1009       }
1010       else if ( e->button() == Qt::RightButton ) //if possible validate and add the face to the mesh
1011       {
1012         if ( testNewVertexInFaceCanditate( -1 ) )
1013         {
1014           mCurrentEditor->addFace( mNewFaceCandidate.toVector() );
1015           mNewFaceBand->reset( QgsWkbTypes::PolygonGeometry );
1016           mNewFaceCandidate.clear();
1017           mCurrentState = Digitizing;
1018         }
1019       }
1020       break;
1021     case Selecting:
1022     {
1023       QgsGeometry selectionGeom = mSelectionBand->asGeometry();
1024       selectByGeometry( selectionGeom, e->modifiers() );
1025       mSelectionBand->reset( QgsWkbTypes::PolygonGeometry );
1026       mCurrentState = Digitizing;
1027     }
1028     break;
1029     case MovingSelection:
1030       if ( mIsMovingAllowed )
1031       {
1032         const QList<int> verticesIndexes = mSelectedVertices.keys();
1033         QList<QgsPointXY> newPosition;
1034         newPosition.reserve( verticesIndexes.count() );
1035 
1036         const QgsMeshVertex &mapPointInNativeCoordinate =
1037           mCurrentLayer->triangularMesh()->triangularToNativeCoordinates( QgsMeshVertex( mapPoint.x(), mapPoint.y() ) );
1038         const QgsMeshVertex &startingPointInNativeCoordinate =
1039           mCurrentLayer->triangularMesh()->triangularToNativeCoordinates( QgsMeshVertex( mStartMovingPoint.x(), mStartMovingPoint.y() ) );
1040         const QgsVector &translationInLayerCoordinate = mapPointInNativeCoordinate - startingPointInNativeCoordinate;
1041 
1042         const QgsMesh &mesh = *mCurrentLayer->nativeMesh();
1043         mKeepSelectionOnEdit = true;
1044         if ( verticesIndexes.count() != 1 )
1045         {
1046           for ( int i = 0; i < verticesIndexes.count(); ++i )
1047             newPosition.append( QgsPointXY( mesh.vertex( verticesIndexes.at( i ) ) ) + translationInLayerCoordinate );
1048           mCurrentEditor->changeXYValues( verticesIndexes, newPosition );
1049         }
1050         else
1051         {
1052           //only one vertex, change also the Z value if snap on a 3D vector layer
1053           if ( e->mapPointMatch().isValid() &&
1054                QgsWkbTypes::hasZ( e->mapPointMatch().layer()->wkbType() ) )
1055           {
1056             const QgsMeshVertex mapPointInMapCoordinate =
1057               QgsMeshVertex( mapPoint.x(), mapPoint.y(), e->mapPointMatch().interpolatedPoint( mCanvas->mapSettings().destinationCrs() ).z() );
1058 
1059             const QgsMeshVertex &mapPointInNativeCoordinate =
1060               mCurrentLayer->triangularMesh()->triangularToNativeCoordinates( mapPointInMapCoordinate ) ;
1061             mCurrentEditor->changeCoordinates( verticesIndexes,
1062                                                QList<QgsPoint>()
1063                                                << mapPointInNativeCoordinate ) ;
1064           }
1065           else
1066             mCurrentEditor->changeXYValues( verticesIndexes, QList<QgsPointXY>()
1067                                             << QgsPointXY( mesh.vertex( verticesIndexes.at( 0 ) ) ) + translationInLayerCoordinate );
1068         }
1069       }
1070       updateSelectecVerticesMarker();
1071       prepareSelection();
1072       clearCanvasHelpers();
1073       mMovingEdgesRubberband->reset();
1074       mMovingFacesRubberband->reset();
1075       mMovingFreeVertexRubberband->reset();
1076       mCadDockWidget->setEnabledZ( mCadDockWidget->cadEnabled() );
1077       mCurrentState = Digitizing;
1078       break;
1079     case SelectingByPolygon:
1080       if ( mSelectionHandler )
1081         mSelectionHandler->canvasReleaseEvent( e );
1082       break;
1083     case ForceByLines:
1084       forceByLineReleaseEvent( e );
1085       break;
1086   }
1087   mDoubleClicks = false;
1088 
1089   QgsMapToolAdvancedDigitizing::cadCanvasReleaseEvent( e );
1090 }
1091 
moveSelection(const QgsPointXY & destinationPoint)1092 void QgsMapToolEditMeshFrame::moveSelection( const QgsPointXY &destinationPoint )
1093 {
1094   const QgsVector &translation = destinationPoint - mStartMovingPoint;
1095   mMovingEdgesRubberband->reset( QgsWkbTypes::LineGeometry );
1096   mMovingFacesRubberband->reset( QgsWkbTypes::PolygonGeometry );
1097   mMovingFreeVertexRubberband->reset( QgsWkbTypes::PointGeometry );
1098   QgsGeometry movingFacesGeometry = mSelectedFacesRubberband->asGeometry();
1099   movingFacesGeometry.translate( translation.x(), translation.y() );
1100   mMovingFacesRubberband->setToGeometry( movingFacesGeometry );
1101 
1102   QSet<int> borderMovingFace;
1103 
1104   for ( QMap<int, SelectedVertexData>::const_iterator it = mSelectedVertices.constBegin(); it != mSelectedVertices.constEnd(); ++it )
1105   {
1106     const QgsPointXY &point1 = mapVertexXY( it.key() ) + translation;
1107     const SelectedVertexData &vertexData = it.value();
1108     for ( int i = 0; i < vertexData.meshFixedEdges.count(); ++i )
1109     {
1110       const QgsPointXY point2 = mapVertexXY( vertexData.meshFixedEdges.at( i ).second );
1111       QgsGeometry edge( new QgsLineString( {point1, point2} ) );
1112       mMovingEdgesRubberband->addGeometry( edge );
1113       int associateFace = vertexData.meshFixedEdges.at( i ).first;
1114       if ( associateFace != -1 )
1115         borderMovingFace.insert( associateFace );
1116     }
1117 
1118     for ( int i = 0; i < vertexData.borderEdges.count(); ++i )
1119     {
1120       const QgsPointXY point2 = mapVertexXY( vertexData.borderEdges.at( i ).second ) + translation;
1121       const QgsGeometry edge( new QgsLineString( {point1, point2} ) );
1122       mMovingEdgesRubberband->addGeometry( edge );
1123     }
1124 
1125     if ( mCurrentEditor->isVertexFree( it.key() ) )
1126       mMovingFreeVertexRubberband->addPoint( mapVertexXY( it.key() ) + translation, false );
1127   }
1128 
1129   mMovingFreeVertexRubberband->setVisible( true );
1130   mMovingFreeVertexRubberband->updatePosition();
1131   mMovingFreeVertexRubberband->update();
1132 
1133   const QgsMeshVertex &mapPointInNativeCoordinate =
1134     mCurrentLayer->triangularMesh()->triangularToNativeCoordinates( QgsMeshVertex( destinationPoint.x(), destinationPoint.y() ) );
1135   const QgsMeshVertex &startingPointInNativeCoordinate =
1136     mCurrentLayer->triangularMesh()->triangularToNativeCoordinates( QgsMeshVertex( mStartMovingPoint.x(), mStartMovingPoint.y() ) );
1137   const QgsVector &translationInLayerCoordinate = mapPointInNativeCoordinate - startingPointInNativeCoordinate;
1138 
1139   auto transformFunction = [translationInLayerCoordinate, this ]( int vi )-> const QgsMeshVertex
1140   {
1141     if ( mSelectedVertices.contains( vi ) )
1142       return mCurrentLayer->nativeMesh()->vertex( vi ) + translationInLayerCoordinate;
1143     else
1144       return mCurrentLayer->nativeMesh()->vertex( vi );
1145   };
1146 
1147 // we test only the faces that are deformed on the border, moving and not deformed faces are tested later
1148   mIsMovingAllowed = mCurrentEditor->canBeTransformed( qgis::setToList( borderMovingFace ), transformFunction );
1149 
1150   if ( mIsMovingAllowed )
1151   {
1152     //to finish test if the polygons formed by the moving faces contains something else
1153     const QList<int> &faceIndexesIntersect = mCurrentLayer->triangularMesh()->nativeFaceIndexForRectangle( movingFacesGeometry.boundingBox() );
1154     for ( const int faceIndex : faceIndexesIntersect )
1155     {
1156       if ( mConcernedFaceBySelection.contains( faceIndex ) )
1157         continue;
1158       const QgsGeometry otherFaceGeom( new QgsPolygon( new QgsLineString( nativeFaceGeometry( faceIndex ) ) ) );
1159       mIsMovingAllowed &= !movingFacesGeometry.intersects( otherFaceGeom );
1160       if ( !mIsMovingAllowed )
1161         break;
1162     }
1163 
1164     if ( mIsMovingAllowed ) //last check, the free vertices...
1165     {
1166       const QList<int> &freeVerticesIndexes = mCurrentEditor->freeVerticesIndexes();
1167       for ( const int vertexIndex : freeVerticesIndexes )
1168       {
1169         const QgsMeshVertex transformedVertex = transformFunction( vertexIndex );
1170         const QgsMeshVertex &mapTransformedVertex = mCurrentLayer->triangularMesh()->nativeToTriangularCoordinates( transformedVertex );
1171         const QgsPointXY pointInMap( mapTransformedVertex );
1172         mIsMovingAllowed &= !movingFacesGeometry.contains( &pointInMap );
1173         if ( !mIsMovingAllowed )
1174           break;
1175       }
1176     }
1177   }
1178 
1179   setMovingRubberBandValidity( mIsMovingAllowed );
1180 }
1181 
select(const QgsPointXY & mapPoint,Qt::KeyboardModifiers modifiers,double tolerance)1182 void QgsMapToolEditMeshFrame::select( const QgsPointXY &mapPoint, Qt::KeyboardModifiers modifiers, double tolerance )
1183 {
1184   Qgis::SelectBehavior behavior;
1185   if ( modifiers & Qt::ShiftModifier )
1186     behavior = Qgis::SelectBehavior::AddToSelection;
1187   else if ( modifiers & Qt::ControlModifier )
1188     behavior = Qgis::SelectBehavior::RemoveFromSelection;
1189   else
1190     behavior = Qgis::SelectBehavior::SetSelection;
1191 
1192   QgsPointXY currentPoint = mapPoint;
1193 
1194   if ( mSelectFaceMarker->isVisible() &&
1195        mapPoint.distance( mSelectFaceMarker->center() ) < tolerance
1196        && mCurrentFaceIndex >= 0 )
1197   {
1198     setSelectedVertices( nativeFace( mCurrentFaceIndex ).toList(), behavior );
1199     currentPoint = mCurrentLayer->triangularMesh()->faceCentroids().at( mCurrentFaceIndex );
1200   }
1201   else if ( mCurrentVertexIndex != -1 )
1202   {
1203     setSelectedVertices( QList<int>() << mCurrentVertexIndex, behavior );
1204     currentPoint = mCurrentLayer->triangularMesh()->vertices().at( mCurrentVertexIndex );
1205   }
1206   else if ( mSelectEdgeMarker->isVisible() &&
1207             mapPoint.distance( mSelectEdgeMarker->center() ) < tolerance &&
1208             mCurrentEdge.first != -1 && mCurrentEdge.second != -1 )
1209   {
1210     QVector<int> edgeVert = edgeVertices( mCurrentEdge );
1211     setSelectedVertices( edgeVert.toList(), behavior );
1212     const QgsMeshVertex v1 = mCurrentLayer->triangularMesh()->vertices().at( edgeVert.at( 0 ) );
1213     const QgsMeshVertex v2 = mCurrentLayer->triangularMesh()->vertices().at( edgeVert.at( 1 ) );
1214     currentPoint = QgsPointXY( ( v1.x() + v2.x() ) / 2, ( v1.y() + v2.y() ) / 2 );
1215   }
1216   else
1217     setSelectedVertices( QList<int>(),  behavior );
1218   mCadDockWidget->setPoints( QList < QgsPointXY>() << currentPoint << currentPoint );
1219 }
1220 
keyPressEvent(QKeyEvent * e)1221 void QgsMapToolEditMeshFrame::keyPressEvent( QKeyEvent *e )
1222 {
1223   bool consumned = false;
1224   switch ( mCurrentState )
1225   {
1226     case Digitizing:
1227     {
1228       if ( e->key() == Qt::Key_Delete && ( e->modifiers() & Qt::ControlModifier ) )
1229       {
1230         removeSelectedVerticesFromMesh( !( e->modifiers() & Qt::ShiftModifier ) );
1231         consumned = true;
1232       }
1233       else if ( e->key() == Qt::Key_Delete && ( e->modifiers() & Qt::ShiftModifier ) )
1234       {
1235         removeFacesFromMesh();
1236         consumned = true;
1237       }
1238 
1239       if ( e->key() == Qt::Key_Escape )
1240       {
1241         clearSelection();
1242         consumned = true;
1243       }
1244 
1245       if ( e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter )
1246       {
1247         applyZValueOnSelectedVertices();
1248         consumned = true;
1249       }
1250     }
1251     break;
1252     case AddingNewFace:
1253     {
1254       if ( e->key() == Qt::Key_Backspace )
1255       {
1256         mNewFaceBand->removePoint( -2, true );
1257         if ( !mNewFaceCandidate.isEmpty() )
1258           mNewFaceCandidate.removeLast();
1259         if ( mNewFaceCandidate.isEmpty() )
1260           mCurrentState = Digitizing;
1261         consumned = true;
1262       }
1263 
1264       if ( e->key() == Qt::Key_Escape )
1265       {
1266         mNewFaceBand->reset( QgsWkbTypes::PolygonGeometry );
1267         mNewFaceCandidate.clear();
1268         mCurrentState = Digitizing;
1269         consumned = true;
1270       }
1271     }
1272     break;
1273     case MovingSelection:
1274       if ( e->key() == Qt::Key_Escape )
1275       {
1276         mCurrentState = Digitizing;
1277         mMovingEdgesRubberband->reset( QgsWkbTypes::LineGeometry );
1278         mMovingFacesRubberband->reset( QgsWkbTypes::PolygonGeometry );
1279         mMovingFreeVertexRubberband->reset( QgsWkbTypes::PointGeometry );
1280         mCadDockWidget->setEnabledZ( mCadDockWidget->cadEnabled() );
1281       }
1282       break;
1283     case ForceByLines:
1284       if ( e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter )
1285       {
1286         if ( !mCadDockWidget->cadEnabled() )
1287           mUserZValue = currentZValue();
1288       }
1289 
1290       if ( e->key() == Qt::Key_Escape )
1291       {
1292         mForceByLineRubberBand->reset( QgsWkbTypes::LineGeometry );
1293         mForcingLineZValue.clear();
1294       }
1295       break;
1296     case Selecting:
1297     case SelectingByPolygon:
1298       if ( e->key() == Qt::Key_Escape )
1299       {
1300         clearSelection();
1301         consumned = true;
1302       }
1303       break;
1304   }
1305 
1306   if ( !consumned && mZValueWidget )
1307     QgsApplication::sendEvent( mZValueWidget->keyboardEntryWidget(), e );
1308   else
1309     e->ignore();
1310 
1311   if ( e->key() == Qt::Key_Backspace || e->key() == Qt::Key_Delete )
1312     e->ignore();
1313 
1314   QgsMapToolAdvancedDigitizing::keyPressEvent( e );
1315 }
1316 
keyReleaseEvent(QKeyEvent * e)1317 void QgsMapToolEditMeshFrame::keyReleaseEvent( QKeyEvent *e )
1318 {
1319   bool consumned = false;
1320   switch ( mCurrentState )
1321   {
1322     case Digitizing:
1323       break;
1324     case AddingNewFace:
1325       if ( e->key() == Qt::Key_Backspace )
1326         consumned = true; //to avoid removing the value of the ZvalueWidget
1327       break;
1328     case SelectingByPolygon:
1329       if ( mSelectionHandler )
1330         mSelectionHandler->keyReleaseEvent( e );
1331       break;
1332     case Selecting:
1333     case MovingSelection:
1334     case ForceByLines:
1335       break;
1336   }
1337 
1338   if ( !consumned && mZValueWidget )
1339     QgsApplication::sendEvent( mZValueWidget->keyboardEntryWidget(), e );
1340 
1341   QgsMapToolAdvancedDigitizing::keyReleaseEvent( e );
1342 }
1343 
canvasDoubleClickEvent(QgsMapMouseEvent * e)1344 void QgsMapToolEditMeshFrame::canvasDoubleClickEvent( QgsMapMouseEvent *e )
1345 {
1346   Q_UNUSED( e )
1347   //canvasReleseaseEvent() will be called just after the last click, so just flag the double clicks
1348   mDoubleClicks = true;
1349 
1350   QgsMapToolAdvancedDigitizing::canvasDoubleClickEvent( e );
1351 }
1352 
onEditingStopped()1353 void QgsMapToolEditMeshFrame::onEditingStopped()
1354 {
1355   mCurrentEditor = nullptr;
1356   deactivate();
1357 }
1358 
mapVertex(int index) const1359 const QgsMeshVertex QgsMapToolEditMeshFrame::mapVertex( int index ) const
1360 {
1361   if ( mCurrentLayer.isNull() || ! mCurrentLayer->triangularMesh() )
1362     return QgsMeshVertex();
1363 
1364   return mCurrentLayer->triangularMesh()->vertices().at( index );
1365 }
1366 
mapVertexXY(int index) const1367 const QgsPointXY QgsMapToolEditMeshFrame::mapVertexXY( int index ) const
1368 {
1369   const QgsMeshVertex &v = mapVertex( index );
1370   return QgsPointXY( v.x(), v.y() );
1371 }
1372 
nativeFace(int index) const1373 const QgsMeshFace QgsMapToolEditMeshFrame::nativeFace( int index ) const
1374 {
1375   if ( mCurrentLayer.isNull() || ! mCurrentLayer->nativeMesh() )
1376     return QgsMeshFace();
1377 
1378   return mCurrentLayer->nativeMesh()->face( index );
1379 }
1380 
currentZValue()1381 double QgsMapToolEditMeshFrame::currentZValue()
1382 {
1383   if ( mDoubleClicks )
1384     return mFirstClickZValue;
1385   else if ( mZValueWidget )
1386     return mZValueWidget->zValue();
1387   else  if ( mCadDockWidget->cadEnabled() )
1388     return mCadDockWidget->currentPointV2().z();
1389 
1390   return defaultZValue();
1391 }
1392 
searchFace(const QgsPointXY & mapPoint)1393 void QgsMapToolEditMeshFrame::searchFace( const QgsPointXY &mapPoint )
1394 {
1395   if ( !mCurrentLayer.isNull() && mCurrentLayer->triangularMesh() )
1396     mCurrentFaceIndex = mCurrentLayer->triangularMesh()->nativeFaceIndexForPoint( mapPoint );
1397 }
1398 
searchEdge(const QgsPointXY & mapPoint)1399 void QgsMapToolEditMeshFrame::searchEdge( const QgsPointXY &mapPoint )
1400 {
1401   mCurrentEdge = {-1, -1};
1402   double tolerance = QgsTolerance::vertexSearchRadius( canvas()->mapSettings() );
1403 
1404   QList<int> candidateFaceIndexes;
1405 
1406   if ( mCurrentFaceIndex != -1 )
1407   {
1408     candidateFaceIndexes.append( mCurrentFaceIndex );
1409   }
1410   else
1411   {
1412     const QgsRectangle searchRect( mapPoint.x() - tolerance, mapPoint.y() - tolerance, mapPoint.x() + tolerance, mapPoint.y() + tolerance );
1413     candidateFaceIndexes = mCurrentLayer->triangularMesh()->nativeFaceIndexForRectangle( searchRect );
1414   }
1415 
1416   double minimumDistance = std::numeric_limits<double>::max();
1417   for ( const int faceIndex : std::as_const( candidateFaceIndexes ) )
1418   {
1419     const QgsMeshFace &face = nativeFace( faceIndex );
1420     int faceSize = face.count();
1421     for ( int i = 0; i < faceSize; ++i )
1422     {
1423       int iv1 = face.at( i );
1424       int iv2 = face.at( ( i + 1 ) % faceSize );
1425 
1426       QgsPointXY pt1 = mapVertexXY( iv1 );
1427       QgsPointXY pt2 = mapVertexXY( iv2 );
1428 
1429       QgsPointXY pointOneEdge;
1430       double distance = sqrt( mapPoint.sqrDistToSegment( pt1.x(), pt1.y(), pt2.x(), pt2.y(), pointOneEdge ) );
1431       if ( distance < tolerance && distance < minimumDistance && edgeCanBeInteractive( iv1, iv2 ) )
1432       {
1433         mCurrentEdge = {faceIndex, iv2};
1434         minimumDistance = distance;
1435       }
1436     }
1437   }
1438 }
1439 
highLight(const QgsPointXY & mapPoint)1440 void QgsMapToolEditMeshFrame::highLight( const QgsPointXY &mapPoint )
1441 {
1442   searchFace( mapPoint );
1443   highlightCurrentHoveredFace( mapPoint );
1444   searchEdge( mapPoint );
1445   highlightCloseVertex( mapPoint );
1446   highlightCloseEdge( mapPoint );
1447 }
1448 
onEditingStarted()1449 void QgsMapToolEditMeshFrame::onEditingStarted()
1450 {
1451   setCurrentLayer( canvas()->currentLayer() );
1452 }
1453 
setCurrentLayer(QgsMapLayer * layer)1454 void QgsMapToolEditMeshFrame::setCurrentLayer( QgsMapLayer *layer )
1455 {
1456   QgsMeshLayer *meshLayer = qobject_cast<QgsMeshLayer *>( layer );
1457 
1458   if ( mCurrentLayer == meshLayer && mCurrentEditor != nullptr )
1459     return;
1460 
1461   if ( mCurrentEditor )
1462     deactivate();
1463 
1464   if ( mIsInitialized )
1465     clearSelection(); //TODO later: implement a mechanism to retrieve selection if the layer is again selected
1466 
1467   if ( mCurrentLayer )
1468   {
1469     disconnect( mCurrentLayer, &QgsMeshLayer::editingStarted, this, &QgsMapToolEditMeshFrame::onEditingStarted );
1470     disconnect( mCurrentLayer, &QgsMeshLayer::editingStopped, this, &QgsMapToolEditMeshFrame::onEditingStopped );
1471   }
1472 
1473   mCurrentLayer = meshLayer;
1474   mCurrentFaceIndex = -1;
1475 
1476   if ( mCurrentEditor )
1477   {
1478     disconnect( mCurrentEditor, &QgsMeshEditor::meshEdited, this, &QgsMapToolEditMeshFrame::onEdit );
1479   }
1480 
1481   mCurrentEditor = nullptr;
1482 
1483   if ( mCurrentLayer )
1484   {
1485     connect( mCurrentLayer, &QgsMeshLayer::editingStarted, this, &QgsMapToolEditMeshFrame::onEditingStarted );
1486     connect( mCurrentLayer, &QgsMeshLayer::editingStopped, this, &QgsMapToolEditMeshFrame::onEditingStopped );
1487 
1488     if ( mCurrentLayer->isEditable() )
1489     {
1490       mCurrentEditor = mCurrentLayer->meshEditor();
1491       if ( !mIsInitialized )
1492         initialize();
1493       connect( mCurrentEditor, &QgsMeshEditor::meshEdited, this, &QgsMapToolEditMeshFrame::onEdit );
1494     }
1495   }
1496 
1497   if ( mCurrentEditor )
1498   {
1499     activate();
1500     updateFreeVertices();
1501   }
1502 
1503   emit selectionChange( mCurrentLayer, mSelectedVertices.keys() );
1504 }
1505 
onEdit()1506 void QgsMapToolEditMeshFrame::onEdit()
1507 {
1508   if ( !mKeepSelectionOnEdit )
1509     clearSelection();
1510   mKeepSelectionOnEdit = false;
1511   clearCanvasHelpers();
1512   updateFreeVertices();
1513 }
1514 
nativeFaceGeometry(int faceIndex) const1515 QgsPointSequence QgsMapToolEditMeshFrame::nativeFaceGeometry( int faceIndex ) const
1516 {
1517   QgsPointSequence faceGeometry;
1518   const QgsMeshFace &face = mCurrentLayer->nativeMesh()->face( faceIndex );
1519 
1520   for ( const int index : face )
1521     faceGeometry.append( mapVertex( index ) );
1522 
1523   return faceGeometry;
1524 }
1525 
edgeGeometry(const QgsMapToolEditMeshFrame::Edge & edge) const1526 QVector<QgsPointXY> QgsMapToolEditMeshFrame::edgeGeometry( const QgsMapToolEditMeshFrame::Edge &edge ) const
1527 {
1528   const QVector<int> &vertexIndexes = edgeVertices( edge );
1529 
1530   return {mapVertexXY( vertexIndexes.at( 0 ) ), mapVertexXY( vertexIndexes.at( 1 ) )};
1531 }
1532 
edgeVertices(const QgsMapToolEditMeshFrame::Edge & edge) const1533 QVector<int> QgsMapToolEditMeshFrame::edgeVertices( const QgsMapToolEditMeshFrame::Edge &edge ) const
1534 {
1535   const QgsMeshFace &face = nativeFace( edge.first );
1536   int faceSize = face.count();
1537   int posInface = ( face.indexOf( edge.second ) + faceSize - 1 ) % faceSize;
1538 
1539   return {face.at( posInface ), edge.second};
1540 }
1541 
newFaceMarkerPosition(int vertexIndex)1542 QgsPointXY QgsMapToolEditMeshFrame::newFaceMarkerPosition( int vertexIndex )
1543 {
1544   QgsVector directionVector;
1545 
1546   const QgsMeshVertex &v = mapVertex( vertexIndex );
1547 
1548   if ( mCurrentEditor->isVertexFree( vertexIndex ) )
1549   {
1550     directionVector = QgsVector( 1, 0 );
1551   }
1552   else
1553   {
1554     QgsMeshVertexCirculator circulator = mCurrentEditor->vertexCirculator( vertexIndex );
1555     circulator.goBoundaryClockwise();
1556     int indexPt1 = circulator.oppositeVertexClockwise();
1557     circulator.goBoundaryCounterClockwise();
1558     int indexPt2 = circulator.oppositeVertexCounterClockwise();
1559 
1560     const QgsMeshVertex &v1 = mapVertex( indexPt1 );
1561     const QgsMeshVertex &v2 = mapVertex( indexPt2 );
1562 
1563     QgsVector vector1 = v - v2;
1564     QgsVector vector2 = v - v1;
1565 
1566     vector1 = vector1.normalized();
1567     vector2 = vector2.normalized();
1568 
1569     double crossProduct = vector1.crossProduct( vector2 );
1570 
1571     if ( crossProduct < - 1e-8 )
1572       directionVector = ( vector1 + vector2 ).normalized();
1573     else if ( crossProduct > 1e-8 )
1574       directionVector = -( vector1 + vector2 ).normalized();
1575     else
1576       directionVector = vector2.perpVector();
1577   }
1578 
1579   double dist = 15 * canvas()->mapSettings().mapUnitsPerPixel();
1580   return v + directionVector * dist;
1581 }
1582 
addVertexToFaceCanditate(int vertexIndex)1583 void QgsMapToolEditMeshFrame::addVertexToFaceCanditate( int vertexIndex )
1584 {
1585   if ( vertexIndex == -1 || ( !mNewFaceCandidate.isEmpty() && vertexIndex == mNewFaceCandidate.last() ) )
1586     return;
1587 
1588   mNewFaceBand->movePoint( mapVertexXY( vertexIndex ) );
1589   mNewFaceBand->addPoint( mapVertexXY( vertexIndex ) );
1590   mNewFaceCandidate.append( vertexIndex );
1591 }
1592 
testNewVertexInFaceCanditate(int vertexIndex)1593 bool QgsMapToolEditMeshFrame::testNewVertexInFaceCanditate( int vertexIndex )
1594 {
1595   QgsMeshFace face = mNewFaceCandidate.toVector();
1596   if ( vertexIndex != -1 && !face.empty() && vertexIndex != mNewFaceCandidate.last() )
1597     face.append( vertexIndex );
1598   return mCurrentEditor->faceCanBeAdded( face );
1599 }
1600 
1601 
addNewSelectedVertex(int vertexIndex)1602 void QgsMapToolEditMeshFrame::addNewSelectedVertex( int vertexIndex )
1603 {
1604   mSelectedVertices.insert( vertexIndex, SelectedVertexData() );
1605   QgsVertexMarker *marker = new QgsVertexMarker( canvas() );
1606   marker->setIconType( QgsVertexMarker::ICON_CIRCLE );
1607   marker->setIconSize( QgsGuiUtils::scaleIconSize( 10 ) );
1608   marker->setPenWidth( QgsGuiUtils::scaleIconSize( 2 ) );
1609   marker->setColor( Qt::blue );
1610   marker->setCenter( mapVertexXY( vertexIndex ) );
1611   marker->setZValue( 2 );
1612   mSelectedVerticesMarker[vertexIndex] = marker;
1613 }
1614 
removeFromSelection(int vertexIndex)1615 void QgsMapToolEditMeshFrame::removeFromSelection( int vertexIndex )
1616 {
1617   mSelectedVertices.remove( vertexIndex );
1618   delete mSelectedVerticesMarker.value( vertexIndex );
1619   mSelectedVerticesMarker.remove( vertexIndex );
1620 }
1621 
isFaceSelected(int faceIndex)1622 bool QgsMapToolEditMeshFrame::isFaceSelected( int faceIndex )
1623 {
1624   const QgsMeshFace &face = nativeFace( faceIndex );
1625 
1626   for ( int i = 0; i < face.size(); ++i )
1627   {
1628     if ( !mSelectedVertices.contains( face.at( i ) ) )
1629       return false;
1630   }
1631   return true;
1632 }
1633 
setSelectedVertices(const QList<int> newSelectedVertices,Qgis::SelectBehavior behavior)1634 void QgsMapToolEditMeshFrame::setSelectedVertices( const QList<int> newSelectedVertices, Qgis::SelectBehavior behavior )
1635 {
1636   if ( mSelectedVertices.isEmpty() )
1637   {
1638     mUserZValue = currentZValue();
1639   }
1640 
1641   bool removeVertices = false;
1642 
1643   switch ( behavior )
1644   {
1645     case Qgis::SelectBehavior::SetSelection:
1646       clearSelection();
1647       break;
1648     case Qgis::SelectBehavior::AddToSelection:
1649       break;
1650     case Qgis::SelectBehavior::RemoveFromSelection:
1651       removeVertices = true;
1652       break;
1653     case Qgis::SelectBehavior::IntersectSelection:
1654       return;
1655       break;
1656   }
1657 
1658   for ( const int vertexIndex : newSelectedVertices )
1659   {
1660     bool contained = mSelectedVertices.contains( vertexIndex );
1661     if ( contained &&  removeVertices )
1662       removeFromSelection( vertexIndex );
1663     else if ( ! removeVertices && !contained )
1664       addNewSelectedVertex( vertexIndex );
1665   }
1666 
1667   prepareSelection();
1668 }
1669 
setSelectedFaces(const QList<int> newSelectedFaces,Qgis::SelectBehavior behavior)1670 void QgsMapToolEditMeshFrame::setSelectedFaces( const QList<int> newSelectedFaces, Qgis::SelectBehavior behavior )
1671 {
1672   bool removeFaces = false;
1673 
1674   switch ( behavior )
1675   {
1676     case Qgis::SelectBehavior::SetSelection:
1677       clearSelection();
1678       break;
1679     case Qgis::SelectBehavior::AddToSelection:
1680       break;
1681     case Qgis::SelectBehavior::RemoveFromSelection:
1682       removeFaces = true;
1683       break;
1684     case Qgis::SelectBehavior::IntersectSelection:
1685       return;
1686       break;
1687   }
1688 
1689   const QSet<int> facesToTreat = qgis::listToSet( newSelectedFaces );
1690 
1691   for ( const int faceIndex : newSelectedFaces )
1692   {
1693     const QgsMeshFace &face = nativeFace( faceIndex );
1694     for ( int i = 0; i < face.size(); ++i )
1695     {
1696       int vertexIndex = face.at( i );
1697       bool vertexContained = mSelectedVertices.contains( vertexIndex );
1698       if ( vertexContained && removeFaces )
1699       {
1700         const QList<int> facesAround = mCurrentEditor->topologicalMesh().facesAroundVertex( vertexIndex );
1701         bool keepVertex = false;
1702         for ( const int faceAroundIndex : facesAround )
1703           keepVertex |= !facesToTreat.contains( faceAroundIndex ) && isFaceSelected( faceAroundIndex );
1704         if ( !keepVertex )
1705           removeFromSelection( vertexIndex );
1706       }
1707       else if ( !removeFaces && !vertexContained )
1708         addNewSelectedVertex( vertexIndex );
1709     }
1710   }
1711 
1712   prepareSelection();
1713 }
1714 
removeSelectedVerticesFromMesh(bool fillHole)1715 void QgsMapToolEditMeshFrame::removeSelectedVerticesFromMesh( bool fillHole )
1716 {
1717   if ( fillHole )
1718   {
1719 
1720     QList<int> remainingVertex = mCurrentEditor->removeVerticesFillHoles( mSelectedVertices.keys() );
1721 
1722     if ( !remainingVertex.isEmpty() )
1723     {
1724       QgisApp::instance()->messageBar()->pushWarning(
1725         tr( "Mesh editing" ),
1726         tr( "%n vertices were not removed", nullptr, remainingVertex.count() ) );
1727     }
1728   }
1729   else
1730   {
1731     QgsMeshEditingError error = mCurrentEditor->removeVerticesWithoutFillHoles( mSelectedVertices.keys() );
1732     if ( error != QgsMeshEditingError() )
1733     {
1734       QgisApp::instance()->messageBar()->pushWarning(
1735         tr( "Mesh editing" ),
1736         tr( "removing the vertex %1 leads to a topological error, operation canceled." ).arg( error.elementIndex ) );
1737     }
1738   }
1739 }
1740 
removeFacesFromMesh()1741 void QgsMapToolEditMeshFrame::removeFacesFromMesh()
1742 {
1743   QgsMeshEditingError error;
1744   if ( ! mSelectedFaces.isEmpty() )
1745     error = mCurrentEditor->removeFaces( mSelectedFaces.values() );
1746   else if ( mCurrentFaceIndex != -1 )
1747     error = mCurrentEditor->removeFaces( {mCurrentFaceIndex} );
1748   else
1749     return;
1750 
1751   if ( error != QgsMeshEditingError() )
1752   {
1753     QgisApp::instance()->messageBar()->pushWarning(
1754       tr( "Mesh editing" ),
1755       tr( "removing the faces %1 leads to a topological error, operation canceled." ).arg( error.elementIndex ) );
1756   }
1757   else
1758   {
1759     clearSelection();
1760   }
1761 }
1762 
splitSelectedFaces()1763 void QgsMapToolEditMeshFrame::splitSelectedFaces()
1764 {
1765   QgsTemporaryCursorOverride waitCursor( Qt::WaitCursor );
1766   if ( mSplittableFaceCount > 0 )
1767     mCurrentEditor->splitFaces( mSelectedFaces.values() );
1768   else if ( mCurrentFaceIndex != -1 && mCurrentEditor->faceCanBeSplit( mCurrentFaceIndex ) )
1769     mCurrentEditor->splitFaces( {mCurrentFaceIndex} );
1770 }
1771 
triggerTransformCoordinatesDockWidget(bool checked)1772 void QgsMapToolEditMeshFrame::triggerTransformCoordinatesDockWidget( bool checked )
1773 {
1774   if ( mTransformDockWidget )
1775   {
1776     mTransformDockWidget->setUserVisible( checked );
1777     return;
1778   }
1779 
1780   onEditingStarted();
1781   mTransformDockWidget = new QgsMeshTransformCoordinatesDockWidget( QgisApp::instance() );
1782   mTransformDockWidget->setToggleVisibilityAction( mActionTransformCoordinates );
1783   mTransformDockWidget->setWindowTitle( tr( "Transform Mesh Vertices" ) );
1784   mTransformDockWidget->setObjectName( QStringLiteral( "TransformMeshVerticesDockWidget" ) );
1785   const QList<int> &inputVertices = mSelectedVertices.keys();
1786   mTransformDockWidget->setInput( mCurrentLayer, inputVertices );
1787 
1788   if ( !QgisApp::instance()->restoreDockWidget( mTransformDockWidget ) )
1789     QgisApp::instance()->addDockWidget( Qt::LeftDockWidgetArea, mTransformDockWidget );
1790   else
1791     QgisApp::instance()->panelMenu()->addAction( mTransformDockWidget->toggleViewAction() );
1792 
1793   mTransformDockWidget->show();
1794 
1795   connect( this, &QgsMapToolEditMeshFrame::selectionChange, mTransformDockWidget, &QgsMeshTransformCoordinatesDockWidget::setInput );
1796 
1797   connect( mTransformDockWidget, &QgsMeshTransformCoordinatesDockWidget::calculationUpdated, this, [this]
1798   {
1799     mMovingFacesRubberband->reset( QgsWkbTypes::PolygonGeometry );
1800     mMovingEdgesRubberband->reset( QgsWkbTypes::LineGeometry );
1801     mMovingFreeVertexRubberband->reset( QgsWkbTypes::PointGeometry );
1802     setMovingRubberBandValidity( mTransformDockWidget->isResultValid() );
1803 
1804     if ( !mCurrentLayer || !mCurrentEditor )
1805       return;
1806 
1807     QList<int> faceList = qgis::setToList( mSelectedFaces );
1808     QgsGeometry faceGeometrie;
1809     if ( faceList.count() == 1 )
1810     {
1811       const QgsMeshFace &face = mCurrentLayer->nativeMesh()->face( faceList.at( 0 ) );
1812       const int faceSize = face.size();
1813       QVector<QgsPointXY> faceVertices( faceSize );
1814       for ( int j = 0; j < faceSize; ++j )
1815         faceVertices[j] = mTransformDockWidget->transformedVertex( face.at( j ) );
1816 
1817       faceGeometrie = QgsGeometry::fromPolygonXY( {faceVertices} );
1818     }
1819     else
1820     {
1821       std::unique_ptr<QgsGeometryEngine> geomEngine( QgsGeometry::createGeometryEngine( faceGeometrie.constGet() ) );
1822       geomEngine->prepareGeometry();
1823       QVector<QgsGeometry> faces( mSelectedFaces.count() );
1824       for ( int i = 0; i < faceList.count(); ++i )
1825       {
1826         const QgsMeshFace &face = mCurrentLayer->nativeMesh()->face( faceList.at( i ) );
1827         const int faceSize = face.size();
1828         QVector<QgsPointXY> faceVertices( faceSize );
1829         for ( int j = 0; j < faceSize; ++j )
1830           faceVertices[j] = mTransformDockWidget->transformedVertex( face.at( j ) );
1831 
1832         faces[i] = QgsGeometry::fromPolygonXY( {faceVertices} );
1833       }
1834       QString error;
1835       faceGeometrie = QgsGeometry( geomEngine->combine( faces, &error ) );
1836     }
1837 
1838     QgsGeometry edgesGeom = QgsGeometry::fromMultiPolylineXY( QgsMultiPolylineXY() );
1839     for ( QMap<int, SelectedVertexData>::const_iterator it = mSelectedVertices.constBegin(); it != mSelectedVertices.constEnd(); ++it )
1840     {
1841       const QgsPointXY &point1 = mTransformDockWidget->transformedVertex( it.key() ) ;
1842       const SelectedVertexData &vertexData = it.value();
1843       for ( int i = 0; i < vertexData.meshFixedEdges.count(); ++i )
1844       {
1845         const QgsPointXY point2 = mTransformDockWidget->transformedVertex( vertexData.meshFixedEdges.at( i ).second );
1846         QgsGeometry edge( new QgsLineString( {point1, point2} ) );
1847         edgesGeom.addPart( edge );
1848       }
1849 
1850       for ( int i = 0; i < vertexData.borderEdges.count(); ++i )
1851       {
1852         const QgsPointXY point2 = mTransformDockWidget->transformedVertex( vertexData.borderEdges.at( i ).second );
1853         const QgsGeometry edge( new QgsLineString( {point1, point2} ) );
1854         edgesGeom.addPart( edge );
1855       }
1856     }
1857 
1858     QgsCoordinateTransform coordinateTransform( mCurrentLayer->crs(), canvas()->mapSettings().destinationCrs(), QgsProject::instance() );
1859 
1860     try
1861     {
1862       faceGeometrie.transform( coordinateTransform );
1863     }
1864     catch ( QgsCsException & )
1865     {}
1866 
1867     try
1868     {
1869       edgesGeom.transform( coordinateTransform );
1870     }
1871     catch ( QgsCsException & )
1872     {}
1873 
1874     mMovingFacesRubberband->setToGeometry( faceGeometrie );
1875     mMovingEdgesRubberband->setToGeometry( edgesGeom );
1876     for ( const int vertexIndex : mSelectedVertices.keys() )
1877       if ( mCurrentEditor->isVertexFree( vertexIndex ) )
1878       {
1879         QgsMeshVertex transformedVertex = mTransformDockWidget->transformedVertex( vertexIndex );
1880         mMovingFreeVertexRubberband->addPoint( mCurrentLayer->triangularMesh()->nativeToTriangularCoordinates( transformedVertex ), false );
1881       }
1882 
1883     mMovingFreeVertexRubberband->setVisible( true );
1884     mMovingFreeVertexRubberband->updatePosition();
1885     mMovingFreeVertexRubberband->update();
1886 
1887     setMovingRubberBandValidity( mTransformDockWidget->isResultValid() );
1888   } );
1889 
1890   connect( mTransformDockWidget, &QgsMeshTransformCoordinatesDockWidget::aboutToBeApplied, this, [this]
1891   {
1892     mKeepSelectionOnEdit = true;
1893   } );
1894 
1895   connect( mTransformDockWidget, &QgsMeshTransformCoordinatesDockWidget::applied, this, [this]
1896   {
1897     mTransformDockWidget->setInput( mCurrentLayer, mSelectedVertices.keys() );
1898     updateSelectecVerticesMarker();
1899     prepareSelection();
1900   } );
1901 
1902   connect( mTransformDockWidget, &QgsDockWidget::closed, this, [this]
1903   {
1904     mActionTransformCoordinates->setChecked( false );
1905     if ( !mIsInitialized )
1906       return;
1907     mMovingFacesRubberband->reset( QgsWkbTypes::PolygonGeometry );
1908     mMovingEdgesRubberband->reset( QgsWkbTypes::LineGeometry );
1909     mMovingFreeVertexRubberband->reset( QgsWkbTypes::PointGeometry );
1910     setMovingRubberBandValidity( false );
1911   } );
1912 
1913 }
1914 
reindexMesh()1915 void QgsMapToolEditMeshFrame::reindexMesh()
1916 {
1917   onEditingStarted();
1918 
1919   if ( !mCurrentLayer || !mCurrentLayer->isEditable() )
1920     return;
1921 
1922   if ( QMessageBox::question( canvas(), tr( "Reindex the Mesh" ),
1923                               tr( "Do you want to reindex the faces and vertices of the mesh layer %1?" ).arg( mCurrentLayer->name() ),
1924                               QMessageBox::Yes | QMessageBox::No, QMessageBox::No )
1925        == QMessageBox::No )
1926     return;
1927 
1928 
1929   QgsCoordinateTransform transform( mCurrentLayer->crs(), canvas()->mapSettings().destinationCrs(), QgsProject::instance() );
1930 
1931   QgsTemporaryCursorOverride waitCursor( Qt::WaitCursor );
1932   mCurrentLayer->reindex( transform, true );
1933 }
1934 
selectByGeometry(const QgsGeometry & geometry,Qt::KeyboardModifiers modifiers)1935 void QgsMapToolEditMeshFrame::selectByGeometry( const QgsGeometry &geometry, Qt::KeyboardModifiers modifiers )
1936 {
1937   if ( mCurrentLayer.isNull() || !mCurrentLayer->triangularMesh() || mCurrentEditor.isNull() )
1938     return;
1939 
1940   Qgis::SelectBehavior behavior;
1941   if ( modifiers & Qt::ShiftModifier )
1942     behavior = Qgis::SelectBehavior::AddToSelection;
1943   else if ( modifiers & Qt::ControlModifier )
1944     behavior = Qgis::SelectBehavior::RemoveFromSelection;
1945   else
1946     behavior = Qgis::SelectBehavior::SetSelection;
1947 
1948   if ( modifiers & Qt::AltModifier )
1949     selectContainedByGeometry( geometry, behavior );
1950   else
1951     selectTouchedByGeometry( geometry, behavior );
1952 }
1953 
selectTouchedByGeometry(const QgsGeometry & geometry,Qgis::SelectBehavior behavior)1954 void QgsMapToolEditMeshFrame::selectTouchedByGeometry( const QgsGeometry &geometry, Qgis::SelectBehavior behavior )
1955 {
1956   QSet<int> selectedVertices;
1957   const QList<int> nativeFaceIndexes = mCurrentLayer->triangularMesh()->nativeFaceIndexForRectangle( geometry.boundingBox() );
1958 
1959   std::unique_ptr<QgsGeometryEngine> engine( QgsGeometry::createGeometryEngine( geometry.constGet() ) );
1960   engine->prepareGeometry();
1961 
1962   for ( const int faceIndex : nativeFaceIndexes )
1963   {
1964     const QgsMeshFace &face = nativeFace( faceIndex );
1965     std::unique_ptr<QgsPolygon> faceGeom( new QgsPolygon( new QgsLineString( nativeFaceGeometry( faceIndex ) ) ) );
1966     if ( engine->intersects( faceGeom.get() ) )
1967     {
1968       QSet<int> faceToAdd = qgis::listToSet( face.toList() );
1969       selectedVertices.unite( faceToAdd );
1970     }
1971   }
1972 
1973   const QList<int> &freeVerticesIndexes = mCurrentEditor->freeVerticesIndexes();
1974   for ( const int freeVertexIndex : freeVerticesIndexes )
1975   {
1976     const QgsMeshVertex &vertex = mapVertex( freeVertexIndex );
1977     if ( engine->contains( &vertex ) )
1978       selectedVertices.insert( freeVertexIndex );
1979   }
1980 
1981   setSelectedVertices( selectedVertices.values(), behavior );
1982 }
1983 
selectContainedByGeometry(const QgsGeometry & geometry,Qgis::SelectBehavior behavior)1984 void QgsMapToolEditMeshFrame::selectContainedByGeometry( const QgsGeometry &geometry, Qgis::SelectBehavior behavior )
1985 {
1986   QSet<int> selectedVertices;
1987   const QList<int> nativeFaceIndexes = mCurrentLayer->triangularMesh()->nativeFaceIndexForRectangle( geometry.boundingBox() );
1988 
1989   std::unique_ptr<QgsGeometryEngine> engine( QgsGeometry::createGeometryEngine( geometry.constGet() ) );
1990   engine->prepareGeometry();
1991   for ( const int faceIndex : nativeFaceIndexes )
1992   {
1993     const QgsMeshFace &face = nativeFace( faceIndex );
1994     for ( const int vertexIndex : face )
1995     {
1996       const QgsMeshVertex &vertex = mapVertex( vertexIndex );
1997       if ( engine->contains( &vertex ) )
1998         selectedVertices.insert( vertexIndex );
1999     }
2000   }
2001 
2002   const QList<int> &freeVerticesIndexes = mCurrentEditor->freeVerticesIndexes();
2003   for ( const int freeVertexIndex : freeVerticesIndexes )
2004   {
2005     const QgsMeshVertex &vertex = mapVertex( freeVertexIndex );
2006     if ( engine->contains( &vertex ) )
2007       selectedVertices.insert( freeVertexIndex );
2008   }
2009 
2010   setSelectedVertices( selectedVertices.values(), behavior );
2011 }
2012 
applyZValueOnSelectedVertices()2013 void QgsMapToolEditMeshFrame::applyZValueOnSelectedVertices()
2014 {
2015   if ( !mZValueWidget )
2016     return;
2017 
2018   if ( mSelectedVertices.isEmpty() )
2019     return;
2020 
2021   QList<double> zValues;
2022   zValues.reserve( mSelectedVertices.count() );
2023   mUserZValue = currentZValue();
2024   for ( int i = 0; i < mSelectedVertices.count(); ++i )
2025     zValues.append( mUserZValue );
2026 
2027   mCurrentEditor->changeZValues( mSelectedVertices.keys(), zValues );
2028 }
2029 
prepareSelection()2030 void QgsMapToolEditMeshFrame::prepareSelection()
2031 {
2032   if ( !mSelectedVertices.isEmpty() )
2033   {
2034     double vertexZValue = 0;
2035     for ( int i : mSelectedVertices.keys() )
2036       vertexZValue += mapVertex( i ).z();
2037     vertexZValue /= mSelectedVertices.count();
2038 
2039     if ( !mZValueWidget )
2040       createZValueWidget();
2041 
2042     mZValueWidget->setZValue( vertexZValue );
2043   }
2044   else
2045   {
2046     if ( cadDockWidget()->cadEnabled() && mZValueWidget )
2047       deleteZValueWidget();
2048     else if ( mZValueWidget )
2049       mZValueWidget->setZValue( mUserZValue );
2050   }
2051 
2052   mConcernedFaceBySelection.clear();
2053   QMap<int, SelectedVertexData> movingVertices;
2054 
2055 
2056   double xMin = std::numeric_limits<double>::max();
2057   double xMax = -std::numeric_limits<double>::max();
2058   double yMin = std::numeric_limits<double>::max();
2059   double yMax = -std::numeric_limits<double>::max();
2060 
2061   // search for moving edges and mesh fixed edges
2062   for ( QMap<int, SelectedVertexData>::iterator it = mSelectedVertices.begin(); it != mSelectedVertices.end(); ++it )
2063   {
2064     SelectedVertexData &vertexData = it.value();
2065     int vertexIndex = it.key();
2066 
2067     QgsPointXY vert = mapVertex( vertexIndex );
2068     if ( vert.x() < xMin )
2069       xMin = vert.x();
2070     if ( vert.x() > xMax )
2071       xMax = vert.x();
2072     if ( vert.y() < yMin )
2073       yMin = vert.y();
2074     if ( vert.y() > yMax )
2075       yMax = vert.y();
2076 
2077     vertexData.borderEdges.clear();
2078     vertexData.meshFixedEdges.clear();
2079     QgsMeshVertexCirculator circulator = mCurrentEditor->vertexCirculator( vertexIndex );
2080 
2081     if ( !circulator.isValid() )
2082       continue;
2083 
2084     circulator.goBoundaryClockwise();
2085     int firstface = circulator.currentFaceIndex();
2086     do
2087     {
2088       int oppositeVertex = circulator.oppositeVertexClockwise();
2089       if ( mSelectedVertices.contains( oppositeVertex ) )
2090         vertexData.borderEdges.append( {circulator.currentFaceIndex(), oppositeVertex} );
2091       else
2092         vertexData.meshFixedEdges.append( {circulator.currentFaceIndex(), oppositeVertex} );
2093 
2094       mConcernedFaceBySelection.insert( circulator.currentFaceIndex() );
2095     }
2096     while ( circulator.turnCounterClockwise() != firstface && circulator.currentFaceIndex() != -1 );
2097 
2098     if ( circulator.currentFaceIndex() == -1 )
2099     {
2100       circulator.turnClockwise();
2101       int oppositeVertex = circulator.oppositeVertexCounterClockwise();
2102       if ( mSelectedVertices.contains( oppositeVertex ) )
2103         vertexData.borderEdges.append( {-1, oppositeVertex} );
2104       else
2105         vertexData.meshFixedEdges.append( {-1, oppositeVertex} );
2106     }
2107   }
2108 
2109   mSelectedMapExtent = QgsRectangle( xMin, yMin, xMax, yMax );
2110 
2111   // remove faces that have at least one vertex not selected
2112   mSelectedFaces = mConcernedFaceBySelection;
2113   for ( const int faceIndex : std::as_const( mConcernedFaceBySelection ) )
2114   {
2115     const QgsMeshFace &face = nativeFace( faceIndex );
2116     for ( const int vi : std::as_const( face ) )
2117       if ( !mSelectedVertices.contains( vi ) )
2118       {
2119         mSelectedFaces.remove( faceIndex );
2120         continue;
2121       }
2122   }
2123 
2124   // here, we search for border edges that have associate face in the selection and remove it
2125   for ( QMap<int, SelectedVertexData>::iterator it = mSelectedVertices.begin(); it != mSelectedVertices.end(); ++it )
2126   {
2127     SelectedVertexData &vertexData = it.value();
2128     int i = 0;
2129     while ( i < vertexData.borderEdges.count() )
2130     {
2131       int associateFace = vertexData.borderEdges.at( i ).first;
2132       if ( associateFace == -1 || mSelectedFaces.contains( associateFace ) )
2133         vertexData.borderEdges.removeAt( i );
2134       else
2135         i++;
2136     }
2137   }
2138 
2139   if ( !mSelectedFaces.isEmpty() )
2140   {
2141     const QList<int> faceList = qgis::setToList( mSelectedFaces );
2142     QgsGeometry faceGeometrie( new QgsPolygon( new QgsLineString( nativeFaceGeometry( faceList.at( 0 ) ) ) ) );
2143     if ( mSelectedFaces.count() == 1 )
2144     {
2145       mSelectedFacesRubberband->setToGeometry( faceGeometrie );
2146     }
2147     else
2148     {
2149       std::unique_ptr<QgsGeometryEngine> geomEngine( QgsGeometry::createGeometryEngine( faceGeometrie.constGet() ) );
2150       geomEngine->prepareGeometry();
2151 
2152       QVector<QgsGeometry> otherFaces( mSelectedFaces.count() );
2153       for ( int i = 0; i < faceList.count(); ++i )
2154         otherFaces[i] = QgsGeometry( new QgsPolygon( new QgsLineString( nativeFaceGeometry( faceList.at( i ) ) ) ) );
2155       QString error;
2156       const QgsGeometry allFaces( geomEngine->combine( otherFaces, &error ) );
2157       mSelectedFacesRubberband->setToGeometry( allFaces );
2158     }
2159 
2160     QColor fillColor = canvas()->mapSettings().selectionColor();
2161 
2162     if ( fillColor.alpha() > 100 ) //set alpha to 150 if the transparency is not enough to see the mesh
2163       fillColor.setAlpha( 100 );
2164 
2165     mSelectedFacesRubberband->setFillColor( fillColor );
2166   }
2167   else
2168     mSelectedFacesRubberband->reset( QgsWkbTypes::PolygonGeometry );
2169 
2170   if ( mSelectedVertices.count() == 1 )
2171   {
2172     mActionRemoveVerticesFillingHole->setText( tr( "Remove selected vertex and fill hole" ) );
2173     mActionRemoveVerticesWithoutFillingHole->setText( tr( "Remove selected vertex without filling hole" ) );
2174   }
2175   else if ( mSelectedVertices.count() > 1 )
2176   {
2177     mActionRemoveVerticesFillingHole->setText( tr( "Remove selected vertices and fill hole(s)" ) );
2178     mActionRemoveVerticesWithoutFillingHole->setText( tr( "Remove selected vertices without filling hole(s)" ) );
2179   }
2180 
2181   if ( mSelectedFaces.count() == 1 )
2182   {
2183     mActionRemoveFaces->setText( tr( "Remove selected face" ) );
2184     mActionFacesRefinement->setText( tr( "Refine selected face" ) );
2185   }
2186   else if ( mSelectedFaces.count() > 1 )
2187   {
2188     mActionRemoveFaces->setText( tr( "Remove %1 selected faces" ).arg( mSelectedFaces.count() ) );
2189     mActionFacesRefinement->setText( tr( "Refine %1 selected faces" ).arg( mSelectedFaces.count() ) );
2190   }
2191   else
2192   {
2193     mActionRemoveFaces->setText( tr( "Remove current face" ) );
2194     mActionFacesRefinement->setText( tr( "Refine current face" ) );
2195   }
2196 
2197   mSplittableFaceCount = 0;
2198   for ( const int faceIndex : std::as_const( mSelectedFaces ) )
2199   {
2200     if ( mCurrentEditor->faceCanBeSplit( faceIndex ) )
2201       mSplittableFaceCount++;
2202   }
2203 
2204   if ( mSplittableFaceCount == 1 )
2205     mActionSplitFaces->setText( tr( "Split selected face" ) );
2206   else if ( mSplittableFaceCount > 1 )
2207     mActionSplitFaces->setText( tr( "Split %1 selected faces" ).arg( mSplittableFaceCount ) );
2208   else
2209     mActionSplitFaces->setText( tr( "Split current face" ) );
2210 
2211   emit selectionChange( mCurrentLayer, mSelectedVertices.keys() );
2212 }
2213 
updateSelectecVerticesMarker()2214 void QgsMapToolEditMeshFrame::updateSelectecVerticesMarker()
2215 {
2216   qDeleteAll( mSelectedVerticesMarker );
2217   mSelectedVerticesMarker.clear();
2218   for ( const int vertexIndex : mSelectedVertices.keys() )
2219   {
2220     QgsVertexMarker *marker = new QgsVertexMarker( canvas() );
2221     marker->setIconType( QgsVertexMarker::ICON_CIRCLE );
2222     marker->setIconSize( QgsGuiUtils::scaleIconSize( 10 ) );
2223     marker->setPenWidth( QgsGuiUtils::scaleIconSize( 2 ) );
2224     marker->setColor( Qt::blue );
2225     marker->setCenter( mapVertexXY( vertexIndex ) );
2226     marker->setZValue( 2 );
2227     mSelectedVerticesMarker[vertexIndex] = marker;
2228   }
2229 }
2230 
setMovingRubberBandValidity(bool valid)2231 void QgsMapToolEditMeshFrame::setMovingRubberBandValidity( bool valid )
2232 {
2233   if ( valid )
2234   {
2235     mMovingFacesRubberband->setFillColor( QColor( 0, 200, 0, 100 ) );
2236     mMovingFacesRubberband->setStrokeColor( QColor( 0, 200, 0 ) );
2237     mMovingEdgesRubberband->setColor( QColor( 0, 200, 0 ) );
2238     mMovingFreeVertexRubberband->setColor( QColor( 0, 200, 0 ) );
2239   }
2240   else
2241   {
2242     mMovingFacesRubberband->setFillColor( QColor( 200, 0, 0, 100 ) );
2243     mMovingFacesRubberband->setStrokeColor( QColor( 200, 0, 0 ) );
2244     mMovingEdgesRubberband->setColor( QColor( 200, 0, 0 ) );
2245     mMovingFreeVertexRubberband->setColor( QColor( 200, 0, 0 ) );
2246   }
2247 }
2248 
isSelectionGrapped(QgsPointXY & grappedPoint)2249 bool QgsMapToolEditMeshFrame::isSelectionGrapped( QgsPointXY &grappedPoint )
2250 {
2251   if ( mCurrentVertexIndex != -1 && mSelectedVertices.contains( mCurrentVertexIndex ) )
2252   {
2253     grappedPoint = mapVertexXY( mCurrentVertexIndex );
2254     return true;
2255   }
2256 
2257   double tolerance = QgsTolerance::vertexSearchRadius( canvas()->mapSettings() );
2258 
2259   if ( mCurrentEdge.first != -1 && mCurrentEdge.second != -1  &&
2260        mSelectEdgeMarker->isVisible() &&
2261        grappedPoint.distance( mSelectEdgeMarker->center() ) < tolerance )
2262   {
2263     const QVector<int> vertices = edgeVertices( mCurrentEdge );
2264     if ( mSelectedVertices.contains( vertices.at( 0 ) ) && mSelectedVertices.contains( vertices.at( 1 ) ) )
2265     {
2266       const QgsPointXY &point1 = mapVertexXY( vertices.at( 0 ) );
2267       const QgsPointXY &point2 = mapVertexXY( vertices.at( 1 ) );
2268       grappedPoint =  QgsPointXY( point1.x() + point2.x(), point1.y() + point2.y() ) / 2;
2269       return true;
2270     }
2271   }
2272 
2273 
2274   if ( ( mSelectFaceMarker->isVisible() &&
2275          grappedPoint.distance( mSelectFaceMarker->center() ) < tolerance
2276          && mCurrentFaceIndex >= 0
2277          && mSelectedFaces.contains( mCurrentFaceIndex ) ) )
2278   {
2279     grappedPoint = mCurrentLayer->triangularMesh()->faceCentroids().at( mCurrentFaceIndex );
2280     return true;
2281   }
2282 
2283   return false;
2284 }
2285 
forceByLineReleaseEvent(QgsMapMouseEvent * e)2286 void QgsMapToolEditMeshFrame::forceByLineReleaseEvent( QgsMapMouseEvent *e )
2287 {
2288   QgsPointXY mapPoint = e->mapPoint();
2289 
2290   if ( e->button() == Qt::LeftButton )
2291   {
2292     double zValue = currentZValue();
2293 
2294     if ( mCurrentVertexIndex != -1 )
2295     {
2296       const QgsPointXY currentPoint =   mapVertexXY( mCurrentVertexIndex );
2297       mForceByLineRubberBand->addPoint( currentPoint );
2298       mCadDockWidget->setZ( QString::number( mapVertex( mCurrentVertexIndex ).z(), 'f' ), QgsAdvancedDigitizingDockWidget::WidgetSetMode::TextEdited );
2299       mCadDockWidget->setPoints( QList < QgsPointXY>() << currentPoint << currentPoint );
2300     }
2301     else
2302     {
2303       if ( e->mapPointMatch().isValid() )
2304       {
2305         QgsPoint layerPoint =  e->mapPointMatch().interpolatedPoint( mCanvas->mapSettings().destinationCrs() );
2306         zValue = layerPoint.z();
2307       }
2308 
2309       mForceByLineRubberBand->addPoint( mapPoint );
2310     }
2311 
2312     mForcingLineZValue.append( zValue );
2313   }
2314   else if ( e->button() == Qt::RightButton )
2315   {
2316     if ( mForceByLineRubberBand->numberOfVertices() == 0 )
2317     {
2318       forceByLineBySelectedFeature( e );
2319       return;
2320     }
2321 
2322     QVector<QgsPoint> points;
2323     QgsPolylineXY rubbergandLines = mForceByLineRubberBand->asGeometry().asPolyline();
2324     double defaultValue = currentZValue();
2325 
2326     if ( std::isnan( defaultValue ) )
2327       defaultValue = defaultZValue();
2328 
2329     for ( int i = 0; i < rubbergandLines.count() - 1; ++i )
2330     {
2331       points.append( QgsPoint( rubbergandLines.at( i ).x(),
2332                                rubbergandLines.at( i ).y(),
2333                                mForcingLineZValue.isEmpty() ? defaultValue : mForcingLineZValue.at( i ) ) );
2334     }
2335     std::unique_ptr<QgsLineString> forcingLine = std::make_unique<QgsLineString>( points );
2336     forceByLine( QgsGeometry( forcingLine.release() ) );
2337     mForceByLineRubberBand->reset( QgsWkbTypes::LineGeometry );
2338     mForcingLineZValue.clear();
2339   }
2340 }
2341 
forceByLine(const QgsGeometry & lineGeometry)2342 void QgsMapToolEditMeshFrame::forceByLine( const QgsGeometry &lineGeometry )
2343 {
2344   QgsMeshEditForceByPolylines forceByPolyline;
2345 
2346   double defaultValue = currentZValue();
2347   if ( std::isnan( defaultValue ) )
2348     defaultValue = defaultZValue();
2349 
2350   forceByPolyline.setDefaultZValue( defaultValue );
2351   forceByPolyline.addLineFromGeometry( lineGeometry );
2352   forceByPolyline.setAddVertexOnIntersection( mWidgetActionForceByLine->newVertexOnIntersectingEdge() );
2353   forceByPolyline.setInterpolateZValueOnMesh( mWidgetActionForceByLine->interpolationMode() == QgsMeshEditForceByLineAction::Mesh );
2354   forceByPolyline.setTolerance( mWidgetActionForceByLine->toleranceValue() );
2355 
2356   mCurrentEditor->advancedEdit( &forceByPolyline );
2357 }
2358 
2359 
highlightCurrentHoveredFace(const QgsPointXY & mapPoint)2360 void QgsMapToolEditMeshFrame::highlightCurrentHoveredFace( const QgsPointXY &mapPoint )
2361 {
2362   searchFace( mapPoint );
2363   if ( mSelectFaceMarker->isVisible() )
2364   {
2365     double tol = QgsTolerance::vertexSearchRadius( canvas()->mapSettings() );
2366     if ( mapPoint.distance( mSelectFaceMarker->center() ) < tol )
2367     {
2368       mSelectFaceMarker->setColor( Qt::red );
2369       mSelectFaceMarker->setFillColor( Qt::red );
2370     }
2371     else
2372     {
2373       mSelectFaceMarker->setColor( Qt::gray );
2374       mSelectFaceMarker->setFillColor( Qt::gray );
2375     }
2376   }
2377 
2378   QgsPointSequence faceGeometry = nativeFaceGeometry( mCurrentFaceIndex );
2379   mFaceRubberBand->reset( QgsWkbTypes::PolygonGeometry );
2380   mFaceVerticesBand->reset( QgsWkbTypes::PointGeometry );
2381   for ( const QgsPoint &pt : faceGeometry )
2382   {
2383     mFaceRubberBand->addPoint( pt );
2384     mFaceVerticesBand->addPoint( pt );
2385   }
2386 
2387   if ( mCurrentFaceIndex != -1 && faceCanBeInteractive( mCurrentFaceIndex ) )
2388   {
2389     mSelectFaceMarker->setCenter( mCurrentLayer->triangularMesh()->faceCentroids().at( mCurrentFaceIndex ) );
2390     mSelectFaceMarker->setVisible( true );
2391   }
2392   else
2393     mSelectFaceMarker->setVisible( false );
2394 
2395   return;
2396 }
2397 
highlightCloseVertex(const QgsPointXY & mapPoint)2398 void QgsMapToolEditMeshFrame::highlightCloseVertex( const QgsPointXY &mapPoint )
2399 {
2400   if ( !mCurrentEditor )
2401     return;
2402 
2403 
2404   if ( mCurrentState == Digitizing && mNewFaceMarker->isVisible() )
2405   {
2406     double tol = QgsTolerance::vertexSearchRadius( canvas()->mapSettings() );
2407     if ( mapPoint.distance( mNewFaceMarker->center() ) < tol )
2408     {
2409       mNewFaceMarker->setColor( Qt::red );
2410       mVertexBand->setVisible( false );
2411       return;
2412     }
2413     else if ( !mVertexBand->isVisible() )
2414     {
2415       mNewFaceMarker->setVisible( false );
2416       mCurrentVertexIndex = -1;
2417     }
2418   }
2419 
2420   if ( mVertexBand->isVisible() && mVertexBand->numberOfVertices() > 0 )
2421   {
2422     double tol = QgsTolerance::vertexSearchRadius( canvas()->mapSettings() );
2423     if ( mVertexBand->getPoint( 0 )->distance( mapPoint ) > tol )
2424     {
2425       mVertexBand->reset( QgsWkbTypes::PointGeometry );
2426       mVertexBand->setVisible( false );
2427       mNewFaceMarker->setVisible( false );
2428       mCurrentVertexIndex = -1;
2429     }
2430   }
2431   else
2432   {
2433     int closeVert = closeVertex( mapPoint );
2434     mCurrentVertexIndex = -1;
2435     bool isBoundary = mCurrentEditor->isVertexOnBoundary( closeVert );
2436     bool isFree = mCurrentEditor->isVertexFree( closeVert );
2437     mVertexBand->reset( QgsWkbTypes::PointGeometry );
2438 
2439     if ( closeVert >= 0 && ( mCurrentState != AddingNewFace || isBoundary || isFree ) )
2440     {
2441       mCurrentVertexIndex = closeVert;
2442       mVertexBand->addPoint( mapVertexXY( closeVert ) );
2443       if ( mCurrentState == Digitizing )
2444       {
2445         if ( isBoundary || isFree )
2446         {
2447           mNewFaceMarker->setCenter( newFaceMarkerPosition( closeVert ) );
2448           mNewFaceMarker->setVisible( true );
2449           mNewFaceMarker->setColor( Qt::gray );
2450         }
2451       }
2452     }
2453   }
2454 
2455   if ( mCadDockWidget->cadEnabled() && !mCadDockWidget->constraintZ()->isLocked() && mCurrentVertexIndex != -1 )
2456     mCadDockWidget->setZ( QString::number( mapVertex( mCurrentVertexIndex ).z(), 'f' ), QgsAdvancedDigitizingDockWidget::WidgetSetMode::TextEdited );
2457 }
2458 
highlightCloseEdge(const QgsPointXY & mapPoint)2459 void QgsMapToolEditMeshFrame::highlightCloseEdge( const QgsPointXY &mapPoint )
2460 {
2461   if ( mCurrentLayer.isNull() )
2462     return;
2463 
2464   double tolerance = QgsTolerance::vertexSearchRadius( mCanvas->mapSettings() );
2465 
2466   if ( mFlipEdgeMarker->isVisible() )
2467   {
2468     if ( mapPoint.distance( mFlipEdgeMarker->center() ) < tolerance )
2469       mFlipEdgeMarker->setColor( Qt::red );
2470     else
2471       mFlipEdgeMarker->setColor( Qt::gray );
2472   }
2473 
2474   if ( mSelectEdgeMarker->isVisible() )
2475   {
2476     if ( mapPoint.distance( mSelectEdgeMarker->center() ) < tolerance )
2477     {
2478       mSelectEdgeMarker->setColor( Qt::red );
2479       mSelectEdgeMarker->setFillColor( Qt::red );
2480     }
2481     else
2482     {
2483       mSelectEdgeMarker->setColor( Qt::gray );
2484       mSelectEdgeMarker->setFillColor( Qt::gray );
2485     }
2486   }
2487 
2488   if ( mMergeFaceMarker->isVisible() )
2489   {
2490     if ( mapPoint.distance( mMergeFaceMarker->center() ) < tolerance )
2491       mMergeFaceMarker->setColor( Qt::red );
2492     else
2493       mMergeFaceMarker->setColor( Qt::gray );
2494   }
2495 
2496   searchEdge( mapPoint );
2497 
2498   mEdgeBand->reset();
2499   mFlipEdgeMarker->setVisible( false );
2500   mMergeFaceMarker->setVisible( false );
2501   mSelectEdgeMarker->setVisible( false );
2502   if ( mCurrentEdge.first != -1 && mCurrentEdge.second != -1 &&  mCurrentState == Digitizing )
2503   {
2504     const QVector<QgsPointXY> &edgeGeom = edgeGeometry( mCurrentEdge );
2505     mEdgeBand->addPoint( edgeGeom.at( 0 ) );
2506     mEdgeBand->addPoint( edgeGeom.at( 1 ) );
2507 
2508     if ( mCurrentFaceIndex == -1 )
2509     {
2510       mFaceVerticesBand->reset( QgsWkbTypes::PointGeometry );
2511       mFaceVerticesBand->addPoint( edgeGeom.at( 0 ) );
2512       mFaceVerticesBand->addPoint( edgeGeom.at( 1 ) );
2513     }
2514 
2515     const QVector<int> edgeVert = edgeVertices( mCurrentEdge );
2516     QgsPointXY basePoint;
2517     QgsVector intervalOfMarkers;
2518     if ( std::fabs( edgeGeom.at( 0 ).x() - edgeGeom.at( 1 ).x() ) > std::fabs( edgeGeom.at( 0 ).y() - edgeGeom.at( 1 ).y() ) )
2519     {
2520       // edge are more horizontal than vertical, take the vertex on the left side
2521       if ( edgeGeom.at( 0 ).x() < edgeGeom.at( 1 ).x() )
2522       {
2523         basePoint = edgeGeom.at( 0 );
2524         intervalOfMarkers = ( edgeGeom.at( 1 ) - edgeGeom.at( 0 ) ) / 4;
2525       }
2526       else
2527       {
2528         basePoint = edgeGeom.at( 1 );
2529         intervalOfMarkers = ( edgeGeom.at( 0 ) - edgeGeom.at( 1 ) ) / 4;
2530       }
2531     }
2532     else
2533     {
2534       // edge are more vertical than horizontal, take the vertex on the bottom
2535       if ( edgeGeom.at( 0 ).y() < edgeGeom.at( 1 ).y() )
2536       {
2537         basePoint = edgeGeom.at( 0 );
2538         intervalOfMarkers = ( edgeGeom.at( 1 ) - edgeGeom.at( 0 ) ) / 4;
2539       }
2540       else
2541       {
2542         basePoint = edgeGeom.at( 1 );
2543         intervalOfMarkers = ( edgeGeom.at( 0 ) - edgeGeom.at( 1 ) ) / 4;
2544       }
2545     }
2546 
2547     if ( mCurrentEditor->edgeCanBeFlipped( edgeVert.at( 0 ), edgeVert.at( 1 ) ) )
2548     {
2549       mFlipEdgeMarker->setVisible( true );
2550       mFlipEdgeMarker->setCenter( basePoint + intervalOfMarkers );
2551     }
2552     else
2553       mFlipEdgeMarker->setVisible( false );
2554 
2555     mSelectEdgeMarker->setVisible( true );
2556     mSelectEdgeMarker->setCenter( basePoint + intervalOfMarkers * 2 );
2557 
2558     if ( mCurrentEditor->canBeMerged( edgeVert.at( 0 ), edgeVert.at( 1 ) ) )
2559     {
2560       mMergeFaceMarker->setVisible( true );
2561       mMergeFaceMarker->setCenter( basePoint + intervalOfMarkers * 3 );
2562     }
2563     else
2564       mMergeFaceMarker->setVisible( false );
2565   }
2566 }
2567 
edgeCanBeInteractive(int vertexIndex1,int vertexIndex2) const2568 bool QgsMapToolEditMeshFrame::edgeCanBeInteractive( int vertexIndex1, int vertexIndex2 ) const
2569 {
2570   // If the edge is less than 90px width, the interactive marker will not be displayed to avoid too close marker and
2571   // avoit the user to click on a marker if he doesn't want
2572   double mapUnitPerPixel = mCanvas->mapSettings().mapUnitsPerPixel();
2573   return mapVertexXY( vertexIndex1 ).distance( mapVertexXY( vertexIndex2 ) ) / mapUnitPerPixel > 90;
2574 }
2575 
faceCanBeInteractive(int faceIndex) const2576 bool QgsMapToolEditMeshFrame::faceCanBeInteractive( int faceIndex ) const
2577 {
2578   // If both side of the face boundng box is less than 60px width, the interactive marker will not be displayed to avoid too close marker and
2579   // avoit the user to click on a marker if he doesn't want
2580 
2581   double mapUnitPerPixel = mCanvas->mapSettings().mapUnitsPerPixel();
2582   QgsGeometry faceGeom( new QgsLineString( nativeFaceGeometry( faceIndex ) ) );
2583   QgsRectangle bbox = faceGeom.boundingBox();
2584 
2585   return bbox.width() / mapUnitPerPixel > 60 || bbox.height() / mapUnitPerPixel > 60;
2586 }
2587 
createZValueWidget()2588 void QgsMapToolEditMeshFrame::createZValueWidget()
2589 {
2590   if ( !mCanvas )
2591     return;
2592 
2593   deleteZValueWidget();
2594 
2595   mZValueWidget = new QgsZValueWidget( tr( "Vertex Z value:" ) );
2596   mZValueWidget->setDefaultValue( defaultZValue() );
2597   mZValueWidget->setZValue( mUserZValue );
2598   QgisApp::instance()->addUserInputWidget( mZValueWidget );
2599 }
2600 
deleteZValueWidget()2601 void QgsMapToolEditMeshFrame::deleteZValueWidget()
2602 {
2603   if ( mZValueWidget )
2604   {
2605     mZValueWidget->releaseKeyboard();
2606     mZValueWidget->deleteLater();
2607   }
2608 
2609   mZValueWidget = nullptr;
2610 }
2611 
clearSelection()2612 void QgsMapToolEditMeshFrame::clearSelection()
2613 {
2614   mSelectedVertices.clear();
2615   mSelectedFaces.clear();
2616   mSelectionBand->reset( QgsWkbTypes::PolygonGeometry );
2617   mSelectedFacesRubberband->reset( QgsWkbTypes::PolygonGeometry );
2618   qDeleteAll( mSelectedVerticesMarker );
2619   mSelectedVerticesMarker.clear();
2620   prepareSelection();
2621 }
2622 
clearCanvasHelpers()2623 void QgsMapToolEditMeshFrame::clearCanvasHelpers()
2624 {
2625   mCurrentFaceIndex = -1;
2626   mCurrentVertexIndex = -1;
2627   mFaceRubberBand->reset();
2628   mFaceVerticesBand->reset();
2629   mVertexBand->reset();
2630 
2631   mSnapIndicator->setMatch( QgsPointLocator::Match() );
2632   mSelectFaceMarker->setVisible( false );
2633   clearEdgeHelpers();
2634 }
2635 
clearEdgeHelpers()2636 void QgsMapToolEditMeshFrame::clearEdgeHelpers()
2637 {
2638   mCurrentEdge = {-1, -1};
2639   mEdgeBand->reset();
2640   mSelectEdgeMarker->setVisible( false );
2641   mFlipEdgeMarker->setVisible( false );
2642   mMergeFaceMarker->setVisible( false );
2643 }
2644 
addVertex(const QgsPointXY & mapPoint,const QgsPointLocator::Match & mapPointMatch)2645 void QgsMapToolEditMeshFrame::addVertex(
2646   const QgsPointXY &mapPoint,
2647   const QgsPointLocator::Match &mapPointMatch )
2648 {
2649   QgsTemporaryCursorOverride waitCursor( Qt::WaitCursor );
2650 
2651   double zValue;
2652 
2653   if ( mCadDockWidget->cadEnabled() && mCurrentFaceIndex == -1 )
2654     zValue = currentZValue();
2655   else if ( mapPointMatch.isValid() )
2656   {
2657     QgsPoint layerPoint = mapPointMatch.interpolatedPoint( mCanvas->mapSettings().destinationCrs() );
2658     zValue = layerPoint.z();
2659   }
2660   else if ( mCurrentFaceIndex != -1 ) //we are on a face -->interpolate the z value
2661   {
2662     const QgsTriangularMesh &triangularMesh = *mCurrentLayer->triangularMesh();
2663     int triangleFaceIndex = triangularMesh.faceIndexForPoint_v2( mapPoint );
2664     const QgsMeshFace &triangleFace = triangularMesh.triangles().at( triangleFaceIndex );
2665     const QgsMeshVertex &v1 = triangularMesh.vertices().at( triangleFace.at( 0 ) );
2666     const QgsMeshVertex &v2 = triangularMesh.vertices().at( triangleFace.at( 1 ) );
2667     const QgsMeshVertex &v3 = triangularMesh.vertices().at( triangleFace.at( 2 ) );
2668     zValue = QgsMeshLayerUtils::interpolateFromVerticesData( v1, v2, v3, v1.z(), v2.z(), v3.z(), mapPoint );
2669   }
2670   else
2671     zValue = currentZValue();
2672 
2673   QVector<QgsMeshVertex> points( 1, QgsMeshVertex( mapPoint.x(), mapPoint.y(), zValue ) );
2674   if ( mCurrentEditor )
2675   {
2676     double tolerance = QgsTolerance::vertexSearchRadius( canvas()->mapSettings() );
2677     mCurrentEditor->addVertices( points, tolerance );
2678   }
2679 }
2680 
updateFreeVertices()2681 void QgsMapToolEditMeshFrame::updateFreeVertices()
2682 {
2683   qDeleteAll( mFreeVertexMarker );
2684   mFreeVertexMarker.clear();
2685 
2686   if ( mCurrentLayer.isNull() || mCurrentEditor.isNull() )
2687     return;
2688 
2689   const QList<int> &freeVertexIndexes = mCurrentEditor->freeVerticesIndexes();
2690   int freeVertexCount = freeVertexIndexes.count();
2691   mFreeVertexMarker.reserve( freeVertexCount );
2692 
2693   QColor freeVertexColor = digitizingStrokeColor();
2694   QColor fillFreeVertexColor = freeVertexColor.lighter();
2695 
2696   for ( const int freeVertexIndex : freeVertexIndexes )
2697   {
2698     mFreeVertexMarker.append( new QgsVertexMarker( canvas() ) );
2699     QgsVertexMarker *marker = mFreeVertexMarker.last();
2700     marker->setCenter( mapVertexXY( freeVertexIndex ) );
2701     marker->setIconType( QgsVertexMarker::ICON_CIRCLE );
2702     marker->setIconSize( QgsGuiUtils::scaleIconSize( 7 ) );
2703     marker->setColor( freeVertexColor );
2704     marker->setFillColor( fillFreeVertexColor );
2705   }
2706 }
2707 
closeVertex(const QgsPointXY & mapPoint) const2708 int QgsMapToolEditMeshFrame::closeVertex( const QgsPointXY &mapPoint ) const
2709 {
2710   if ( !mCurrentEditor )
2711     return -1;
2712 
2713   double tolerance = QgsTolerance::vertexSearchRadius( canvas()->mapSettings() );
2714 
2715   if ( mCurrentEdge.first != -1 && mCurrentEdge.second  != -1 )
2716   {
2717     const QVector<int> &edge = edgeVertices( mCurrentEdge );
2718 
2719     for ( const int vertexIndex : edge )
2720     {
2721       const QgsPointXY &meshVertex = mapVertexXY( vertexIndex );
2722       if ( meshVertex.distance( mapPoint ) < tolerance )
2723         return vertexIndex;
2724     }
2725   }
2726 
2727   if ( mCurrentFaceIndex >= 0 )
2728   {
2729     const QgsMeshFace &face = mCurrentLayer->nativeMesh()->face( mCurrentFaceIndex );
2730     for ( const int vertexIndex : face )
2731     {
2732       const QgsPointXY &meshVertex = mapVertexXY( vertexIndex );
2733       if ( meshVertex.distance( mapPoint ) < tolerance )
2734         return vertexIndex;
2735     }
2736 
2737     //nothing found int the face --> return -1;
2738     return -1;
2739   }
2740 
2741   //if we are here, we are outside faces, need to search for free vertices;
2742   const QList<int> &freeVertexIndexes = mCurrentEditor->freeVerticesIndexes();
2743   for ( const int vertexIndex : freeVertexIndexes )
2744   {
2745     const QgsPointXY &meshVertex = mapVertexXY( vertexIndex );
2746     if ( meshVertex.distance( mapPoint ) < tolerance )
2747       return vertexIndex;
2748   }
2749 
2750   return -1;
2751 }
2752 
selectByExpression(const QString & textExpression,Qgis::SelectBehavior behavior,QgsMesh::ElementType elementType)2753 void QgsMapToolEditMeshFrame::selectByExpression( const QString &textExpression, Qgis::SelectBehavior behavior, QgsMesh::ElementType elementType )
2754 {
2755   if ( !mCurrentEditor || !mCurrentLayer )
2756     return;
2757   QgsExpression expression( textExpression );
2758 
2759   std::unique_ptr<QgsDistanceArea> distArea = std::make_unique<QgsDistanceArea>();
2760   distArea->setSourceCrs( mCurrentLayer->crs(), QgsProject::instance()->transformContext() );
2761   distArea->setEllipsoid( QgsProject::instance()->ellipsoid() );
2762   expression.setAreaUnits( QgsProject::instance()->areaUnits() );
2763   expression.setDistanceUnits( QgsProject::instance()->distanceUnits() );
2764   expression.setGeomCalculator( distArea.release() );
2765 
2766   switch ( elementType )
2767   {
2768     case QgsMesh::Vertex:
2769       setSelectedVertices( mCurrentLayer->selectVerticesByExpression( expression ), behavior );
2770       break;
2771     case QgsMesh::Face:
2772       setSelectedFaces( mCurrentLayer->selectFacesByExpression( expression ), behavior );
2773       break;
2774     case QgsMesh::Edge:
2775       //not supported
2776       break;
2777   }
2778 }
2779 
2780 
onZoomToSelected()2781 void QgsMapToolEditMeshFrame::onZoomToSelected()
2782 {
2783   canvas()->zoomToFeatureExtent( mSelectedMapExtent );
2784 }
2785 
showSelectByExpressionDialog()2786 void QgsMapToolEditMeshFrame::showSelectByExpressionDialog()
2787 {
2788   onEditingStarted();
2789   QgsMeshSelectByExpressionDialog *dialog = new QgsMeshSelectByExpressionDialog( canvas() );
2790   dialog->setAttribute( Qt::WA_DeleteOnClose );
2791   dialog->show();
2792   connect( dialog, &QgsMeshSelectByExpressionDialog::select, this, &QgsMapToolEditMeshFrame::selectByExpression );
2793   connect( dialog, &QgsMeshSelectByExpressionDialog::zoomToSelected, this, &QgsMapToolEditMeshFrame::onZoomToSelected );
2794 }
2795